Question

J'ai une collection d'objets Base de données , chacun contenant des collections d'objets Schema et d'objets Utilisateur . Je souhaite les lier à un TreeView, mais en ajoutant des niveaux statiques supplémentaires dans la hiérarchie, de sorte que le TreeView résultant ressemble plus ou moins à ceci:

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

J'ai pu me rapprocher de ce que je veux en utilisant les modèles suivants:

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

Ensuite, dans le code, je fixe la liaison comme suit:

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

L’arborescence obtenue ressemble à ce que je souhaite, mais il n’est pas possible de sélectionner un schéma ou un utilisateur particulier. Apparemment, WPF considère que le sous-arbre entier est enraciné sur un nœud de base de données comme un seul élément et ne sélectionne que le tout. Je dois pouvoir sélectionner un schéma, un utilisateur ou une base de données particulier. Comment définir les modèles et les liaisons de manière à ce que cela fonctionne comme il me faut?

Était-ce utile?

La solution

Oh mec, c’est une tâche incroyablement frustrante. J'ai essayé de le faire moi-même plusieurs fois. J'ai eu une exigence très similaire où j'ai quelque chose comme une classe de client qui a à la fois une collection Emplacements et une collection Commandes. Je voulais que les localisations et les commandes soient & "; Dossiers &"; dans l'arborescence. Comme vous l'avez découvert, tous les exemples TreeView qui vous expliquent comment lier des types à auto-référencement sont quasiment inutiles.

Tout d'abord, j'ai créé manuellement une arborescence d'objets FolderItemNode et ItemNode que je générerais dans le ViewModel, mais cette option a déjoué l'objectif de la liaison car elle ne répondrait pas aux modifications de collection sous-jacentes.

Ensuite, j'ai proposé une approche qui semble fonctionner assez bien.

  • Dans le modèle d'objet décrit ci-dessus, j'ai créé les classes LocationCollection et OrderCollection. Ils héritent tous deux de ObservableCollection et substituent ToString () pour renvoyer & "Emplacements &"; et " Commandes " respectivement.
  • Je crée une classe MultiCollectionConverter qui implémente IMultiValueConverter
  • J'ai créé une classe FolderNode qui possède une propriété Name and Items. C'est l'objet de marque de réservation qui représentera vos & "; Dossiers &"; dans l'arborescence.
  • Définissez les modèles hiérarchiques qui utilisent MultiBinding partout où vous souhaitez regrouper plusieurs collections d'enfants dans des dossiers.

Le code XAML obtenu est similaire au code ci-dessous et vous pouvez Prenez un fichier zip contenant toutes les classes et XAML dans un exemple de travail .

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

Autres conseils

Le problème, c’est que la TreeView n’est pas très bien adaptée à ce que vous voulez accomplir: elle attend que tous les sous-nœuds soient du même type. Comme votre noeud de base de données a un noeud de type Collection <Schemas & Gt; et de type Collection <Users > vous ne pouvez pas utiliser un HierarchicalDataTemplate. Une meilleure approche consiste à utiliser des expandeurs imbriqués contenant des ListBox.

Le code ci-dessous fait ce que vous voulez à mon avis, tout en étant aussi proche que possible de votre intention initiale:

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

Vous devez renseigner les propriétés que vous utilisez dans votre liaison avec les données de votre base de données. Actuellement, vous utilisez un nouveau TreeViewItem et vous l'utilisez en tant que source de données. Par conséquent, il est logique de tout voir comme un seul nœud, car vous l'avez placé dans un seul nœud.

Vous devez charger les données de votre base de données et les attacher aux propriétés que vous avez utilisées dans votre modèle WPF en tant qu'éléments de liaison.

Voici une modification de la solution de Josh pour travailler avec SMO (mon énoncé de problème 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>

et le convertisseur modifié:

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

Note d'attribution : contenu copié à partir de la solution finale des OP , publiée sous la forme < un href = "https://stackoverflow.com/revisions/1815056/3"> modifier la question , plutôt que comme une réponse

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top