2009-06-02 68 views
22

Tengo una vista de árbol de tres niveles. ¿Cómo selecciono cualquier elemento en el tercer nivel del código? Probé un método mencionado en muchos blogs y en stackoverflow pero parece funcionar solo para el primer nivel (dbObject es nulo para los elementos en el primer nivel inferior).Cómo seleccionar elemento TreeView desde el código

Aquí está el código que estoy usando para seleccionar TreeViewItem. ¿Extraño algo?

public static void SetSelectedItem(this TreeView control, object item) 
{ 
    try 
    { 
     var dObject = control.ItemContainerGenerator.ContainerFromItem(item); 

     //uncomment the following line if UI updates are unnecessary 
     ((TreeViewItem)dObject).IsSelected = true; 

     MethodInfo selectMethod = typeof(TreeViewItem).GetMethod("Select", 
      BindingFlags.NonPublic | BindingFlags.Instance); 

     selectMethod.Invoke(dObject, new object[] { true }); 
    } 
    catch { } 
} 
+12

sacrificios TreeView de WPF * * cada último bit de usabilidad para lograr características que la mayoría de las personas no lo hacen parece necesitar la mayor parte del tiempo ... –

Respuesta

31

Otra opción sería el uso de unión. Si usted tiene un objeto que está utilizando la unión con conseguir el texto de cada TreeViewItem (por ejemplo), puede crear un estilo que también se une a la propiedad IsSelected:

<TreeView> 
    <TreeView.Resources> 
     <Style TargetType="TreeViewItem"> 
      <Setter Property="IsSelected" 
        Value="{Binding Path=IsSelected, Mode=TwoWay}" /> 
     </Style> 
    </TreeView.Resources> 
</TreeView> 

Esto asume que el objeto dependiente tiene una IsSelected propiedad del tipo bool. Puede seleccionar un TreeViewItem configurando IsSelected en true para su objeto correspondiente.

El mismo enfoque se puede utilizar con la propiedad IsExpanded para controlar cuándo se expande o se contrae TreeViewItem.

+0

Sí, era consciente de esto. Pero parece que introduce el acoplamiento de código. De todos modos, es bueno que tengas esa respuesta aquí. Las personas que vendrán a esta página pueden preferir su camino sobre el mío –

+0

@Andy: ¿Cómo puedo hacer esto en Silverlight? Intento este código obtengo el error 'No puedo establecer la propiedad de solo lectura '' .'. –

+1

@Navid No estoy seguro si puedes. No he hecho mucho con Silverlight, pero no creo que TreeViewItem.IsSelected sea DependencyProperty en Silverlight. No puede usar el enlace para las propiedades que no son DependencyProperty. – Andy

0

Sí, el método ContainerFromItem no devuelve nada, incluso cuando lo llama desde el elemento primario TreeViewItem.

Es posible que deba hacer un poco de rediseño. Si crea todo como un TreeViewItem explícito, debería poder mantener una referencia y establecer IsSelected en él.

4

Después de intentar diferentes soluciones llegué al sitio this. Zhou Yong muestra cómo expandir programáticamente todos los nodos de TreeView. Hay dos ideas principales en su método:

  • ContainerFromItem devolverá el contenedor solo si el elemento es hijo directo del elemento. En TreeView eso significa que solo se devolverá el contenedor secundario de primer nivel y tendrá que llamar a ContainerFromItem en TreeViewItem hijo para obtener el contenedor del siguiente nivel
  • Para que ContainerFromItem funcione TreeViewItem debe crearse elementos secundarios visuales, y esto ocurre solo cuando se expande TreeViewItem. Esto significa que para seleccionar TreeViewItem, se deben expandir todos los elementos que preceden al elemento requerido. En la práctica, eso significa que tendremos que proporcionar la ruta al elemento que queremos seleccionar en lugar de solo el elemento.

Este es el código que terminó con

public static void SelectItem(this ItemsControl parentContainer, List<object> path) 
{ 
    var head = path.First(); 
    var tail = path.GetRange(1, path.Count - 1); 
    var itemContainer = parentContainer.ItemContainerGenerator.ContainerFromItem(head) as TreeViewItem; 

    if (itemContainer != null && itemContainer.Items.Count == 0) 
    { 
     itemContainer.IsSelected = true; 

     var selectMethod = typeof(TreeViewItem).GetMethod("Select", BindingFlags.NonPublic | BindingFlags.Instance); 
     selectMethod.Invoke(itemContainer, new object[] { true }); 
    } 
    else if (itemContainer != null) 
    { 
     itemContainer.IsExpanded = true; 

     if (itemContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) 
     { 
      itemContainer.ItemContainerGenerator.StatusChanged += delegate 
      { 
       SelectItem(itemContainer, tail); 
      }; 
     } 
     else 
     { 
      SelectItem(itemContainer, tail); 
     } 
    } 
} 
+0

Parece que tengo que * tener * el elemento que quiero seleccionar. Sin embargo, generalmente ese mismo objeto no está disponible; solo tienes una identificación de ese objeto, ¿verdad? No estoy seguro de cómo llamar a 'ContainerFromItem' sin tener la instancia del elemento real. –

0

En mi caso (tuve el mismo problema) pero no era apropiado utilizar el enlace a la propiedad IsSelected del objeto Data y tampoco pude obtener fácilmente la ruta al elemento tree, por lo que el siguiente código hizo el trabajo perfectamente:

