2009-11-29 9 views
12

I tienen una colección de Base de datos objetos, cada uno con colecciones de esquema objetos y usuario objetos. Quiero unir a un TreeView, pero la adición de niveles estáticos adicionales en la jerarquía, por lo que el TreeView resultante se ve más o menos así:¿Cómo mezclar niveles enlazados y estáticos en un TreeView?

<TreeView> 
    <TreeViewItem Header="All the databases:"> 
     <TreeViewItem Header="Db1"> 
      <TreeViewItem Header="Here's all the schemas:"> 
       <TreeViewItem Header="Schema1"/> 
       <TreeViewItem Header="Schema2"/> 
      </TreeViewItem> 
      <TreeViewItem Header="Here's all the users:"> 
       <TreeViewItem Header="User1"/> 
       <TreeViewItem Header="User2"/> 
      </TreeViewItem> 
     </TreeViewItem> 
     <TreeViewItem Header="Db2"> 
      <TreeViewItem Header="Here's all the schemas:"> 
       <TreeViewItem Header="Schema1"/> 
       <TreeViewItem Header="Schema2"/> 
      </TreeViewItem> 
      <TreeViewItem Header="Here's all the users:"> 
       <TreeViewItem Header="User1"/> 
       <TreeViewItem Header="User2"/> 
      </TreeViewItem> 
     </TreeViewItem> 
    </TreeViewItem> 
</TreeView> 

pude llegar muy cerca de lo que quiero mediante el uso de las siguientes plantillas:

<Window.Resources> 
    <HierarchicalDataTemplate DataType="{x:Type smo:Database}"> 
     <TreeViewItem Header="{Binding Path=Name}"> 
      <TreeViewItem Header="Here's all the schemas:" ItemsSource="{Binding Path=Schemas}"/> 
      <TreeViewItem Header="Here's all the users:" ItemsSource="{Binding Path=Users}"/> 
     </TreeViewItem> 
    </HierarchicalDataTemplate> 
    <DataTemplate DataType="{x:Type smo:Schema}"> 
     <TextBlock Text="{Binding Path=Name}"/> 
    </DataTemplate> 
    <DataTemplate DataType="{x:Type smo:User}"> 
     <TextBlock Text="{Binding Path=Name}"/> 
    </DataTemplate> 
</Window.Resources> 

Luego, en el código que establecen la unión de esta manera:

TreeViewItem treeViewItem = new TreeViewItem(); 
treeViewItem.Header = "All the databases:"; 
treeViewItem.ItemsSource = server.Databases; 
treeView.Items.Add(treeViewItem); 

el TreeView resultante se ve como yo quiero que, pero no es posible seleccionar un esquema o usuario particular. Aparentemente, WPF considera que el subárbol completo está enraizado en un nodo de base de datos como un solo elemento, y solo selecciona todo. Necesito poder seleccionar un esquema, usuario o base de datos en particular. ¿Cómo configuro las plantillas y los enlaces para que funcione de la manera que necesito?

Respuesta

12

Oh hombre esto es una tarea muy frustrante. He intentado hacerlo yo mismo muchas veces. Tenía un requisito muy similar en el que tengo algo así como una clase de Cliente que tiene una colección de Ubicaciones y una colección de Órdenes. Quería que las ubicaciones y las órdenes fueran "carpetas" en la vista de árbol. Como ha descubierto, todos los ejemplos de TreeView que muestran cómo enlazar a los tipos de autorreferencia son bastante inútiles.

Primero, recurrí a la construcción manual de un árbol de objetos FolderItemNode y ItemNode que generaría en el ViewModel, pero esto eliminó el propósito de la vinculación porque no respondería a los cambios de colección subyacentes.

Luego se me ocurrió un enfoque que parece funcionar bastante bien.

  • En el modelo de objetos descrito anteriormente, creé las clases LocationCollection y OrderCollection. Ambos heredan de ObservableCollection y anulan ToString() para devolver "Ubicaciones" y "Pedidos", respectivamente.
  • Creo una clase MultiCollectionConverter que implementa IMultiValueConverter
  • Creé una clase FolderNode que tiene una propiedad Name and Items.Este es el objeto de marcador de posición que representará sus "carpetas" en la vista de árbol.
  • Defina hierarchicaldatatemplate que use MultiBinding en cualquier lugar donde desee agrupar varias colecciones secundarias en carpetas.

El XAML resultante es similar al código siguiente y puede grab a zip file which has all the classes and XAML in a working example.

