Laden...

TreeView Nodes hinzufügen und Aktuallisieren.

Letzter Beitrag vor 11 Monaten 14 Posts 991 Views
TreeView Nodes hinzufügen und Aktuallisieren.

Hallo

Ich bin neu hier im Forum und hoffe das man mir helfen kann.

Ich beschäftige mich gerade mit dem Thema TreeView ich habe die Aufteilung schon hinbekommen. Nur wenn ich ein Nodes hinzufüge dann wird es nicht Aktuallisiert.

Hier mein xaml code:

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="180*" />
            <ColumnDefinition Width="3" />
            <ColumnDefinition Width="500*" />
        </Grid.ColumnDefinitions>

        <GridSplitter Grid.Column="1" Width="3" HorizontalAlignment="Stretch" Background="#FF616060" />
        <Image Height="91" VerticalAlignment="Top" Margin="0,0,6,0"/>
        <TreeView Name="myTreeView" ItemsSource="{Binding RootItems}" Grid.ColumnSpan="2" Margin="0,92,3,0" SelectedItemChanged="TreeView_SelectedItemChanged">
            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                    <Setter Property="FontWeight" Value="Normal" />
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">

                            <Setter Property="FontWeight" Value="ExtraBold" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TreeView.ItemContainerStyle>
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding ChildrenItems}">
                    <TextBlock Text="{Binding Row.TreeView_Name}" />
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
        <TextBox Name="TextBox1" Grid.Column="2" HorizontalAlignment="Left" Margin="45,39,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="340"/>
        <Button Grid.Column="2" Content="Button" HorizontalAlignment="Left" Margin="85,159,0,0" VerticalAlignment="Top" Click="Button_Click"/>
    </Grid>

und hier mein C# Code:

    public partial class MainWindow : Window
    {
        DataSet1 DataSet_TreeView = new DataSet1();
        ObservableCollection<Data_Treeview> col = new ObservableCollection<Data_Treeview>();
        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = this;
            DataSet_TreeView.Data_Treeview.Rows.Add("10", "", "test0");
            DataSet_TreeView.Data_Treeview.Rows.Add("11", "", "test1");
            DataSet_TreeView.Data_Treeview.Rows.Add("12", "10", "test01");
            DataSet_TreeView.Data_Treeview.Rows.Add("13", "12", "test02");
            foreach (DataRow row in DataSet_TreeView.Tables["Data_Treeview"].Rows) col.Add(new Data_Treeview((DataSet1.Data_TreeviewRow)row));
            cvs.Source = col.Where((d) => d.TreeViewUnterNummer == "");
           
        }


        private CollectionViewSource cvs = new CollectionViewSource();
        

        public ICollectionView RootItems { get => cvs.View; }

        private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            TreeView treeView = sender as TreeView;
            Data_Treeview item = treeView.SelectedItem as Data_Treeview;

            if (item == null)
                return;

            MessageBox.Show("Nummer: " + item.TreeViewNummer);

        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            
            DataSet_TreeView.Data_Treeview.Rows.Add("15", "", TextBox1.Text);
            
        }
    }

    public class Data_Treeview
    {
        public Data_Treeview(DataSet1.Data_TreeviewRow row) { this.Row = row; this.dt = (DataSet1.Data_TreeviewDataTable)row.Table; this.ds = (DataSet1)row.Table.DataSet; }
        DataSet1 ds;
        DataSet1.Data_TreeviewDataTable dt;

        public DataSet1.Data_TreeviewRow Row { get; set; }
        public string TreeViewNummer { get => Row.TreeView_Nummer; set { Row.TreeView_Nummer = value; } }
        public string TreeViewUnterNummer { get => Row.TreeView_UnterNummer; set { Row.TreeView_UnterNummer = value; } }
        public string TreeViewName { get => Row.TreeView_Name; set { Row.TreeView_Name = value; } }
        public object ChildrenItems => new ObservableCollection<Data_Treeview>(from d in dt where d.TreeView_UnterNummer == this.TreeViewNummer select new Data_Treeview(d));


        public bool IsExpanded { get; set; } = true;
        public bool IsSelected { get; set; }
    }

Ich hoffe man kann verstehen was ich meine.

Gruß

Mezzo

Du solltest den MVVM Pattern folgen, denn WPF ist so designed, dass es verwendet wird. Ansonsten wirst sehr viel Spaß mit Workarounds haben.
Das gilt auch für das ganze Event-Zeug, das man zwar in WinForms so machen kann, aber nicht in WPF tun sollte.
[Artikel] MVVM und DataBinding

