You do something completely wrong, the binding is better done through XAML, not code. Also binding is done by specifying the DataContext.
Let's see how everything should be:
INotifyPropertyChanged and ViewModel:
First we need a base class that will implement INotifyPropertyChanged:
public class VM : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
Then, we will write a certain class that will contain all the properties for binding inside:
public class TreeViewModel : VM { public TreeViewModel(string name, string foreground = "black", bool isExpander = false, ObservableCollection<TreeViewModel> children = null) { Name = name; Foreground = foreground; IsExpander = isExpander; if (children != null) Children = children; } public string Name { get; set; } public string foreground; public string Foreground { get => foreground; set { foreground = value; OnPropertyChanged(); } } private bool isExpander; public bool IsExpander { get => isExpander; set { isExpander = value; OnPropertyChanged(); } } public ObservableCollection<TreeViewModel> Children { get; set; } = new ObservableCollection<TreeViewModel>(); }
What do we see here? And everything is simple, we implement the Name, Foreground, IsExpander properties, as well as a collection of the same type for the child elements. For the necessary properties (those that may change during the program operation) we implement INPC (so that our interface can accept these changes). Actually all the standard. And yes, for convenience, we give all the properties the desired values.
Filling and DataContext
I personally will do everything right in MainWindow, but for good it should be done in a separate VM.
Create a property of our collection inside the MainWindow class (for binding it is better to use ObservableCollection, because it implements everything necessary for updating the interface):
public ObservableCollection<TreeViewModel> Items { get; set; } = new ObservableCollection<TreeViewModel>();
Well, right in the MainWindow constructor (I repeat, this is not entirely correct, it’s better to use a separate VM!) We fill it with test data, well, we immediately set the DataContext. It should be something like this:
public MainWindow() { InitializeComponent(); var languages = new ObservableCollection<TreeViewModel>() { new TreeViewModel("Русский"), new TreeViewModel("Английский", "Red"), new TreeViewModel("Испанский") }; Items.Add(new TreeViewModel("Языки", isExpander: true, children: languages)); var people = new ObservableCollection<TreeViewModel>() { new TreeViewModel("Вася"), new TreeViewModel("Маша"), new TreeViewModel("Вова", children: new ObservableCollection<TreeViewModel>{new TreeViewModel("Аня")}) }; Items.Add(new TreeViewModel("Люди", "Blue", children: people)); DataContext = this; }
For the test, I personally will add a button and, on a click, hang the IsExpander change from the second element of the collection:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { Items[1].IsExpander = !Items[1].IsExpander; }
XAML markup:
And so, the finish line! It remains for us to create a View with all the elements we need, in my case we need to create a TreeView and Button:
<Grid> <TreeView ItemsSource="{Binding Items}" > <TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="{Binding IsExpander, Mode=TwoWay}"/> </Style> </TreeView.ItemContainerStyle> <TreeView.Resources> <HierarchicalDataTemplate DataType="{x:Type local:TreeViewModel}" ItemsSource="{Binding Children}" > <TreeViewItem Header="{Binding Name}" Foreground="{Binding Foreground}" /> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView> <Button Height="30" VerticalAlignment="Bottom" Content="Открыть/Закрыть" Click="ButtonBase_OnClick"/> </Grid>
With Button everything is clear, I think, but the TreeView is more complicated:
- First of all, we bind the TreeView to our collection (which was filled in) via ItemsSource.
- Next, we set the style for items of type TreeViewItem. In it, we bind the IsExpanded property. Note that
Mode=TwoWay is Mode=TwoWay - which allows you to track changes made to this property from both the interface and the code side. - Next, we set the HierarchicalDataTemplate, this is a kind of template that sets the data level display format. In it, we specify the type that is used in the associated collection (via DataType), set the child anchoring of the elements (via ItemsSource), well, bind the color and name.
That's all, the result will be something like this:

