WPF MVVM TreeView SelectedItem
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7153813/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WPF MVVM TreeView SelectedItem
提问by Kyeotic
This cannot be this difficult. The TreeView in WPF doesn't allow you to set the SelectedItem, saying that the property is ReadOnly. I have the TreeView populating, even updating when it's databound collection changes.
这不可能这么困难。WPF中的TreeView不允许你设置SelectedItem,说属性是ReadOnly。我有 TreeView 填充,甚至在它的数据绑定集合更改时更新。
I just need to know what item is selected. I am using MVVM, so there is no codebehind or variable to reference the treeview by. This is the only solutionI have found, but it is an obvious hack, it creates another element in XAML that uses ElementName binding to set itself to the treeviews selected item, which you must then bind your Viewmodel too. Severalother questionsare asked about this, but no other working solutions are given.
我只需要知道选择了什么项目。我正在使用 MVVM,所以没有代码隐藏或变量来引用树视图。这是我找到的唯一解决方案,但它是一个明显的 hack,它在 XAML 中创建另一个元素,该元素使用 ElementName 绑定将自身设置为树视图所选项目,然后您也必须绑定您的 Viewmodel。关于此问题还询问了其他几个问题,但没有给出其他可行的解决方案。
I have seen this question, but using the answer given gives me compile errors, for some reason I cannot add a reference to the blend sdk System.Windows.Interactivity to my project. It says "unknown error system.windows has not been preloaded" and I haven't yet figured out how to get past that.
我见过这个问题,但是使用给出的答案给了我编译错误,由于某种原因,我无法向我的项目添加对混合 sdk System.Windows.Interactivity 的引用。它说“未知错误 system.windows 尚未预加载”,我还没有想出如何解决这个问题。
For Bonus Points: why the hell did Microsoft make this element's SelectedItem property ReadOnly?
奖励积分:微软为什么要把这个元素的 SelectedItem 属性设为只读?
回答by H.B.
You should not really need to deal with the SelectedItem property directly, bind IsSelected
to a property on your viewmodel and keep track of the selected item there.
您不应该真的需要直接处理 SelectedItem 属性,绑定IsSelected
到视图模型上的属性并在那里跟踪所选项目。
A sketch:
草图:
<TreeView ItemsSource="{Binding TreeData}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
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));
}
}
回答by Bas
You can create an attached property that is bindable and has a getter and setter:
您可以创建一个可绑定并具有 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;
}
}
}
Add the namespace declaration containing that class to your XAML and bind as follows (local is how I named the namespace declaration):
将包含该类的命名空间声明添加到您的 XAML 并绑定如下(本地是我命名命名空间声明的方式):
<TreeView ItemsSource="{Binding Path=Root.Children}"
local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
Now you can bind the selected item, and also set it in your view model to change it programmatically, should that requirement ever arise. This is, of course, assuming that you implement INotifyPropertyChanged on that particular property.
现在,您可以绑定所选项目,并在您的视图模型中设置它,以便在出现该需求时以编程方式更改它。当然,这是假设您在该特定属性上实现 INotifyPropertyChanged。
回答by heltonbiker
A very unusual but quite effective way to solve this in a MVVM-acceptable way is the following:
以 MVVM 可接受的方式解决此问题的一种非常不寻常但非常有效的方法如下:
- Create a visibility-collapsed ContentControl on the same View the TreeView is. Name it appropriately, and bind its Content to some
SelectedSomething
property in viewmodel. This ContentControl will "hold" the selected object and handle it's binding, OneWayToSource; - Listen to the
SelectedItemChanged
in TreeView, and add a handler in code-behind to set your ContentControl.Content to the newly selected item.
- 在 TreeView 所在的视图上创建一个可见性折叠的 ContentControl。适当命名,并将其内容绑定到
SelectedSomething
视图模型中的某个属性。这个 ContentControl 将“持有”选定的对象并处理它的绑定,OneWayToSource; - 收听
SelectedItemChanged
TreeView 中的 ,并在代码隐藏中添加一个处理程序以将您的 ContentControl.Content 设置为新选择的项目。
XAML:
XAML:
<ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/>
<TreeView ItemsSource="{Binding SomeCollection}"
SelectedItemChanged="TreeView_SelectedItemChanged">
Code Behind:
背后的代码:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItemHelper.Content = e.NewValue;
}
ViewModel:
视图模型:
public object SelectedObject // Class is not actually "object"
{
get { return _selected_object; }
set
{
_selected_object = value;
RaisePropertyChanged(() => SelectedObject);
Console.WriteLine(SelectedObject);
}
}
object _selected_object;
回答by Aphex
Use the This doesn't work. See edit.OneWayToSource
binding mode.
使用这不起作用。见编辑。OneWayToSource
绑定模式。
Edit: Looks like this is a bug or "by design" behavior from Microsoft, according to this question; there are some workarounds posted, though. Do any of those work for your TreeView?
编辑:根据这个问题,看起来这是微软的一个错误或“设计使然”行为;不过,已经发布了一些解决方法。这些对您的 TreeView 有用吗?
The Microsoft Connect issue: https://connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings
Microsoft Connect 问题:https: //connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings
Posted by Microsoft on 1/10/2010 at 2:46 PM
We cannot do this in WPF today, for the same reason we cannot support bindings on properties that are not DependencyProperties. The runtime per-instance state of a binding is held in a BindingExpression, which we store in the EffectiveValueTable for the target DependencyObject. When the target property is not a DP or the DP is read-only, there's no place to store the BindingExpression.
It's possible we may some day choose to extend binding functionality to these two scenarios. We get asked about them pretty frequently. In other words, your request is already on our list of features to consider in future releases.
Thanks for your feedback.
Microsoft 于 2010 年 1 月 10 日下午 2:46 发布
我们今天不能在 WPF 中这样做,出于同样的原因,我们不能支持对不是 DependencyProperties 的属性的绑定。绑定的运行时每个实例状态保存在 BindingExpression 中,我们将其存储在目标 DependencyObject 的 EffectiveValueTable 中。当目标属性不是 DP 或 DP 是只读的时,就没有地方存储 BindingExpression。
有可能有一天我们会选择将绑定功能扩展到这两种情况。我们经常被问到他们。换句话说,您的请求已经在我们未来版本中要考虑的功能列表中。
感谢您的反馈意见。
回答by allan
I decided to use a combination of code behind and viewmodel code. the xaml is like this:
我决定使用隐藏代码和视图模型代码的组合。xaml 是这样的:
<TreeView
Name="tvCountries"
ItemsSource="{Binding Path=Countries}"
ItemTemplate="{StaticResource ResourceKey=countryTemplate}"
SelectedValuePath="Name"
SelectedItemChanged="tvCountries_SelectedItemChanged">
Code behind
背后的代码
private void tvCountries_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var vm = this.FindResource("vm") as ViewModels.CoiEditorViewModel;
if (vm != null)
{
var treeItem = sender as TreeView;
vm.TreeItemSelected = treeItem.SelectedItem;
}
}
And in the viewmodel there is a TreeItemSelected object which you can then access in the viewmodel.
在视图模型中有一个 TreeItemSelected 对象,您可以在视图模型中访问它。
回答by stricq
You can always create a DependencyProperty that uses ICommand and listen to the SelectedItemChanged event on the TreeView. This can be a bit easier than binding IsSelected, but I imagine you will wind up binding IsSelected anyway for other reasons. If you just want to bind on IsSelected you can always have your item send a message whenever IsSelected changes. Then you can listen to those messages anyplace in your program.
您始终可以创建一个使用 ICommand 的 DependencyProperty 并侦听 TreeView 上的 SelectedItemChanged 事件。这可能比绑定 IsSelected 容易一些,但我想您无论如何都会因为其他原因绑定 IsSelected。如果您只想绑定 IsSelected,您可以随时让您的项目在 IsSelected 更改时发送消息。然后,您可以在程序中的任何位置收听这些消息。