WPF MVVM TreeView SelectedItem

Kye*_*ica 31 wpf treeview mvvm

这不可能是这么困难.WPF中的TreeView不允许您设置SelectedItem,表示该属性是ReadOnly.我有TreeView填充,甚至在数据绑定集合更改时更新.

我只需要知道选择了什么项目.我正在使用MVVM,因此没有代码隐藏或变量来引用树视图.这是我找到的唯一解决方案,但它是一个明显的黑客,它在XAML中创建了另一个元素,它使用ElementName绑定将自己设置为树视图选定项,然后您必须绑定Viewmodel.关于此问题还有其他几个问题,但没有给出其他有效的解决方案.

我已经看到了这个问题,但是使用给出的答案给出了编译错误,由于某种原因,我无法将混合sdk System.Windows.Interactivity的引用添加到我的项目中.它说"未知的错误系统.窗口没有被预加载",我还没有想出如何通过它.

对于奖励积分:为什么微软会让这个元素的SelectedItem属性ReadOnly?

H.B*_*.B. 47

您不应该直接处理SelectedItem属性,绑定IsSelected到viewmodel上的属性并跟踪所选项目.

草图:

<TreeView ItemsSource="{Binding TreeData}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsSelected" Value="{Binding IsSelected}" />
        </Style>
    </TreeView.ItemContainerStyle>
</TreeView>
Run Code Online (Sandbox Code Playgroud)
public class TViewModel : INotifyPropertyChanged
{
    private static object _selectedItem = null;
    // This is public get-only here but you could implement a public setter which
    // also selects the item.
    // Also this should be moved to an instance property on a VM for the whole tree, 
    // otherwise there will be conflicts for more than one tree.
    public static object SelectedItem
    {
        get { return _selectedItem; }
        private set
        {
            if (_selectedItem != value)
            {
                _selectedItem = value;
                OnSelectedItemChanged();
            }
        }
    }

    static virtual void OnSelectedItemChanged()
    {
        // Raise event / do other things
    }

    private bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set
        {
            if (_isSelected != value)
            {
                _isSelected = value;
                OnPropertyChanged("IsSelected");
                if (_isSelected)
                {
                    SelectedItem = this;
                }
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Tyrsius:伙计,我可以咆哮人们从SO复制粘贴代码...你有没有见过一个提升`PropertyChanged`的财产?您应该能够在不使用该扩展方法的情况下提升它(您显然没有这种方法). (4认同)
  • 是的,我在大约十二个教程中看到了它.对不起,我不明白你没有提供的背景. (4认同)
  • 我不明白这将如何回到树上.`SelectedItem = this`在TreeViewItem级别发生,在TreeViewItem上设置属性,而不是树视图.我仍然需要找到具有该属性集的TreeViewItem,我正好回到第一个方块. (3认同)
  • @HB因为您依赖静态属性(`TViewModel.SelectedItem`),当您在当前进程中有多个TreeView时,此解决方案不起作用. (3认同)
  • “您实际上不应该直接处理 SelectedItem 属性”我不同意,我希望 TreeView 作为项目容器以与 ListBox 相同的方式公开其选定项目。也就是说,+1 提供正确的答案和最佳实践解决方法 (2认同)

Bas*_*Bas 12

您可以创建一个可绑定的附加属性,并具有一个getter和setter:

public class TreeViewHelper
{
    private static Dictionary<DependencyObject, TreeViewSelectedItemBehavior> behaviors = new Dictionary<DependencyObject, TreeViewSelectedItemBehavior>();

    public static object GetSelectedItem(DependencyObject obj)
    {
        return (object)obj.GetValue(SelectedItemProperty);
    }

    public static void SetSelectedItem(DependencyObject obj, object value)
    {
        obj.SetValue(SelectedItemProperty, value);
    }

    // Using a DependencyProperty as the backing store for SelectedItem.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedItemProperty =
        DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));

    private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (!(obj is TreeView))
            return;

        if (!behaviors.ContainsKey(obj))
            behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView));

        TreeViewSelectedItemBehavior view = behaviors[obj];
        view.ChangeSelectedItem(e.NewValue);
    }

    private class TreeViewSelectedItemBehavior
    {
        TreeView view;
        public TreeViewSelectedItemBehavior(TreeView view)
        {
            this.view = view;
            view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
        }

        internal void ChangeSelectedItem(object p)
        {
            TreeViewItem item = (TreeViewItem)view.ItemContainerGenerator.ContainerFromItem(p);
            item.IsSelected = true;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

将包含该类的名称空间声明添加到XAML并按如下方式绑定(本地是我命名名称空间声明的方式):

<TreeView ItemsSource="{Binding Path=Root.Children}"
          local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
Run Code Online (Sandbox Code Playgroud)

现在,您可以绑定所选项目,并在视图模型中将其设置为以编程方式更改它,如果该要求出现的话.当然,这是假设您在该特定属性上实现INotifyPropertyChanged.

  • 在这个解决方案中有一个**内存泄漏**.你的静态`behavior`字典包含永远连接到内存中的所有TreeView(包括他们可能拥有的所有项目). (10认同)
  • @Bas:每个`TreeViewItem`本身都是一个`ItemsControl`,你不能确定它的子节点是否已经生成.您必须检查`ItemContainerGenerator`是否返回null,展开当前的`TreeViewItem`,等待它完成生成,然后再递归尝试.在一棵可以花很多时间的大树上 (2认同)

hel*_*ker 6

以MVVM可接受的方式解决此问题的一种非常不寻常但非常有效的方法如下:

  1. 在TreeView所在的View上创建一个可见性折叠的ContentControl.适当地命名,并将其内容绑定到SelectedSomethingviewmodel中的某个属性.这个ContentControl将"保持"所选对象并处理它的绑定,OneWayToSource;
  2. SelectedItemChanged在TreeView中收听,并在代码隐藏中添加一个处理程序,将ContentControl.Content设置为新选择的项目.

XAML:

<ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/>
<TreeView ItemsSource="{Binding SomeCollection}"
    SelectedItemChanged="TreeView_SelectedItemChanged">
Run Code Online (Sandbox Code Playgroud)

代码背后:

    private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        SelectedItemHelper.Content = e.NewValue;
    }
Run Code Online (Sandbox Code Playgroud)

视图模型:

    public object SelectedObject  // Class is not actually "object"
    {
        get { return _selected_object; }
        set
        {
            _selected_object = value;
            RaisePropertyChanged(() => SelectedObject);
            Console.WriteLine(SelectedObject);
        }
    }
    object _selected_object;
Run Code Online (Sandbox Code Playgroud)


Aph*_*hex 5

使用OneWayToSource绑定模式。这不起作用。见编辑。

编辑:根据这个问题,看起来这是微软的一个错误或“设计使然”行为;不过,已经发布了一些解决方法。这些对您的 TreeView 有用吗?

Microsoft Connect 问题:https : //connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings

Microsoft 于 2010 年 1 月 10 日下午 2:46 发布

我们今天不能在 WPF 中这样做,出于同样的原因,我们不能支持对不是 DependencyProperties 的属性的绑定。绑定的运行时每个实例状态保存在 BindingExpression 中,我们将其存储在目标 DependencyObject 的 EffectiveValueTable 中。当目标属性不是 DP 或 DP 是只读的时,就没有地方存储 BindingExpression。

有可能有一天我们会选择将绑定功能扩展到这两种情况。我们经常被问到他们。换句话说,您的请求已经在我们未来版本中要考虑的功能列表中。

感谢您的反馈意见。