Pregunta

Tengo una colección de objetos Database , cada uno de los cuales contiene colecciones de objetos Schema y Usuario . Quiero vincularlos a un TreeView, pero agregando niveles estáticos adicionales en la jerarquía, para que el TreeView resultante se vea más o menos así:

<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 acercarme bastante a lo que quería usando 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, establezco el enlace de esta manera:

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

El TreeView resultante se parece a lo que quiero, pero no es posible seleccionar un esquema o usuario en particular. Aparentemente, WPF ve todo el subárbol enraizado en un nodo de la 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 como lo necesito?

¿Fue útil?

Solución

Oh hombre, esta es una tarea increíblemente frustrante. He intentado hacerlo yo mismo muchas veces. Tuve un requisito muy similar cuando obtuve algo así como una clase de Cliente que tiene tanto una colección de Ubicaciones como una colección de Órdenes. Quería que las ubicaciones y los pedidos fueran & Quot; carpetas & Quot; En la vista de árbol. Como ha descubierto, todos los ejemplos de TreeView que le muestran cómo unirse a tipos de autorreferencia son prácticamente inútiles.

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

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

  • En el modelo de objeto descrito anteriormente, creé las clases LocationCollection y OrderCollection. Ambos heredan de ObservableCollection y anulan ToString () para devolver & Quot; Locations & Quot; y " Órdenes " respectivamente.
  • Creo una clase MultiCollectionConverter que implementa IMultiValueConverter
  • Creé una clase FolderNode que tiene una propiedad Name y Items. Este es el objeto marcador de posición que representará su & Quot; carpetas & Quot; en la vista de árbol.
  • Defina las plantillas de datos jerárquicos que usan MultiBinding en cualquier lugar donde desee agrupar varias colecciones secundarias en carpetas.

El XAML resultante se parece al siguiente código y puede tome un archivo zip que tenga todas las clases y XAML en un ejemplo de trabajo .

<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>

Carpetas en TreeView

Otros consejos

El problema es que un TreeView no se adapta muy bien a lo que desea lograr: espera que todos los subnodos sean del mismo tipo. Como su nodo de base de datos tiene un nodo de tipo Colección <Schemas & Gt; y de tipo Collection <Users > no puede usar un HierarchicalDataTemplate. Un mejor enfoque es utilizar expansores anidados que contengan ListBoxes.

El siguiente código hace lo que quiere, creo, mientras está lo más cerca posible de 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;
        }
    }
}

Debe completar las propiedades que está utilizando en su enlace con datos de su base de datos. Actualmente está utilizando un nuevo TreeViewItem, y lo está utilizando como fuente de datos, por lo que lo que está diciendo al respecto 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.

Aquí hay una modificación de la solución de Josh para trabajar con SMO (mi enunciado del 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;
}
  

Nota de atribución: Contenido copiado de OP's solución final, publicado como una < a href = "https://stackoverflow.com/revisions/1815056/3"> edite la pregunta , en lugar de responderla

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top