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
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
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.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
<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.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
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.
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.
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.