Fragensteller
TreeView als UserControl in WPF

Frage
-
Hallo
Dank an Peter der dabei geholfen hat das mein TreeView jetzt so funktioniert wie ich wollte.
Wie schon bestimmt schon gelesen beschäftige ich mich seit Monaten schon mir den TreeView nun ist des TreeView so wie mir es gefällt, daher möchte ich dieses TreeView als UserControl erstellen damit man diesen ganzen Code nicht immer neu schreiben muss.
Ich muss dazu sagen das es mein erstes UserControl ist und ich damit keine erfährung gesammelt habe. in WinForms kenne ich das schon nur bei WPF ist alles anders und lerne es neu.
Ich hab mir nun ein WPF-Benutzersteuerelement erstellt und habe den Code eingefügt und geschaft in den UserControl anzupassen nun sollte es eigentlich passen.
Hier mal mein Code:
XAML:
<UserControl x:Class="Mezzo_TreeView.Mezzo_TreeView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:Mezzo_TreeView" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Control.DataContext> <local:ViewModel/> </Control.DataContext> <Grid> <TreeView ItemsSource="{Binding RootItems}"> <i:Interaction.Behaviors> <local:TreeViewBehavior/> </i:Interaction.Behaviors> <TreeView.Resources> <HierarchicalDataTemplate DataType="{x:Type local:Data_Kategorie}" ItemsSource="{Binding ChildrenItems}"> <TextBlock Text="{Binding KategorieName}"/> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> </Grid> </UserControl>
und hier hab ich auch die Klasse ViewModel.cs erstellt.
using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Interactivity; namespace Mezzo_TreeView { public class ViewModel { public ViewModel() { cvs.Source = (new Model()).GetData(0); cvs.SortDescriptions.Add(new SortDescription("KategorieReihenfolge", ListSortDirection.Ascending)); } private CollectionViewSource cvs = new CollectionViewSource(); public ICollectionView RootItems { get => cvs.View; } public Data_Kategorie Detail { set { MessageBox.Show($"KategorieNummer: {value.KategorieNummer}\nKategorieUnterNummer: {value.KategorieUnterNummer}\nKategorieName: {value.KategorieName}"); } } } internal class Model { public Model() { // Demo-Daten erzeugen //Random rnd = new Random(); //for (int i = 100; i < 1000; i++) col.Add(new Data_Kategorie(col) //{ // KategorieNummer = i, // KategorieUnterNummer = (rnd.NextDouble() > .5) ? 0 : rnd.Next(100, i), // KategorieName = $"Node {i}" //}); } // Datenspeicher internal ObservableCollection<Data_Kategorie> col = new ObservableCollection<Data_Kategorie>(); // Sicht für Root-Level internal ObservableCollection<Data_Kategorie> GetData(int levelID) => new ObservableCollection<Data_Kategorie>(col.Where((d) => d.KategorieUnterNummer == 0)); } public class Data_Kategorie { public Data_Kategorie(ObservableCollection<Data_Kategorie> data) => col = data; private ObservableCollection<Data_Kategorie> col; public int KategorieNummer { get; set; } public int KategorieUnterNummer { get; set; } public string KategorieName { get; set; } public int KategorieReihenfolge { get; set; } public ObservableCollection<Data_Kategorie> ChildrenItems { get => new ObservableCollection<Data_Kategorie>(col.Where((d) => d.KategorieUnterNummer == this.KategorieNummer)); } public bool IsExpanded { get; set; } = true; public bool IsSelected { get; set; } } public class TreeViewBehavior : Behavior<TreeView> { protected override void OnAttached() => AssociatedObject.SelectedItemChanged += AssociatedObject_SelectedItemChanged; private void AssociatedObject_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { var tv = sender as TreeView; if (tv == null) return; var vm = tv.DataContext as ViewModel; if (vm == null) return; vm.Detail = tv.SelectedItem as Data_Kategorie; } } }
Nun weiß ich nicht ob das Richtig so ist, wenn es anders besser wäre dann nur her damit.
Nun zu mein Thema.
Wenn man das UserControll (Mezzo_TreeView) nun in das MainWindow einfügt. Wie kann man da jetzt den TreeView befüllen?
Ich möchte das von mein MainWindow nun die Einträge in des TreeView geladen werden.
Und es wird mir bei einfügen in MainWindow dieser Fehler angezeigt.
Gruß
Mezzo
- Bearbeitet Mezzo80 Samstag, 4. Dezember 2021 20:56
Alle Antworten
-
Hi Mezzo,
dein Konzept ist unklar.1. Willst ein UserControl haben oder nur ein TreeView mit eingebauten zusätzlichen Funktionalitäten. In Einem UserControl werden üblicherweise weitere Steuerelemente eingefügt (z.B. Button, TextBox usw.). Ein spezielles TreeView mit zusätzlichen Funktionalitäten baut man als Erbe des TreeVews auf.
2. Soll das TreeView später parametriert werden (z.B. separate Templates)? In einem UserControl musst du solche "äußeren" Parameter irgendwie in das UserControl "transportieren", um dann dem TreeView zuzuweisen. Einfach ist es, wenn das UserControl überall gleich aussehen soll und es nur einige gebundene Werte gibt, wie beispielsweise nur die darzustellenden Daten.
3. Wie soll Sicht (XAML) und Code getrennt werden? Deine bisherigen Beiträge lassen vermuten, dass du lieber chaotischen Code im CodeBehind programmierst, als sauber und übersichtlicher zwischen Design und Code zu trennen, z.B. indem das MVVM Entwurfsmuster durchgängig angewandt wird. Wenn das UserControl mit dem TreeView sehr einfach gehalten wird, kann man die wenigen Befehle auch im CodeBehind des UserControls platzieren. Ich empfehle aber, auch dort MVVM anzuwenden. Man muss dann nur entscheiden, ob man pro Nutzung des UserControls separate Instanzen eines (UserControl)-ViewModels nutzt, oder die ViewModel-Instanz des Fensters, in dem das UserControl platziert wird.
Zu deinem gezeigten Fehler:
Die Designer-Ansicht benötigt fehlerfrei übersetzte Steuerelemente (u.a. auch dein UserControl). Solange die Assembly mit dem UserControl nicht fehlerfrei übersetzt wurde, kann der Designer das UserControl auch nicht darstellen. Ich empfehle dir deshalb, eine Bibliothek für die UserControls anzulegen, diese dann fehlerfrei zu übersetzen und erst danach diese Steuerelemente aus der Bibliothek im Projekt einzusetzen.
Zu deiner Frage, wie das TreeView im UserControl befüllen:
Wenn dein Konzept klar ist, sollte auch die Antwort einfach sein. Die einfachste Lösung ist, dass die ItemsSource des TreeViews an eine Eigenschaft im Main-ViewModel gebunden wird. Das bedeutet aber, dass bei jeder Nutzung des UserControls, wo eine andere Datenquelle genutzt werden soll, auch eine andere ViewModel-Instanz zu nutzen ist. Wenn die ein Mammut-ViewModel mit vielen Eigenschaften hast, für jede UserControl-Nutzung eine separat zu bindende Eigenschaft mit den Daten für das TreeView im UserControl, dann brauchst du im UserControl eine entsprechende Eigenschaft, die einerseits im Fenster an die betreffende Eigenschaft im Main-ViewModel gebunden ist und andererseits die Bindung im TreeView des UserControls organisiert. Bei Notwendigkeit ist dann ggf. noch zu berücksichtigen, dass es Ereignisse gebenh kann, wie z.B. die Änderung der Bindung.
--
Best Regards / Viele Grüße
Peter Fleischer (former MVP for Developer Technologies)
Homepage, Tipps, Tricks- Bearbeitet Peter Fleischer Montag, 6. Dezember 2021 11:45
-
Hallo Peter
Danke für deine Antwort.
Ich weiß mein Coding ist chaotisch. Ich vermute das es an GWBasic und Vidual Basic 6.0 liegt. Mit dem Windows Forms war das Model mit den Binding nicht nicht und das Design und dem Code waren nicht so getrennt. Ich habe das WPF immer weg geschoben bis ich jetzt gesagt habe ich will das Lernen. Nun beschäftige ich es nur mit dem WPF und bin nun die ganze Zeit an den TreeView und dann Später das TreeView in die ComboBox. Ich möchte mein altes Software die ich mit Visual Basic 6.0 programmiert hatte wieder nach programmieren mit WPF und C#.
Das TreeView wird sehr häufig in verschiedene Windows Fenster benutzt. Ab und zu auch mit die gleichen Inhalten aber es kommt auch vor das andere Inhalte rein kommen.
Ich möchte das mit UserControl so haben das es die komplette Anzeige und Design übernimmt. Mann kann später bestimmt die Funktionen hinzufügen wenn man das Design ändern will. Das heißt das ViewModel muss in UserControl komplett rein.
Schritt für Schritt.
1) Mein UserControl soll dann als DLL Datei gespeichert werden, die ich dann in mein Projekt einfüge.
2) in mein MainWindow möchte ich dann die Klasse erstellen z.b.
public Mezzo_TreeView myTreeView = new Mezzo_TreeView
Das public möchte ich so benutzen das ich auch von andern WPF Fenster auch drauf zugreifen kann und nicht nur im MainWindow.
Ich möchte dann den Inhalt von TreeView dann so herstellen.
myTreeView.nodes.add(KategorieNummer, KategorieUnterNummer, KategorieName, KategorieReihenfolge)
3) Wenn der User im TreeView eine Nodes Markiert möchte ich es auch einfach auslesen können also die Funktion auch gleich in UserControl rein so das ich es so aufrufen kann.
myTreeView.selected.KategorieName.toString;
und ich das so als Inhalt bekomme.
Wäre das so möglich?
Kannst du mir ein Beispiel geben?
Danke im vorraus.
Gruß
Mezzo
-
Hi Mezzo,
auch in VB6- und in WindowsForms-Projekten war es ein guter Stil, Funktionalitäten zu kapseln. Typisch in VB6-Projekten war eine DataSorce-Klasse, die über eine GetDataMember-Methode ein Recordset für die Bindung bereitgestellt hat.In VB.NET-Projekten kann man die DataBindings-Eigenschaft nutzen, um beliebige Eigenschaft zu binden.
Wenn du den Programmcode aus VB6 nachnutzen möchtest, ist das einfach möglich, indem dieser Code in das .NET-Projekt kopiert wird. Angepasst solltendann die Objekte für die Datenhaltung (aus Recordsets werden DataTables oder besser Klassen für das EF). Etwas größerer Anpassungsaufwand erfordert die Datenklasse, insbesondere, wenn auch anstelle Recordsets besser DataTables oder EF-Klassen genutzt werden.
Ich vermute aber, dass du bisher sowohl in VB6 als auch in WindowsForms-Anwendungen nicht objektorientiert programmiert hast. Und diese Spaghetti-Code-Technologie auch versuchst in einer OOP-Umgebung umzusetzen. So wirst du immer mit einem sehr hohen Inbetriebnahme- und Wartungsaufwand rechnen müssen.
zu 1) - unklar ist, warum du ein UserControl benötigst, wenn du weiter nichts über zusätzliche Steuerelemente und Funktionen im UserControl schreibst.
zu 2) - unklar ist, warum du weiter direkt im Code des Fensters (Windows) Steuerelemente erstellen willst und nicht im XAML Damit bleibst du bei deinem chaotischen Programmierstil, der dann - wie auch deine Fragen zeigen - zu kaum beherrschbaren Problemen führen kann.
Da andere Fenster eigene Objekte sind, kannst du nicht einfach auf die Innereien in anderen Fenstern zugreifen. Du benötigst entweder einen Verweis auf das Object oder die Klasse des anderen Objektes oder deren Innereien müssen statisch sein. Das sind aber Grundlagen der objektorientierten Programmierung (OOP).
zu 3) - Im Thread TreeView mit mehreren Daten habe ich dir bereits ein Beispiel dazu gezeigt. Mein Posting im Thread vom 3.12.21 scheinst du aber nicht verinnerlicht zu haben, weil du vermutlich Teile daraus irgendwo in deinen Code kopierst hast und dann die Fehler nicht beseitigt hast.
Hier mal eine kleine Demo.
XAML des UserControls:
<UserControl x:Class="WpfControlLibrary1.Window087UC1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfControlLibrary087" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <TreeView Grid.Row="3" ItemsSource="{Binding RootItems}" AllowDrop="True"> <i:Interaction.Behaviors> <local:TreeViewBehavior/> <local:DragDropBehavior/> </i:Interaction.Behaviors> <TreeView.Resources> <HierarchicalDataTemplate DataType="{x:Type local:Data_Kategorie}" ItemsSource="{Binding ChildrenItems}"> <TextBlock Text="{Binding KategorieName}"/> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> </Grid> </UserControl>
XAML im MainWindow:
<Window x:Class="WpfApp1.Window087" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp087" xmlns:data="clr-namespace:WpfControlLibrary087;assembly=WpfControlLibrary1" xmlns:uc="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1" mc:Ignorable="d" Title="Mezzo 6.12.21" Height="450" Width="800"> <Window.DataContext> <local:ViewModel/> </Window.DataContext> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition Height="auto"/> <RowDefinition/> </Grid.RowDefinitions> <uc:Window087UC1 Grid.Row="3"/> <TextBox Grid.Row="0" Text="{Binding KategorieName}" HorizontalAlignment="Right" Height="23" Margin="5" TextWrapping="Wrap" Width="594"/> <Button Grid.Row="1" Content="Hinzufügen" HorizontalAlignment="Right" Margin="5" Width="594" Command="{Binding}"/> ...
Ergebnis:
--
Best Regards / Viele Grüße
Peter Fleischer (former MVP for Developer Technologies)
Homepage, Tipps, Tricks
- Bearbeitet Peter Fleischer Montag, 6. Dezember 2021 19:45
-
Hallo Peter,
Danke für deine Antwort.
Zunächst klar habe ich den Code kopiert aber 1 zu 1 also bei meine TreeView Projekt ist noch nicht mehr dazu gekommen. Weil ich erst das TreeView komplett verstehen will. Danach kommt dann des DataGrid dazu aber soweit bin ich noch nicht.
Es macht noch kein Sinn mein Projekt zu Starten wenn ich das Grund noch nicht richtig verstanden habe.
Mein Altes Programm ist wo ich meine Ausgaben und Einnahmen eintrage und ein anderes wo meine Bücher, Filme und Musik gespeichert sind.
Ich möchte es aber weiter ausbauen wo dann noch meine Briefeingange auch gespeichert sind. Aber das ist alles noch nicht zu weit.
Ich habe dein Beispiel angeschaut. und habe gesehen das du das UserControl in dem selben Projekt drin hast und mit den <uc:----> einbindest. Ich habe das TreeView als ein extra projekt und als DLL Datei gespeichert und dann in ein anderes Projekt als Verweis hinzugefügt.
Nun Trage ich es in Xaml in MainWindow so ein.
<Mezzo_TreeView:Mezzo_TreeView x:Name="TreeViewTest" HorizontalAlignment="Left" VerticalAlignment="Top" Height="306" Width="305"/>
Warum ich es Hinzufügen von Nodes in Codebehind haben möchte ist der folgenden Grund, ich möchte später aus einer Datenbank ob es bei Access bleibt oder ich lieber doch auf SQL gehe ist noch unklar, aber aus welcher Datenbank es kommt spielt ja noch keine Rolle.
Somit will ich es dann z.b. so hinzufügen können.
myTreeView.nodes.add(KategorieNummer, KategorieUnterNummer, KategorieName, KategorieReihenfolge);
Warum ich das Beispiel mit den Textbox und dem Button gemacht habe ist das wenn ich in mein Programm dann ein Fenster mache Kategorien Hinzufügen das er mir 1. in die Datenbank einspeichertn tut und 2. das gleich angezeigt wird.
Zu dem Selected warum ich das auch in Codebehind haben will ist dieser, Wenn man in TreeView eine Kategorie Anklickt soll er aus der Tabelle oder List die ich vorher aus der DB geladen haben suchen welche Inhalte zu dieser Kategorie gehört. Datum habe ich das so gemeint
myTreeView.selected.KategorieName.toString;
Mir ist eine Idee gekommen ich denke es könnte funktionieren.
du hast in den ViewModel das
internal class Model
Wenn ich das ändere zu
public class Model { public Model(int EingabeKategorieNummer, int EingabeKategorieUnterNummer, string EingabeKategorieName, int KategorieReihenfoge) { col.Add(new Data_Kategorie(col) { KategorieNummer = EingabeKategorieNummer, KategorieUnterNummer = EingabeKategorieUnterNummer, KategorieName = EingabeKategorieName, KategorieReihenfolge = EingabeKategorieReihenfolge }); } //Datenspeicher internal ObservableCollection<Data_Kategorie> col = new ObservableCollection<Data_Kategorie>(); //Sicht für Root-Level internal ObservableCollection<Data_Kategorie> GetData(int levelID) => new ObservableCollection<Data_Kategorie>(col.Where((d) => d.KategorieUnterNummer == 0)); }
Dann sollte ich doch normal drauf zugreifen können oder?
Das probiere ich gleich morgen Vormittag oder Nachmittag mal aus und schaue ob das funktioniert.
Nur wie ich es dann wieder zurück gebe ist noch unklar.
Ich melde mich morgen wie ich vorran komme.
Gruß
Mezzo
-
Hi Mezzo,
beim TreeView ist vor allem zu verstehen, dass die Liste der Datenobjekte, deren Inhalt in der obersten Ebene darzustellen ist an die ItemsSource-Eigenschaft des TreeView gebunden wird. Für die Darstellung der Knoten sind Vorlagen (Template) als HierarchicalDataTemplate's zu erstellen. Die Templates werden für den darin deklarierten DataType angewandt, d.h. man kann für verschiedene Knoten unterschiedlich darstellen, je nach Typ des Datenobjektes, aus dem die Knotendarstellung ihre Werte bezieht. Im HierarchicalDataTemplate wird außerdem festgelegt, welche Eigenschaft eines Datenobjektes die Daten für die untergeordneten Knoten enthält.
Ich empfehle dir, erst einmal mit einem kleinen Projekt zu starten, um die Details zu verstehen (z.B. als Demo-Projekt).
In meinem letzten Beipiel war das UserControl nicht in gleichen Assembly. Das sollte aus den Namensräumen erkennbar sein.
<Window x:Class="WpfApp1.Window087" ... <UserControl x:Class="WpfControlLibrary1.Window087UC1" ...
Warum ich es Hinzufügen von Nodes in Codebehind haben möchte ist der folgenden Grund, ich möchte später aus einer Datenbank ob es bei Access bleibt oder ich lieber doch auf SQL gehe ist noch unklar, aber aus welcher Datenbank es kommt spielt ja noch keine Rolle.
Wenn du weiter bei deiner alten chaotischen Programmiertechnologie bleiben willst, wirst du aus meiner Erfahrung deine Zeit für unendliche Fehlersuchen verbrauchen und erst sehr spät eine brauchbare (fehlerarme) Lösung bekommen. Der Vorteil der OOP ist, dass man Codeabschnitte kapselt, austestet und danach nicht mehr anfassen muss bzw. erst bei späteren Änderungen (in der Kapsel).
Der gut überschaubare Weg in WPF ist der Aufbau einer Model-Klasse, die Daten als .NET-Objekte bereitstellt. Da ist es egal, welche Datenbank genutzt wird (Access oder SQL Server). Solch ein Model stellt die Daten als Auflistung von einzelnen Datenobjekten bereit, z.B. als ObservableCollection(Of DatenObjekt). Datenobjekt ist wiederum eine Klasse, die die Daten eines zu verabeitenden Datensatzes enthält. Solch ein Model kann man separat testen, ohne Rücksich nehmen zu müssen, wie die Model-Klasse später eingesetzt wird.
Die Auflistung aus dem Model kann dann an die Eigenschaften der Oberfläche (z.B. ItemsSource) gebunden werden. Für die Bindung wird eine Eigenschaft in der Instanz des dem DataContext zugewiesenen Objektes genutzt. Dafür wird im MVVM Entwurfsmuster ein ViewModel genutzt. Der ViewModel holt sich die Daten vom Model. bearbeitet sie bei Bedarf (z.B. Filter) und stellt sie in einer Eigenschaft für die weitere Verwendung (z.B. Bindung) in der Sicht (View) bereit. Damit kann man den ViewModel undabhängig von der Darstellung testen.
Wenn in der Oberfläche im TreeView ein Knoten angeklickt wird, wird dieser Knoten selektiert und das betreffende Datenobjekt steht damit in der SelectedItem-Eigenschaft bereit. Da der ViewModel die Oberfläche nicht kennt (er ist selbst nur eine Kapsel der Programmlogik ohne Oberfläche), müssen Ereignisse der Oberfläche über passende Techniken der ViewModel-Instanz "gemeldet" werden. Dafür können "attached properties" genutzt werden. Eine Vereinfachung dieser Nutzung kann man mit der Interactivity-dll erreichen. Ein Codebehind ist damit nicht erforderlich.
Die Suche in der Auflistung der Datenobjekte ist Aufgabe des ViewModels. Dazu ist wie in jedem Algorithmus zu definieren: wie wird der Start ausgelöst, welche Parameter charakterisieren den Start, wie sieht die Verarbeitung aus, wie sieht das Ergebnis, wo und wie wird es abgelegt und wie werden die Nutzer informiert, dass es neue Ergebnisse gibt. Den Start kann man durch Eingabeende einer TextBox einleiten, der Startparameter ist der Inhalt der TextBox, die Verarbeitung ist das Setzen eines Filterkriteriums. Zum Schluss bleibt dann nur noch die Benachrichtigung der Oberfläche (INotifyPropertyChanged), damit sie sich aktualisiert, d.h. dass sie die darzustellenden Daten erneut abruft. Mit dem erneuten Abruf der Daten wird dann auch der Filter wirksam.
Neue Datenobjekte werden erst einmal der Auflistung hinzugefügt und der Oberfläche wird mitgeteilt (INotifyPropertyChanged), dass sie sich zu aktualisieren hat. Damit werden die darzustellenden Daten (mit dem neuen Datenobjekt) für die Anzeige abgerufen. Bei Bedarf wird dem Model mitgeteilt (z.B. Save-Button, Methodenaufruf im Model), welcher neue Datensatz im externen Speicher (Datenbank) hinzuzufügen ist.
Mir ist eine Idee gekommen ich denke es könnte funktionieren.
Das ist keine gute Einstellung eines Programmierers. Man sollte die Überzeugung haben, dass man einen Vorgang versteht und man damit weiß, dass es funktionieren muss.
Dann sollte ich doch normal drauf zugreifen können oder?
Auf eine Klasse kann man nicht zugreifen. Ein Zugriff geht nur auf Objekte, die vom Typ einer Klasse sind. Bei nicht statischen Objekten benötigt man da immer eine Verweisvariable.
--
Best Regards / Viele Grüße
Peter Fleischer (former MVP for Developer Technologies)
Homepage, Tipps, Tricks -
Hallo
Ich hoffe das des so mein Code nun Besser aussieht:
Mein Usercontrol:
Mezzo_TreeView.xaml:
<UserControl x:Class="Mezzo_TreeView.Mezzo_TreeView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:Mezzo_TreeView" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Control.DataContext> <local:ViewModel/> </Control.DataContext> <Grid> <TreeView ItemsSource="{Binding RootItems}"> <i:Interaction.Behaviors> <local:TreeViewBehavior/> </i:Interaction.Behaviors> <TreeView.Resources> <HierarchicalDataTemplate DataType="{x:Type local:Data_Kategorie}" ItemsSource="{Binding ChildrenItems}"> <TextBlock Text="{Binding KategorieName}"/> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> </Grid> </UserControl>
ViewModel.cs in UserControl
using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Interactivity; namespace Mezzo_TreeView { public class ViewModel { public ViewModel() { cvs.Source = (new Model()).GetData(0); cvs.SortDescriptions.Add(new SortDescription("KategorieReihenfolge", ListSortDirection.Ascending)); } private CollectionViewSource cvs = new CollectionViewSource(); public ICollectionView RootItems { get => cvs.View; } public Data_Kategorie Detail { set { MessageBox.Show($"KategorieNummer: {value.KategorieNummer}\nKategorieUnterNummer: {value.KategorieUnterNummer}\nKategorieName: {value.KategorieName}"); } } } public class Model { public void Eingabe(int EingabeKategorieNummer, int EingabeKategorieUnterNummer, string EingabeKategorieName, int EingabeKategorieReihenfolge) { col.Add(new Data_Kategorie(col) { KategorieNummer = EingabeKategorieNummer, KategorieUnterNummer = EingabeKategorieUnterNummer, KategorieName = EingabeKategorieName, KategorieReihenfolge = EingabeKategorieReihenfolge }); } public Model() { } //Datenspeicher internal ObservableCollection<Data_Kategorie> col = new ObservableCollection<Data_Kategorie>(); //Sicht für Root-Level internal ObservableCollection<Data_Kategorie> GetData(int levelID) => new ObservableCollection<Data_Kategorie>(col.Where((d) => d.KategorieUnterNummer == 0)); } public class Data_Kategorie { public Data_Kategorie(ObservableCollection<Data_Kategorie> data) => col = data; private ObservableCollection<Data_Kategorie> col; public int KategorieNummer { get; set; } public int KategorieUnterNummer { get; set; } public string KategorieName { get; set; } public int KategorieReihenfolge { get; set; } public ObservableCollection<Data_Kategorie> ChildrenItems { get => new ObservableCollection<Data_Kategorie>(col.Where((d) => d.KategorieUnterNummer == this.KategorieNummer)); } public bool IsExpanded { get; set; } = true; public bool IsSelected { get; set; } } public class TreeViewBehavior : Behavior<TreeView> { protected override void OnAttached() => AssociatedObject.SelectedItemChanged += AssociatedObject_SelectedItemChanged; private void AssociatedObject_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { var tv = sender as TreeView; if (tv == null) return; var vm = tv.DataContext as ViewModel; if (vm == null) return; vm.Detail = tv.SelectedItem as Data_Kategorie; } } }
Mein anderes Project TreeView_Test
MainWindow.xaml:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:uc="clr-namespace:Mezzo_TreeView;assembly=Mezzo_TreeView" xmlns:local="clr-namespace:TreeView_Test" xmlns:Mezzo_TreeView="clr-namespace:Mezzo_TreeView;assembly=Mezzo_TreeView" x:Class="TreeView_Test.MainWindow" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <uc:Mezzo_TreeView Margin="0,0,558,0"/> </Grid> </Window>
MainWindow.xaml.cs:
using Mezzo_TreeView; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace TreeView_Test { /// <summary> /// Interaktionslogik für MainWindow.xaml /// </summary> public partial class MainWindow : Window { public Model addtest = new Model(); public MainWindow() { InitializeComponent(); Datenbankabfrage(); } private void Datenbankabfrage() { // Hier kommt später die Datenbank abfrage rein. addtest.Eingabe(100, 0, "Test", 1); } } }
Leider wird mir mein Eintrag nicht angezeigt ich vermute das es ancvs.Source = (new Model()).GetData(0);
liegt. Wenn ich das umändere auf (new Eintrag()).GetData(0); dann kommt mir der Fehler.
So sieht der Code schon ordenlicher aus oder?
Wie kann man das Lösen?
Gruß
Mezzo
-
Hi Mezzo,
es ist immer noch etwa chaotisch.1. das oberste Sicht-Object ist dein MainWindow mit CodeBehind. Warum nutzt du da CodeBehind anstelle eines ViewModels mit Bindung an den DataContext?
2. Logisch darunter befindet sich das UserControl, welches eine eigene ViewModel-Instanz erzeugt und als eigenen (sparaten) DataContext nutzt. Warum nutzt nicht den "übergeordneten" DataContext?
3. Die einzige Verbindung zwischen MainWindow und UserControl ist das Einbinden des UserControls in die übergeordnete Sicht (MainWindow). Das UnserControl ist eine Kapsel, auf deren Innereien ohne besonde3re Techniken nicht zugegriffen werden kann.
Unklar ist, wie du dir diese Verbindung in Zukunft vorstellst.
Üblich ist der Weg, den ich dir in meinen Beispielen bereits mehrfach gezeigt habe. Dem MainWindow wird eine ViewModel-Instanz als DataContext zugewiesen. Ein im Mainwindow eingebettetes UserControl bekommt damit automatisch auch diesen DataContext. Über diese ViewModel-Instanz können Daten zwischen MainWindow und UserControl "ausgetauscht" bzw. gemeinsam genutzt werden. Änderungen der Daten können als Start eines Prozesses dienen, z.B. Filtern mit nachfolgender Benachrichtigung der Sicht, damit diese die aktuellen Werte für eine erneute Anzeige nutzen.
--
Best Regards / Viele Grüße
Peter Fleischer (former MVP for Developer Technologies)
Homepage, Tipps, Tricks -
Hallo Peter
Danke für deine Antwort.
Also das ich es jetzt Richtig verstanden habe ich muss bei MainWindow auch ein ViewModel klasse erstellen und in xaml mit Datacontext mit ViewModel verbinden.
Dann greift mein MainWindow auf 2 ViewModel zu von UserControl und von MainWindow.
Ich probiere es dann gleich mal aus.
Warum ich das Codebehind benutzt habe ist nur aus dem Grund wegen der Datanbank abfrage.
Wie ich es jetzt verstanden habe soll die Datenbank Abfrage in ViewModel rein.
Ich probiere es gleich mal aus und sende dann den Code.
Gruß
Mezzo
-
Also das ich es jetzt Richtig verstanden habe ich muss bei MainWindow auch ein ViewModel klasse erstellen und in xaml mit Datacontext mit ViewModel verbinden.
Hi Mezzo,
du musst nur das machen, was in deinem Programmablauf erforderlich ist, also das machen, was du in dem zu realisierenden Algorithmus definiert hast. Wenn da zwei ViewModels vorgesehen sind, dann brauchst du zwei. Ich weiß aber nicht, warum du zwei brauchst.Das MVVM Entwurfsmuster sagt: View (also XAML) ist gebunden an den oder die ViewModel. Der ViewModel kommuniziert mit dem Model, der alle Datenbankzugriffe realisiert. Wenn du dieses Entwurfsmuster mit CodeBehind durchbrechen willst, dann musst du das daraus entstehende Chaos auch beherrschen. Ich kann dir da wenig helfen.
--
Best Regards / Viele Grüße
Peter Fleischer (former MVP for Developer Technologies)
Homepage, Tipps, Tricks -
Hallo Peter
Ich wollte eigentlich nur außerhalb von UserControl die Daten hinzufügen.
Aber so wie ich es jetzt verstanden habe ist das ich die Datenbank Abfrage in das UserControl machen muss.
Mein Gedanke war so das ich ein Extra UserControl von TreeView habe die ich immer wieder verwenden kann. Wo ich dann die DLL in Verweiß hinzufüge und ich das TreeView in meiner Toolbar habe und ich es in meiner Xaml reinziehen kann und es dann auch befüllen kann.
Ob ich es dann mit der Datenbankabfrage befülle oder manuell das ist dann egal. Es geht nur darum das es dann immer so angezeigt wird.
Dies wird wahrscheinlich nicht möglich sein.
Gruß
Mezzo
-
Hi Mezzo,
möglich ist vieles. Ich habe nicht verstanden, was konkret deine Frage ist.--
Best Regards / Viele Grüße
Peter Fleischer (former MVP for Developer Technologies)
Homepage, Tipps, Tricks -
Hallo Peter
Ich versuche besser es zu erklären.
Ich bin so ein Mensch der alles Trennen tut.
Wir haben das TreeView nun wo es die Anzeige mit die unterknoten richtig darstellt. Diese will es als UserControl haben so wie manche User es ja schon für Verschiede sachen schon gemacht haben ich glaube das nennt man UI Controls bin mir aber nicht sicher.
Diese möchte ich dann in ein anderes Projekt in MainWindow einfügen.
Aber ich wollte das nicht mit <UserControl:Mezzo_TreeView /> machen sondern so wie auch ein Textbox oder ein normales TreeView eingefügt wird. <Mezzo_TreeView />
Und da muss die TreeView nur noch gefüllt werden z.b.
Mezzo_TreeView.Nodes.Add(100, 0, "Name", 1);
so das es dann angezeigt wird.
Genauso wenn es ein Nodes Markiert wird das es mit von dem Mezzo_TreeView UserControl es mir in mein MainWindow zurück gibt.
Ob man die Daten von CodeBehind oder in einer extra klasse macht spielt auch keine rolle.
Ich hoffe ich konnte es besser erklären.
Gruß
Mezzo
-
Hallo
Ich habe jetzt mehrmals Probiert und bekomme es irgendwie nicht so hin wie ich es will.
ICh habe in der UserCondroll in ViewModell diese Funktion
public void Eingabe(int EingabeKategorieNummer, int EingabeKategorieUnterNummer, string EingabeKategorieName, int EingabeKategorieReihenfolge) { col.Add(new Data_Kategorie(col) { KategorieNummer = EingabeKategorieNummer, KategorieUnterNummer = EingabeKategorieUnterNummer, KategorieName = EingabeKategorieName, KategorieReihenfolge = EingabeKategorieReihenfolge }); }
Jetzt hab ich das Problem mit dem Sourece, ich kann die Funktion Eingabe nich in cvs.Source eintragen
cvs.Source = (new Model()).GetData(0);
Wie müsste jetzt mein cvs.Source Aussehen?
Mein Code ist ja schon oben drin von Dienstag, 7. Dezember 2021 08:16
Gruß
Mezzo
-
Hi Mezzo,
in der GetData-Methode füllst du die ObservableCollection mit Datenobjekten vom Typ Data_Kategorie. Im Konstruktor des ViewModels weist du diese Auflistung der Source-Eigenschaft des CollectionViewSource-Objektes zu. Wenn du jetzt neue Data_Kategorie-Objekte der Auflistung hinzufügst, brauchst du diese Auflistung nicht erneut der CollectionViewSource zuzuweisen. Die ObservableCollection implementiert das Interface INotifyCollectionChanged und benachrichtigt darüber die Oberfläche über Änderungen, wie z.B. das Hinzufügen von Datenobjekten. Wenn das bei dir nicht funktioniert, nutzt du vermutlich in der Variablem "col" einen falschen Verweis. Prüfe das mal.Wenn ich dir wirklich helfen soll, dann solltest erst einmal dein Konzept für das UserControl mit dem TreeView darlegen:
Welche Darstellung und welche Funktionen sollen gekapselt werden?
Was soll von "außen" sichtbar sein und damit vom MainWindow parametriert werden können und ggf. auch gebunden werden?Mit diesem Konzept sollte dann eine kleine Demo (/Test) erstellt werden. Vielleicht beginnst du mit Top-Down und stellst erst einmal diese Demo vor, aus der man dann erkennen kann, wie das UserControl genutzt werden soll. Daraus kann man dann ableiten, welche öffentlichen Eigenschaften das UserColtrol haben muss.
--
Best Regards / Viele Grüße
Peter Fleischer (former MVP for Developer Technologies)
Homepage, Tipps, Tricks
- Bearbeitet Peter Fleischer Mittwoch, 15. Dezember 2021 04:51
-
Hallo Peter
Ich komm irgendwie nicht klar ich sende dir mein Code
Hier eine Klassenanwendung die als DLL Datei gespeichert wird.
UserControl
<UserControl x:Class="Mezzo_TreeView.Mezzo_TreeView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:Mezzo_TreeView" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Control.DataContext> <local:ViewModel/> </Control.DataContext> <Grid> <TreeView ItemsSource="{Binding RootItems}"> <i:Interaction.Behaviors> <local:TreeViewBehavior/> </i:Interaction.Behaviors> <TreeView.Resources> <HierarchicalDataTemplate DataType="{x:Type local:Data_Kategorie}" ItemsSource="{Binding ChildrenItems}"> <TextBlock Text="{Binding KategorieName}"/> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> </Grid> </UserControl>
ViewModel.cs
using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Interactivity; namespace Mezzo_TreeView { public class ViewModel { public ViewModel() { //cvs.Source = (new Model()).GetData(0); cvs.Source = new Model().GetData(0); cvs.SortDescriptions.Add(new SortDescription("KategorieReihenfolge", ListSortDirection.Ascending)); } private CollectionViewSource cvs = new CollectionViewSource(); public ICollectionView RootItems { get => cvs.View; } public Data_Kategorie Detail { set { MessageBox.Show($"KategorieNummer: {value.KategorieNummer}\nKategorieUnterNummer: {value.KategorieUnterNummer}\nKategorieName: {value.KategorieName}"); } } } public class Model { public void Eingabe(int EingabeKategorieNummer, int EingabeKategorieUnterNummer, string EingabeKategorieName, int EingabeKategorieReihenfolge) { col.Add(new Data_Kategorie(col) { KategorieNummer = EingabeKategorieNummer, KategorieUnterNummer = EingabeKategorieUnterNummer, KategorieName = EingabeKategorieName, KategorieReihenfolge = EingabeKategorieReihenfolge }); } public Model() { } //Datenspeicher internal ObservableCollection<Data_Kategorie> col = new ObservableCollection<Data_Kategorie>(); //Sicht für Root-Level internal ObservableCollection<Data_Kategorie> GetData(int levelID) => new ObservableCollection<Data_Kategorie>(col.Where((d) => d.KategorieUnterNummer == 0)); } public class Data_Kategorie { public Data_Kategorie(ObservableCollection<Data_Kategorie> data) => col = data; private ObservableCollection<Data_Kategorie> col; public int KategorieNummer { get; set; } public int KategorieUnterNummer { get; set; } public string KategorieName { get; set; } public int KategorieReihenfolge { get; set; } public ObservableCollection<Data_Kategorie> ChildrenItems { get => new ObservableCollection<Data_Kategorie>(col.Where((d) => d.KategorieUnterNummer == this.KategorieNummer)); } public bool IsExpanded { get; set; } = true; public bool IsSelected { get; set; } } public class TreeViewBehavior : Behavior<TreeView> { protected override void OnAttached() => AssociatedObject.SelectedItemChanged += AssociatedObject_SelectedItemChanged; private void AssociatedObject_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) { var tv = sender as TreeView; if (tv == null) return; var vm = tv.DataContext as ViewModel; if (vm == null) return; vm.Detail = tv.SelectedItem as Data_Kategorie; } } }
dann ein neues Projekt so der Verweiß auf TreeView drauf ist
MainWindow
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:uc="clr-namespace:Mezzo_TreeView;assembly=Mezzo_TreeView" xmlns:local="clr-namespace:TreeView_Test" xmlns:Mezzo_TreeView="clr-namespace:Mezzo_TreeView;assembly=Mezzo_TreeView" x:Class="TreeView_Test.MainWindow" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <uc:Mezzo_TreeView Margin="0,0,558,0"/> </Grid> </Window>
MainWindow.CS
using Mezzo_TreeView; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace TreeView_Test { /// <summary> /// Interaktionslogik für MainWindow.xaml /// </summary> public partial class MainWindow : Window { public Model addtest = new Model(); public MainWindow() { InitializeComponent(); Datenbankabfrage(); } private void Datenbankabfrage() { // Hier kommt später die Datenbank abfrage rein. addtest.Eingabe(100, 0, "Test", 1); } } }
Wie kann ich das durch .Engaben die Daten eintragen.
Und wichtig ist das ich eine Klasse habe so mir die Daten von Markierten Knoten wieder zurück gibt.
Wie setzt man das um?
Danke im Vorraus.
Gruß
Mezzo