none
TreeView mit CheckBoxes dynamisch mit VB.Net erzeugen RRS feed

  • Frage

  • Hallo,

    ich muss dynamisch ein TreeView mit CheckBoxes für alle Nodes erzeugen. Das eigentliche Erzeugen und Anzeigen des TreeViews und der Nodes funktioniert, aber wie bekomme ich bei einem dynamisch generierten TreeView die CheckBoxes hin?

    Danke
    Gruss Carsten

    Dienstag, 14. Dezember 2010 18:33

Antworten

  • Hie rmal ein Beispiel, wie man das machen kann. Um im Code auf die Oberfläche zuzugreifen, kann man Bindungen nutzen.

    <Window x:Class="MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="MainWindow" Height="300" Width="300"
        xmlns:local="clr-namespace:WpfApplication1">
     <Window.Resources>
      <local:ViewModel x:Key="vm" />
     </Window.Resources>
     <StackPanel DataContext="{Binding Source={StaticResource vm}}">
      <TreeView Name="tv" ItemsSource="{Binding Source}" ItemTemplate="{Binding Templ}" />
     </StackPanel>
    </Window>
    

    Dazu der ViewModel:

    Imports System.ComponentModel
    Imports System.Data
    Imports System.Collections.ObjectModel
    Imports System.Windows.Markup
    
    
    Public Class ViewModel
    
     Private i As Integer = 1
     Private rnd As New Random
    
     Public Sub New()
      NewNode(Source, 0)
     End Sub
    
     Private Sub NewNode(ByRef s As List(Of C1), ByVal level As Integer)
      s = New List(Of C1)
      If rnd.NextDouble < level / 5 Then Exit Sub
      For k = 1 To rnd.Next(5, 10)
       Dim c As New C1 With {.IsChecked = False, .Name = "Element " & i.ToString}
       s.Add(c)
       i += 1
       NewNode(c.Children, level + 1)
      Next
     End Sub
    
     Public Property Source As List(Of C1)
    
     Public ReadOnly Property Templ As HierarchicalDataTemplate
      Get
       Dim t = <HierarchicalDataTemplate ItemsSource="{Binding Children, Mode=OneTime}"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
            <StackPanel Orientation="Horizontal">
             <CheckBox Focusable="False" IsChecked="{Binding IsChecked}" VerticalAlignment="Center"/>
             <ContentPresenter Content="{Binding Name, Mode=OneTime}" Margin="2,0"/>
            </StackPanel>
           </HierarchicalDataTemplate>
       Dim sRdr As New IO.StringReader(t.ToString)
       Dim xmlRdr = Xml.XmlReader.Create(sRdr)
       Return CType(XamlReader.Load(xmlRdr), HierarchicalDataTemplate)
      End Get
     End Property
    
     Class C1
      Public Property Children As List(Of C1)
      Public Property IsChecked As Boolean
      Public Property Name As String
     End Class
    
    End Class
    
    --
    Peter
    • Als Antwort vorgeschlagen Peter Fleischer Dienstag, 21. Dezember 2010 11:50
    • Als Antwort markiert CarstenDD Dienstag, 21. Dezember 2010 19:14
    Dienstag, 21. Dezember 2010 11:50
  • Hier noch eine Demo, in der nur die Source inhaltlich geändert wird. XAML:

    <Window x:Class="MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="MainWindow" Height="300" Width="300"
        xmlns:local="clr-namespace:WpfApplication1">
     <Window.Resources>
      <local:ViewModel x:Key="vm" />
      <HierarchicalDataTemplate x:Key="CheckBoxItemTemplate" ItemsSource="{Binding Children, Mode=OneTime}" >
       <StackPanel Orientation="Horizontal">
        <!-- These elements are bound to a FooViewModel object. -->
        <CheckBox Focusable="False" IsChecked="{Binding IsChecked}" VerticalAlignment="Center" />
        <ContentPresenter Content="{Binding Name, Mode=OneTime}" Margin="2,0" />
       </StackPanel>
      </HierarchicalDataTemplate>
     </Window.Resources>
     <StackPanel DataContext="{Binding Source={StaticResource vm}}">
      <Button Content="Neuer Inhalt" Command="{Binding Cmd}"/>
      <TreeView Name="tv" ItemsSource="{Binding Source}" ItemTemplate="{Binding Source={StaticResource CheckBoxItemTemplate}}" />
     </StackPanel>
    </Window>
    

    Dazu der ViewModel:

    Imports System.ComponentModel
    Imports System.Data
    Imports System.Collections.ObjectModel
    Imports System.Windows.Markup
    
    
    Public Class ViewModel
     Implements INotifyPropertyChanged
    
     Public ReadOnly Property Cmd As ICommand
      Get
       Return New RelayCommand(New Action(Of Object)(AddressOf CmdExec))
      End Get
     End Property
    
     Private Sub CmdExec(ByVal sattet As Object)
      NewNode(Source, 0)
      OnPropertyChanged("Source")
     End Sub
    
     Private i As Integer = 1
     Private rnd As New Random
    
     Public Sub New()
      NewNode(Source, 0)
     End Sub
    
     Private Sub NewNode(ByRef s As List(Of C1), ByVal level As Integer)
      s = New List(Of C1)
      If rnd.NextDouble < level / 5 Then Exit Sub
      For k = 1 To rnd.Next(5, 10)
       Dim c As New C1 With {.IsChecked = False, .Name = "Element " & i.ToString}
       s.Add(c)
       i += 1
       NewNode(c.Children, level + 1)
      Next
     End Sub
    
     Public Property Source As List(Of C1)
    
     Class C1
      Public Property Children As List(Of C1)
      Public Property IsChecked As Boolean
      Public Property Name As String
     End Class
    
    #Region " PropertyChanged"
     Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
     Friend Sub OnPropertyChanged(ByVal prop As String)
      RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
     End Sub
    #End Region
    
    End Class
    
    --
    Peter
    • Als Antwort vorgeschlagen Peter Fleischer Mittwoch, 22. Dezember 2010 10:12
    • Als Antwort markiert CarstenDD Mittwoch, 22. Dezember 2010 11:42
    Mittwoch, 22. Dezember 2010 10:12
  • In meinem Beispiel liefert die Eigenschaft "Source" einen Objektverweis auf
    eine Liste mit Objekten vom Typ C1. In jedem Objekt vom Typ C1 ist eine
    Eigenschaft mit Verweis auf eine weitere (eingebettete) Liste mit Objekten
    vom Typ C1 enthalten. Damit gibt es eine Hierarchie von Objekten vom Typ C1.
    In dieser Hierarchie ein konkretes Objekt zu finden bedeutet, dass rekursiv
    die Struktur zu durchsuchen ist. Das kann u.U. recht kompliziert werden. In
    jedem Fall ist zu prüfen, ob ein Objektverweis auf die Liste vorliegt
    (äußere und auch eingebettete) und ob bei vorliegendem Objektverweis auch
    Elemente in der Liste vorhanden sind. Je nach Anwendungsfall kann es
    sinnvoll sein, neben der hierarchischen Speicherung der Objekte zusätzlich
    eine flache Speicherung von Verweisen auf die Objekte zu halten, z.B. in
    einer List(Of C1). In solcher Liste kann man sehr einfach Objekte suchen und
    dann die hierarchische Liste manipulieren. Solche flache Liste kann auch
    eine Datenbanktabelle sein, die die darzustellenden Elemente mit
    Fremdschlüsselverweis auf das übergeordnete Element in der Liste enthält.
    Basis der hierarchischen Liste kann auch ein XML-Strom sein, z.B. als
    XElement. In solchen XElement lassen sich Knoten einfach mit LinQ to XML
    finden und bearbeiten. Es ist dann lediglich die Zuordnung zum Objekt in der
    hierarchischen Liste zu finden. Das kann man wieder über eine flache Liste
    machen, wie beispielsweise eine List(Of ...) oder auch ein Dictionary.
     
    --
    Viele Grüße
    Peter
     
     
    • Als Antwort markiert CarstenDD Donnerstag, 23. Dezember 2010 10:51
    Donnerstag, 23. Dezember 2010 04:56

