Daten für TreeView dynamisch erzeugen

  • WPF MVVM
  • .NET (FX) 4.5–4.8

Es gibt 22 Antworten in diesem Thema. Der letzte Beitrag () ist von DTF.

    Daten für TreeView dynamisch erzeugen

    Hallo miteinander :)

    Ich hab da ne Frage, die vielleicht sehr einfach zu beantworten ist, bin aber die ganze Zeit schon am Überlegen, wie ich das mache und komm einfach auf keinen grünen Zweig:

    Und zwar möchte ich ein Art Quick Start Guide für mein Programm machen, mit links einem TreeView und rechst einer Detailansicht.

    Das TreeView zu befüllen scheint recht einfach zu sein, aber wie erzeuge ich die Datenstruktur dafür dynamisch? Hintergrund: Da es doch recht umfangreich ist schreibe ich ein Tool, das mir die Arbeit erleichtern soll. Man soll Überschrift (zur Anzeige im TreeView), den Hilfetext selbst und den Pfad zu einem Screenshot adden können, indem man diese in Textboxen eingibt und dann auf einen Button klickt. Auch mit dabei: ein Prefix (z.B. 1.2.1), damit das Programm weiss, ich welcher Hierarchieebene der Eintrag zu machen ist. In meinem Model hab ich also diese Klasse:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Option Strict On
    2. Imports System.Collections.ObjectModel
    3. Public Class KapitelModel
    4. Public Property Prefix As String
    5. Public Property Ueberschrift As String
    6. Public Property Inhalt As String
    7. Public Property BildPfad As String
    8. Public Property UnterKapitel As ObservableCollection(Of KapitelModel)
    9. Public Sub New(argPrefix As String, argUeberschrift As String, argInhalt As String, argBildPfad As String)
    10. Prefix = argPrefix
    11. Ueberschrift = argUeberschrift
    12. Inhalt = argInhalt
    13. BildPfad = argBildPfad
    14. End Sub
    15. End Class


    Aber wie erzeuge ich die Datenstruktur dann am besten, wie gesagt? Im Beispielprogramm von Nofear23m sieht das ja so aus, das ist also klar wie das funktioniert, aber es ist halt statisch:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.Collections.ObjectModel
    2. Public Class HierarchialDataTemplate
    3. Public Sub New()
    4. ' Dieser Aufruf ist für den Designer erforderlich.
    5. InitializeComponent()
    6. ' Fügen Sie Initialisierungen nach dem InitializeComponent()-Aufruf hinzu.
    7. Dim parent1 As New Parent("Ferry", "Porsche", "Porsche")
    8. Dim ferryKinder As New ObservableCollection(Of Child) From {New Child("Ferdinand Alexander", "Porsche") With {.Kinder = New ObservableCollection(Of Child) From {New Child("Ferdinand Oliver", "Porsche")}}, New Child("Gerhard Anton", "Porsche")}
    9. parent1.Kinder = ferryKinder
    10. Dim parent2 As New Parent("Anton", "Piech", "Volkswagen (Werksleiter)")
    11. Dim antonKinder As New ObservableCollection(Of Child) From {New Child("Ernst", "Piech"), New Child("Louise", "Daxer-Piech"), New Child("Ferdinand", "Piech"), New Child("Hans Michael", "Piech")}
    12. parent2.Kinder = antonKinder
    13. HierarchieDaten = New ObservableCollection(Of WPF_Templates.Parent)
    14. HierarchieDaten.Add(parent1)
    15. HierarchieDaten.Add(parent2)
    16. Me.DataContext = Me
    17. ' RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("HierarchieDaten"))
    18. End Sub
    19. Public Property HierarchieDaten As ObservableCollection(Of Parent)
    20. End Class


    _____________________________________________________________________________
    Edit:

    Hab es jetzt so halb hinbekommen, aber so richtig dynamisch ist es noch nicht, es funktioniert nur mit zwei Hierarchieebenen:


    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private KapitelListe() As Model.KapitelModel = {}
    2. Private Sub Speichern()
    3. Dim Prefixes() As String
    4. Dim Separator() As String = {"."}
    5. Prefixes = Prefix.Split(Separator, StringSplitOptions.None)
    6. If KapitelListe.Length < CInt(Prefixes(0)) Then
    7. ReDim Preserve KapitelListe(CInt(Prefixes(0)))
    8. End If
    9. If Prefixes.Length = 2 Then
    10. If KapitelListe(CInt(Prefixes(0))).UnterKapitel.Length < CInt(Prefixes(1)) Then
    11. ReDim Preserve KapitelListe(CInt(Prefixes(0))).UnterKapitel(CInt(Prefixes(1)))
    12. End If
    13. End If
    14. If Prefixes.Length = 1 Then
    15. WerteSetzen(KapitelListe(CInt(Prefixes(0))))
    16. ElseIf Prefixes.Length = 2 Then
    17. WerteSetzen(KapitelListe(CInt(Prefixes(0))).UnterKapitel(CInt(Prefixes(1))))
    18. End If
    19. MainModule.Root = New ObservableCollection(Of Model.KapitelModel)(KapitelListe)
    20. End Sub


    Weiss jemand wie ich das noch verbessern könnte?

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „kafffee“ ()

    kafffee schrieb:

    Hab es jetzt so halb hinbekommen, aber so richtig dynamisch ist es noch nicht, es funktioniert nur mit zwei Hierarchieebenen:


    Dann hast du das nicht richtig verstanden. Im WPF-Tutorial zeigt Nefear23m wie das geht.

    Alle Parents können Child haben, alle Childs können Childs haben. Fügst du den untergeordneten weitere Childs hinzu und denen dann noch weitere, geht die Hierarchie tiefer. Du hast ObservableCollections, wie kamst du auf die Idee mit dem Redim? Mit dem Array bist du auf dem falschen Dampfer. Du hast kurz und knapp gesagt eine Auflistung von Elementen, jedes Element in dieser Auflistung hat auch eine eigene Auflistung und so entsteht der "Stammbaum" von den ganzen Parents und Childs.

    PS Stell dir das wie eine Ordner-Struktur vor. Oberster Ordner HauptKategorie, da drin viele Ordner von Unterkategorien, jeder dieser Unterkategorie Ordner kann dann weitere Ordner haben. Versuch dir so ein Gebilde mal visuell im Kopf vorzustellen, sollte dann so ausschauen wie ein komplett aufgeklapptes TreeView.


    PS:
    Spoiler anzeigen

    C#-Quellcode

    1. internal class Category : Notifyable
    2. {
    3. private string categoryName = "Not Set";
    4. public string CategoryName { get => categoryName; set { categoryName = value; RaisePropertyChanged(); } }
    5. private ObservableCollection<Category> subCategories = new ObservableCollection<Category>();
    6. public ObservableCollection<Category> SubCategories { get => subCategories; set { subCategories = value; RaisePropertyChanged(); } }
    7. public Category(string name)
    8. {
    9. CategoryName = name;
    10. }
    11. }

    C#-Quellcode

    1. internal class VM : Notifyable
    2. {
    3. private ObservableCollection<Category> categories = new ObservableCollection<Category>();
    4. public ObservableCollection<Category> Categories { get => categories; set { categories = value; RaisePropertyChanged(); } }
    5. public VM()
    6. {
    7. Category c = new Category("A");
    8. Category c1 = new Category("A1");
    9. Category c2 = new Category("A2");
    10. Category c3 = new Category("A3");
    11. c.SubCategories.Add(c1);
    12. c1.SubCategories.Add(c2);
    13. c2.SubCategories.Add(c3);
    14. Categories.Add(c);
    15. Categories.Add(new Category("B"));
    16. }
    17. }

    XML-Quellcode

    1. <Window x:Class="TvTest.MainWindow"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    6. xmlns:local="clr-namespace:TvTest"
    7. mc:Ignorable="d"
    8. Title="MainWindow" Height="450" Width="800">
    9. <Window.DataContext>
    10. <local:VM/>
    11. </Window.DataContext>
    12. <Grid>
    13. <TreeView DataContext="{Binding}" ItemsSource="{Binding Categories}">
    14. <TreeView.ItemTemplate>
    15. <HierarchicalDataTemplate ItemsSource="{Binding SubCategories}">
    16. <StackPanel>
    17. <StackPanel Orientation="Horizontal">
    18. <TextBlock Text="{Binding CategoryName}"/>
    19. </StackPanel>
    20. </StackPanel>
    21. </HierarchicalDataTemplate>
    22. </TreeView.ItemTemplate>
    23. </TreeView>
    24. </Grid>
    25. </Window>[/spoiler][spoiler]


    Brauchst du aber nun verschiedene Ansichten, hier nur einen Text, dort ein Text plus Bild, brauchste für jede Variante eine Klasse, mehrere HierarchicalDataTemplates im XAML, bei jedem DataType property setzen. Um dann ein Problem mit der ObservableCollection aus dem Weg zu gehen, machste eine Basisklasse, jedes Klasse von den "Items" erbt dann davon, so kannste eine ObservableCollection der Basisklasse führen und die verschiedenen Typen die von dieser einen Klasse erben in einer Auflistung haben. Man könnte hier auch gut das Builder-Pattern anwenden um die Struktur zu machen.


    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D

    Dieser Beitrag wurde bereits 11 mal editiert, zuletzt von „DTF“ ()

    DTF schrieb:

    Dann hast du das nicht richtig verstanden. Im WPF-Tutorial zeigt Nefear23m wie das geht.


    Jo, dein Code und das Kapitel von Nofear23m hab ich verstanden denke ich. Aber ich hab mich da bisschen schwammig ausgedrückt mit dem "dynamisch". Ich meinte dynamisch beim Einfüllen der Daten in die OCs bzw. Arrays in meinem Fall. Das Anzeigen im TreeView ist da kein Problem. Wie erwähnt, ich mach ja grad ein kleines Tool, was mir dann ein JSON File erstellt mit den Daten, so dass ich später weder am Code noch an der UI rumfummeln muss.

    In deinem Code ist das Befüllen der Daten aber auch wieder statisch. Du hast ja hier:

    DTF schrieb:


    C#-Quellcode

    1. public VM()
    2. {
    3. Category c = new Category("A");
    4. Category c1 = new Category("A1");
    5. Category c2 = new Category("A2");
    6. Category c3 = new Category("A3");
    7. c.SubCategories.Add(c1);
    8. c1.SubCategories.Add(c2);
    9. c2.SubCategories.Add(c3);
    10. Categories.Add(c);
    11. Categories.Add(new Category("B"));
    12. }


    Weil was wenn mein Tool nicht vorn vornherein weiss wie wieviel Kategorien und Unterkategorien und dessen Unterkategorien das dann hat. Weisst du wie ich mein? Ich möchte einfach ein Prefix eingeben und den Rest erledigt mein Tool für mich.

    Hab mir gestern sehr lange Gedanken drüber gemacht, aber ich glaub das ist mit OOP einfach nicht möglich, da müsste man vorher noch das Benutzen von Wurmlöchern für Sprünge in Raum und Zeit erfinden...

    DTF schrieb:

    wie kamst du auf die Idee mit dem Redim?


    Da kann ich einfach leere Platzhalter erzeugen, falls der User mal mit Kapitel 3 anfängt.

    Und damit du weisst wovon ich rede hier mal mein Testprogramm:

    github.com/kafffee/TreeViewHelpCreator

    Edit: Hab meine Änderungen nochmal zu Ende gespushed, dieser Link führt jetzt also zur Lösung des Threads.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „kafffee“ ()

    kafffee schrieb:

    In deinem Code ist das Befüllen der Daten aber auch wieder statisch. Du hast ja hier:


    Ja es ist im meinem Beispiel statisch. Aber ich verstehe nicht das Problem, aus einer Datei zu lesen, je nach Inhalt den "Baum" zu generieren.

    Wie meinst du: Falls der User mal mit Kapitel3 anfängt? Du baust das ganze Ding beim starten zusammen. Da ist dann nichts leeres, du machst die Datei mit deinem Tool nicht deine User. Selbst ein "leeres" Item kann man sich machen das dann als Platzhalten dient.
    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D

    DTF schrieb:

    Aber ich verstehe nicht das Problem, aus einer Datei zu lesen, je nach Inhalt den "Baum" zu generieren.


    Das Lesen der Datei und das TreeView zu füllen ist nicht das Problem. Aber du generierst in deinem Beispiel die Daten vom Code aus:

    C#-Quellcode

    1. Category c = new Category("A");
    2. Category c1 = new Category("A1");
    3. Category c2 = new Category("A2");
    4. Category c3 = new Category("A3");


    Das möchte ich dass mein Tool übernimmt. Und dieses generiert dann ein JSON, das ich von meinem Hauptprogramm dann nur noch einlesen und das TreeView damit befüllen.


    DTF schrieb:

    Wie meinst du: Falls der User mal mit Kapitel3 anfängt?


    Damit mein ich mich, den User des Tools. Mit Kapitel 3 anfangen soll heissen, wenn z.B. der erste Datensatz der eingegeben wird z.B. das Prefix 3.1 hat.
    Ich glaube du gehst komplett falsch an die Sache dran. Du denkst gerade zu statisch. Ich serialisiere und deserialisiere sowas, da hat man solche Probleme nicht. Aber neben dem Serialisieren, gehts auch einfach mit einer Datei im anderen Format, also eine eigenbau von serialisierung. Es ist egal ob da nun ein Prefix mit 3.1 existiert oder nicht, auch wenn keins mit 1.x oder 2.x da ist. Was da ist, ist da, was nicht, das nicht.
    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D
    Juhuu :D Hab hinbekommen was ich wollte:

    Im Model:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Option Strict On
    2. Imports System.Collections.ObjectModel
    3. Imports System.ComponentModel
    4. Imports System.Runtime.CompilerServices
    5. Public Class KapitelModel
    6. Implements INotifyPropertyChanged
    7. Public Property Prefix As String
    8. Public Property Ueberschrift As String
    9. Private _Inhalt As String
    10. Public Property Inhalt As String
    11. Get
    12. Return _Inhalt
    13. End Get
    14. Set(value As String)
    15. _Inhalt = value
    16. RaisePropertyChanged()
    17. End Set
    18. End Property
    19. Private _BildPfad As String
    20. Public Property BildPfad As String
    21. Get
    22. Return _BildPfad
    23. End Get
    24. Set(value As String)
    25. _BildPfad = value
    26. RaisePropertyChanged()
    27. End Set
    28. End Property
    29. Private _Icon As String
    30. Public Property Icon As String
    31. Get
    32. Return _Icon
    33. End Get
    34. Set(value As String)
    35. _Icon = value
    36. RaisePropertyChanged()
    37. End Set
    38. End Property
    39. Private _UnterKapitel As New ObservableCollection(Of Model.KapitelModel)
    40. Public Property UnterKapitel As ObservableCollection(Of KapitelModel)
    41. Get
    42. Return _UnterKapitel
    43. End Get
    44. Set(value As ObservableCollection(Of KapitelModel))
    45. _UnterKapitel = value
    46. RaisePropertyChanged()
    47. End Set
    48. End Property
    49. Public Sub New(argPrefix As String, argUeberschrift As String, argInhalt As String, argBildPfad As String, argIcon As String)
    50. Prefix = argPrefix
    51. Ueberschrift = argUeberschrift
    52. Inhalt = argInhalt
    53. BildPfad = argBildPfad
    54. Icon = argIcon
    55. End Sub
    56. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    57. Protected Overridable Sub RaisePropertyChanged(<CallerMemberName> Optional ByVal prop As String = "")
    58. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
    59. End Sub
    60. End Class


    Und im ViewModel:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub Speichern()
    2. Dim strPrefixes() As String
    3. Dim Separator() As String = {"."}
    4. strPrefixes = Prefix.Split(Separator, StringSplitOptions.None)
    5. Dim Prefixes As New List(Of Integer)
    6. For Each Eintrag In strPrefixes
    7. Prefixes.Add(CInt(Eintrag))
    8. Next
    9. Select Case Prefixes.Count
    10. Case 1
    11. ErzeugeEinträge(MainModule.Root, strPrefixes(0))
    12. Case 2
    13. ErzeugeEinträge(MainModule.Root(Prefixes(0) - 1).UnterKapitel, strPrefixes(1))
    14. End Select 'man kann diese Select-Anweisung erweitern je nach Belieben wieviele Ebenen an Unterkapiteln man braucht...
    15. MainModule.HelpDisplay.AktuelleDetails = Nothing
    16. End Sub
    17. Private Sub ErzeugeEinträge(ByRef Ebene As ObservableCollection(Of Model.KapitelModel), argPrefix As String)
    18. Dim EintragErsetzt As Boolean = False
    19. If Ebene.Count = 0 Then
    20. Ebene.Add(New Model.KapitelModel(argPrefix, Ueberschrift, Inhalt, BildPfad, Icon))
    21. Else
    22. For i = 0 To Ebene.Count - 1
    23. If Ebene(i).Prefix = argPrefix Then
    24. Dim Ergebnis As MessageBoxResult = MessageBox.Show("Ein Eintrag mit diesem argPrefix ist bereits vorhanden. Soll dieser überschrieben werden?", "", MessageBoxButton.YesNo)
    25. If Ergebnis = MessageBoxResult.Yes Then
    26. Ebene(i) = New Model.KapitelModel(CStr(argPrefix), Ueberschrift, Inhalt, BildPfad, Icon)
    27. EintragErsetzt = True
    28. Exit For
    29. Else
    30. EintragErsetzt = True
    31. End If
    32. End If
    33. Next
    34. If Not EintragErsetzt Then Ebene.Add(New Model.KapitelModel(argPrefix, Ueberschrift, Inhalt, BildPfad, Icon))
    35. End If
    36. Ebene = New ObservableCollection(Of Model.KapitelModel)(Ebene.OrderBy(Function(x) x.Prefix))
    37. End Sub


    Ist klar worauf ich die ganze Zeit hinauswollte?

    Ich werde das jetzt noch erweitern, eine Funktion zum Entfernen von Kategorien zufügen und ein bissle testen, dann stell ichs wahrscheinlich in den SourceCode-Austausch...

    @DTF
    Edit:

    Habs mal auf Herz und Nieren geprüft, aber da ist noch ein Indexfehler drin. In Zeile 159 im ViewModel in der JSONCreator.vb.

    "Der Index darf nicht negativ oder kleiner als die Sammlung sein."

    Der Fehler tritt aber nicht immer auf. Wenn man zuerst einen Datensatz mit Prefix 1 und dann 1.1 macht dann geht es. Wenn man jetzt aber zuerst 2 und dann 2.1 zufügt dann scheppert es.

    Ich guck mir das grad schon geschlagene zwei Stunden an und komm einfach nicht drauf. Haltepunkte gesetzt, Daten überprüft, es sollte eigentlich (aber natürlich nur eigentlich) stimmen.

    Ich hab meine Änderungen nochmal committed und gepusht auf GitHub, also am besten dort einfach nochmal reinschauen bzw. pullen :)

    Wahrscheinlich ein Fall von klassischer Betriebsblindheit...

    ____________________________________________________________________

    Edit 2:

    Hab mittlerweile rausgefunden woran es liegt, ich muss natürlich, wenn ich ein Kapitel 2 anlege, auch vorher ein Kapitel 1 festlegen... Daher der Indexfehler. Hab versucht das zu beheben, und es ist mir teils (aber nur teils gelingen). Jetzt bekomm ich, wenn ich ein Kapitel 1 anlege, dann 2, dann 2.1, dann 2.3 das Gebilde wie auf dem Screenshot zu sehen. Wenigstens kein Fehler mehr, aber da ist noch was im Argen... Die zwei Einträge mit <nicht festgelegt> sollten nicht erscheinen, dafür sollte ein Eintrag 2.2 <nicht festgelegt> erscheinen...

    Findet jemand den Fehler?

    Ich hab das nochmal auf GitHub committed und gepusht. Der Link ist in Post 3, da nur draufklicken und zur Datei JSONCreator.vb im ViewModel-Ordner klicken. Die betroffene Methode ist wahrscheinlich die ErzeugeEintraege() (Zeile 168) oder AddeEintrag() (Zeile 194).
    Bilder
    • treeview.PNG

      7,26 kB, 269×230, 153 mal angesehen

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „kafffee“ ()

    @DTF

    Jetzt brauch ich doch nochmal deine Hilfe in zwei Sachen:

    (1) Ich hab jetzt Folgendes:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub AddeEintrag(ByRef Ebene As ObservableCollection(Of Model.KapitelModel), argPrefixes As List(Of Integer))
    2. Dim LaengeLetztesPrefix As Integer = CStr(argPrefixes(argPrefixes.Count - 1)).Length
    3. Dim strPrefix As String = String.Join(".", argPrefixes)
    4. Dim VorhandenePrefixes As New List(Of Integer)
    5. For i = 0 To Ebene.Count - 1
    6. VorhandenePrefixes.Add(i + 1)
    7. Next
    8. For j = 1 To argPrefixes(argPrefixes.Count - 1) - 1
    9. If Not VorhandenePrefixes.Contains(j) Then
    10. Ebene.Add(New Model.KapitelModel(strPrefix.Substring(0, strPrefix.Length - LaengeLetztesPrefix) & CStr(j), "<nicht festgelegt>", "", "", ""))
    11. End If
    12. Next
    13. Dim Separator() As String = {"."}
    14. If argPrefixes.Count = Prefix.Split(Separator, StringSplitOptions.None).Count Then
    15. Ebene.Add(New Model.KapitelModel(Prefix, Ueberschrift, Inhalt, BildPfad, Icon))
    16. End If
    17. End Sub


    Das wird so aufgerufen (vereinfacht):

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Dim strPrefixes() As String
    2. Dim Separator() As String = {"."}
    3. strPrefixes = Prefix.Split(Separator, StringSplitOptions.None) 'Prefix ist der vom User eingegebene String für das Prefix
    4. Dim Prefixes As New List(Of Integer)
    5. For Each Eintrag In strPrefixes
    6. Prefixes.Add(CInt(Eintrag))
    7. Next
    8. Select Case Prefixes.Count
    9. Case 1
    10. AddeEintrag(MainModule.Root, Prefixes) 'erste Hierarchieebene
    11. Case 2
    12. AddeEintrag(MainModule.Root, Prefixes) 'erste Hierarchieebene
    13. AddeEintrag(MainModule.Root(Prefixes(0) - 1).UnterKapitel, Prefixes) 'zweite Hierarchiebene
    14. 'usw.
    15. End Select


    Mein Model:

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class KapitelModel
    2. Implements INotifyPropertyChanged
    3. Public Property Prefix As String
    4. Public Property Ueberschrift As String
    5. Private _Inhalt As String
    6. Public Property Inhalt As String
    7. Get
    8. Return _Inhalt
    9. End Get
    10. Set(value As String)
    11. _Inhalt = value
    12. RaisePropertyChanged()
    13. End Set
    14. End Property
    15. Private _BildPfad As String
    16. Public Property BildPfad As String
    17. Get
    18. Return _BildPfad
    19. End Get
    20. Set(value As String)
    21. _BildPfad = value
    22. RaisePropertyChanged()
    23. End Set
    24. End Property
    25. Private _Icon As String
    26. Public Property Icon As String
    27. Get
    28. Return _Icon
    29. End Get
    30. Set(value As String)
    31. _Icon = value
    32. RaisePropertyChanged()
    33. End Set
    34. End Property
    35. Private _UnterKapitel As New ObservableCollection(Of Model.KapitelModel)
    36. Public Property UnterKapitel As ObservableCollection(Of KapitelModel) 'die hier ist wichtig
    37. Get
    38. Return _UnterKapitel
    39. End Get
    40. Set(value As ObservableCollection(Of KapitelModel))
    41. _UnterKapitel = value
    42. RaisePropertyChanged()
    43. End Set
    44. End Property
    45. Public ReadOnly Property Prefixes As List(Of Integer)
    46. Get
    47. Dim strPrefixes() As String
    48. Dim Separator() As String = {"."}
    49. strPrefixes = Prefix.Split(Separator, StringSplitOptions.None)
    50. Dim retPrefixes As New List(Of Integer)
    51. For Each Eintrag In strPrefixes
    52. retPrefixes.Add(CInt(Eintrag))
    53. Next
    54. Return retPrefixes
    55. End Get
    56. End Property
    57. Public Sub New(argPrefix As String, argUeberschrift As String, argInhalt As String, argBildPfad As String, argIcon As String)
    58. Prefix = argPrefix
    59. Ueberschrift = argUeberschrift
    60. Inhalt = argInhalt
    61. BildPfad = argBildPfad
    62. Icon = argIcon
    63. End Sub
    64. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    65. Protected Overridable Sub RaisePropertyChanged(<CallerMemberName> Optional ByVal prop As String = "")
    66. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
    67. End Sub
    68. End Class


    Meine Ebenen sind alle gleich, ich unterscheide nicht zwischen Parent und Child, ist in meinem Fall nicht nötig.

    Im angehängten Screenshot treeview2 siehst du, dass es auf erster Hierarchieebene einen Eintrag 2.2 gibt. Das darf natürlich nicht sein und möchte ich verhindern. Ich hab das in den Zeilen 18-20 des ersten Snippets versucht, aber krieg es nicht hin, da mach ich irgendeinen Denkfehler.

    (2) Ich möchte die Daten natürlich am Ende noch sortieren, da hab ich bisher Folgendes:

    VB.NET-Quellcode

    1. ​Ebene = New ObservableCollection(Of Model.KapitelModel)(Ebene.OrderBy(Function(x) x.Prefix))


    Aber das hat den Nachteil, dass es alles alphabetisch sortiert wird (Prefix ist ein String, z.B. 1.2.2) (siehe Screenshot treeview3)

    Ich möchte dafür aber die Property Prefixes as List(Of Integer) aus dem Model hernehmen, so dass alles numerisch sortiert wird. GIbt es dafür einen Befehl, den ich noch nicht kenne?
    Bilder
    • treeview2.PNG

      8,57 kB, 262×224, 152 mal angesehen
    • treeview3.PNG

      14,8 kB, 261×359, 140 mal angesehen
    Ich denke in dem Fall musst du selbst sortieren. Das sollte helfen:
    learn.microsoft.com/en-us/dotn…-sorts-within-collections

    IMO ein wichtiges Thema, genau so sortiere ich auch, hatte ich dir glaube ich mal gezeigt, mit Albennamen.
    Album 1
    Album 200
    Da muss ja erst "Album" sortiert werden, wie auch die Nummern hinten wenn der string-part gleich ist. Aber in deinem Fall sind ja nur Zahlen, was die Sache einfach macht.
    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „DTF“ ()

    Ich glaube ich habs, aber zum richtig testen müsste ich erst Problem (1) aus meinem letzten Post gelöst bekommen. Jedenfalls sieht es so aus:

    VB.NET-Quellcode

    1. Select Case argPrefixes.Count
    2. Case 1
    3. Ebene = New ObservableCollection(Of Model.KapitelModel)(From items In Ebene Order By items.Prefixes(0))
    4. Case 2
    5. Ebene = New ObservableCollection(Of Model.KapitelModel)(From items In Ebene Order By items.Prefixes(0) - 1, items.Prefixes(1))
    6. Case 3
    7. Ebene = New ObservableCollection(Of Model.KapitelModel)(From items In Ebene Order By items.Prefixes(0) - 1, items.Prefixes(1) - 1, items.Prefixes(2))
    8. 'usw.
    9. End Select


    @DTF
    Hast du mein erstes Problem mal angeschaut? Sieht erstmal umfangreich ausm aber ist glaube ich am Ende nur eine Kleinigkeit....
    Du denkst zu statisch. Würdest einfach die Hirachie bauen, müsstest du nicht mal selbst sortieren. Was wenn ein User 1.A, 1.B haben möchte, geht bei dir nicht. Oder was ist mit § oder sonstwelchen Dingen die da noch in Frage kommen. mach lieber buttons rein, so das ein selektiertes Ding verschoben werden kann, also auf dieser Ebene 1 rauf oder runter, oder in eine andere Ebene verschieben, dann ist keine automatische sortierung nötig, mehr Freiheit für den User. Ehrlich gesagt sehe ich jetzt nicht , wo das 2.2 herkommt, das müsste ich StepByStep debuggen.

    PS:
    Schuss im Dunkeln:

    VB.NET-Quellcode

    1. Case 2
    2. Ebene = New ObservableCollection(Of Model.KapitelModel)(From items In Ebene Order By items.Prefixes(0) - 1, items.Prefixes(1))

    Fehlt da ein - 1? So das du einen Index zu weit bist und so das 2.2 dorthin kommt?

    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D

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

    DTF schrieb:

    mach lieber buttons rein, so das ein selektiertes Ding verschoben werden kann


    Stimmt, ist ne Idee, muss ich mich mal reindenken...

    DTF schrieb:

    Ehrlich gesagt sehe ich jetzt nicht , wo das 2.2 herkommt.

    Das müsste an Zeile 16 in meinem 2. Snippet in Post 8 liegen. Ist ja auch egal wo's herkommt, wenn ich verhindere dass es geaddet wird oder es hinterher wieder rausmache...

    Für ersteres:
    Im 1. Snippet in Zeile 18 ist natürlich immer argPrefixes.Count = Prefix.Split(Separator, StringSplitOptions.None).Count, weil beide aus der gleichen Quelle stammen. Entweder man lässt sich da was Anderes einfallen oder man macht...

    ...letzteres:
    Da hab ich auch schon einen Lösungsansatz, der aber nicht funktioniert:

    VB.NET-Quellcode

    1. For i = 0 To Ebene.Count - 1
    2. Select Case Ebene(i).Prefixes.Count
    3. Case 1
    4. If Not Ebene(i).Prefixes.Count = 1 Then
    5. Ebene.RemoveAt(i)
    6. End If
    7. Case 2
    8. If Not Ebene(i).Prefixes.Count = 2 Then
    9. Ebene.RemoveAt(i)
    10. End If
    11. End Select
    12. Next


    Ich bin grad echt fast am Heulen, ob ich das noch hinkrieg...

    Der zielt darauf ab, dass alle Prefixes, die nicht in die Ebene passen anhand von deren .Count rausgeschmissen werden...

    ________________________________________________________________________

    Edit @DTF

    kafffee schrieb:

    Das müsste an Zeile 16 in meinem 2. Snippet in Post 8 liegen.

    Habs grad mal überprüft, es liegt daran. Da wird ein Item erzeugt, ohne zu fragen ob es schon vorhanden ist. Also hab ich jetzt diese Idee hier gehabt:

    VB.NET-Quellcode

    1. If argPrefixes.Count = 2 Then
    2. Dim index As Integer = MainModule.Root.IndexOf(Function(x) x.Prefixes(0) = argPrefixes(0))
    3. If index = -1 Then
    4. Dim prfx As New List(Of Integer)
    5. prfx.Add(argPrefixes(0))
    6. AddeEintrag(MainModule.Root, prfx)
    7. End If
    8. End If


    Aber da unterstreicht er mir das Function(x) x.Prefixes(0) = argPrefixes(0) in Zeile 2 und sagt:

    BC36625: Der Lambdaausdruck kann nicht in "KapitelModel" konvertiert werden, da "KapitelModel" kein Delegattyp ist.

    Hä? Was ist daran falsch? Ich denke du kannst ja erkennen was ich da vorhab, aber warum kommt der Fehler?

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „kafffee“ ()

    Verstehe ich das richtig: Du hast eine Baumstruktur, und willst da Nodes einhängen. Den Nodes gibst du etwas mit, was bei mir "IndexPath" heissen würde, bei dir scheinbar "Prefix".
    Also ein Prefix 0.2.4.0
    würde den ersten Node im Baum meinen, von dessen Childs den dritten, von dessen Childs den fünften, von dessen Childs den ersten.

    Um sowas hinzukriegen brauchst du eine Such-Funktion: "Gib mir Node für diesen Prefix".

    Nun kann sein, dass, wohin der prefix addressiert, dass da noch gar kein Node existiert. In diesem Falle soll die Suchfunktion die nächste Annährung als Node returnen, sowie den Prefix-Teil, der noch nicht erledigt ist.
    Ich nenne die SuchFunktion Approach(), weil sie sich dem Ziel möglichst annähert, und ihr Return-Wert ist eine spezielle Klasse ApproachResult, die den best-angenäherten Node enthält, sowie den noch nicht beschrittenen Prefix-Teil.

    Nun kannst du hingehen, und Nodes generieren. Du hast ja den Node, wo als nächstes einzuhängen ist, und den Pfad-Rest - das sind die Infos, anhand deren du gewährleisten kannst, dass der Prefix dann auch auf einen Node führt.

    Übrigens soll der Prefix keine Property des Nodes sein - das wäre redundant. Sondern jeder Node soll bei Bedarf seine Position im Tree selbst ausrechnen können.
    Dazu muss aber der Node mit seinem Parent verknüpft sein.

    Wenn du auf diese Weise redundanzfrei arbeitest, kannst du Nodes auch wo zwischen einfügen (oder was rauslöschen), ohne das bei allen Folge-Nodes die Prefixe ungültig werden.

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

    @ErfinderDesRades

    Du hast genau richtig verstanden, worauf ich hinaus will, und was ich ganz am Anfang zu realisieren versucht hab.

    Hab dann schnell feststellen müssen, dass ich das mit meinem Wissen nicht hinbekomm, und hab dann eine "halb-dynamische" Lösung gefunden. Soll heißen, für jede Hierarchieebene muss ich extra Code schreiben.

    Was ich gestern Abend nach auch hinbekommen hab, jedenfalls scheint auf den ersten Blick alles zu funktionieren.

    Wenn bei mir ein Item noch nicht existiert, wird heraufgezählt und bis dorthin leere Items generiert. Also wenn ich zum Anfang gleich 2.2 eingebe wir 1 generiert, wird 2 generiert wird 2.1 generiert (alles leer) und wird 2.2 generiert.

    Ist dein Projekt WinForms oder WPF? Kannst du mal bisschen relevanten Code posten? Ich hab mir sehr lange Gedanken gemacht, wie ich das realisieren kann, hatte aber nicht mal einen Anfangspunkt...
    Da ist was in diesem Tut: Tree-Übungen - Rekursion , Abschnitt "Addressierung via Pfad".

    Es ist allerdings mit Treenodes, und statt "Prefix/IndexPath" wird der Fullpath der Treenodes verwendet, in "segments" zerlegt.
    Ist im Tut auch nicht ausführlich beschrieben, aber im Code isses. Und zwar ist bei mir die Function Approach eine Extension-Function der TreenodeCollection-Klasse.

    Das Generieren anhand eines IndexPathes ist aber bischen anders als anhand von Fullpath-Segmenten:
    Einen Node für ein bestimmtes Segment kann man immer einfügen, bei einem Index muss man u.U. mehrere Nodes einfügen, damit der Index nicht "OutOfRange" geht.
    Aber das sind Implementations-Details.

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

    @ErfinderDesRades

    Wärst mal bisschen früher damit um die Ecke gekommen :P Ne Spass muss sein...

    Bin schon zu weit fortgeschritten gewesen und hab ne Lösung gefunden, die für mich vertretbar ist.

    Projekt ist erledigt, ich hab die finale Version nochmal committed und gespushed, wer will kann sich also die Lösung anschauen über den Link zu GitHub in meinem Post 3 ganz am Ende.

    So far, kafffee

    PS: Wie war das nochmal, wenn man ein Projekt hier hochladen will? Alle DLLs und EXEs aus dem \bin und \obj-Ordner löschen, oder? Wie ist es mit NugetPaketen, da gibt es ja einen Ordner \packages, den einfach auch komplett löschen? Wie ist es mit dem Ordner \.vs, muss ich den auch löschen?

    Haudruferzappeltnoch schrieb:

    bin, obj, .vs kannst komplett löschen, da ist nur Volumen drin nichts sonst.


    Nur Volumen? Sieh mal im obj Ordner genau nach. -> apphost.exe, mehrfach APPNAME.dll. Also nur ein bissl Volumen is das nich.
    Zitat von mir 2023:
    Was interessiert mich Rechtschreibung? Der Compiler wird meckern wenn nötig :D