Domanda

Ho una raccolta di oggetti Database , ognuno contenente raccolte di oggetti Schema e oggetti Utente . Voglio associarli a TreeView, ma aggiungendo ulteriori livelli statici nella gerarchia, in modo che TreeView risultante sia più o meno simile a questo:

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

Sono stato in grado di avvicinarmi molto a ciò che volevo utilizzando i seguenti modelli:

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

Quindi nel codice ho impostato l'associazione in questo modo:

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

Il TreeView risultante sembra che io lo voglia, ma non è possibile selezionare un particolare schema o utente. Apparentemente WPF vede l'intera sottostruttura radicata in un nodo del database come un singolo elemento e seleziona solo l'intera cosa. Devo essere in grado di selezionare un particolare schema, utente o database. Come posso impostare i modelli e le associazioni in modo che funzioni nel modo che mi serve?

È stato utile?

Soluzione

Oh amico, questo è un compito incredibilmente frustrante. Ho provato a farlo da solo molte volte. Avevo un requisito molto simile in cui ho qualcosa come una classe Customer che ha sia una raccolta Locations che una raccolta Ordini. Volevo che le posizioni e gli ordini fossero & Quot; cartelle & Quot; nella vista ad albero. Come hai scoperto, tutti gli esempi di TreeView che mostrano come legarsi ai tipi di autoreferenziazione sono praticamente inutili.

Per prima cosa ho fatto ricorso alla costruzione manuale di un albero di oggetti FolderItemNode e ItemNode che avrei generato in ViewModel, ma ciò ha vanificato lo scopo dell'associazione perché non avrebbe risposto alle modifiche alla raccolta sottostante.

Poi ho trovato un approccio che sembra funzionare abbastanza bene.

  • Nel modello a oggetti sopra descritto, ho creato le classi LocationCollection e OrderCollection. Entrambi ereditano da ObservableCollection e sovrascrivono ToString () per restituire & Quot; Locations & Quot; e " Ordini " rispettivamente.
  • Creo una classe MultiCollectionConverter che implementa IMultiValueConverter
  • Ho creato una classe FolderNode che ha una proprietà Name e Items. Questo è l'oggetto segnaposto che rappresenterà il tuo & Quot; cartelle & Quot; nella vista ad albero.
  • Definisci la piastra dati gerarchica che utilizza MultiBinding ovunque tu voglia raggruppare più raccolte figlio in cartelle.

L'XAML risultante è simile al codice seguente e puoi prendi un file zip che contiene tutte le classi e XAML in un esempio funzionante .

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

Cartelle in TreeView

Altri suggerimenti

Il problema è che TreeView non è molto adatto a ciò che si desidera realizzare: si aspetta che tutti i nodi secondari siano dello stesso tipo. Poiché il nodo del database ha un nodo di tipo Collection <Schemas & Gt; e di tipo Collection <Users > non è possibile utilizzare un HierarchicalDataTemplate. Un approccio migliore consiste nell'utilizzare espansori nidificati che contengono ListBox.

Il codice qui sotto fa quello che vuoi, penso, pur essendo il più vicino possibile al tuo intento originale:

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

Devi riempire le proprietà che stai usando nella tua associazione con i dati del tuo database. Attualmente stai usando un nuovo TreeViewItem e lo usi come una fonte di dati, quindi quello che stai dicendo a riguardo vede tutto come un singolo nodo ha senso, come lo hai inserito in un singolo nodo.

Devi caricare i dati del tuo database e allegarli alle proprietà che hai usato nel tuo modello WPF come elementi vincolanti.

Ecco una modifica della soluzione di Josh per lavorare con SMO (la mia dichiarazione del problema originale):

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

e il convertitore modificato:

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 di attribuzione: contenuto copiato da soluzione finale di OP, pubblicata come < a href = "https://stackoverflow.com/revisions/1815056/3"> modifica alla domanda , anziché come risposta

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top