My task is complicated by the fact that the nodes of the tree are formed from a different-child form and the typed text is synchronously displayed in the treeView, using the binding.
This is what creates the division for MVVM. See, for example, the collection of the collection and the collection of the Items itself will be transferred to a certain ViewModel:
public class MainViewModel { public ObservableCollection<TreeViewModel> Items { get; set; } = new ObservableCollection<TreeViewModel>(); public MainViewModel() { var languages = new ObservableCollection<TreeViewModel>() { new TreeViewModel("Русский"), new TreeViewModel("Английский", "Red"), new TreeViewModel("Испанский") }; Items.Add(new TreeViewModel("Языки", isExpander: true, children: languages)); var people = new ObservableCollection<TreeViewModel>() { new TreeViewModel("Вася"), new TreeViewModel("Маша"), new TreeViewModel("Вова", children: new ObservableCollection<TreeViewModel>{new TreeViewModel("Аня")}) }; Items.Add(new TreeViewModel("Люди", "Blue", children: people)); } }
Next, instead of what was previously in MainWindow, we make the property of our VM and assign a DataContext to it (well, don’t forget about the button ...):
public partial class MainWindow : Window { public MainViewModel MainViewModel { get; set; } = new MainViewModel(); public MainWindow() { InitializeComponent(); DataContext = MainViewModel; } private void ButtonBase_OnClick(object sender, RoutedEventArgs e) { MainViewModel.Items[1].IsExpander = !MainViewModel.Items[1].IsExpander; } }
Actually, that's all, now we have a separate VM, which is responsible for its functionality. How do we access it from another window? See, the binding works with the object that was created earlier and if we initialize the object again (like MainViewModel model = new... ), then most likely we will lose all the bindings (I'm not talking about the data), and in general what's the point of producing hundreds of identical objects? So, our task is to transfer the link to our new window, the easiest option is to get it in the constructor of the new window:
public MainViewModel MainViewModel { get; set; } public Window2(MainViewModel mainViewModel) { InitializeComponent(); MainViewModel = mainViewModel; }
Well, then the call from the first window will be something like this (but here I’m not sure of MVVM, it’s better to tie it to events, etc.):
Window2 window2 = new Window2(MainViewModel); window2.Show();
That's all, so we have a link to our VM in the right window, where we can safely work with it. Binding in this case is not as not affected.
You offer a different approach in organizing a tree node class.
Let's talk on this topic ...
I offer you the right approach, something that a person must know when using WPF (I’m talking about MVVM). Want to work with WPF, work correctly and use MVVM. And MVVM itself means that all your code will be divided into 3 layers (Model, ViewModel, and View). The View layer is not how it should not be associated with the application code, it should only know that "an object with the name Name should appear here" and that's it. At first, this is a bit annoying, you think "what and what?", But over time (and very quickly) you get used to it and everything else seems to be a completely wrong approach. By the way, I used to use the old approach too, sculpted everything as in WinForms, then they helped me with the implementation of MVVM here and you know, after I rewrote my old application with the new one - it began to work faster and more correctly at times. In general, as for me, without MVVM it’s better not to take on WPF and sit on forms ...
and typed text is synchronously displayed in treeView
UpdateSourceTrigger=PropertyChanged in binding properties is responsible for synchronization. But I understand that you must have some kind of logic for the work. I suggested that the user should enter the text, if he finds a match, he edits it. How to do?
Create another ViewModel, in it we will create the Name property for the binding, implement the INPC and pass the MainViewModel. The code will generally be something like this:
public class SecondViewModel : VM { private MainViewModel MainViewModel; public SecondViewModel(MainViewModel mainViewModel) { MainViewModel = mainViewModel; } private string name; public string Name { get => name; set { name = value; Search(value); OnPropertyChanged(); } } private TreeViewModel model; private void Search(string text) { if (model == null) { model = MainViewModel.Items.FirstOrDefault(x => x.Name == text); } else { model.Name = text; } } }
Notice, I call Search(value); with a change in the Name property. The Search(); method itself Search(); implements the simplest logic of searching for the desired object; if the object is found, then we save the link to it in the previously prepared field. With further update, we will change the Name value of the found object.
Let's tie our second window to the created VM:
public MainViewModel MainViewModel { get; set; } public SecondViewModel SecondViewModel { get; set; } public Window2(MainViewModel mainViewModel) { InitializeComponent(); MainViewModel = mainViewModel; SecondViewModel = new SecondViewModel(mainViewModel); DataContext = SecondViewModel; }
Well and in XAML we will make a binding:
* focus on UpdateSourceTrigger!
In the end, we get something like this:

Like everything, as you can see - we changed the value of "Languages" to "Languages of the world!" In TreeView, and outputting all this in "real time". It remains only to implement the save button and a couple of small things, but this is already on you ...