Enlace de datos a SelectedItem en una vista de árbol de WPF
¿Cómo puedo recuperar el elemento seleccionado en una vista de árbol de WPF? Quiero hacer esto en XAML porque quiero vincularlo.
Se podría pensar que lo es SelectedItem
pero aparentemente eso no existe es de sólo lectura y por tanto inutilizable.
Esto es lo que quiero hacer:
<TreeView ItemsSource="{Binding Path=Model.Clusters}"
ItemTemplate="{StaticResource ClusterTemplate}"
SelectedItem="{Binding Path=Model.SelectedCluster}" />
Quiero vincularlo SelectedItem
a una propiedad en mi modelo.
Pero esto me da el error:
La propiedad 'SelectedItem' es de solo lectura y no se puede configurar desde el marcado.
Editar: Ok, esta es la forma en que resolví esto:
<TreeView
ItemsSource="{Binding Path=Model.Clusters}"
ItemTemplate="{StaticResource HoofdCLusterTemplate}"
SelectedItemChanged="TreeView_OnSelectedItemChanged" />
y en el archivo de código subyacente de mi xaml:
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
Model.SelectedCluster = (Cluster)e.NewValue;
}
Me doy cuenta de que ya se aceptó una respuesta, pero la preparé para resolver el problema. Utiliza una idea similar a la solución de Delta, pero sin la necesidad de subclasificar 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;
}
}
Luego puedes usar esto en tu XAML como:
<TreeView>
<e:Interaction.Behaviors>
<behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
</e:Interaction.Behaviors>
</TreeView>
¡Ojalá ayude a alguien!
Esta propiedad existe: TreeView.SelectedItem
Pero es de sólo lectura, por lo que no puedes asignarlo mediante un enlace, sólo recuperarlo.
Bueno, encontré una solución. Mueve el desorden para que MVVM funcione.
Primero agregue esta clase:
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));
}
y agrega esto a tu xaml:
<local:ExtendedTreeView ItemsSource="{Binding Items}" SelectedItem_="{Binding Item, Mode=TwoWay}">
.....
</local:ExtendedTreeView>
Responde un poco más de lo que espera el OP... Pero espero que al menos pueda ayudar a alguien.
Si desea ejecutar a ICommand
cada vez que SelectedItem
cambie, puede vincular un comando en un evento y el uso de una propiedad SelectedItem
en ya ViewModel
no será necesario.
Para hacerlo:
1- Agregar referencia aSystem.Windows.Interactivity
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
2- Vincula el comando al eventoSelectedItemChanged
<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>
Esto se puede lograr de una manera "mejor" usando solo el enlace y EventToCommand de la biblioteca GalaSoft MVVM Light. En su VM agregue un comando que se llamará cuando se cambie el elemento seleccionado e inicialice el comando para realizar cualquier acción que sea necesaria. En este ejemplo utilicé un RelayCommand y simplemente estableceré la propiedad 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; }
}
Luego agregue el comportamiento EventToCommand en su xaml. Esto es realmente fácil usando 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>