Alle Antworten

  • Hallo,

    die Links, insbesondere der Erste, sind schon sehr interessant, aber ich habe immer noch das Problem, dass das ganze komplett dynamisch geschehen muss. Mir fehlt die Umsetzung von

    <HierarchicalDataTemplate 
     x:Key="CheckBoxItemTemplate"
     ItemsSource="{Binding Children, Mode=OneTime}"
     >
     <StackPanel Orientation="Horizontal">
      <!-- These elements are bound to a FooViewModel object. -->
      <CheckBox
       Focusable="False" 
       IsChecked="{Binding IsChecked}" 
       VerticalAlignment="Center"
       />
      <ContentPresenter 
       Content="{Binding Name, Mode=OneTime}" 
       Margin="2,0"
       />
     </StackPanel>
    </HierarchicalDataTemplate>
    

    in eine entsprechende DataTemplate-Anweisung im VB.Net Code. Alternativ ein Tipp, wie man auf die XAML-Definition aus dem Code heraus zugreifen kann. Vielleicht hat hierzu ja jemand ein paar hilfreiche Anmerkungen.

    Danke
    Gruss Carsten

    Montag, 20. Dezember 2010 16:53
  • Hiermit kannst du auf die Controls zugreifen:

    http://msdn.microsoft.com/de-de/library/system.windows.media.visualtreehelper.aspx

    Was möchtest du denn genau mit dem XAML machen?

    Dienstag, 21. Dezember 2010 08:13
    Beantworter
  • Hie rmal ein Beispiel, wie man das machen kann. Um im Code auf die Oberfläche zuzugreifen, kann man Bindungen nutzen.

    <Window x:Class="MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="MainWindow" Height="300" Width="300"
        xmlns:local="clr-namespace:WpfApplication1">
     <Window.Resources>
      <local:ViewModel x:Key="vm" />
     </Window.Resources>
     <StackPanel DataContext="{Binding Source={StaticResource vm}}">
      <TreeView Name="tv" ItemsSource="{Binding Source}" ItemTemplate="{Binding Templ}" />
     </StackPanel>
    </Window>
    

    Dazu der ViewModel:

    Imports System.ComponentModel
    Imports System.Data
    Imports System.Collections.ObjectModel
    Imports System.Windows.Markup
    
    
    Public Class ViewModel
    
     Private i As Integer = 1
     Private rnd As New Random
    
     Public Sub New()
      NewNode(Source, 0)
     End Sub
    
     Private Sub NewNode(ByRef s As List(Of C1), ByVal level As Integer)
      s = New List(Of C1)
      If rnd.NextDouble < level / 5 Then Exit Sub
      For k = 1 To rnd.Next(5, 10)
       Dim c As New C1 With {.IsChecked = False, .Name = "Element " & i.ToString}
       s.Add(c)
       i += 1
       NewNode(c.Children, level + 1)
      Next
     End Sub
    
     Public Property Source As List(Of C1)
    
     Public ReadOnly Property Templ As HierarchicalDataTemplate
      Get
       Dim t = <HierarchicalDataTemplate ItemsSource="{Binding Children, Mode=OneTime}"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
            <StackPanel Orientation="Horizontal">
             <CheckBox Focusable="False" IsChecked="{Binding IsChecked}" VerticalAlignment="Center"/>
             <ContentPresenter Content="{Binding Name, Mode=OneTime}" Margin="2,0"/>
            </StackPanel>
           </HierarchicalDataTemplate>
       Dim sRdr As New IO.StringReader(t.ToString)
       Dim xmlRdr = Xml.XmlReader.Create(sRdr)
       Return CType(XamlReader.Load(xmlRdr), HierarchicalDataTemplate)
      End Get
     End Property
    
     Class C1
      Public Property Children As List(Of C1)
      Public Property IsChecked As Boolean
      Public Property Name As String
     End Class
    
    End Class
    
    --
    Peter
    • Als Antwort vorgeschlagen Peter Fleischer Dienstag, 21. Dezember 2010 11:50
    • Als Antwort markiert CarstenDD Dienstag, 21. Dezember 2010 19:14
    Dienstag, 21. Dezember 2010 11:50
  • Hallo Peter,

    vielen Dank für deine umfangreichen Ausführungen und das Code-Beispiel. Ich habe es in ein Testprojekt eingebaut und hoffentlich auch soweit verstanden. Insbesondere die HierarchicalDataTemplate Property hat mich einen ganz entscheidenden Schritt weitergebracht. Das ganze funktioniert auch bei meiner Konstellation, da ich im XAML-Code keinerlei Treeview habe, sondern diese werden komplett im VB-Code zur Laufzeit dynamisch erzeugt.

    Da ich im Verlauf der Programmausführung Elemente zum Treeview hinzufügen und entfernen muss, muss ich mir jetzt noch überlegen, entweder die gesamte Source C1 neu zu erzeugen, dann muss ich jedoch den status quo von den verbleibenden Elementen mit der neuen Liste abgleichen (auf-/zugeklappt, Checkbox gesetzt) oder aber das ganze um das einzelne Hinzufügen/Entfernen von Einträgen erweitern. Ich werde mich damit mal beschäftigen.

    Nochmals vielen Dank
    Gruss Carsten

    Dienstag, 21. Dezember 2010 19:14
  • Hallo,

    der XAML beschreibt, wie die Einträge des Treeview gestaltet sind und das muss, so wie dankenswerterweise von Peter beschrieben, bei einer dynamischen Erzeugung eben auch dynamisch generiert werden.

    Gruss Carsten

    Dienstag, 21. Dezember 2010 19:16
  • Den XAML brauchst Du nicht dynamisch zu generieren. Es reicht, wenn die DataSource inhaltlich verändert wird und über NotifyPropertyChanged dies der Oberfläche mitgeteilt wird.
     
    --
    Viele Grüße
    Peter
    Dienstag, 21. Dezember 2010 20:07
  • Hier noch eine Demo, in der nur die Source inhaltlich geändert wird. XAML:

    <Window x:Class="MainWindow"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="MainWindow" Height="300" Width="300"
        xmlns:local="clr-namespace:WpfApplication1">
     <Window.Resources>
      <local:ViewModel x:Key="vm" />
      <HierarchicalDataTemplate x:Key="CheckBoxItemTemplate" ItemsSource="{Binding Children, Mode=OneTime}" >
       <StackPanel Orientation="Horizontal">
        <!-- These elements are bound to a FooViewModel object. -->
        <CheckBox Focusable="False" IsChecked="{Binding IsChecked}" VerticalAlignment="Center" />
        <ContentPresenter Content="{Binding Name, Mode=OneTime}" Margin="2,0" />
       </StackPanel>
      </HierarchicalDataTemplate>
     </Window.Resources>
     <StackPanel DataContext="{Binding Source={StaticResource vm}}">
      <Button Content="Neuer Inhalt" Command="{Binding Cmd}"/>
      <TreeView Name="tv" ItemsSource="{Binding Source}" ItemTemplate="{Binding Source={StaticResource CheckBoxItemTemplate}}" />
     </StackPanel>
    </Window>
    

    Dazu der ViewModel:

    Imports System.ComponentModel
    Imports System.Data
    Imports System.Collections.ObjectModel
    Imports System.Windows.Markup
    
    
    Public Class ViewModel
     Implements INotifyPropertyChanged
    
     Public ReadOnly Property Cmd As ICommand
      Get
       Return New RelayCommand(New Action(Of Object)(AddressOf CmdExec))
      End Get
     End Property
    
     Private Sub CmdExec(ByVal sattet As Object)
      NewNode(Source, 0)
      OnPropertyChanged("Source")
     End Sub
    
     Private i As Integer = 1
     Private rnd As New Random
    
     Public Sub New()
      NewNode(Source, 0)
     End Sub
    
     Private Sub NewNode(ByRef s As List(Of C1), ByVal level As Integer)
      s = New List(Of C1)
      If rnd.NextDouble < level / 5 Then Exit Sub
      For k = 1 To rnd.Next(5, 10)
       Dim c As New C1 With {.IsChecked = False, .Name = "Element " & i.ToString}
       s.Add(c)
       i += 1
       NewNode(c.Children, level + 1)
      Next
     End Sub
    
     Public Property Source As List(Of C1)
    
     Class C1
      Public Property Children As List(Of C1)
      Public Property IsChecked As Boolean
      Public Property Name As String
     End Class
    
    #Region " PropertyChanged"
     Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
     Friend Sub OnPropertyChanged(ByVal prop As String)
      RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
     End Sub
    #End Region
    
    End Class
    
    --
    Peter
    • Als Antwort vorgeschlagen Peter Fleischer Mittwoch, 22. Dezember 2010 10:12
    • Als Antwort markiert CarstenDD Mittwoch, 22. Dezember 2010 11:42
    Mittwoch, 22. Dezember 2010 10:12
  • Hallo Peter,

    ganz recht herzlichen Dank für den Beispielcode, er funktioniert wunderbar.
    Eine Frage habe ich jedoch noch, da meine Lernkurve offensichtlich etwas flach verläuft: Wenn ich auf die dem TreeView aktuell zugewiesene Liste mit Elementen innerhalb der Klasse zugreifen möchte, mache ich das dann auf "Source" oder greife ich wie bei

    Private Sub NewNode(ByRef s As List(Of C1), ByVal level As Integer)

    auf eine Referenz der List(Of C1) zu? Beim Versuch, auf die Referenz s zuzugreifen ohne voher ein  s = New List(Of C1) zu machen, bekomme ich eine Fehlermeldung wegen fehlender Objektreferenz und wenn ich  s = New List(Of C1) mache, dann ist die Liste natürlich leer.

    Vielleicht kannst du mir hierzu noch einen Tipp geben.
    Danke Gruss Carsten

    Mittwoch, 22. Dezember 2010 11:42
  • In meinem Beispiel liefert die Eigenschaft "Source" einen Objektverweis auf
    eine Liste mit Objekten vom Typ C1. In jedem Objekt vom Typ C1 ist eine
    Eigenschaft mit Verweis auf eine weitere (eingebettete) Liste mit Objekten
    vom Typ C1 enthalten. Damit gibt es eine Hierarchie von Objekten vom Typ C1.
    In dieser Hierarchie ein konkretes Objekt zu finden bedeutet, dass rekursiv
    die Struktur zu durchsuchen ist. Das kann u.U. recht kompliziert werden. In
    jedem Fall ist zu prüfen, ob ein Objektverweis auf die Liste vorliegt
    (äußere und auch eingebettete) und ob bei vorliegendem Objektverweis auch
    Elemente in der Liste vorhanden sind. Je nach Anwendungsfall kann es
    sinnvoll sein, neben der hierarchischen Speicherung der Objekte zusätzlich
    eine flache Speicherung von Verweisen auf die Objekte zu halten, z.B. in
    einer List(Of C1). In solcher Liste kann man sehr einfach Objekte suchen und
    dann die hierarchische Liste manipulieren. Solche flache Liste kann auch
    eine Datenbanktabelle sein, die die darzustellenden Elemente mit
    Fremdschlüsselverweis auf das übergeordnete Element in der Liste enthält.
    Basis der hierarchischen Liste kann auch ein XML-Strom sein, z.B. als
    XElement. In solchen XElement lassen sich Knoten einfach mit LinQ to XML
    finden und bearbeiten. Es ist dann lediglich die Zuordnung zum Objekt in der
    hierarchischen Liste zu finden. Das kann man wieder über eine flache Liste
    machen, wie beispielsweise eine List(Of ...) oder auch ein Dictionary.
     
    --
    Viele Grüße
    Peter
     
     
    • Als Antwort markiert CarstenDD Donnerstag, 23. Dezember 2010 10:51
    Donnerstag, 23. Dezember 2010 04:56
  • Hallo Peter,

    danke für die Erläuterungen. Glücklicherweise ist bei mir die Schachteltiefe mit Parent und einer Childebene überschaubar und auch immer identisch. Somit werde ich mal schauen, welchen Weg ich wählen werde. Ich werde jetzt alles zusammennehmen und eine für meinen Einsatzzweck hoffentlich sauber programmierte Lösung erstellen.

    Danke und eine schöne Weihnacht
    Gruss Carsten

    Donnerstag, 23. Dezember 2010 10:51