<Window x:Class="WpfApplication2.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:Local="clr-namespace:WpfApplication2" 
     Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded"> 

    <Window.Resources> 

     <!-- THIS IS YOUR FOLDER NODE --> 
     <HierarchicalDataTemplate DataType="{x:Type Local:FolderNode}" ItemsSource="{Binding Items}"> 
      <Label FontWeight="Bold" Content="{Binding Name}" /> 
     </HierarchicalDataTemplate> 

     <!-- THIS CUSTOMER HAS TWO FOLDERS, LOCATIONS AND ORDERS --> 
     <HierarchicalDataTemplate DataType="{x:Type Local:Customer}"> 
      <HierarchicalDataTemplate.ItemsSource> 
       <MultiBinding> 
        <MultiBinding.Converter> 
         <Local:MultiCollectionConverter /> 
        </MultiBinding.Converter> 
        <Binding Path="Locations" /> 
        <Binding Path="Orders" /> 
       </MultiBinding> 
      </HierarchicalDataTemplate.ItemsSource> 
      <Label Content="{Binding Name}" /> 
     </HierarchicalDataTemplate> 

     <!-- OPTIONAL, YOU DON'T NEED SPECIFIC DATA TEMPLATES FOR THESE CLASSES --> 
     <DataTemplate DataType="{x:Type Local:Location}"> 
      <Label Content="{Binding Title}" /> 
     </DataTemplate> 
     <DataTemplate DataType="{x:Type Local:Order}"> 
      <Label Content="{Binding Title}" /> 
     </DataTemplate> 

    </Window.Resources> 

    <DockPanel> 
     <TreeView Name="tree" Width="200" DockPanel.Dock="Left" /> 
     <Grid /> 
    </DockPanel> 

</Window> 

Folders in TreeView

+0

¡Yay! Funciona perfectamente –

+3

¿Existe la posibilidad de que el ejemplo de trabajo que proporcionó aún exista? Intenté acceder a él y el enlace parece estar roto. – psubsee2003

0

Debe completar las propiedades que está utilizando en su enlace con los datos de su base de datos. Actualmente está utilizando un nuevo TreeViewItem y lo usa como fuente de datos, por lo que lo que está diciendo al ver todo como un solo nodo tiene sentido, ya que lo ha colocado en un solo nodo.

Debe cargar los datos de su base de datos y adjuntarlos a las propiedades que ha utilizado en su plantilla WPF como elementos vinculantes.

+0

, Tony, no estoy seguro de lo que quiere decir. Aunque estoy usando TreeViewItem nuevo, estoy vinculando la colección de bases de datos utilizando la propiedad ItemsSource de TreeViewItem. La colección de bases de datos se llena automáticamente por SMO (no incluí el código de configuración para evitar saturar mi publicación con detalles irrelevantes). ¿Podría proporcionarnos algún ejemplo de código que ilustre lo que está sugiriendo? –

+0

Supongo que malentendí tu pregunta original. Su propiedad .Databases carga todos los datos en la clase y las propiedades de dependencia que está utilizando en sus enlaces, ¿verdad? Pensé que tus enlaces no se cargaron correctamente desde tu fuente de datos. Pero no parece ser el problema. –

2

El problema es que un TreeView no es muy adecuado para lo que desea realizar: espera que todos los subnodos sean del mismo tipo. Como su nodo de base de datos tiene un nodo de tipo Colección <Schemas> y de tipo Colección <Users> no puede usar una HierarchicalDataTemplate. Un mejor enfoque es usar expansores anidados que contienen ListBoxes.

El código siguiente hace lo que quiere pienso, mientras que ser lo más cercano posible a su intención original:

<Window x:Class="TreeViewSelection.Window1" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:smo="clr-namespace:TreeViewSelection" 
    Title="Window1" Height="300" Width="300"> 
    <Window.Resources> 
     <Style TargetType="ListBox"> 
      <Setter Property="BorderThickness" Value="0"/> 
     </Style> 
     <DataTemplate DataType="{x:Type smo:Database}"> 
       <TreeViewItem Header="{Binding Name}"> 
        <TreeViewItem Header="Schemas"> 
         <ListBox ItemsSource="{Binding Schemas}"/> 
        </TreeViewItem> 
        <TreeViewItem Header="Users"> 
        <ListBox ItemsSource="{Binding Users}"/> 
       </TreeViewItem> 
       </TreeViewItem> 
     </DataTemplate> 
     <DataTemplate DataType="{x:Type smo:User}" > 
      <TextBlock Text="{Binding Name}"/> 
     </DataTemplate> 
     <DataTemplate DataType="{x:Type smo:Schema}"> 
      <TextBlock Text="{Binding Name}"/> 
     </DataTemplate> 
    </Window.Resources> 
    <StackPanel> 
     <TreeViewItem ItemsSource="{Binding DataBases}" Header="All DataBases"> 
     </TreeViewItem> 
    </StackPanel> 