Nur als Hinweis:

Du fügst im Konstruktor alle Daten der ObservableCollection<Data_Treeview> col hinzu (welche per Binding im TreeView angezeigt werden), während du dies beim Button_Click nicht tust.

Trotzdem solltest du den Tip von Abt folgen und MVVM benutzen.

Danke Abt und Th69

Könnt ihr mir bitte ein Beispiel geben wie ich es Richtig machen muss?

th69: Wie soll ich in Button_Click es richtig einfügen?

Gruß

Mezzo

Hab Dir ein MVVM Tutorial verlinkt.

    <TreeView Name="myTreeView" ItemsSource="{Binding RootItems}" Grid.ColumnSpan="2" Margin="0,92,3,0" SelectedItemChanged="TreeView_SelectedItemChanged">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                <Setter Property="FontWeight" Value="Normal" />
                <Style.Triggers>
                    <Trigger Property="IsSelected" Value="True">

                        <Setter Property="FontWeight" Value="ExtraBold" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding ChildrenItems}">
                <TextBlock Text="{Binding Row.TreeView_Name}" />
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>

ist das nicht Richtig so? es ist doch so wie bei MVVM oder?

Die Verwendung des DataSets und dem ganzen Code Behind Zeug ist nicht im Sinne von MVVM.
Dir fehlen komplett die entsprechenden ViewModels zum Binden.

Wie sollte dann mein Code Behind dann aussehen?

Kannst du mir da bitte ein Beispiel geben. Wie man es richtig mit TreeView es macht.

Gruß

Mezzo

Im Code Behind steht normalerweise nichts. Dass steht alles im ViewModel. Schau dir den Link von Abt an. Besonders Punkt 2.1.

Fang am Besten mit Strings an. Das muss erstmal funktionieren.

  • Wer lesen kann, ist klar im Vorteil
  • Meistens sitzt der Fehler vorm Monitor
  • "Geht nicht" ist keine Fehlermeldung!
  • "Ich kann programmieren" != "Ich habe den Code bei Google gefunden"

GidF

Vielleicht hilft das weiter:

Xaml:

<Window x:Class="MyCSharp125410.MainWindow"
        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:MyCSharp125410"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="auto" />
        </Grid.RowDefinitions>
        <TreeView Name="myTreeView" Grid.Row="0" ItemsSource="{Binding Nodes}">
            <TreeView.ItemContainerStyle>
                <Style TargetType="{x:Type TreeViewItem}">
                    <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                    <Setter Property="FontWeight" Value="Normal" />
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="FontWeight" Value="ExtraBold" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TreeView.ItemContainerStyle>
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <TextBlock Text="{Binding Name}" />
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
        <Button x:Name="AddNodeButton" 
                Grid.Row="1" 
                Content="Add Child-Node to Node 05" 
                Command="{Binding AddNodeCommand}"/>
    </Grid>
</Window>

Das ViewModel:

using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Windows.Input;

namespace MyCSharp125410
{
    public class MainWindowViewModel
    {
        public ObservableCollection<Node> Nodes { get; set; }

        public ICommand AddNodeCommand { get; private set; }

        public MainWindowViewModel()
        {
            Nodes = new ObservableCollection<Node>
            {
                new Node() { Name = "Node 01" },
                new Node() { Name = "Node 02" },
                new Node() { Name = "Node 03" },
                new Node() { Name = "Node 04" },
                new Node() { Name = "Node 05" },
                new Node() { Name = "Node 06" }
            };

            Nodes[1].Children.Add(new Node() { Name = "Node 02.1" });

            AddNodeCommand = new RelayCommand(OnAddNodeExecuted);
        }

        private void OnAddNodeExecuted()
        {
            Nodes[4].Children.Add(new Node() { Name = $"Node 05.{Nodes[4].Children.Count + 1}" });
        }
    }
}

Und ein Node Model:

using System.Collections.ObjectModel;

namespace MyCSharp125410
{
    public class Node
    {
        public string Name { get; set; }

        public ObservableCollection<Node> Children { get; set; }

        public Node() 
        {
            Children = new ObservableCollection<Node>();
        }

    }
}

In dem Beispiel habe ich das CommunityToolkit.Mvvm wegen dem RelayCommand noch über Nuget installiert.

Wenn alles zusammengerührt ist, dann sollte bei jedem Klick auf den Button ein neuer Child-Knoten im Node 05 erstellt werden.
Wpf TreeView Item-Property anzeigen