private void SelectTreeViewItem(object item) 
    { 
     try 
     { 
      var tvi = GetContainerFromItem(this.MainRegion, item); 

      tvi.Focus(); 
      tvi.IsSelected = true; 

      var selectMethod = 
       typeof(TreeViewItem).GetMethod("Select", 
       System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); 

      selectMethod.Invoke(tvi, new object[] { true }); 
     } 
     catch { } 
    } 

    private TreeViewItem GetContainerFromItem(ItemsControl parent, object item) 
    { 
     var found = parent.ItemContainerGenerator.ContainerFromItem(item); 
     if (found == null) 
     { 
      for (int i = 0; i < parent.Items.Count; i++) 
      { 
       var childContainer = parent.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl; 
       TreeViewItem childFound = null; 
       if (childContainer != null && childContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) 
       { 
        childContainer.ItemContainerGenerator.StatusChanged += (o, e) => 
         { 
          childFound = GetContainerFromItem(childContainer, item); 
         }; 
       } 
       else 
       { 
        childFound = GetContainerFromItem(childContainer, item);        
       } 
       if (childFound != null) 
        return childFound;     
      } 
     } 
     return found as TreeViewItem; 
    } 
3

se puede utilizar el siguiente TreeView extensión, lo que encuentro es una solución más simple:

public static class TreeViewExtension 
{ 
    public static bool SetSelectedItem(this TreeView treeView, object item) 
    { 
     return SetSelected(treeView, item); 
    } 

    private static bool SetSelected(ItemsControl parent, object child) 
    { 
     if (parent == null || child == null) 
      return false; 

     TreeViewItem childNode = parent.ItemContainerGenerator 
     .ContainerFromItem(child) as TreeViewItem; 

     if (childNode != null) 
     { 
      childNode.Focus(); 
      return childNode.IsSelected = true; 
     } 

     if (parent.Items.Count > 0) 
     { 
      foreach (object childItem in parent.Items) 
      { 
      ItemsControl childControl = parent 
       .ItemContainerGenerator 
       .ContainerFromItem(childItem) 
       as ItemsControl; 

      if (SetSelected(childControl, child)) 
       return true; 
      } 
     } 

     return false; 
    } 
} 

Para obtener más información, lea este artículo en el blog; http://decompile.it/blog/2008/12/11/selecting-an-item-in-a-treeview-in-wpf/

+0

Funciona y no requiere cambiar la estructura de datos, pero no es eficiente en absoluto. –

0

muy tarde a la fiesta con mi respuesta, pero para aquellos que quieran una solución MVVM pura esto se puede hacer con un disparador de eventos (para actualizar la unión cuando el usuario selecciona un elemento nuevo) y un activador de datos (para actualizar el elemento seleccionado cuando cambia el valor de la vinculación).

Para que esto funcione el principal modelo de vista las necesidades de los artículos, una propiedad para el elemento seleccionado en ese momento y una propiedad de comando que se llama cuando cambia el elemento seleccionado actualmente:

public class MainViewModel : ViewModelBase 
{ 
    // the currently selected node, can be changed programmatically 
    private Node _CurrentNode; 
    public Node CurrentNode 
    { 
     get { return this._CurrentNode; } 
     set { this._CurrentNode = value; RaisePropertyChanged(() => this.CurrentNode); } 
    } 

    // called when the user selects a new node in the tree view 
    public ICommand SelectedNodeChangedCommand { get { return new RelayCommand<Node>(OnSelectedNodeChanged); } } 
    private void OnSelectedNodeChanged(Node node) 
    { 
     this.CurrentNode = node; 
    } 

    // list of items to display in the tree view 
    private ObservableCollection<Node> _Items; 
    public ObservableCollection<Node> Items 
    { 
     get { return this._Items; } 
     set { this._Items = value; RaisePropertyChanged(() => this.Items); } 
    } 
} 

El TreeView necesita una activación de un evento llamar SelectedNodeChangedCommand cuando cambia la selección, y un DataTrigger en el estilo TreeViewItem para que los elementos de control de ser seleccionado cuando el valor de CurrentNode se cambia mediante programación en el código:

<TreeView x:Name="treeView" ItemsSource="{Binding Items}" 
      xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
      xmlns:cmd ="http://www.galasoft.ch/mvvmlight"> 
     <TreeView.Resources> 

      <conv:EqualityConverter x:Key="EqualityConverter" /> 

      <Style TargetType="TreeViewItem"> 
       <Setter Property="IsExpanded" Value="True" /> 
       <Setter Property="IsSelected" Value="False" /> 
       <Style.Triggers> 
        <!-- DataTrigger updates TreeViewItem selection when vm code changes CurrentNode --> 
        <DataTrigger Value="True"> 
         <DataTrigger.Binding> 
          <MultiBinding Converter="{StaticResource EqualityConverter}"> 
           <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type TreeView}}" Path="DataContext.CurrentNode" /> 
           <Binding /> 
          </MultiBinding> 
         </DataTrigger.Binding> 
         <Setter Property="IsSelected" Value="True" /> 
        </DataTrigger> 
       </Style.Triggers> 
      </Style> 


      <!-- *** HierarchicalDataTemplates go here *** --> 

     </TreeView.Resources> 

     <!-- EventTrigger invokes SelectedNodeChangedCommand when selection is changed by user interaction --> 
     <i:Interaction.Triggers> 
      <i:EventTrigger EventName="SelectedItemChanged"> 
       <cmd:EventToCommand Command="{Binding SelectedNodeChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=SelectedItem}" /> 
      </i:EventTrigger> 
     </i:Interaction.Triggers> 

    </TreeView> 

el DataTrig ger funciona al detectar cuando el valor de CurrentNode coincide con el nodo para el elemento de la lista actual. Desafortunadamente DataTriggers no pueden obligar a su valor, por lo que tiene que probar con una EqualityConverter lugar, que sólo hace una comparación sencilla:

public class EqualityConverter : IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
    { 
     return values[0] == values[1]; 
    } 

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) 
    { 
     throw new NotImplementedException(); 
    } 
} 
Cuestiones relacionadas