您可能会认为它是
SelectedItem
,但显然不存在,因此是只读的,因此无法使用。这就是我想要做的:
<TreeView ItemsSource="{Binding Path=Model.Clusters}"
ItemTemplate="{StaticResource ClusterTemplate}"
SelectedItem="{Binding Path=Model.SelectedCluster}" />
我想将
SelectedItem
绑定到我模型的属性。这给了我错误:
'SelectedItem'属性是只读的,无法从标记中设置。
编辑:
好,这是我解决此问题的方法:
<TreeView
ItemsSource="{Binding Path=Model.Clusters}"
ItemTemplate="{StaticResource HoofdCLusterTemplate}"
SelectedItemChanged="TreeView_OnSelectedItemChanged" />
,在我的xaml的代码隐藏文件中:
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
Model.SelectedCluster = (Cluster)e.NewValue;
}
>
#1 楼
我知道这已经有一个可以接受的答案,但是我将其汇总以解决问题。它使用了与Delta解决方案类似的思想,但是不需要将TreeView子类化:public class BindableSelectedItemBehavior : Behavior<TreeView>
{
#region SelectedItem Property
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this.SelectedItem = e.NewValue;
}
}
然后您可以在XAML中将其用作:
<TreeView>
<e:Interaction.Behaviors>
<behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
</e:Interaction.Behaviors>
</TreeView>
希望能对某人有所帮助!
评论
正如布伦特指出的那样,我还需要向绑定添加Mode = TwoWay。我不是“混合器”,所以对System.Windows.Interactivity的Behavior <>类不熟悉。该程序集是Expression Blend的一部分。对于不想购买/安装试用版以获得此程序集的用户,可以下载BlendSystemSDK,其中包括System.Windows.Interactivity。适用于3.5的BlendSDK 3 ...我认为它是适用于4.0的BlendSDK 4。注意:这仅允许您获取选中的项目,而不允许您设置选中的项目
–迈克·罗利(Mike Rowley)
2011年6月17日在20:53
您还可以通过FrameworkPropertyMetadata(null,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,OnSelectedItemChanged))替换UIPropertyMetadata。
–Filimindji
2011-12-04 2:50
这将是解决问题的一种方法:stackoverflow.com/a/18700099/4227
– bitbonk
2013年9月9日14:07在
@Lukas完全与上面的XAML代码片段所示。只需将{Binding SelectedItem,Mode = TwoWay}替换为{Binding MyViewModelField,Mode = TwoWay}
–史蒂夫·格里特雷克斯
2013年12月11日17:22
@Pascal是xmlns:e =“ http://schemas.microsoft.com/expression/2010/interactivity”
–史蒂夫·格里特雷克斯
2014年6月11日下午6:39
#2 楼
该属性存在:TreeView.SelectedItem,但是它是只读的,因此您不能通过绑定分配它,只能检索它
评论
我接受此答案,因为在那里找到了此链接,该链接可以转到我自己的答案:msdn.microsoft.com/en-us/library/ms788714.aspx
– Natrium
09年6月16日在11:56
因此,当用户选择一个项目(又名OneWayToSource)时,我是否可以让TreeView.SelectedItem影响模型上的属性?
– Shimmy Weitzhandler
17年11月21日在21:23
#3 楼
如有需要,请提供具有附加属性且没有外部依赖关系的答案!您可以创建可绑定且具有吸气剂和吸气剂的附加属性:
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}">
</TreeView>
现在您可以绑定选定的项目,还可以在视图模型中对其进行设置,以通过编程方式更改该项目(如果有此需求的话)。当然,这是假设您在该特定属性上实现了INotifyPropertyChanged。
评论
+1,这个线程最好的答案恕我直言。不依赖于System.Windows.Interactivity,并允许双向绑定(在MVVM环境中以编程方式设置)。完善。
–克里斯·雷(Chris Ray)
2013年6月26日22:02
这种方法的问题是,只有通过绑定(即从ViewModel)设置了选定项后,行为才会开始起作用。如果VM中的初始值为null,则绑定将不会更新DP值,并且不会激活该行为。您可以使用其他默认选择的项目(例如无效的项目)来解决此问题。
–马克
2014年7月17日在10:32
@Mark:实例化附加属性的UIPropertyMetadata时,只需使用new object()而不是上面的null。那问题应该消失了...
–藤壶
2014年11月4日在18:34
对于我来说,强制转换为TreeViewItem失败了,因为我正在使用按数据类型从资源中应用的HierarchicalDataTemplate。但是,如果删除ChangeSelectedItem,则可以绑定到视图模型并检索该项目。
–Casey Sebben
15年1月15日在20:21
我在投射到TreeViewItem时也遇到了问题。那时,ItemContainerGenerator仅包含对根项目的引用,但是我需要它也能够获取非根项目。如果传递对引用的引用,则转换将失败并返回null。不确定如何解决?
–鲍勃·特威(Bob Tway)
2015年11月3日17:11
#4 楼
好吧,我找到了解决方案。它可以移动混乱,以便MVVM起作用。首先添加此类:
public class ExtendedTreeView : TreeView
{
public ExtendedTreeView()
: base()
{
this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(___ICH);
}
void ___ICH(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (SelectedItem != null)
{
SetValue(SelectedItem_Property, SelectedItem);
}
}
public object SelectedItem_
{
get { return (object)GetValue(SelectedItem_Property); }
set { SetValue(SelectedItem_Property, value); }
}
public static readonly DependencyProperty SelectedItem_Property = DependencyProperty.Register("SelectedItem_", typeof(object), typeof(ExtendedTreeView), new UIPropertyMetadata(null));
}
并将其添加到您的xaml中:
<local:ExtendedTreeView ItemsSource="{Binding Items}" SelectedItem_="{Binding Item, Mode=TwoWay}">
.....
</local:ExtendedTreeView>
评论
到目前为止,这是唯一为我工作的东西。我真的很喜欢这个解决方案。
– Rachael
13年8月8日在19:32
不知道为什么,但是它对我不起作用:(我成功地从树上获取了所选项目,反之亦然-从树外更改了所选项目。
– Erez
2013年9月24日7:33
将依赖项属性设置为BindsTwoWayByDefault会更加整洁,那么您无需在XAML中指定TwoWay
–斯蒂芬·霍尔特(Stephen Holt)
18-10-2在15:43
这是最好的方法。它不使用交互性引用,不使用背后的代码,不像某些行为那样发生内存泄漏。谢谢。
–亚历山大·迪库(Alexandru Dicu)
2月17日19:01
如前所述,该解决方案不适用于双向绑定。如果在视图模型中设置该值,则更改不会传播到TreeView。
–理查德·摩尔(Richard Moore)
6月17日下午16:46
#5 楼
它的回答比OP期望的要多。。。但我希望它至少可以对某些人有所帮助。如果要在
ICommand
发生变化时执行SelectedItem
,则可以将命令绑定到要这样做:
1-添加对
SelectedItem
的引用/>
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
2-将命令绑定到事件
ViewModel
<TreeView x:Name="myTreeView" Margin="1"
ItemsSource="{Binding Directories}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding SomeCommand}"
CommandParameter="
{Binding ElementName=myTreeView
,Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<!-- ... -->
</TreeView.ItemTemplate>
</TreeView>
评论
可以从NuGet安装参考System.Windows.Interactivity:nuget.org/packages/System.Windows.Interactivity.WPF
–李俊乐
16年5月27日在14:28
我已经尝试解决这个问题好几个小时了,已经实现了,但是我的命令不起作用,请您能帮我吗?
– Alfie
18年9月5日在9:24
Microsoft于2018年底引入了WPF的XAML行为。它可以代替System.Windows.Interactivity使用。它为我工作(与.NET Core项目一起试用)。要进行设置,只需添加Microsoft.Xaml.Behaviors.Wpf nuget包,将名称空间更改为xmlns:i =“ http://schemas.microsoft.com/xaml/behaviors”。要获取更多信息-请参阅博客
–rychlmoj
2月3日,7:25
#6 楼
仅使用绑定和GalaSoft MVVM Light库的EventToCommand,就可以“更精细”的方式完成此操作。在您的VM中,添加一个将在更改所选项目时调用的命令,并初始化该命令以执行所需的任何操作。在此示例中,我使用了RelayCommand,将仅设置SelectedCluster属性。public class ViewModel
{
public ViewModel()
{
SelectedClusterChanged = new RelayCommand<Cluster>( c => SelectedCluster = c );
}
public RelayCommand<Cluster> SelectedClusterChanged { get; private set; }
public Cluster SelectedCluster { get; private set; }
}
然后在xaml中添加EventToCommand行为。使用blend真的很容易。
<TreeView
x:Name="lstClusters"
ItemsSource="{Binding Path=Model.Clusters}"
ItemTemplate="{StaticResource HoofdCLusterTemplate}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding SelectedClusterChanged}" CommandParameter="{Binding ElementName=lstClusters,Path=SelectedValue}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
评论
这是一个不错的解决方案,尤其是如果您已经在使用MvvmLight工具包。但是,它不能解决设置所选节点并使树视图更新所选内容的问题。
–keft
2015年10月5日14:13
#7 楼
一切都很复杂...使用Caliburn Micro(http://caliburnmicro.codeplex.com/)查看:
<TreeView Micro:Message.Attach="[Event SelectedItemChanged] = [Action SetSelectedItem($this.SelectedItem)]" />
ViewModel :
public void SetSelectedItem(YourNodeViewModel item) {};
评论
是的...在TreeView上设置SelectedItem的部分在哪里?
–mnn
13年5月3日在13:15
卡利本很优雅。对于嵌套层次结构非常容易工作
–Purusartha
17年9月6日在1:29
#8 楼
我碰到了该页面,寻找与原始作者相同的答案,并且证明总是有不止一种方法,对我来说,解决方案比到目前为止提供的答案还要容易,所以我想我也可以添加绑定的动机是保持其美观和MVVM。 ViewModel的可能用法是拥有一个带有名称的属性,例如“ CurrentThingy”,而在其他地方,DataContext在其他地方绑定到“ CurrentThingy”。
而不是通过为了支持从TreeView到我的模型的良好绑定,然后从其他东西到我的模型的良好绑定,需要执行其他步骤(例如:自定义行为,第三者控制),我的解决方案是使用简单的Element绑定,将另一件事绑定到TreeView.SelectedItem,而不是将其他东西绑定到我的ViewModel,从而跳过了所需的额外工作。
XAML:
<TreeView x:Name="myTreeView" ItemsSource="{Binding MyThingyCollection}">
.... stuff
</TreeView>
<!-- then.. somewhere else where I want to see the currently selected TreeView item: -->
<local:MyThingyDetailsView
DataContext="{Binding ElementName=myTreeView, Path=SelectedItem}" />
当然,这很棒用于读取当前选择的项目,但不进行设置,这是我所需要的。
评论
什么是本地:MyThingyDetailsView?我得到的是本地信息:MyThingyDetailsView包含选定的项目,但是您的视图模型如何获取此信息?这看起来像是一种不错的方法,但是我只需要更多信息...
–鲍勃·霍恩(Bob Horn)
2012年2月4日在4:03
local:MyThingyDetailsView只是一个XAML的UserControl,构成了有关一个“ thingy”实例的详细信息视图。它作为内容嵌入在另一个视图的中间,该视图的DataContext是当前选择的树视图项,使用元素绑定。
– Wes
2012年2月14日下午6:21
#9 楼
您也许还可以使用TreeViewItem.IsSelected属性评论
我认为这可能是正确的答案。但是我想看看有关Items的IsSelected属性如何传递到TreeView的示例或最佳实践建议。
–anhoppe
16 Sep 26'在11:57
#10 楼
还有一种无需使用Interaction.Behaviors即可创建XAML可绑定SelectedItem属性的方法。public static class BindableSelectedItemHelper
{
#region Properties
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(BindableSelectedItemHelper),
new FrameworkPropertyMetadata(null, OnSelectedItemPropertyChanged));
public static readonly DependencyProperty AttachProperty = DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(BindableSelectedItemHelper), new PropertyMetadata(false, Attach));
private static readonly DependencyProperty IsUpdatingProperty = DependencyProperty.RegisterAttached("IsUpdating", typeof(bool), typeof(BindableSelectedItemHelper));
#endregion
#region Implementation
public static void SetAttach(DependencyObject dp, bool value)
{
dp.SetValue(AttachProperty, value);
}
public static bool GetAttach(DependencyObject dp)
{
return (bool)dp.GetValue(AttachProperty);
}
public static string GetSelectedItem(DependencyObject dp)
{
return (string)dp.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject dp, object value)
{
dp.SetValue(SelectedItemProperty, value);
}
private static bool GetIsUpdating(DependencyObject dp)
{
return (bool)dp.GetValue(IsUpdatingProperty);
}
private static void SetIsUpdating(DependencyObject dp, bool value)
{
dp.SetValue(IsUpdatingProperty, value);
}
private static void Attach(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
TreeListView treeListView = sender as TreeListView;
if (treeListView != null)
{
if ((bool)e.OldValue)
treeListView.SelectedItemChanged -= SelectedItemChanged;
if ((bool)e.NewValue)
treeListView.SelectedItemChanged += SelectedItemChanged;
}
}
private static void OnSelectedItemPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
TreeListView treeListView = sender as TreeListView;
if (treeListView != null)
{
treeListView.SelectedItemChanged -= SelectedItemChanged;
if (!(bool)GetIsUpdating(treeListView))
{
foreach (TreeViewItem item in treeListView.Items)
{
if (item == e.NewValue)
{
item.IsSelected = true;
break;
}
else
item.IsSelected = false;
}
}
treeListView.SelectedItemChanged += SelectedItemChanged;
}
}
private static void SelectedItemChanged(object sender, RoutedEventArgs e)
{
TreeListView treeListView = sender as TreeListView;
if (treeListView != null)
{
SetIsUpdating(treeListView, true);
SetSelectedItem(treeListView, treeListView.SelectedItem);
SetIsUpdating(treeListView, false);
}
}
#endregion
}
然后可以在XAML中将其用作:
<TreeView helper:BindableSelectedItemHelper.Attach="True"
helper:BindableSelectedItemHelper.SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
#11 楼
我尝试了此问题的所有解决方案。没有人完全解决我的问题。因此,我认为最好将此类继承的类与重新定义的属性SelectedItem一起使用。如果您从GUI中选择树元素,并且在代码中设置了此属性值,它将非常有效。public class TreeViewEx : TreeView
{
public TreeViewEx()
{
this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeViewEx_SelectedItemChanged);
}
void TreeViewEx_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this.SelectedItem = e.NewValue;
}
#region SelectedItem
/// <summary>
/// Gets or Sets the SelectedItem possible Value of the TreeViewItem object.
/// </summary>
public new object SelectedItem
{
get { return this.GetValue(TreeViewEx.SelectedItemProperty); }
set { this.SetValue(TreeViewEx.SelectedItemProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public new static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(TreeViewEx),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedItemProperty_Changed));
static void SelectedItemProperty_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
TreeViewEx targetObject = dependencyObject as TreeViewEx;
if (targetObject != null)
{
TreeViewItem tvi = targetObject.FindItemNode(targetObject.SelectedItem) as TreeViewItem;
if (tvi != null)
tvi.IsSelected = true;
}
}
#endregion SelectedItem
public TreeViewItem FindItemNode(object item)
{
TreeViewItem node = null;
foreach (object data in this.Items)
{
node = this.ItemContainerGenerator.ContainerFromItem(data) as TreeViewItem;
if (node != null)
{
if (data == item)
break;
node = FindItemNodeInChildren(node, item);
if (node != null)
break;
}
}
return node;
}
protected TreeViewItem FindItemNodeInChildren(TreeViewItem parent, object item)
{
TreeViewItem node = null;
bool isExpanded = parent.IsExpanded;
if (!isExpanded) //Can't find child container unless the parent node is Expanded once
{
parent.IsExpanded = true;
parent.UpdateLayout();
}
foreach (object data in parent.Items)
{
node = parent.ItemContainerGenerator.ContainerFromItem(data) as TreeViewItem;
if (data == item && node != null)
break;
node = FindItemNodeInChildren(node, item);
if (node != null)
break;
}
if (node == null && parent.IsExpanded != isExpanded)
parent.IsExpanded = isExpanded;
if (node != null)
parent.IsExpanded = true;
return node;
}
}
评论
如果某些节点不调用UpdateLayout()和IsExpanded,它将更快。什么时候不需要调用UpdateLayout()和IsExpanded?当树项目以前被访问过时。怎么知道对于未访问的节点,ContainerFromItem()返回null。因此,只有在ContainerFromItem()为子代返回null时,才可以扩展父节点。
–CoperNick
2013年10月9日14:53
#12 楼
我的需求是基于PRISM-MVVM的解决方案,其中需要TreeView并且绑定对象的类型为Collection <>,因此需要HierarchicalDataTemplate。默认的BindableSelectedItemBehavior将无法识别子TreeViewItem。要使其在这种情况下正常工作。public class BindableSelectedItemBehavior : Behavior<TreeView>
{
#region SelectedItem Property
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var behavior = sender as BindableSelectedItemBehavior;
if (behavior == null) return;
var tree = behavior.AssociatedObject;
if (tree == null) return;
if (e.NewValue == null)
foreach (var item in tree.Items.OfType<TreeViewItem>())
item.SetValue(TreeViewItem.IsSelectedProperty, false);
var treeViewItem = e.NewValue as TreeViewItem;
if (treeViewItem != null)
treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
else
{
var itemsHostProperty = tree.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (itemsHostProperty == null) return;
var itemsHost = itemsHostProperty.GetValue(tree, null) as Panel;
if (itemsHost == null) return;
foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
{
if (WalkTreeViewItem(item, e.NewValue))
break;
}
}
}
public static bool WalkTreeViewItem(TreeViewItem treeViewItem, object selectedValue)
{
if (treeViewItem.DataContext == selectedValue)
{
treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
treeViewItem.Focus();
return true;
}
var itemsHostProperty = treeViewItem.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (itemsHostProperty == null) return false;
var itemsHost = itemsHostProperty.GetValue(treeViewItem, null) as Panel;
if (itemsHost == null) return false;
foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
{
if (WalkTreeViewItem(item, selectedValue))
break;
}
return false;
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this.SelectedItem = e.NewValue;
}
}
这使得可以遍历所有元素,而与级别无关。
评论
谢谢!这是唯一适用于我的方案的方案,与您的方案相同。
–罗伯特
16年5月21日在20:42
效果很好,并且不会导致所选/扩展的绑定变得混乱。
–生锈
17 Mar 3 '17 at 11:16
#13 楼
我建议对Steve Greatrex提供的行为进行补充。他的行为无法反映出源头的变化,因为它可能不是TreeViewItems的集合。因此,只需在树中查找数据上下文是源中的selectedValue的TreeViewItem。
TreeView具有一个名为“ ItemsHost”的受保护属性,该属性保存TreeViewItem集合。我们可以通过反射获得它,然后在树上搜寻所选项目。
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var behavior = sender as BindableSelectedItemBehaviour;
if (behavior == null) return;
var tree = behavior.AssociatedObject;
if (tree == null) return;
if (e.NewValue == null)
foreach (var item in tree.Items.OfType<TreeViewItem>())
item.SetValue(TreeViewItem.IsSelectedProperty, false);
var treeViewItem = e.NewValue as TreeViewItem;
if (treeViewItem != null)
{
treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
}
else
{
var itemsHostProperty = tree.GetType().GetProperty("ItemsHost", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (itemsHostProperty == null) return;
var itemsHost = itemsHostProperty.GetValue(tree, null) as Panel;
if (itemsHost == null) return;
foreach (var item in itemsHost.Children.OfType<TreeViewItem>())
if (WalkTreeViewItem(item, e.NewValue)) break;
}
}
public static bool WalkTreeViewItem(TreeViewItem treeViewItem, object selectedValue) {
if (treeViewItem.DataContext == selectedValue)
{
treeViewItem.SetValue(TreeViewItem.IsSelectedProperty, true);
treeViewItem.Focus();
return true;
}
foreach (var item in treeViewItem.Items.OfType<TreeViewItem>())
if (WalkTreeViewItem(item, selectedValue)) return true;
return false;
}
这种方式适用于双向绑定。另外,也可以将ItemsHost的获取移至Behavior的OnAttached方法,从而节省每次绑定更新时使用反射的开销。
#14 楼
WPF MVVM TreeView SelectedItem...是一个更好的答案,但没有提及在ViewModel中获取/设置SelectedItem的方法。
将IsSelected布尔属性添加到ItemViewModel,并在TreeViewItem的样式设置器中将其绑定。
将SelectedItem属性添加到ViewModel中,用作TreeView的DataContext。这是上面解决方案中缺少的部分。
' ItemVM... Public Property IsSelected As Boolean Get Return _func.SelectedNode Is Me End Get Set(value As Boolean) If IsSelected value Then _func.SelectedNode = If(value, Me, Nothing) End If RaisePropertyChange() End Set End Property ' TreeVM... Public Property SelectedItem As ItemVM Get Return _selectedItem End Get Set(value As ItemVM) If _selectedItem Is value Then Return End If Dim prev = _selectedItem _selectedItem = value If prev IsNot Nothing Then prev.IsSelected = False End If If _selectedItem IsNot Nothing Then _selectedItem.IsSelected = True End If End Set End Property
<TreeView ItemsSource="{Binding Path=TreeVM}"
BorderBrush="Transparent">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
#15 楼
经过一天的互联网学习,我找到了在普通的WPF / C#环境中创建普通树形视图后选择项目的解决方案。private void BuildSortTree(int sel)
{
MergeSort.Items.Clear();
TreeViewItem itTemp = new TreeViewItem();
itTemp.Header = SortList[0];
MergeSort.Items.Add(itTemp);
TreeViewItem prev;
itTemp.IsExpanded = true;
if (0 == sel) itTemp.IsSelected= true;
prev = itTemp;
for(int i = 1; i<SortList.Count; i++)
{
TreeViewItem itTempNEW = new TreeViewItem();
itTempNEW.Header = SortList[i];
prev.Items.Add(itTempNEW);
itTempNEW.IsExpanded = true;
if (i == sel) itTempNEW.IsSelected = true;
prev = itTempNEW ;
}
}
#16 楼
也可以使用TreeView项的IsSelected属性完成此操作。这就是我的管理方式,public delegate void TreeviewItemSelectedHandler(TreeViewItem item);
public class TreeViewItem
{
public static event TreeviewItemSelectedHandler OnItemSelected = delegate { };
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
if (value)
OnItemSelected(this);
}
}
}
然后在包含ModelView绑定数据的ViewModel中,只需订阅TreeViewItem类中的事件即可。
TreeViewItem.OnItemSelected += TreeViewItemSelected;
最后,在同一ViewModel中实现此处理程序,
private void TreeViewItemSelected(TreeViewItem item)
{
//Do something
}
当然还有绑定,
/>
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
评论
这实际上是一个被低估的解决方案。通过更改思维方式并绑定每个treeview元素的IsSelected属性,并冒泡IsSelected事件,您可以使用内置功能,该功能可以很好地与双向绑定结合使用。我已经尝试了许多针对此问题的建议解决方案,这是第一个可行的方案。连接起来有点复杂。谢谢。
–理查德·摩尔(Richard Moore)
6月17日下午16:48
#17 楼
我知道此线程已有10年历史了,但问题仍然存在。...原始问题是“检索”所选项目。我还需要“获取”我的视图模型中的选定项目(不设置它)。在该主题的所有答案中,“ Wes”一词是唯一以不同方式解决该问题的答案:如果可以将“ Selected Item”用作数据绑定的目标,则可以将其用作数据绑定的源。我们对另一个view属性进行了处理,我将对viewmodel属性进行了处理:
我们需要做两件事:
在viewmodel中创建一个依赖项属性(在我的类型为“ MyObject”的情况下,因为我的树视图绑定到“ MyObject”类型的对象)
从Treeview.SelectedItem绑定到视图的构造函数中的此属性(是,后面有代码,但是很可能是您也将在其中初始化数据上下文)
视图模型:
public static readonly DependencyProperty SelectedTreeViewItemProperty = DependencyProperty.Register("SelectedTreeViewItem", typeof(MyObject), typeof(MyViewModel), new PropertyMetadata(OnSelectedTreeViewItemChanged));
private static void OnSelectedTreeViewItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as MyViewModel).OnSelectedTreeViewItemChanged(e);
}
private void OnSelectedTreeViewItemChanged(DependencyPropertyChangedEventArgs e)
{
//do your stuff here
}
public MyObject SelectedWorkOrderTreeViewItem
{
get { return (MyObject)GetValue(SelectedTreeViewItemProperty); }
set { SetValue(SelectedTreeViewItemProperty, value); }
}
视图构造函数:
Binding binding = new Binding("SelectedItem")
{
Source = treeView, //name of tree view in xaml
Mode = BindingMode.OneWay
};
BindingOperations.SetBinding(DataContext, MyViewModel.SelectedTreeViewItemProperty, binding);
#18 楼
(让所有人都同意,TreeView在此问题上显然被破坏了。绑定到SelectedItem将会是显而易见的。感叹)我需要解决方案才能与TreeViewItem的IsSelected属性正确交互,所以这里是我是怎么做到的:
// the Type CustomThing needs to implement IsSelected with notification
// for this to work.
public class CustomTreeView : TreeView
{
public CustomThing SelectedCustomThing
{
get
{
return (CustomThing)GetValue(SelectedNode_Property);
}
set
{
SetValue(SelectedNode_Property, value);
if(value != null) value.IsSelected = true;
}
}
public static DependencyProperty SelectedNode_Property =
DependencyProperty.Register(
"SelectedCustomThing",
typeof(CustomThing),
typeof(CustomTreeView),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.None,
SelectedNodeChanged));
public CustomTreeView(): base()
{
this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(SelectedItemChanged_CustomHandler);
}
void SelectedItemChanged_CustomHandler(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SetValue(SelectedNode_Property, SelectedItem);
}
private static void SelectedNodeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var treeView = d as CustomTreeView;
var newNode = e.NewValue as CustomThing;
treeView.SelectedCustomThing = (CustomThing)e.NewValue;
}
}
使用此XAML:
<local:CustonTreeView ItemsSource="{Binding TreeRoot}"
SelectedCustomThing="{Binding SelectedNode,Mode=TwoWay}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</local:CustonTreeView>
#19 楼
我为您带来了具有以下功能的解决方案:支持2种绑定方式
自动更新TreeViewItem.IsSelected属性(根据SelectedItem)
没有TreeView子类化
绑定到ViewModel的项目可以是任何类型(甚至为null)
1 /将以下代码粘贴到CS中:
public class BindableSelectedItem
{
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.RegisterAttached(
"SelectedItem", typeof(object), typeof(BindableSelectedItem), new PropertyMetadata(default(object), OnSelectedItemPropertyChangedCallback));
private static void OnSelectedItemPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var treeView = d as TreeView;
if (treeView != null)
{
BrowseTreeViewItems(treeView, tvi =>
{
tvi.IsSelected = tvi.DataContext == e.NewValue;
});
}
else
{
throw new Exception("Attached property supports only TreeView");
}
}
public static void SetSelectedItem(DependencyObject element, object value)
{
element.SetValue(SelectedItemProperty, value);
}
public static object GetSelectedItem(DependencyObject element)
{
return element.GetValue(SelectedItemProperty);
}
public static void BrowseTreeViewItems(TreeView treeView, Action<TreeViewItem> onBrowsedTreeViewItem)
{
var collectionsToVisit = new System.Collections.Generic.List<Tuple<ItemContainerGenerator, ItemCollection>> { new Tuple<ItemContainerGenerator, ItemCollection>(treeView.ItemContainerGenerator, treeView.Items) };
var collectionIndex = 0;
while (collectionIndex < collectionsToVisit.Count)
{
var itemContainerGenerator = collectionsToVisit[collectionIndex].Item1;
var itemCollection = collectionsToVisit[collectionIndex].Item2;
for (var i = 0; i < itemCollection.Count; i++)
{
var tvi = itemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
if (tvi == null)
{
continue;
}
if (tvi.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
{
collectionsToVisit.Add(new Tuple<ItemContainerGenerator, ItemCollection>(tvi.ItemContainerGenerator, tvi.Items));
}
onBrowsedTreeViewItem(tvi);
}
collectionIndex++;
}
}
}
2 /在您的XAML文件中使用的示例
<TreeView myNS:BindableSelectedItem.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />
#20 楼
我提出了这种解决方案(我认为这是最简单且无内存泄漏的解决方案),该解决方案非常适合从View的选定项更新ViewModel的选定项。请注意,从ViewModel更改选定的项不会t更新视图的选定项。
public class TreeViewEx : TreeView
{
public static readonly DependencyProperty SelectedItemExProperty = DependencyProperty.Register("SelectedItemEx", typeof(object), typeof(TreeViewEx), new FrameworkPropertyMetadata(default(object))
{
BindsTwoWayByDefault = true // Required in order to avoid setting the "BindingMode" from the XAML
});
public object SelectedItemEx
{
get => GetValue(SelectedItemExProperty);
set => SetValue(SelectedItemExProperty, value);
}
protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
{
SelectedItemEx = e.NewValue;
}
}
XAML用法
<l:TreeViewEx ItemsSource="{Binding Path=Items}" SelectedItemEx="{Binding Path=SelectedItem}" >
评论
真烂它也打我。我来到这里是希望找到一种体面的方式,而我只是个白痴。这是我第一次对自己不是白痴感到难过。这确实使绑定概念糟透了
希望这可以帮助某些人绑定到树视图项目,然后将其更改为Icommandjacobaloysious.wordpress.com/2012/02/19/…
在绑定和MVVM方面,后面的代码不是“禁止”的,而是后面的代码应支持该视图。在我所见过的所有其他解决方案中,我认为,背后的代码是一个更好的选择,因为它仍在处理将视图“绑定”到视图模型的问题。唯一的缺点是,如果您的团队中只有一名设计师使用XAML工作,那么后面的代码可能会被破坏/忽略。只需花10秒钟即可实施一个解决方案,这是一个很小的代价。
最简单的解决方案之一可能是:stackoverflow.com/questions/1238304/…