Связывание данных с SelectedItem в WPF Treeview

Как получить элемент, который выбран в WPF-treeview? Я хочу сделать это в XAML, потому что я хочу связать его.

Вы можете подумать, что это SelectedItem но, по-видимому, он не существует, и он не используется.

Это то, что я хочу сделать:

  

Я хочу привязать SelectedItem к свойству на моей модели.

Но это дает мне ошибку:

Свойство «SelectedItem» доступно только для чтения и не может быть установлено из разметки.

Edit: Хорошо, так я решил это:

  

и в файле codebehind моего xaml:

 private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) { Model.SelectedCluster = (Cluster)e.NewValue; } 

Я понимаю, что это уже приняло ответ, но я поставил это вместе, чтобы решить проблему. Он использует аналогичную идею для решения Delta, но без необходимости подclassа TreeView:

 public class BindableSelectedItemBehavior : Behavior { #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 e) { this.SelectedItem = e.NewValue; } } 

Затем вы можете использовать это в своем XAML как:

      

Надеюсь, это поможет кому-то!

Это свойство существует: TreeView.SelectedItem

Но он доступен только для чтения, поэтому вы не можете назначить его через привязку, только получить его

Ну, я нашел решение. Он перемещает беспорядок, так что MVVM работает.

Сначала добавьте этот class:

 public class ExtendedTreeView : TreeView { public ExtendedTreeView() : base() { this.SelectedItemChanged += new RoutedPropertyChangedEventHandler(___ICH); } void ___ICH(object sender, RoutedPropertyChangedEventArgs 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:

   .....  

Ответьте на приложенные свойства и внешние зависимости, если возникнет необходимость!

Вы можете создать прикрепленное свойство, которое может быть привязано и имеет getter и setter:

 public class TreeViewHelper { private static Dictionary behaviors = new Dictionary(); 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; } } } 

Добавьте объявление пространства имен, содержащее этот class, в ваш XAML и привяжите его следующим образом (локальный – это то, как я назвал объявление пространства имен):

    

Теперь вы можете привязать выбранный элемент, а также установить его в своей модели просмотра, чтобы изменить его программно, если это требование когда-либо возникнет. Это, конечно, предполагает, что вы реализуете INotifyPropertyChanged по этому конкретному свойству.

Это может быть выполнено «лучше», используя только привязку и EventToCommand библиотеки GalvSoft MVVM Light. В вашей VM добавьте команду, которая будет вызываться, когда выбранный элемент будет изменен, и инициализируйте команду для выполнения любого действия. В этом примере я использовал RelayCommand и просто установил свойство SelectedCluster.

 public class ViewModel { public ViewModel() { SelectedClusterChanged = new RelayCommand( c => SelectedCluster = c ); } public RelayCommand SelectedClusterChanged { get; private set; } public Cluster SelectedCluster { get; private set; } } 

Затем добавьте поведение EventToCommand в свой xaml. Это очень просто, используя смесь.

        

Он отвечает немного больше, чем ожидает OP … Но я надеюсь, что это может помочь кому-то хотя бы.

Если вы хотите выполнить ICommand всякий раз, когда SelectedItem , вы можете связать команду с событием, и использование свойства SelectedItem в ViewModel больше не требуется.

Для этого:

1- Добавить ссылку на System.Windows.Interactivity

 xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 

2- Привяжите команду к событию SelectedItemChanged

           

Все для сложного … Идите с Caliburn Micro (http://caliburnmicro.codeplex.com/)

Посмотреть:

  

ViewModel:

 public void SetSelectedItem(YourNodeViewModel item) {}; 

Я столкнулся с этой страницей, ища тот же ответ, что и оригинальный автор, и доказывая, что всегда есть более чем один способ сделать это, решение для меня было еще проще, чем ответы, представленные здесь до сих пор, поэтому я решил, что могу добавить к куче.

Мотивация для привязки – сохранить ее и MVVM. Вероятное использование ViewModel состоит в том, чтобы иметь свойство w / name, такое как «CurrentThingy», а где-то еще, DataContext для какой-то другой вещи привязан к «CurrentThingy».

Вместо того, чтобы выполнять дополнительные шаги (например: пользовательское поведение, сторонний элемент управления), чтобы поддерживать хорошее привязку от TreeView к моей модели, а затем от чего-то еще к моей модели, моим решением было использовать простой элемент, связывающий другую вещь с TreeView.SelectedItem, вместо того, чтобы привязывать другое к моей ViewModel, тем самым пропуская дополнительную работу.

XAML:

  .... stuff    

Конечно, это отлично подходит для чтения выбранного элемента, но не для его настройки, и это все, что мне нужно.

Вы также можете использовать свойство TreeViewItem.IsSelected

Существует также способ создания свойства XAML bindable 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 как:

  

Я пробовал все решения этих вопросов. Никто не решил мою проблему полностью. Поэтому я считаю, что лучше использовать такой унаследованный class с переопределенным свойством SelectedItem. Он будет отлично работать, если вы выберете элемент дерева из графического интерфейса и если вы установите это значение свойства в своем коде

 public class TreeViewEx : TreeView { public TreeViewEx() { this.SelectedItemChanged += new RoutedPropertyChangedEventHandler(TreeViewEx_SelectedItemChanged); } void TreeViewEx_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) { this.SelectedItem = e.NewValue; } #region SelectedItem ///  /// Gets or Sets the SelectedItem possible Value of the TreeViewItem object. ///  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; } } 

Я предлагаю дополнение к поведению, предоставленному Стивом Великим. Его поведение не отражает изменения в источнике, потому что это может быть не коллекция TreeViewItems. Таким образом, это вопрос поиска TreeViewItem в дереве, который datacontext является выбраннымValue из источника. TreeView имеет защищенное свойство «ItemsHost», которое содержит коллекцию TreeViewItem. Мы можем получить это через reflection и пройти дерево, которое ищет выбранный элемент.

 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()) 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()) 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()) if (WalkTreeViewItem(item, selectedValue)) return true; return false; } 

