Pergunta

Eu tenho uma coleção de Banco de Dados objetos, cada um contendo coleções de Esquema objetos e user objetos. Eu quero ligá-los a um TreeView, mas a adição de níveis estáticos adicionais na hierarquia, de modo que o TreeView resultante é mais ou menos assim:

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

Eu era capaz de ficar muito perto do que eu quero usando os seguintes modelos:

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

Em seguida, no código eu definir a ligação como esta:

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

Os olhares TreeView resultantes como eu quero que ele, mas não é possível selecionar um esquema ou usuário específico. Aparentemente WPF vê toda a subárvore com raiz em um nó de banco de dados como um único item, e só seleciona a coisa toda. Eu preciso ser capaz de selecionar um esquema particular, usuário ou banco de dados. Como faço para definir os modelos e ligações para que ele funcione a necessidade maneira que eu?

Foi útil?

Solução

Oh homem esta é uma tarefa incrivelmente frustrante. Eu tentei fazê-lo eu mesmo muitas vezes. Eu tinha um requisito muito semelhante onde eu tenho algo como uma classe Cliente que tem tanto uma coleção Locais e uma coleção Orders. Eu queria Locais e ordens para ser "pastas" na visualização em árvore. Como você descobriu, todos os exemplos TreeView que mostrar-lhe como ligar-se a tipos de auto-referência são praticamente inúteis.

Em primeiro lugar eu recorri para a construção manualmente uma árvore de FolderItemNode e ItemNode objetos que eu iria gerar no ViewModel mas isso derrotou o efeito de vincular porque ele não iria responder ao subjacente alterações de cobrança.

Então eu vim com uma abordagem que parece funcionar muito bem.

  • No modelo de objeto descrito acima, criei aulas LocationCollection e OrderCollection. Ambos herdam ObservableCollection e ToString override () para retornar "Locais" e "Pedidos", respectivamente.
  • I criar uma classe MultiCollectionConverter que implementos IMultiValueConverter
  • Eu criei uma classe FolderNode que tem uma propriedade Nome e itens. Este é o objeto espaço reservado que irá representar seus "pastas" na exibição em árvore.
  • Definir HierarchicalDataTemplate é que o uso de ligações múltiplas em qualquer lugar que você quiser múltiplas grupo coleções filho em pastas.

O XAML resultante é semelhante ao código abaixo e você pode pegar um arquivo zip que tem todas as classes e XAML em um exemplo trabalhando.

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

Pastas em TreeView

Outras dicas

O problema é que a TreeView não é muito bem adequado para o que você quer acomplish: Ele espera que todos os subnós ser do mesmo tipo. Como o nó de banco de dados tem um nó do tipo Collection<Schemas> e do tipo Collection<Users> você não pode usar uma HierarchicalDataTemplate. A melhor abordagem é usar expansores aninhados que contêm ListBoxes.

O código a seguir faz o que quiser Eu acho que, ao ser o mais próximo possível ao seu original intenção:

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

Você precisa preencher as propriedades que você está usando em sua ligação com dados de seu banco de dados. Atualmente você está usando um novo TreeViewItem, e usá-lo como uma fonte de dados, então o que você está dizendo sobre ele vendo tudo como um único nó faz sentido, como você colocá-lo em um único nó.

Você precisa carregar seus dados de banco de dados e anexá-lo para as propriedades que você usou no seu modelo de WPF como itens de ligação.

Aqui está uma modificação da solução de Josh para trabalhar com SMO (minha declaração do 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>

eo conversor 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;
}

Atribuição Nota: conteúdo copiado de solução final , postado como um < a href = "https://stackoverflow.com/revisions/1815056/3"> Editar para a questão , e não como uma resposta

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top