我看到这个问题,但使用给定的答案会给我编译错误,原因是我无法将对Blend SDK System.Windows.Interactivity的引用添加到我的项目中。它说“未预加载未知错误系统.windows”,我还没有想出如何解决这个问题。
额外加分:为什么微软要将此元素的SelectedItem属性设置为只读?
您不需要直接处理SelectedItem属性,将IsSelected
绑定到视图模型上的属性并在那里跟踪所选项目即可。
草图:
<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));
}
}
SelectedItem = this
是在TreeViewItem级别上发生的,它设置了TreeViewItem的属性,而不是树形视图。我仍然需要找到那个设置了该属性的TreeViewItem,这让我又回到了起点。 - KyeoticPropertyChanged
的属性吗?你应该能够在不使用那个显然你没有的扩展方法的情况下触发它。 - H.B.TViewModel.SelectedItem
),所以当您在当前进程中有多个 TreeView 时,该解决方案将无法工作。 - bitbonk在MVVM中,解决这个问题的一个非常不寻常但相当有效的方法是:
SelectedSomething
属性。这个ContentControl将“持有”所选的对象并处理它的绑定,使用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;
您可以创建一个可绑定的附加属性,并具有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,并按以下方式进行绑定(local 是我命名的命名空间声明):
<TreeView ItemsSource="{Binding Path=Root.Children}"
local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
现在你可以将所选项绑定,并且在需要时也可以在视图模型中进行编程更改。当然,这是假设您在该特定属性上实现了INotifyPropertyChanged。
TreeViewItem
本身都是一个ItemsControl
,你不能确定它的子项是否已经生成。你需要检查ItemContainerGenerator
是否返回null,展开当前的TreeViewItem
,等待其完成生成,然后递归尝试。在一个大树中,这可能需要很长时间。 - Fredrik Hedbladbehaviors
字典会永久地保留所有曾经被附加到内存中的 TreeView(包括它们可能拥有的所有项)。请注意,这些对象将永远不会被释放回收。为避免内存泄漏,请确保正确地管理你创建的对象。 - bitbonk使用这种方法行不通。请参见编辑。OneWayToSource
绑定模式。
编辑:根据此问题,看起来这是Microsoft的一个bug或“按设计行为”。尽管如此,有一些解决方法贴出来了。这些方法是否适用于您的TreeView?
Microsoft Connect问题:https://connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings
Microsoft在2010年1月10日发布
我们无法在WPF中实现此功能,因为我们不能支持非DependencyProperties属性上的绑定。绑定的每个实例运行时状态都保存在BindingExpression中,我们将其存储在目标DependencyObject的EffectiveValueTable中。当目标属性不是DP或DP为只读时,没有地方可以存储BindingExpression。
可能我们会在未来选择扩展绑定功能到这两种情况。我们经常被问及它们。换句话说,您的请求已经在我们考虑未来版本的功能列表中。
感谢您的反馈。
我决定使用代码后台和视图模型代码的组合。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中访问它。