Таким образом, поведение работает для двусторонних привязок. В качестве альтернативы, можно перенести получение ItemsHost в метод OnAttached для поведения, сохраняя накладные расходы при использовании отражения каждый раз при обновлении привязки.

WPF MVVM TreeView SelectedItem

… это лучший ответ, но не упоминает способ получить / установить SelectedItem в ViewModel.

  1. Добавьте к элементу ItemViewModel свойство IsSelected boolean и привяжите его к настройке стиля для TreeViewItem.
  2. Добавьте свойство SelectedItem в ViewModel, используемый в качестве DataContext для TreeView. Это недостающая часть в вышеприведенном решении.
     «ItemVM ...
     Открытое свойство IsSelected As Boolean
         Получить
             Возrotation _func.SelectedNode Is Me
         Конец Get
         Set (значение As Boolean)
             Если значение IsSelected Then
                 _func.SelectedNode = If (значение, Me, Nothing)
             Конец Если
             RaisePropertyChange ()
         Конец
     Конец собственности
     «TreeVM ...
     Публичная собственность SelectedItem As ItemVM
         Получить
             Вернуться _selectedItem
         Конец Get
         Set (значение как ItemVM)
             Если _selectedItem Is value Then
                 Вернуть
             Конец Если
             Dim prev = _selectedItem
             _selectedItem = значение
             Если prev IsNot Nothing Then
                 prev.IsSelected = False
             Конец Если
             Если _selectedItem IsNot Nothing Then
                 _selectedItem.IsSelected = True
             Конец Если
         Конец
     Конец собственности
           

Мое требование было для решения на основе PRISM-MVVM, где требовалось TreeView, а связанный объект имеет тип Collection <> и, следовательно, нуждается в HierarchicalDataTemplate. По умолчанию BindableSelectedItemBehavior не сможет идентифицировать дочерний TreeViewItem. Чтобы он работал в этом сценарии.

 public class BindableSelectedItemBehavior : Behavior { #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()) 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()) { 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()) { 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 e) { this.SelectedItem = e.NewValue; } } 

Это позволяет выполнять итерацию всех элементов независимо от уровня.

После изучения Интернета в течение дня я нашел свое собственное решение для выбора элемента после создания нормального дерева в обычной среде 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 

Это также можно сделать, используя свойство 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, просто подпишитесь на событие в classе TreeViewItem.

 TreeViewItem.OnItemSelected += TreeViewItemSelected; 

И, наконец, реализовать этот обработчик в той же ViewModel,

 private void TreeViewItemSelected(TreeViewItem item) { //Do something } 

И, конечно, привязка,

  

Я приношу вам свое решение, которое предлагает следующие функции:

  • Поддерживает 2 способа связывания

  • Авто обновляет свойства TreeViewItem.IsSelected (в соответствии с SelectedItem)

  • Нет подclassа 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 onBrowsedTreeViewItem) { var collectionsToVisit = new System.Collections.Generic.List> { new Tuple(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(tvi.ItemContainerGenerator, tvi.Items)); } onBrowsedTreeViewItem(tvi); } collectionIndex++; } } } 

2/ Example of use in your XAML file

  

(Let’s just all agree that TreeView is obviously busted in respect to this problem. Binding to SelectedItem would have been obvious. Sigh )

I needed the solution to interact properly with the IsSelected property of TreeViewItem, so here’s how I did it:

 // 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(SelectedItemChanged_CustomHandler); } void SelectedItemChanged_CustomHandler(object sender, RoutedPropertyChangedEventArgs 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; } } 

With this XAML:

      

If the XAML has

  

What’s wrong with just finding that item in the list? I also use

  

To make sure when I’m setting IsSelected = true in the VM the parents are expanded.

  • Загрузите ResourceDictionary из сборки
  • Закрыть окно из ViewModel
  • Использование управления изображением в WPF для отображения System.Drawing.Bitmap
  • Шаблон управления существующими элементами управления в WPF
  • Как получить доступ к элементу управления WPF c # в streamовом безопасном режиме?
  • Разница между Label и TextBlock
  • Открыть окно WPF в WindowsForm APP
  • .Net v4 DataGridTextColumn.IsReadOnly кажется ошибочным
  • Связывание данных в пользовательских элементах WPF
  • Как я могу получить WPF EventTrigger в триггере View, если он требует диктует базовую модель Viewmodel?
  • Привязка отображает состояние в режиме просмотра MVVM?
  • Давайте будем гением компьютера.