Сделать шаблон данных DataTemplate из объекта данных в ListBox

у меня есть ListBox чей ItemTemplate выглядит так:

<DataTemplate DataType="local:Column">
    <utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"/>
</DataTemplate>

Column - Это простой класс, который выглядит так:

public Column(string name, bool isVisibleInTable)
{
    Name = name;
    IsVisibleInTable = isVisibleInTable;
}

public string Name { get; set; }
public bool IsVisibleInTable { get; set; }

на EditableTextBlock это UserControl превращается в TextBox когда дважды щелкнул и превращается обратно в TextBlock когда потерял фокус. Он также имеет свойство под названием IsInEditMode который по умолчанию false. Когда это правда, это.

Вопрос:
The ItemsSouce из списка-это ObservableCollection<Column>. У меня есть кнопка, которая добавляет новый Columns в коллекции. Но моя проблема в том, что я хочу IsInEditMode чтобы быть истинным для недавно добавленных EditableTextBlockС кнопка. Но я могу только получить доступ Column в ViewModel. Как я получу доступ к EditableTextBlock указанного Column на ItemsSource коллекция?

единственное решение, которое я могу придумать-это производный класс от Column и добавление свойства для этого (например: name:IsInEditMode) (или, возможно, класса. здесьаналогичный ответ, который предлагает использовать класс-оболочку) и привязку к этому свойству в DataTemplate следующим образом:

<DataTemplate DataType="local:DerivedColumn">
    <utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"
                             IsInEditMode="{Binding IsInEditMode}"/>
</DataTemplate>

но я не хочу этого. Я хочу каким-то образом сделать это в XAML без получения классов и добавления ненужного кода. (А также соблюдение правил MVVM)

2 ответов


если у вас есть область для добавления нового свойства зависимостей в EditableTextBlock user control вы можете рассмотреть возможность добавления одного, который имеет имя StartupInEditMode научиться false сохранить существующее поведение.

на Loaded обработчик для элемента UserControl затем можно определить статус StartupInEditMode чтобы решить, как изначально задано значение IsInEditMode.

//..... Added to EditableTextBlock user control
    public bool StartupInEdit
    {
        get { return (bool)GetValue(StartupInEditProperty); }
        set { SetValue(StartupInEditProperty, value); }
    }

    public static readonly DependencyProperty StartupInEditProperty = 
        DependencyProperty.Register("StartupInEdit", typeof(bool), typeof(EditableTextBlock ), new PropertyMetadata(false));

    private void EditableTextBlock_OnLoaded(object sender, RoutedEventArgs e)
    {
        IsInEditMode = StartupInEditMode;
    }

для элементов управления уже в визуальном дереве изменяется значение StartupInEdit не имеет значения, как это только однажды на создание. Это означает, что вы можете заполнить коллекцию ListBox каждая EditableTextBlock не находится в режиме редактирования, затем замените StartupInEditmMode режим True когда вы начинаете добавлять новые элементы. Тогда каждый новый EditableTextBlock управление запускается в режиме редактирования.

вы можете выполнить этот переключатель в поведении, указав DataTemplate здесь Binding этого нового свойства указывает на переменную представления, а не элементы коллекции.

    <DataTemplate DataType="local:Column">
      <utils:EditableTextBlock x:Name="editableTextBlock"
            Text="{Binding Name, Mode=TwoWay}" 
            StartupInEditMode="{Binding ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
    </DataTemplate>

вам нужно добавьте свойство к родительскому Window (или Page или все, что используется в качестве контейнера для представления) называется ANewViewProperty в этом примере. Это значение может быть частью модели представления, если изменить привязку на {Binding DataContext.ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}.

это новое свойство (ANewViewProperty) даже не нужно реализовывать INotifyPropertyChanged поскольку привязка получит начальное значение, поскольку она создает новый EditableTextBlock control, и если значение изменится позже, оно все равно не повлияет.

вы установили бы значение из ANewViewProperty to False как вы загрузите ListBox ItemSource изначально. При нажатии кнопки для добавления нового элемента в список установите значение ANewViewProperty to True означает элемент управления, который теперь будет создан при запуске в режиме редактирования.

Update: альтернатива только для C#, только для просмотра

альтернатива только для кода, только для просмотра (аналогично ответу user2946329) - подключить к ListBox.ItemContainerGenerator.ItemsChanged обработчик, который будет срабатывать при добавлении нового элемента. Однажды сработал и ты теперь действуют на новые элементы (через Boolean DetectingNewItems), который находит первого потомка EditableTextBlock управление для соответствующего ListBoxItem визуальный контейнер для вновь добавленного элемента. Как только у вас есть ссылка для элемента управления, измените IsInEditMode собственность.

//.... View/Window Class

    private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
    {
      MyListBox.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
    }

    private void ItemContainerGenerator_ItemsChanged(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
    {
      if ((e.Action == NotifyCollectionChangedAction.Add) && DetectingNewItems)
      {
        var listboxitem = LB.ItemContainerGenerator.ContainerFromIndex(e.Position.Index + 1) as ListBoxItem;

        var editControl = FindFirstDescendantChildOf<EditableTextBlock>(listboxitem);
        if (editcontrol != null) editcontrol.IsInEditMode = true;
      }
    }

    public static T FindFirstDescendantChildOf<T>(DependencyObject dpObj) where T : DependencyObject
    {
        if (dpObj == null) return null;

        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dpObj); i++)
        {
            var child = VisualTreeHelper.GetChild(dpObj, i);
            if (child is T) return (T)child;

            var obj = FindFirstChildOf<T>(child);

            if (obj != null) return obj;
        }

        return null;
    }

обновление #2 (на основе комментариев)

добавьте свойство в представление, которое ссылается на ViewModel, предполагая, что вы сохраняете ссылку на модель представления в DataContext:-

    .....  // Add this to the Window/Page

    public bool DetectingNewItems
    {
        get
        {
            var vm = DataContext as MyViewModel;
            if (vm != null)
                return vm.MyPropertyOnVM;
            return false;
        }
    }

    .....  

чтобы получить элемент внутри шаблона и изменить его свойства в коде, вам нужно FrameworkTemplate.FindName Method (String, FrameworkElement) :

private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child != null && child is childItem)
            return (childItem)child;
        else
        {
            childItem childOfChild = FindVisualChild<childItem>(child);
            if (childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}

затем:

for (int i = 0; i < yourListBox.Items.Count; i++)
{
    ListBoxItem yourListBoxItem = (ListBoxItem)(yourListBox.ItemContainerGenerator.ContainerFromIndex(i));
    ContentPresenter contentPresenter = FindVisualChild<ContentPresenter>(yourListBoxItem);
    DataTemplate myDataTemplate = contentPresenter.ContentTemplate;
    EditableTextBlock editable = (EditableTextBlock) myDataTemplate.FindName("editableTextBlock", contentPresenter);
    //Do stuff with EditableTextBlock
    editable.IsInEditMode = true;
}