TreeView - множественное выделение по типу ноды
Предисловие
Решаю для меня сложную задачку по визуализации данных, проект большой и требует рефакторинга, застрял. Не могу рефакторить, пока не приму архитектурное решение по данной фиче. Поэтому создал отдельный демо-проект, чтобы воспроизвести то, что мне требуется.
Это не простой TreeView с мультиселектом, поэтому готовые решения с гитхаба мне не подошли, к тому же мне требуется особая визуализация, которой я уже смог добиться в XAML разметке. Проблема только с логикой.
Задача
- Дано дерево элементов, каждая нода имеет свой айдишник и тип.
TreeViewотображает эти ноды в виде визуального дерева. Ноды могут быть выбранными (активными) или не выбранными (не активными).- По умолчанию выбраны ноды верхнего уровня.
- Одновременно может быть выбрана только одна нода для каждого типа. - вот здесь застрял
- Всегда должно быть выбрано постоянное количество нод, сколько типов нод присутствует в дереве - столько и выбрано.
- Отдельно должна формироваться коллекция с айдишниками выбранных. На основе этой коллекции я буду заполнять другой контрол контентом.
Для видимости, вот реализация интерфейса INPC
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Данные
public enum TreeNodeType
{
Red,
Green,
Blue
}
public class TreeItem : NotifyPropertyChanged
{
private TreeNodeType _nodeType;
private int _nodeId;
private ObservableCollection<TreeItem> _items;
private bool _selected;
private bool _active;
public int NodeId
{
get => _nodeId;
set
{
_nodeId = value;
OnPropertyChanged();
}
}
public TreeNodeType NodeType
{
get => _nodeType;
set
{
_nodeType = value;
OnPropertyChanged();
}
}
public ObservableCollection<TreeItem> Items // TreeViewItem.ItemsSource
{
get => _items;
set
{
_items = value;
OnPropertyChanged();
}
}
public bool Selected // TreeViewItem.IsSelected
{
get => _selected;
set
{
_selected = value;
Active = value;
OnPropertyChanged();
}
}
public bool Active // моя попытка выделять элемент
{
get => _active;
set
{
if (value) _active = !_active;
OnPropertyChanged();
}
}
}
View Model
public class MainViewModel : NotifyPropertyChanged
{
private ObservableCollection<TreeItem> _treeItems;
private ObservableCollection<int> _selectedItems;
public ObservableCollection<TreeItem> TreeItems
{
get => _treeItems;
set
{
_treeItems = value;
OnPropertyChanged();
}
}
public ObservableCollection<int> SelectedItems // сюда хочу записать айдищшники выбранных нод
{
get => _selectedItems;
set
{
_selectedItems = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
// тестовые данные
TreeItems = new ObservableCollection<TreeItem>
{
new TreeItem
{
NodeId = 0,
NodeType = TreeNodeType.Red,
Items = new ObservableCollection<TreeItem>
{
new TreeItem
{
NodeId = 1,
NodeType = TreeNodeType.Red,
Items = new ObservableCollection<TreeItem>
{
new TreeItem { NodeId = 2, NodeType = TreeNodeType.Green },
new TreeItem { NodeId = 3, NodeType = TreeNodeType.Red }
}
},
new TreeItem { NodeId = 4, NodeType = TreeNodeType.Red }
},
Selected = true
},
new TreeItem
{
NodeId = 5,
NodeType = TreeNodeType.Blue,
Items = new ObservableCollection<TreeItem>
{
new TreeItem
{
NodeId = 6,
NodeType = TreeNodeType.Blue,
Items = new ObservableCollection<TreeItem>
{
new TreeItem { NodeId = 7, NodeType = TreeNodeType.Blue }
}
},
new TreeItem { NodeId = 8, NodeType = TreeNodeType.Green }
},
Selected = true
},
new TreeItem
{
NodeId = 9,
NodeType = TreeNodeType.Green,
Selected = true
}
};
SelectedItems = new ObservableCollection<int>();
}
}
Разметка
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TreeView ItemsSource="{Binding TreeItems}">
<TreeView.Resources>
<Style TargetType="{x:Type TreeView}">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeView}">
<ItemsPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="TreeViewItem">
<Setter Property="ItemsSource" Value="{Binding Items}"/>
<Setter Property="IsSelected" Value="{Binding Selected}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<StackPanel Margin="2" >
<Border BorderThickness="1" HorizontalAlignment="Center" Margin="2">
<Border.Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<DataTrigger Binding="{Binding Active}" Value="True">
<Setter Property="BorderBrush" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid>
<Rectangle Width="30" Height="30" Margin="2">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Style.Triggers>
<DataTrigger Binding="{Binding NodeType}" Value="Red">
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding NodeType}" Value="Green">
<Setter Property="Fill" Value="Green"/>
</DataTrigger>
<DataTrigger Binding="{Binding NodeType}" Value="Blue">
<Setter Property="Fill" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<TextBlock Text="{Binding NodeId}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
</Grid>
</Border>
</StackPanel>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
<TreeView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</TreeView.ItemsPanel>
</TreeView>
<ItemsControl Grid.Column="1" ItemsSource="{Binding SelectedItems}"/>
</Grid>
Подскажите, куда копать? Как найти ранее выделенную ноду этого же типа, снять с нее выделение, и только после этого выделить текущую?
Ответы (1 шт):
Что то вы намудрили с этим Selected vs Active, по хорошему логику с отметками вынести куда то модель нужно и протестировать. Я то просто сделал ваш пример работоспособным. Что я сделал:
В главной модели добавил пару методов
public void Deactive(TreeNodeType type)
{
if (TreeItems != null)
foreach (var treeItem in TreeItems)
Deactive(treeItem, type);
}
private void Deactive(TreeItem node, TreeNodeType type)
{
if (node.Active && node.NodeType == type)
{
node.Active = false;
}
if (node.Items != null)
foreach (var item in node.Items)
Deactive(item, type);
}
Добавил в ноду ссылку на главную модель
public class TreeItem : NotifyPropertyChanged
{
....
public MainViewModel MainViewModel { get; set; }
Чуть поправил логику на Selected/Active
public bool Selected // TreeViewItem.IsSelected
{
get => _selected;
set
{
_selected = value;
if (value)
Active = value;
OnPropertyChanged();
}
}
public bool Active // моя попытка выделять элемент
{
get => _active;
set
{
if (value) MainViewModel.Deactive(NodeType);
_active = value;
OnPropertyChanged();
}
}
Что вышло из этого
Закинул что наклепал на Github. Но повторюсь, я бы такую логику вынес куда то в модель, а то вся эта мешанина через лет 5 вам аукнется.