Ich arbeite mich gerade in die wpf-Technologie ein. Die größten Probleme habe ich mit dem Verständnis des Bindings. Ich möchte Inhalte des selektierten Node ausserhalb eines TreeViews nutzen. Um dies zu testen habe ich Cavemans (ich danke Dir) Beispiel angepasst und eine TextBox mit dem CurrentNodeName gebunden:

XAML:

<Window x:Class="ApTest.MainWindow"
        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:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:local="clr-namespace:ApTest"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <local:MultiToBrushConverter x:Key="conv"/>
        <ObjectDataProvider x:Key="drives"
                            ObjectType="{x:Type sys:Environment}"
                            MethodName="GetLogicalDrives"/>
    </Window.Resources>
    <Grid>
        <StackPanel>
            <TextBox Text="{Binding CurrentNodeName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged }"/>
            <TreeView Name="myTreeView" Grid.Row="0" ItemsSource="{Binding Nodes}">
                <TreeView.ItemContainerStyle>
                    <Style TargetType="{x:Type TreeViewItem}">
                        <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                        <Setter Property="FontWeight" Value="Normal" />
                        <Style.Triggers>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter Property="FontWeight" Value="ExtraBold" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </TreeView.ItemContainerStyle>
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                        <TextBlock Text="{Binding Name}" />
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
        </StackPanel>
    </Grid>
</Window>

MainWindowViewModel:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Text;
using System.Threading.Tasks;

namespace ApTest
{
    internal class MainWindowViewModel : Node
    {

        public ObservableCollection<Node> Nodes { get; set; }

        internal MainWindowViewModel()
        {
            Nodes = new ObservableCollection<Node>
            {
                new Node() { Name = "Node 01" },
                new Node() { Name = "Node 02" },
                new Node() { Name = "Node 03" },
                new Node() { Name = "Node 04" },
                new Node() { Name = "Node 05" },
                new Node() { Name = "Node 06" }
            };

            Nodes[1].Children.Add(new Node() { Name = "Node 02.1" });
            CurrentNodeName = "not assigned";
        }
    }
}

Node:

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace ApTest
{
    public class Node : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private string currentNodeName;
        public string CurrentNodeName
        {
            get { return currentNodeName; }
            set {
                currentNodeName = value;
                OnPropertyChanged(nameof(CurrentNodeName));
            }
        }

        public string Name { get; set; }

        public ObservableCollection<Node> Children { get; set; }

        public Node()
        {
            Children = new ObservableCollection<Node>();
        }

        bool isSelected;
        public bool IsSelected { get => isSelected;
            set
            { 
                isSelected = value;
                if (isSelected)
                    CurrentNodeName = Name;
            }
        }
    }
}

Ich verstehe nicht, warum für "CurrentNodeName" nur die Initialisierung "not assigned" angezeigt wird. In der Klasse Node wird "CurrentNodeName" gesetzt. OnPropertyChanged wird aufgerufen. Der aktuelle Wert wird aber nicht abgeholt. Kann mir jemand helfen?

Und wirst du älter als ´ne Kuh,
musst doch lernen immerzu.

Dein Design paßt nicht: dein MainWindowViewModel hast du von Node abgeleitet, so daß nur dessen CurrentNodeName gebunden ist, jeder Node setzt jedoch in  IsSelected nur seine eigene CurrentNodeName-Eigenschaft.

Du solltest also die Ableitung von Node entfernen (und stattdessen ein ViewModelBase benutzen). Und dann hat nur MainWindowViewModel die CurrentNodeName-Eigenschaft. Und die einzelnen Node-Objekte müssen dann über eine Referenz (oder per Ereignis) die eine MainWindowViewModel.CurrentNodeName-Eigenschaft setzen.

PS. Warum hast du sowohl Nodes als auch, durch die Ableitung von Node,Children in deinem MainWindowViewModel?

Hallo Th69, danke für Deine Mühe und die schnelle Antwort. Mein Problem ist die Kaskadierung. Events, welche ich versuchte funktionierten nur in der ersten parent-ebene. Also zwischen "node" und den ersten children. Ich habe zwar etwas von blubbernden Events gelesen, denke aber, dies bezieht sich nur auf GUI-Container.

Und wirst du älter als ´ne Kuh,
musst doch lernen immerzu.

Hallo Th69, ich bin auf den Weg in den Ski-Urlaub. Sobald ich wieder da bin, versuche ich eine Refernz von "CurrentNodeName" an die child-nodes zu übergeben. Ich danke Dir für die Anregung.

Und wirst du älter als ´ne Kuh,
musst doch lernen immerzu.