La propriété SelectedItem d'un contrôle TreeView est en lecture seule, ce qui pose un défi pour l'implémentation du pattern MVVM où les interactions utilisateur doivent être liées aux propriétés du ViewModel. Pour surmonter cette limitation, deux approches pricnipales peuvent être adoptées.
- Utilisation d'un Comportement Personnalisé (Custom Behavior) Cette méthode implique la création d'une classe dérivée de
Behavior<TreeView>qui gère explicitement la synchronisation entre la propriétéSelectedItemduTreeViewet une propriété correspondante dans le ViewModel. Vous devrez ajouter une référence à l'assembSystem.Windows.Interactivity.dll.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
public class TreeViewSelectedItemBehavior : Behavior<TreeView>
{
// Propriété pour lier le SelectedItem du TreeView
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(TreeViewSelectedItemBehavior), new PropertyMetadata(null, OnSelectedItemPropertyChanged));
// Gère le changement de la propriété SelectedItem dans le ViewModel
private static void OnSelectedItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var behavior = d as TreeViewSelectedItemBehavior;
if (behavior != null && behavior.AssociatedObject != null)
{
// Tente de sélectionner l'élément correspondant dans le TreeView
SetTreeViewSelectedItem(behavior.AssociatedObject, e.NewValue);
}
}
// Méthode récursive pour trouver et sélectionner un TreeViewItem
private static void SetTreeViewSelectedItem(TreeView treeView, object itemToSelect)
{
if (treeView == null || itemToSelect == null) return;
// Parcours des éléments de premier niveau
for (int i = 0; i < treeView.Items.Count; i++)
{
var treeViewItem = treeView.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
if (FindAndSelectTreeViewItem(treeViewItem, itemToSelect))
{
return; // Élément trouvé et sélectionné
}
}
}
// Méthode récursive pour trouver et sélectionner un TreeViewItem spécifique
private static bool FindAndSelectTreeViewItem(TreeViewItem currentItem, object itemToSelect)
{
if (currentItem == null) return false;
if (currentItem.DataContext == itemToSelect)
{
currentItem.IsSelected = true;
currentItem.BringIntoView(); // Assure que l'élément est visible
return true;
}
// Exploration des enfants si l'élément actuel n'est pas celui recherché
for (int i = 0; i < currentItem.Items.Count; i++)
{
var childItem = currentItem.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
if (childItem != null && FindAndSelectTreeViewItem(childItem, itemToSelect))
{
return true; // Trouvé dans les sous-éléments
}
}
return false; // Non trouvé dans cette branche
}
protected override void OnAttached()
{
base.OnAttached();
// S'abonne à l'événement SelectedItemChanged du TreeView
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
// Se désabonne de l'événement pour éviter les fuites de mémoire
AssociatedObject.SelectedItemChanged -= OnTreeView রোগেরSelectedItemChanged;
}
// Gère le changement du SelectedItem du TreeView et met à jour le ViewModel
private void OnTreeViewSelectedItemChanged(object sender, RoutedEventArgs e)
{
var treeView = sender as TreeView;
if (treeView != null)
{
// Met à jour la propriété SelectedItem du ViewModel avec le nouveau SelectedItem du TreeView
SelectedItem = treeView.SelectedItem;
}
}
}
Utilisation dans le XAML :
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TreeView ItemsSource="{Binding Nodes}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<i:Interaction.Behaviors>
<local:TreeViewSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
</TreeView>
<Label Grid.Column="1" Content="{Binding SelectedItem.Name}"/>
<Button Grid.Column="1" Margin="100" Content="Action" Click="PerformAction"/>
</Grid>
- Utilisation d'une Propriété Attachée et d'un Gestionnaire d'Événement Routé Cette approche consiste à créer une classe d'assistance qui enregistre un gestionnaire pour l'événement
TreeView.SelectedItemChangedEvent. Une propriété attachée est définie pour permettre la liaison bidirectionnelle.
using System.Windows;
using System.Windows.Controls;
public static class TreeViewAttachedProperties
{
// Propriété attachée pour lier le SelectedItem
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewAttachedProperties), new PropertyMetadata(null, OnSelectedItemPropertyChanged));
public static object GetSelectedItem(DependencyObject obj)
{
return obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedItemProperty, value);
}
// Constructeur statique pour enregistrer le gestionnaire d'événements
static TreeViewAttachedProperties()
{
// Enregistre un gestionnaire pour l'événement SelectedItemChanged au niveau de la classe TreeView
EventManager.RegisterClassHandler(typeof(TreeView), TreeView.SelectedItemChangedEvent, new RoutedEventHandler(OnTreeViewSelectedItemChangedGlobal));
}
// Callback appelé lorsque la propriété attachée SelectedItem change
private static void OnSelectedItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Tente de sélectionner l'élément correspondant dans le TreeView
SetTreeViewSelectedItem(d as TreeView, e.NewValue);
}
// Méthode pour trouver et sélectionner un TreeViewItem (identique à la méthode du Behavior)
private static void SetTreeViewSelectedItem(TreeView treeView, object itemToSelect)
{
if (treeView == null || itemToSelect == null) return;
for (int i = 0; i < treeView.Items.Count; i++)
{
var treeViewItem = treeView.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
if (FindAndSelectTreeViewItem(treeViewItem, itemToSelect))
{
return;
}
}
}
// Méthode récursive pour trouver et sélectionner un TreeViewItem spécifique (identique à la méthode du Behavior)
private static bool FindAndSelectTreeViewItem(TreeViewItem currentItem, object itemToSelect)
{
if (currentItem == null) return false;
if (currentItem.DataContext == itemToSelect)
{
currentItem.IsSelected = true;
currentItem.BringIntoView();
return true;
}
for (int i = 0; i < currentItem.Items.Count; i++)
{
var childItem = currentItem.ItemContainerGenerator.ContainerFromIndex(i) as TreeViewItem;
if (childItem != null && FindAndSelectTreeViewItem(childItem, itemToSelect))
{
return true;
}
}
return false;
}
// Gestionnaire global appelé lorsque le SelectedItem d'un TreeView change
private static void OnTreeViewSelectedItemChangedGlobal(object sender, RoutedEventArgs e)
{
var treeView = sender as TreeView;
if (treeView != null)
{
// Met à jour la propriété attachée SelectedItem du TreeView
SetSelectedItem(treeView, treeView.SelectedItem);
}
}
}
Utilisation dans le XAML :
<Window x:Class="WPF_TreeViewMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPF_TreeViewMVVM"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TreeView ItemsSource="{Binding Nodes}"
local:TreeViewAttachedProperties.SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Label Grid.Column="1" Content="{Binding SelectedItem.Name}"/>
<Button Grid.Column="1" Margin="100" Content="Action" Click="PerformAction"/>
</Grid>
</Window>
Les deux méthodes atteignent l'objectif de permettre une liaison bidirectionnelle pour la propriété SelectedItem d'un TreeView en environnement MVVM, offrant ainsi une meilleure intégrasion avec le pattern.