</Window> 

using System.Collections.ObjectModel; 
using System.Windows; 

namespace TreeViewSelection 
{ 
    public partial class Window1 : Window 
    { 
     public ObservableCollection<Database> DataBases { get; set; } 
     public Window1() 
     { 
      InitializeComponent(); 
      DataBases = new ObservableCollection<Database> 
          { 
           new Database("Db1"), 
           new Database("Db2") 
          }; 
      DataContext = this; 
     } 
    } 

    public class Database:DependencyObject 
    { 
     public string Name { get; set; } 
     public ObservableCollection<Schema> Schemas { get; set; } 
     public ObservableCollection<User> Users { get; set; } 

     public Database(string name) 
     { 
      Name = name; 
      Schemas=new ObservableCollection<Schema> 
         { 
          new Schema("Schema1"), 
          new Schema("Schema2") 
         }; 
      Users=new ObservableCollection<User> 
         { 
          new User("User1"), 
          new User("User2") 
         }; 
     } 
    } 

    public class Schema:DependencyObject 
    { 
     public string Name { get; set; } 
     public Schema(string name) 
     { 
      Name = name; 
     } 
    } 

    public class User:DependencyObject 
    { 
     public string Name { get; set; } 
     public User(string name) 
     { 
      Name = name; 
     } 
    } 
} 
+1

Un TreeView ciertamente no espera que todos los subnodos sean del mismo tipo. Ver, por ejemplo, http://www.codeplex.com/ComplexDataTemplates. –

+0

OK, puedo estar equivocado al respecto. Quizás el único problema es que el contenido predeterminado de TreeViewItem no admite la noción de un elemento seleccionado (lo que hace un ListBox). El artículo que menciona parece ofrecer una solución, sin embargo, no prueba el hecho de que TreeViewItems en el mismo nivel necesita tener contenido del mismo tipo. – Dabblernl

+0

¡Gracias por tu aportación! Confirmo el comentario de Robert de que TreeView no espera que todos los subnodos sean del mismo tipo. Desafortunadamente, su enfoque no funciona tan bien. Aunque ahora sí permite seleccionar nodos de nivel de hoja (algún progreso), aún selecciona el subárbol completo si hago clic en el nombre de la base de datos, solo dejando un rectángulo blanco (no seleccionado) donde está el ListBox. –

0

Aquí es una modificación de la solución de Josh trabajar con SMO (mi declaración problema original):

<Window.Resources> 
    <HierarchicalDataTemplate DataType="{x:Type local:FolderNode}" ItemsSource="{Binding Items}"> 
     <TextBlock Text="{Binding Name}"/> 
    </HierarchicalDataTemplate> 
    <HierarchicalDataTemplate DataType="{x:Type smo:Database}"> 
     <HierarchicalDataTemplate.ItemsSource> 
      <MultiBinding> 
       <MultiBinding.Converter> 
        <local:MultiCollectionConverter /> 
       </MultiBinding.Converter> 
       <Binding Path="Schemas" /> 
       <Binding Path="Users" /> 
      </MultiBinding> 
     </HierarchicalDataTemplate.ItemsSource> 
     <TextBlock Text="{Binding Name}"/> 
    </HierarchicalDataTemplate> 
    <DataTemplate DataType="{x:Type smo:User}" > 
     <TextBlock Text="{Binding Name}"/> 
    </DataTemplate> 
    <DataTemplate DataType="{x:Type smo:Schema}"> 
     <TextBlock Text="{Binding Name}"/> 
    </DataTemplate> 
</Window.Resources> 

y el convertidor modificado:

public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
{ 
    FolderNode[] result = new FolderNode[values.Length]; 
    for (int i = 0; i < values.Length; ++i) 
    { 
     result[i].Items = (IEnumerable)values[i]; 
     result[i].Name = values[i] is UserCollection ? "Users" : "Schemas"; 
    } 
    return result; 
} 

Atribución Nota: contenido copiado de OP's solución final, publicada como un edit to the question, más que como una respuesta

Cuestiones relacionadas