A treeView is populated programmatically using an ObservableCollection. The binding is set including the opening-closing of Items TreeView, which would open Items, including from another form. So, if you click on the drop-down list of any of the Items, its attachment to the isExpander property disappears, but remains to the Foreground. How to restore binding for this Item-a.

public class NodeNap: INotifyPropertyChanged { .......... public string Foreground { get { return foreground; } set { NotifiyPropertyChanged(ref foreground, value); } } protected bool isExpander = false; public bool IsExpander { get { return isExpander; } set { NotifiyPropertyChanged(ref isExpander, value); } } public event PropertyChangedEventHandler PropertyChanged; protected bool NotifiyPropertyChanged<NodeNap>(ref NodeNap oldValue, NodeNap newValue, [CallerMemberName]string property = "") { oldValue = newValue; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); return true; } private ObservableCollection<NodeNap> node; public ObservableCollection<NodeNap> Node { get { if (node == null) node = new ObservableCollection<NodeNap>(); return node; } } } 

So I get attached in the code:

 private ObservableCollection<NodeNap> node = new ObservableCollection<NodeNap>(); public ObservableCollection<NodeNap> Node { get;set; } void Node.Add() { NodeNap root = new NodeNap(++ID, 0, ((ComboBox)sender).Text) { IsExpander = true, Foreground = "Yellow" }; comboBox1.Text = root.Nap; bin1 = new Binding { Source = root, Path = new PropertyPath("Nap"), Mode = BindingMode.TwoWay }; comboBox1.SetBinding(ComboBox.TextProperty, bin1); Node.Add(root); NodeNap child = new NodeNap(); root.Node.Add(child); } 

enter image description here

  • Are you sure your code compiles at all? - Andrew NOP
  • Well, I see the ComboBox.Text binding to some Nap. How this should work, I do not understand at all. - Andrew NOP
  • @EvgeniyZ thanks for the detailed answer. You offer a different approach in organizing a tree node class. And I see that the binding remains in place. Maybe I will have to change my code .. My task is complicated by the fact that the nodes of the tree are formed from another child form and the typed text is synchronously displayed in a treeView, using the binding. Here I am confused - all sorts of non-docking climbed. I am not a programmer, but an amateur, and I am just starting to deal with WPF. While I will disassemble your material. Unsubscribe later. - Alex Drongo
  • @AlexDrongo I added some information about "You offer a different approach" and "from a different form." As for "I am not a programmer" - write the code, it means that you are it, I am also a person without any special education, an ordinary self-taught person, and I had this as a hobby from the beginning. And to write the code, it is necessary from the very beginning correctly, to get used to it from the "diaper". PS Please write a comment UNDER my answer, it’s good that I came here, because I’m not receiving any alerts (until I become a “participant” of this conversation and leave a comment). - EvgeniyZ

1 answer 1

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:

Result


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:

Result 2

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 ...

  • I understand your code. Believe me, mine is not much different. Asking a question, I deleted a lot from the code, so as not to litter the question myself, and probably this I only complicated everything ... I also implement MVVM. I try at least. I know that your ButtonBase_OnClick event needs to be forwarded to the VM via EventHandler and there it is already handled. I do that. I also have XAML markup, though in the TreeViewItem template (because I have a multi-column TreeView), the class properties (NodeNap) that form the TreeView nodes bind. - Alex Drongo
  • The difficulty with me is that the text typed in the ComboBox in the child window (which has its own VM) must be synchronously displayed in the TreeView. ObservableCollection cannot do this, because by that time not yet created. I haven’t found anything better so far than at the time of changing the text in the VM TextBoxBase.TextChanged = "CmbCat_TextChanged" VM handler to create an empty node and bind ComboBox.Text to the name (I have Nap) of the class forming the node during its creation. Perhaps this is not correct. - Alex Drongo
  • Although in principle everything works, except that the binding described in my question disappears. Can you tell me how to correctly implement my idea and avoid similar jambs. Do not judge strictly if anything, wpf for me while this is a brain gap). - Alex Drongo
  • @AlexDrongo And you can give a screenshot of what you are implementing (with a detailed description is desirable), I would try to write it all correctly. Well, in general, your whole problem is that IsExpanded is tied "in one direction", specify TwoWay and should not fly. - EvgeniyZ
  • I added, only there are two more columns, they are just not filled - Alex Drongo