Привязка данных к SelectedItem в древовидном представлении WPF
Как я могу получить элемент, выбранный в WPF-дереве? Я хочу сделать это в XAML, потому что я хочу связать это.
Вы можете подумать, что это 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" />
и в codebehindfile моего xaml:
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
Model.SelectedCluster = (Cluster)e.NewValue;
}
23 ответа
Я понимаю, что этот ответ уже принят, но я собрал его вместе, чтобы решить проблему. Он использует идею, аналогичную решению 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>
Надеюсь, это кому-нибудь поможет!
Это свойство существует: TreeView.SelectedItem
Но это только для чтения, поэтому вы не можете назначить его через привязку, только получить его
Ответьте с прикрепленными свойствами и без внешних зависимостей, если необходимость когда-либо возникнет!
Вы можете создать присоединяемое свойство, которое является привязываемым и имеет геттер и сеттер:
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 для этого конкретного свойства.
Ну, я нашел решение. Он перемещает беспорядок, так что 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>
Он отвечает немного больше, чем ожидает OP... Но я надеюсь, что он может помочь кому-то, по крайней мере.
Если вы хотите выполнить ICommand
всякий раз, когда SelectedItem
изменено, вы можете связать команду на событие и использование свойства SelectedItem
в ViewModel
больше не нужен
Для этого:
1- Добавить ссылку на System.Windows.Interactivity
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
2- Привязать команду к событию SelectedItemChanged
<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>
Это может быть выполнено "более хорошим" способом, используя только привязку и EventToCommand библиотеки GalaSoft MVVM Light. В вашей виртуальной машине добавьте команду, которая будет вызываться при изменении выбранного элемента, и инициализируйте команду для выполнения любого необходимого действия. В этом примере я использовал 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; }
}
Затем добавьте поведение EventToCommand в ваш xaml. Это действительно легко, используя смесь.
<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>
Все к сложному... Пойдите с Калибурном Микро (http://caliburnmicro.codeplex.com/)
Посмотреть:
<TreeView Micro:Message.Attach="[Event SelectedItemChanged] = [Action SetSelectedItem($this.SelectedItem)]" />
ViewModel:
public void SetSelectedItem(YourNodeViewModel item) {};
Я наткнулся на эту страницу в поисках того же ответа, что и первоначальный автор, и доказав, что всегда есть более чем один способ сделать это, решение для меня было даже проще, чем ответы, представленные здесь до сих пор, поэтому я подумал, что я мог бы также добавить в кучу.
Мотивация для привязки состоит в том, чтобы держать это хорошим & 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}" />
Конечно, это отлично подходит для чтения выбранного в данный момент элемента, но не для его настройки, и это все, что мне нужно.
Вы также можете использовать свойство TreeViewItem.IsSelected
Моим требованием было решение на основе 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;
}
}
Это позволяет перебирать все элементы независимо от уровня.
Существует также способ создания привязываемого свойства XAML SelectedItem без использования Interaction.Behaviors.
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}">
Я предлагаю дополнение к поведению, предоставленному Стивом Грейтрексом. Его поведение не отражает изменений из источника, потому что это не может быть коллекция TreeViewItems. Таким образом, нужно найти TreeViewItem в дереве, для которого datacontext является выбранным значением из источника. 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 в метод OnAttached поведения, сохраняя издержки на использование отражения каждый раз, когда привязка обновляется.
Я перепробовал все решения этих вопросов. Никто не решил мою проблему полностью. Поэтому я думаю, что лучше использовать такой унаследованный класс с переопределенным свойством 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;
}
}
WPF MVVM TreeView SelectedItem
... является лучшим ответом, но не упоминает способ получить / установить SelectedItem во ViewModel.
- Добавьте логическое свойство IsSelected в ItemViewModel и привяжите его в Setter Style для TreeViewItem.
- Добавьте свойство SelectedItem в вашу ViewModel, используемую в качестве DataContext для TreeView. Это недостающая часть в решении выше.
'ItemVM... Открытое свойство IsSelected As Boolean Get Return _func.SelectedNode Is Me End Get Set(значение As Boolean) If IsSelected value Then _func.SelectedNode = If(value, Me, Nothing) End If RaisePropertyChange() End Set End End Property ' TreeVM... Открытое свойство SelectedItem As ItemVM Get Return _selectedItem End Get Set(значение As ItemVM) Если _selectedItem Is значение, затем Return End, если Dim prev = _selectedItem _selectedItem = значение, если prev IsNot Nothing Then prev.IsSelected = False End, если _selectedItem IsNot Nothing Then _selectedItem.IsSelected = True End If End Установить свойство End End
<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>
Я предлагаю вам свое решение, которое предлагает следующие возможности:
Поддерживает 2 способа привязки
Автоматически обновляет свойства TreeViewItem.IsSelected (согласно SelectedItem)
Нет TreeView подклассов
Элементы, привязанные к ViewModel, могут быть любого типа (даже нулевые)
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}" />
Я знаю, что этой теме 10 лет, но проблема все еще существует....
Первоначальный вопрос заключался в том, чтобы "получить" выбранный элемент. Мне также нужно было "получить" выбранный элемент в моей модели просмотра (а не устанавливать его). Из всех ответов в этой ветке только ответ "Уэса" подходит к проблеме иначе: если вы можете использовать "Выбранный элемент" в качестве цели для привязки данных, используйте его как источник для привязки данных. Вес сделал это с другим свойством представления, я сделаю это со свойством модели представления:
Нам понадобятся две вещи:
- Создайте свойство зависимости в модели просмотра (в моем случае типа MyObject, поскольку мое древовидное представление привязано к объекту типа MyObject)
- Выполните привязку из Treeview.SelectedItem к этому свойству в конструкторе View (да, это код, но вполне вероятно, что вы также инициализируете свой контекст данных там)
Viewmodel:
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);
Это также можно сделать с помощью свойства IsSelected элемента TreeView. Вот как мне это удалось,
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);
}
}
}
Затем в ViewModel, которая содержит данные, с которыми связан TreeView, просто подпишитесь на событие в классе TreeViewItem.
TreeViewItem.OnItemSelected += TreeViewItemSelected;
И, наконец, реализовать этот обработчик в той же ViewModel,
private void TreeViewItemSelected(TreeViewItem item)
{
//Do something
}
И обязательна, конечно,
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
После изучения Интернета в течение одного дня я нашел свое собственное решение для выбора элемента после создания нормального дерева в обычной среде 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 ;
}
}
Я предлагаю это решение (которое я считаю самым простым и свободным от утечек памяти), которое отлично работает для обновления выбранного элемента ViewModel из выбранного элемента View.
Обратите внимание, что изменение выбранного элемента в ViewModel не обновляет выбранный элемент View.
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}" >
Если XAML имеет
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
Что плохого в том, чтобы просто найти этот элемент в списке? Я также использую
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
Чтобы убедиться, что когда я устанавливаю IsSelected = true в виртуальной машине, родители расширяются.
(Давайте просто согласимся, что TreeView явно обанкротился из-за этой проблемы. Привязка к SelectedItem была бы очевидна. Вздох)
Мне нужно было решение для правильного взаимодействия со свойством IsSelected TreeViewItem, поэтому вот как я это сделал:
// 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>
При нажатии на какой-либо список элементов вы получите данные в свойстве «Выбрано».
ВидМодель :
public class ShellViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private ObservableCollection<Books> _books;
private List<Books> bookList = new List<Books>();
public ObservableCollection<Books> Books
{
get { return _books; }
set { _books = value; NotifyPropertyChanged("Books"); }
}
private Books books;
public Books Selected
{
get { return books; }
set { books = value; }
}
public ShellViewModel()
{
bookList = new List<Books>()
{
new Books{BookName = "Harry Poter",Price ="15$"},
new Books{BookName = "Harry Poter 2 ",Price ="14.95$"},
new Books{BookName = "Harry Poter 3",Price ="18.50$"},
new Books{BookName = "Harry Poter 4",Price ="32.90$"},
};
Books = new ObservableCollection<Books>(bookList);
}
}
public class Books
{
public string BookName { get; set; }
public string Price { get; set; }
}
XAML:
<ListView x:Name="lst" Grid.Row="2" ItemsSource="{Binding Books}" SelectedItem="{Binding Selected}">
<ListView.View>
<GridView >
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding BookName}" />
<GridViewColumn Header="Price" Width="100" DisplayMemberBinding="{Binding Price}"/>
</GridView>
</ListView.View>
</ListView>
Я понимаю, что прошло некоторое время с тех пор, как это было опубликовано, но FWIW я использую RadTreeView от Telerik, и SelectedItem, кажется, работает нормально - либо проблема была устранена за это время, либо Telerik поработал над ней для нас.