WPF TreeView Categorien mit UnterCategorien und/oder Artikel

  • WPF

Es gibt 16 Antworten in diesem Thema. Der letzte Beitrag () ist von MichaHo.

    WPF TreeView Categorien mit UnterCategorien und/oder Artikel

    Hallo,
    ich bin gerade an einem kleinen Programm.
    Dort möchte ich Artikelstämme mit Kategorien, UnterKategorien und/oder Artikeln in einem TreeView anzeigen.
    Ich habe dazu ein kleines Beispielprojekt angehangen.
    Es gibt 2 Artikelstämme. Diese haben jeweils 2 Kategorien. Diese wiederum haben in der ersten Kategorie Artikel und in der 2. Kategorie eine Unterkategorie die wiederum 1 Artikel hat.
    Die Model dazu sehen so aus:

    C#-Quellcode

    1. public class ArticleMaster
    2. {
    3. public int Id { get; set; }
    4. public string MasterName { get; set; }
    5. public ICollection<ArticleCategory> Categories { get; set; }
    6. }
    7. public class ArticleCategory
    8. {
    9. public int Id { get; set; }
    10. public string CategoryName { get; set; }
    11. public ICollection<ArticleCategory> SubCategories { get; set; }
    12. public ICollection<Article> Articles { get; set; }
    13. }
    14. public class Article
    15. {
    16. public int Id { get; set; }
    17. public string Name { get; set; }
    18. public int CategoryId { get; set; }
    19. public ArticleCategory Category { get; set; }
    20. }

    Im ViewModel habe ich eine ObservableCollection von ArtikelMaster die im Constructor gefüllt wird.

    C#-Quellcode

    1. public class MainWindowViewModel : ViewModelBase
    2. {
    3. private readonly IMemoryDataProvider _dataProvider;
    4. public MainWindowViewModel()
    5. {
    6. _dataProvider = new MemoryDataProvider();
    7. LoadArticleMasters();
    8. }
    9. private ObservableCollection<ArticleMaster> _masters;
    10. public ObservableCollection<ArticleMaster> Masters { get => _masters; set => SetValue(ref _masters, value); }
    11. private void LoadArticleMasters()
    12. {
    13. var list = _dataProvider.GetArticleMasters();
    14. Masters = new ObservableCollection<ArticleMaster>();
    15. list.ForEach(m => Masters.Add(m));
    16. }
    17. }


    im Provider wird eine FakeListe erstellt:

    C#-Quellcode

    1. public class MemoryDataProvider : IMemoryDataProvider
    2. {
    3. public List<ArticleMaster> GetArticleMasters()
    4. {
    5. var list = new List<ArticleMaster>();
    6. list.Add(CreateArticleMaster("ArtikelStamm_1"));
    7. list.Add(CreateArticleMaster("ArtikelStamm_2"));
    8. return list;
    9. }
    10. private ArticleMaster CreateArticleMaster(string masterName)
    11. {
    12. var master = new ArticleMaster
    13. {
    14. Id = 1,
    15. MasterName = masterName
    16. };
    17. master.Categories = CreateCategories();
    18. return master;
    19. }
    20. private List<ArticleCategory> CreateCategories()
    21. {
    22. var list = new List<ArticleCategory>();
    23. list.Add(new ArticleCategory
    24. {
    25. Id = 1,
    26. CategoryName = "CategoryWithChildren",
    27. Articles = new List<Article> { new Article
    28. {
    29. Id = 1,
    30. Name = "FirstArticle", CategoryId = 1
    31. } }
    32. });
    33. list.Add(new ArticleCategory
    34. {
    35. Id = 2,
    36. CategoryName = "´CategoryWithSubCategories",
    37. SubCategories = new List<ArticleCategory> { new ArticleCategory
    38. {
    39. Id = 1,
    40. CategoryName = "SubCategoryWithChildren",
    41. Articles = new List<Article>
    42. {
    43. new Article
    44. {
    45. Id = 1,
    46. Name = "ArticleInSub",
    47. CategoryId = 1
    48. }
    49. }
    50. } }
    51. });
    52. return list;
    53. }
    54. }


    Im MainWindow habe ich ein TreeView welches als ItemSource an die Property im VM gebunden ist (Masters).
    Dann füge ich dem TreeView.ItemTemplate HierarchicalDataTemplate hinzu und setze die ItemSource jeweils auf die Propertys der Masters Collection:

    XML-Quellcode

    1. <Window.DataContext>
    2. <vm:MainWindowViewModel/>
    3. </Window.DataContext>
    4. <Grid>
    5. <TreeView ItemsSource="{Binding Masters}">
    6. <TreeView.ItemTemplate>
    7. <HierarchicalDataTemplate ItemsSource="{Binding Categories}">
    8. <StackPanel Orientation="Horizontal">
    9. <Label Content="{Binding Id}"/>
    10. <Label Content="{Binding MasterName}"/>
    11. </StackPanel>
    12. <HierarchicalDataTemplate.ItemTemplate>
    13. <HierarchicalDataTemplate ItemsSource="{Binding SubCategories}">
    14. <StackPanel Orientation="Horizontal">
    15. <Label Content="{Binding Id}"/>
    16. <Label Content="{Binding CategoryName}"/>
    17. </StackPanel>
    18. <HierarchicalDataTemplate.ItemTemplate>
    19. <HierarchicalDataTemplate ItemsSource="{Binding Articles}">
    20. <StackPanel Orientation="Horizontal">
    21. <Label Content="{Binding Id}"/>
    22. <Label Content="{Binding CategoryName}"/>
    23. </StackPanel>
    24. <HierarchicalDataTemplate.ItemTemplate>
    25. <DataTemplate>
    26. <StackPanel Orientation="Horizontal">
    27. <Label Content="{Binding Id}"/>
    28. <Label Content="{Binding Name}"/>
    29. </StackPanel>
    30. </DataTemplate>
    31. </HierarchicalDataTemplate.ItemTemplate>
    32. </HierarchicalDataTemplate>
    33. </HierarchicalDataTemplate.ItemTemplate>
    34. </HierarchicalDataTemplate>
    35. </HierarchicalDataTemplate.ItemTemplate>
    36. </HierarchicalDataTemplate>
    37. </TreeView.ItemTemplate>
    38. </TreeView>
    39. </Grid>


    Das funktioniert soweit auch bestens. allerdings fehlt mir jeweils in der ersten Kategorie der Artikel.
    Es muss also vor den SubCategories noch irgendwie die Artikelliste rein.
    Ich stehe nur grad auf dem Schlauch wie.
    Hab einiges im Netz gelesen aber das bezieht sich meist darauf, wie man ein Treeview mit einer Liste und einer UnterListe aufbaut.

    Hat jemand ne Idee wie ich das hin bekomme?
    Danke Euch
    Grüße
    Dateien
    • WpfTreeView.zip

      (6,37 kB, 10 mal heruntergeladen, zuletzt: )
    "Hier könnte Ihre Werbung stehen..."
    ich hab mal sowas ähnliches gemacht, aber da hab ich einfach DataTemplates in die Treeview-Resourcen gekippt.
    Also nicht HierarchicalDataTemplate ineinander verschachtelt, sondern je Typ der anzuzeigen ist ein DataTemplate in die Resourcen.
    Wenn SubNodes vorgesehen, dann eben ein HierarchicalDataTemplate.

    Mein Werk müsste inne wpf-Tut zu finden sein.
    Hi Du,
    ja, dein Tut hab ich quer gelesen und die solution runter geladen.
    ich hab obige Solution auch probiert mit HirachicalDataTemplates in den TreeView.Resources.
    Ging leider alles nicht.
    Mein Problem ist halt, das Jede Category eigene Artikel haben kann und SubKategorien die wiederum eigene Artikel haben und sogar wieder eigene Subkategorien.
    also ich schaffe es bis zur 2. ebene aber nicht bis runter auf alle Ebenen.
    hab auch im Netz gelesen wegen CompositeCollection das hat auch nur bedingt funktioniert.
    bis zur 2. ebene halt.
    ich guck nochmal in deine solution, vielleicht ist mein Model ja auch grütze…
    "Hier könnte Ihre Werbung stehen..."
    jo, nicht dass du das falsche Tut erwischt hast - ich meine Grundlagen - MVVM-Pattern, DataContext und DataTemplates im Treeview

    Daraus das Xaml:

    XML-Quellcode

    1. <Window x:Class="MainWindow"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. Title="MainWindow" Height="350" Width="525"
    5. xmlns:my="clr-namespace:WpfFilesystemTreeView"
    6. DataContext="{x:Static my:MainModel.Root}">
    7. <Grid>
    8. <Grid.RowDefinitions>
    9. <RowDefinition Height="Auto" />
    10. <RowDefinition />
    11. </Grid.RowDefinitions>
    12. <Menu>
    13. <MenuItem Header="Load" Command="{Binding Path=FileSystemTree.Load}" />
    14. </Menu>
    15. <TreeView Grid.Row="1" ItemsSource="{Binding Path=FileSystemTree.Roots}" >
    16. <TreeView.Resources>
    17. <HierarchicalDataTemplate DataType="{x:Type my:DirectoryNode}"
    18. ItemsSource="{Binding Path=Childs}">
    19. <StackPanel Orientation="Horizontal">
    20. <Image Width="16" Height="16" Source="/WpfFilesystemTreeView;component/Icons/Folder.ico" />
    21. <TextBlock Margin="2 1 0 1" Text="{Binding Path=Item.Name}" />
    22. </StackPanel>
    23. </HierarchicalDataTemplate>
    24. <DataTemplate DataType="{x:Type my:FileSystemNode}" >
    25. <StackPanel Orientation="Horizontal">
    26. <Image Width="16" Height="16" Source="/WpfFilesystemTreeView;component/Icons/File.ico" />
    27. <TextBlock Margin="2 1 0 1" Text="{Binding Path=Item.Name}" />
    28. </StackPanel>
    29. </DataTemplate>
    30. </TreeView.Resources>
    31. </TreeView>
    32. </Grid>
    33. </Window>
    Wie du siehst, für die beiden Viewmodel-Typen einfach je ein (Hierarchical-)DataTemplate hineingeworfen und gut.
    Wesentlich die Angabe von DataType - danach sucht ein Daten-Item sich nämlich "sein" Template aus.

    Die DatenItem werden in einer OC(Of Object) bereitgestellt, und zwar nur eine.
    Also im Viewmodel wird nicht unterschieden zw.DirectoryItem und FileItem - das wird zusammengeworfen, und das GUI differenziert das je nach DataTemplate-Type.
    Ach nee - ist keine OC(Of Object), sondern (Of FileSystemNode), also der Basisklasse beider. Aber das macht kein Unterschied.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Ahh, siehste falsches Tut. Ich hatte das WPF TreeView Tut.
    Ich guck es mir an.
    Danke Dir.

    Edit: ah, warte, das ist aber das gleiche TV.
    ich guck es mir nochmal an vielleicht hab ich wieder irgendwo was vergessen.
    "Hier könnte Ihre Werbung stehen..."

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „MichaHo“ ()

    Hallo @ErfinderDesRades
    Ich hab es nun geschafft, allerdings nur mit einer CompositeCollection im Model (siehe Anhang)
    Für den Test ist das OK, aber in einer MVVM App hole ich mir damit ja das Model in die View.
    Ich bin gerade dabei das in ViewModels umzubauen und dann mal schauen ob es wieder klappt.
    Wobei ich im ViewModel Projekt dann auch wieder die Presentation Framework hinzufügen müsste....

    EDIT:
    So, hab mal alles auf ViewModel umgebogen und die CompositeCollection nun im ViewModel.
    Klappt soweit auch.
    Nun muss ich gucken wie ich die CompositeCollection gegen was anneres tausche um das PresentationFramework nicht im ViewModel zu haben.
    anbei noch das 2. Projekt mit den ViewModels
    Dateien
    • WpfTreeView.zip

      (6,61 kB, 5 mal heruntergeladen, zuletzt: )
    • WpfTreeView_VM.zip

      (7,77 kB, 7 mal heruntergeladen, zuletzt: )
    "Hier könnte Ihre Werbung stehen..."

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „MichaHo“ ()

    Hi,
    naja, das PresentationFramework ist ja ein View Bestandteil und den will man nicht im ViewModel oder Model haben.
    Ich hab versucht die Composite Collection (kommt aus System.Windows.Data) als ObservableCollection nach zu bauen, aber da fehlt mir ein gemeinsames Model oder ViewMoodel was entweder eine ArticleCategory oder ein Article sein kann.
    "Hier könnte Ihre Werbung stehen..."
    jo, scheint tatsächlcih bischen so, dass CompositeCollection eher eine Innerei von Wpf-Windows ist, die man als Coder normal garnet zu gesicht bekommt.
    Wüsste auch nicht, warum du dasselbe nicht ebenso mit einer OC(Of Object) erreichen können solltest.
    Aber ich guck mir jetzt auch mal dein werk an...
    ich versteh jetzt das Problem besser.
    also bei mir hab ich ja alles in dieselbe OC gekippt - dassis ziemlich statisch.

    Mir gefällt das mit der CompositeCollection jetzt recht gut, vor allem, wenn die UnterCollections nachwievor databinding-mässig funktionieren, also wenn man mw. der UnterCollection SubCategories ein Element löscht, dass das dann im View auch wirksam ist.
    Das müsste man aber mal testen.
    Hmm - nach meinem Test-Versuch funzt das leider doch nicht - also da kann mans auch so statisch machen wie annodazumal.

    Ich würde da nicht das Rad neu erfinden, nur weil es aus dem "falschen Hause" stammt. Naja, aber wenn die nix bringt, dann kann sie natürlich gerne Zuhause bleiben.
    Hi,
    mein Problem ist halt, das die Artikel Kategorien selber wieder Kategorien haben kann UND Artikel, und Unter Kategorien mit Artikeln.
    Das bekomme ich ohne diese Composite Collection einfach nicht hin.
    Man muss ja auch in der Lage sein, einen Artikel zu einer Kategorie oder einer Unterkategorie hinzufügen zu können und auch dort raus nehmen zu können.
    Alle Beispiele die man so findet richten sich immer an das Dateisystem, das ist ja dann auch recht einfach weil man einfach nur recursiv durchs Dateisystem durchwandert und die Einträge hinzufügt.
    Aber hier, das ganze dann noch mit ViewModels anstatt des Models ist schon echt kniffelig, zumindest für mich.
    Ohne CompositeCollection hab ich aber auch noch keinen Weg gefunden...

    In eine einzige OC das klappt ja auch wunderbar, allerdings nur mit dem Model. Mit dem ViewModel wird das nix, weil ich muss ja dann der OC nicht nur ein ArticleCategoryViewModel rein schmeißen, sondern noch ein ArticleViewModel.
    Hatte es mal mit einem ItemViewModel probiert aber da bekomme ich die Unterscheidung zwischen Article und ArticleCategory nicht hin.

    Puhh... ich muss noch was rum basteln...
    "Hier könnte Ihre Werbung stehen..."
    @ErfinderDesRades ich hab eine erste Lösung ohne die View (PresentationFramework) ins ViewModel zu holen.
    Einen IMultiValueConverter erstellt und darin die CompositeCollection gefüllt.
    Dann im Xaml den Converter hinzugefügt und ein Multibinding im HirachicalDataTemplate von den SubCategories hinzugefügt.
    Die Datatypes auf die entsprechenden ViewModels gesetzt und voila... läuft.
    Jetzt muss ich noch das hinzufügen und entnehmen testen.
    Projekt im Anhang
    Dateien
    "Hier könnte Ihre Werbung stehen..."

    MichaHo schrieb:

    ohne die View (PresentationFramework) ins ViewModel zu holen.
    Einen IMultiValueConverter erstellt
    Nach meiner Auffassung sind so spezialisierte ValueConverter ebensogut Viewmodel wie anderes Viewmodel auch.
    Iein Wpf-Guru hat auch mal gesagt: "Ich verwende keine ValueConverter - mein Viewmodel ist der ValueConverter"
    Dann müssten die ViewModels aber im View Projekt sein.
    das wäre doof wenn man dann statt ner WPF App eine UWP bauen will.
    Daher sind in meinen Projekten die ViewModels in einer eignen Bibliothek
    Aber da streiten sich die Geister
    Wobei ValueConverter benutze ich aber einige. Vor allem BooleanToVisibility Converter
    Auch AttachedProperties um zum Beispiel Rahmenlose Fenster zu schließen, verschieben und minimieren.

    Edit:
    ich kann ja das Projekt mal zu meinem MVVM Verständnis umbauen, dann wird es vermutlich klarer.
    "Hier könnte Ihre Werbung stehen..."
    tja, ich sollte immer erst reingucken, bevor ich rumsülze.
    Dein Converter ist ja garnet hochspezialisiert - ich hatte mir iwas vorgestellt, was denn mit Articles und Categories und Kram rummacht.
    Nee - so allgemeine Converter würde ich auch net als Viewmodel bezeichnen - und den Bool2Visibility natürlich auch nicht.
    Also findich sehr hübsch, wies gebastelt ist - thx, und kommt in meine Sammlung ;)
    Oder mach doch einen tip daraus - ich find das eine überzeugende Vorführung von Multibinding.

    Mal andere Frage:

    XML-Quellcode

    1. <HierarchicalDataTemplate DataType="{x:Type vm:ArticleViewModel }" >
    2. <StackPanel Orientation="Horizontal">
    3. <Label Content="{Binding Id}"/>
    4. <Label Content="{Binding Name}"/>
    5. </StackPanel>
    6. </HierarchicalDataTemplate>
    wieso ein HierarchicalDataTemplate, wenn ItemsSouce garnet gesetzt ist?

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Ja, cool.
    ich bin darauf gestoßen beim googeln.
    da gibt es viele viele Dinge mit CodeBehind oder sonstigem kram.
    In irgendeinem dieser ganzen Einträge gab es unter den Antworten einen, der meinte warum der Threadersteller nicht einfach einen Multivalueconverter nimmt und dann im TreeView ein Multibinding macht. Das war der Ausschlaggebende Tip.
    Das Multibinding muss dann eben da hin, wo die Collections im ViewModel verfügbar sind, deswegen sind die bei mir nicht im TreeView sondern im HirachicalDataTemplate der Categories.
    ich find die Lösung prima.
    Hab auch schon den AddArticleCommand hinzugefügt, aber leider wird die View noch nicht aktualisiert, ich vermute da muss ich die Collections neu befüllen statt nur OnPropertyChanged aufzurufen.
    Naja, wird schon.
    "Hier könnte Ihre Werbung stehen..."