TreeView заполняется программно при помощи ObservableCollection. Привязка устанавливается в том числе и на открытие-закрытие Items TreeView, что бы открывать Item-ы в том числе и из другой формы. Так вот если щелкнуть мышкой по раскрывающемуся списку любого из Items его привязка к свойству isExpander исчезает, а к Foreground останется. Как восстановить привязку для этого 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;
}
}
}
Вот так привязываюсь в коде:
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);
}
Вы делаете что то совершенно не то, привязку лучше делать через XAML, а не код. Также привязка осуществляется путем указания DataContext.
Давайте разберем как все должно быть:
Для начала нам понадобиться базовый класс, который будет реализовывать INotifyPropertyChanged:
public class VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Затем, напишем некий класс, который будет содержать внутри все свойства для привязки:
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>();
}
Что мы тут видим? А все просто, реализуем свойства Name, Foreground, IsExpander, а также коллекцию того же типа для дочерних элементов. У необходимых свойств (те, что могут измениться в ходе работы программы) мы реализуем INPC (что бы наш интерфейс мог эти изменения принять). Собственно все по стандарту. А да, еще для удобства задаем всем свойствам нужные значения.
Я лично буду все делать прям в MainWindow, но по хорошему все это надо делать в отдельной VM.
Создаем внутри класса MainWindow свойство нашей коллекции (для привязки лучше использовать ObservableCollection, ибо она реализует все необходимое для обновления интерфейса):
public ObservableCollection<TreeViewModel> Items { get; set; } = new ObservableCollection<TreeViewModel>();
Ну и прям в конструкторе MainWindow (повторюсь, это не совсем правильно, лучше использовать отдельную VM!) заполняем тестовыми данными, ну и сразу задаем DataContext. Должно получиться что то вроде этого:
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;
}
Для теста я лично еще добавлю кнопку и на клик повешу ей изменение IsExpander у второго элемента коллекции:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Items[1].IsExpander = !Items[1].IsExpander;
}
И так, финишная прямая! Нам осталось создать View со всеми необходимыми нам элементами, в моем случае нужно создать TreeView и 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>
С Button все понятно я думаю, а вот TreeView по сложнее:
Mode=TwoWay - что позволяет отслеживать изменения, произведенные с данным свойством как со стороны интерфейса, так и со стороны кода.Вот собственно и все, результатом будет что то на подобие этого:
Моя задача усложняется тем, что узлы дерева формируются из другой-дочерней формы и набираемый текст синхронно выводится в treeView, при помощи привязки.
Вот для этого и создается разделение на MVVM. Смотрите, к примеру заполнение коллекции и саму коллекцию Items мы перенесем в некую 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));
}
}
Далее, заместо того, что было раньше в MainWindow мы делаем свойство нашей VM и к нему привяжем DataContext (ну и про кнопку не забываем...):
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;
}
}
Собственно и все, теперь у нас есть отдельная VM, которая отвечает за свой функционал. Как же нам обратиться к ней из другого окна? Смотрите, привязка работает с тем объектом, что был создан ранее и если мы будем инициализировать объект заново (по типу MainViewModel model = new...), то вероятней всего потеряем все привязки (я уж не говорю о данных), да и вообще, какой смысл плодить сотню одинаковых объектов? Так вот, наша задача - это передача ссылки в наше новое окно, самый простой вариант, это получать ее в конструкторе нового окна:
public MainViewModel MainViewModel { get; set; }
public Window2(MainViewModel mainViewModel)
{
InitializeComponent();
MainViewModel = mainViewModel;
}
Ну и дальше вызов из первого окна будет примерно такой (но тут я не уверен в MVVM, лучше это завязывать на эвенты и тому подобное):
Window2 window2 = new Window2(MainViewModel);
window2.Show();
Вот собственно и все, таким образом у нас передается ссылка на нашу VM в нужное окно, где мы с ней спокойно можем работать. Привязка в этом случае не как не затронется.
Вы предлагаете другой подход в организации класса узла дерева.
Давайте поговорим на эту тему...
Я вам предлагаю правильный подход, то, что обязательно должен знать человек при использование WPF (я про MVVM). Хотите работать с WPF, работайте правильно и используйте MVVM. А сам MVVM подразумевает то, что весь ваш код будет разделен на 3 слоя (Model, ViewModel, и View). Слой View не как не должен быть связан с кодом приложения, он должен только знать, что "тут должен отобразиться объект с именем Name" и все. В первое время это немного напрягает, думаешь "как и что?", но со временем (причем очень быстро) привыкаешь и все другое кажется совершенно неверным подходом. Кстати, я раньше тоже использовал старый подход, лепил все как в WinForms, потом мне здесь помогли с реализацией MVVM и знаете, после того, как я переписал свое старое приложение по новой - оно стало в разы работать шустрее и правильней. В общем, как по мне, без MVVM лучше вовсе не браться за WPF и сидеть на формах...
и набираемый текст синхронно выводится в treeView
За синхронность отвечает UpdateSourceTrigger=PropertyChanged в свойствах привязки. Но я так понял, что у вас должна быть какая то логика для работы. Я предположил, что пользователь должен ввести текст, если находит совпадение, то редактирует его. Как сделать?
Создадим еще одну ViewModel, в ней мы создадим свойство Name для привязки, реализуем INPC и передадим MainViewModel. Код в общем будет примерно такой:
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;
}
}
}
Заметьте, я вызываю Search(value); при изменение в свойстве Name. Сам метод Search(); реализует простейшую логику поиска нужного объекта, если объект найден, то сохраняем на него ссылку в ранее подготовленное поле. При дальнейшем обновление - мы будем менять значение Name у найденного объекта.
Привяжем наше второе окно к созданной VM:
public MainViewModel MainViewModel { get; set; }
public SecondViewModel SecondViewModel { get; set; }
public Window2(MainViewModel mainViewModel)
{
InitializeComponent();
MainViewModel = mainViewModel;
SecondViewModel = new SecondViewModel(mainViewModel);
DataContext = SecondViewModel;
}
Ну и в XAML сделаем привязку:
*внимание на UpdateSourceTrigger!
В итоге у нас получится что то вроде этого:
Вроде все, как можно заметить - мы изменили в TreeView значение "Языки" на "Языки мира!", причем выводя все это в "реальном времени". Остается только реализовать кнопку сохранения и пару мелочей, но это уже на вас...
Апостиль в Лос-Анджелесе без лишних нервов и бумажной волокиты
Основные этапы разработки сайта для стоматологической клиники
Продвижение своими сайтами как стратегия роста и независимости