Я не это имел ввиду! Я имел в виду, что нам нужно что-то, где мы можем создать новый проект приложения Visual Studio WPF, вставить код, запустить и увидеть проблему. Вы не можете сделать это с помощью кода в текущем вопросе, верно?
Я понимаю, что с оригинальным кодом это не так просто. Тем не менее, я на самом деле создал версию ранее сегодня. Это не идеально, потому что я не вижу весь ваш код, поэтому мне пришлось угадывать в нескольких местах.
Этот код находится в самом низу этого ответа. Это то, что мы подразумеваем под минимальным воспроизводимым примером.
Если вы создадите новый проект и вставите код внизу в файлы MainWindow XAML и C#, он покажет пользовательский элемент управления с полем со списком и древовидная структура, и нажатие в любом из них выберет правильный элемент в другом. Он основан на вашем исходном коде, и XAML практически идентичен. На самом деле вам также нужно установить пакет Microsoft.Xaml.Behaviors.Wpf NuGet, поскольку ваш XAML использует поведения.
Проблема в исходном вопросе заключалась в том, что свойство IsSelected в соответствующем элементе OrgElementViewModel в TreeView не был установлен, когда ComboBox был изменен. Это произошло потому, что единственное свойство, которое обновлялось при изменении ComboBox, было SelectedId.
Решением ниже является использование установщика SelectedId для обновления свойства IsSelected и для полного завершения элемента Selected в OrgTreeViewModel, если вы все еще со мной. Ниже приведен важный код, который заменяет существующий код SelectedId в OrgTreeViewModel:
public string SelectedId
{
get { return selectedId; }
set
{
selectedId = value;
OrgElementViewModel orgElementViewModel = FindById(selectedId);
if (orgElementViewModel != null) this.Selected = orgElementViewModel;
OnPropertyChanged("SelectedId");
}
}
Обратите внимание, что здесь есть метод FindById, который должен принять selectedId и найти соответствующий элемент OrgElementViewModel в дереве. Я не вижу метода, который делает это, и его немного сложно написать без вашего рабочего кода: почти наверняка есть лучший способ сделать это, чем рекурсивный поиск в решении ниже. Также я не мог понять, как комбо было заполнено, поэтому создал свой собственный список и связал его с ним.
Дайте мне знать, если это поможет!
Полный рабочий пример: XAML
<Window x:Class="ComboTreeBinding.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:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:ComboTreeBinding"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel Height="200" Width="200">
<TextBlock HorizontalAlignment="Left" Margin="20,0,0,0" Width="Auto" Text="Search:" TextWrapping="Wrap" VerticalAlignment="Center" FontFamily="Arial"/>
<ComboBox x:Name="cmbSerchBox" Width="120" ItemsSource="{Binding List}" DisplayMemberPath = "FirstName" SelectedIndex = "-1" SelectedValuePath = "Id" SelectedValue="{Binding Path=SelectedId,Mode=TwoWay}" HorizontalAlignment="Left" Margin="8" >
</ComboBox>
<TreeView Height="150" x:Name="tvMain" ItemsSource="{Binding Root}" BorderThickness="0" ScrollViewer.VerticalScrollBarVisibility="Visible">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat=" {0} ">
<Binding Path="FirstName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<StackPanel.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="true">
<Setter Property="StackPanel.Background" Value="LightBlue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</TreeView.ItemContainerStyle>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding SelectedCommand}"
CommandParameter="{Binding ElementName=tvMain, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
</StackPanel>
</Window>
Полный рабочий пример, C#:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
namespace ComboTreeBinding
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new OrgTreeViewModel();
}
}
public class OrgTreeViewModel: INotifyPropertyChanged
{
public OrgTreeViewModel()
{
this.CreateDataList();
}
public ObservableCollection<OrgElementViewModel> List { get; set; }
private List<OrgElementViewModel> root;
public List<OrgElementViewModel> Root
{
get
{
if (root == null) root = new List<OrgElementViewModel>{List[0]};
return root;
}
}
public void CreateDataList()
{
ObservableCollection<OrgElementViewModel> list = new ObservableCollection<OrgElementViewModel>();
list.Add(new OrgElementViewModel("1", "AAAAA", "0"));
list.Add(new OrgElementViewModel("2", "BBBBB", "1"));
list.Add(new OrgElementViewModel("3", "CCCCC", "1"));
list.Add(new OrgElementViewModel("7", "DDDDD", "2"));
this.List = list;
foreach (OrgElementViewModel item in list) SetChildren(item);
}
private void SetChildren(OrgElementViewModel Parent)
{
foreach (OrgElementViewModel listItem in List)
{
if (listItem.ParentId == Parent.Id) Parent.Children.Add(listItem);
}
}
private ICommand selectedCommand;
public ICommand SelectedCommand
{
get
{
if (selectedCommand == null)
{
selectedCommand = new CommandBase(i => this.SetSelected(i), null);
}
return selectedCommand;
}
}
private void SetSelected(object orgElement)
{
this.Selected = orgElement as OrgElementViewModel;
SelectedId = this.Selected.Id;
OnPropertyChanged("SelectedId");
}
private OrgElementViewModel selected;
public OrgElementViewModel Selected
{
get { return selected; }
set
{
selected = value;
selected.IsSelected = true;
//ShowChildrenLevel(); //show only the levels chosen by the user
OnPropertyChanged("Selected");
}
}
private string selectedId;
public string SelectedId
{
get { return selectedId; }
set
{
selectedId = value;
OrgElementViewModel orgElementViewModel = FindById(selectedId);
if (orgElementViewModel != null) this.Selected = orgElementViewModel;
OnPropertyChanged("SelectedId");
}
}
private OrgElementViewModel FindById(string ID)
{
foreach(OrgElementViewModel item in this.List)
{
if (item.Id == ID) return item;
}
return null;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class OrgElementViewModel: INotifyPropertyChanged
{
public string Id { get; set; }
public string FirstName { get; set; }
public string ParentId { get; set; }
private bool isSelected;
public ObservableCollection<OrgElementViewModel> Children { get; set; }
public OrgElementViewModel(string Id, string FirstName, string ParentId)
{
this.Id = Id;
this.FirstName = FirstName;
this.ParentId = ParentId;
this.Children = new ObservableCollection<OrgElementViewModel>();
}
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CommandBase : ICommand
{
private Action<object> commandDelegate;
private object commandParameter;
public CommandBase(Action<object> commandDelegate, object commandParameter)
{
this.commandDelegate = commandDelegate;
this.commandParameter = commandParameter;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
commandDelegate?.Invoke(parameter);
}
}
}