从ListBox中的数据对象获取DataTemplate

Win*_*don 6 c# wpf listbox datatemplate mvvm

我有一个ListBox它ItemTemplate看起来像这样:

<DataTemplate DataType="local:Column">
    <utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"/>
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)

Column 是一个简单的类,看起来像这样:

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

public string Name { get; set; }
public bool IsVisibleInTable { get; set; }
Run Code Online (Sandbox Code Playgroud)

该EditableTextBlock是UserControl说变成了TextBox双次点击时回百转成TextBlock失落时对焦.它还有一个被称为属性的属性IsInEditMode,默认为false.如果是,TextBox则显示.

问题:
在ItemsSouceListBox的是ObservableCollection<Column>.我有一个按钮,Column可以为集合添加新的内容.但我的问题是,我希望通过该ButtonIsInEditMode为新添加的EditableTextBlocks 变为真.但我只能在ViewModel中访问.我如何访问集合中指定的? ColumnEditableTextBlockColumnItemsSource

我能想出的唯一解决方案是从中派生一个类Column并为其添加一个属性(例如:name:IsInEditMode)(或者也许是一个包装类.这里是一个类似的答案,建议使用包装类)并绑定到该属性DataTemplate如下:

<DataTemplate DataType="local:DerivedColumn">
    <utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"
                             IsInEditMode="{Binding IsInEditMode}"/>
</DataTemplate>
Run Code Online (Sandbox Code Playgroud)

但我不想要这个.我想要一些方法在XAML中执行此操作,而无需派生类和添加不必要的代码.(并且还遵守MVVM规则)

Rhy*_*hys 2

如果您有能力向用户控件添加新的依赖属性,EditableTextBlock您可以考虑添加一个名为 的依赖属性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;
    }
Run Code Online (Sandbox Code Playgroud)

对于视觉树中已有的控件,更改值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>
Run Code Online (Sandbox Code Playgroud)

Window您需要向本示例中调用的父级(或Page用作视图容器的任何内容)添加一个属性ANewViewProperty。如果您将绑定更改为 ,则该值可能是视图模型的一部分{Binding DataContext.ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}。

这个新属性 ( ANewViewProperty) 甚至不需要实现INotifyPropertyChanged,因为绑定将在创建新EditableTextBlock控件时获取初始值,并且如果该值稍后发生更改,则无论如何都不会产生影响。

您可以在最初加载时设置ANewViewPropertyto的值。当您按下按钮将新项目添加到列表中时,将 的值设置为 ,表示现在将在编辑模式下启动创建的控件。FalseListBox ItemSourceANewViewPropertyTrue

更新:仅 C#、仅查看的替代方案

仅代码、仅查看的替代方案(类似于 user2946329 的答案)是挂钩到ListBox.ItemContainerGenerator.ItemsChanged添加新项目时将触发的处理程序。一旦触发,您现在就可以对新项目进行操作(通过 Boolean ),它会为新添加的项目找到适当视觉容器的DetectingNewItems第一个后代控件。一旦获得了控件的引用,就可以更改属性。EditableTextBlockListBoxItemIsInEditMode

//.... 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;
    }
Run Code Online (Sandbox Code Playgroud)

更新#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;
        }
    }

    .....  
Run Code Online (Sandbox Code Playgroud)