这不可能是这么困难。 WPF中的TreeView不允许您设置SelectedItem,表示该属性是ReadOnly。我有TreeView填充,甚至在数据绑定集合更改时更新。
我只需知道选择了哪个项目。我正在使用MVVM,因此没有代码隐藏或变量来引用树视图。 This is the only solution我找到了,但这是一个明显的黑客攻击,它在XAML中创建了另一个元素,它使用ElementName绑定将自己设置为树视图选择项,然后你必须绑定你的Viewmodel。 Several其他questions被问及这个问题,但没有给出其他工作解决方案。
我见过this question,但是使用给出的答案给出了编译错误,由于某种原因我无法在我的项目中添加对sdk System.Windows.Interactivity的混合引用。它说“未知的错误系统。窗口没有被预加载”,我还没有想出如何超越它。
对于奖励积分:为什么微软会让这个元素的SelectedItem属性ReadOnly?
答案 0 :(得分:46)
您不应该直接处理SelectedItem属性,将IsSelected
绑定到viewmodel上的属性并跟踪所选项目。
草图:
<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));
}
}
答案 1 :(得分: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;
}
}
}
将包含该类的名称空间声明添加到XAML并按如下方式绑定(本地是我命名名称空间声明的方式):
<TreeView ItemsSource="{Binding Path=Root.Children}"
local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
现在,您可以绑定所选项目,并在视图模型中将其设置为以编程方式更改它,如果该要求出现的话。当然,这是假设您在该特定属性上实现INotifyPropertyChanged。
答案 2 :(得分:6)
以MVVM可接受的方式解决此问题的一种非常不寻常但非常有效的方法如下:
SelectedSomething
属性。这个ContentControl将会#34;持有&#34;选定的对象并处理它的绑定,OneWayToSource; SelectedItemChanged
,并在代码隐藏中添加处理程序,将ContentControl.Content设置为新选择的项目。XAML:
<ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/>
<TreeView ItemsSource="{Binding SomeCollection}"
SelectedItemChanged="TreeView_SelectedItemChanged">
代码背后:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItemHelper.Content = e.NewValue;
}
视图模型:
public object SelectedObject // Class is not actually "object"
{
get { return _selected_object; }
set
{
_selected_object = value;
RaisePropertyChanged(() => SelectedObject);
Console.WriteLine(SelectedObject);
}
}
object _selected_object;
答案 3 :(得分:4)
使用这不起作用。见编辑。OneWayToSource
绑定模式。
编辑:根据this question,看起来这是微软的错误或“按设计”行为;但是,有一些解决方法。这些是否适合您的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。
我们可能有一天会选择扩展绑定功能 这两种情况。我们经常被问到这些问题。在 换句话说,您的请求已经在我们的功能列表中 在将来的版本中考虑。
感谢您的反馈。
答案 4 :(得分:2)
我决定使用代码隐藏和viewmodel代码的组合。 xaml是这样的:
<TreeView
Name="tvCountries"
ItemsSource="{Binding Path=Countries}"
ItemTemplate="{StaticResource ResourceKey=countryTemplate}"
SelectedValuePath="Name"
SelectedItemChanged="tvCountries_SelectedItemChanged">
背后的代码
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;
}
}
在viewmodel中有一个TreeItemSelected对象,您可以在viewmodel中访问该对象。
答案 5 :(得分:1)
您始终可以创建使用ICommand的DependencyProperty并在TreeView上侦听SelectedItemChanged事件。这可能比绑定IsSelected有点容易,但我想你会因为其他原因而最终结束IsSelected。如果您只想在IsSelected上绑定,只要IsSelected发生更改,您就可以始终让项目发送消息。然后,您可以在程序中的任何位置收听这些消息。