前言
我正在为我解决一个复杂的数据可视化问题,项目很大并且需要重构,我被卡住了。在我为此功能做出架构决策之前,我无法重构。因此,我创建了一个单独的演示项目来重现我所需要的。
多选并不容易TreeView,所以 github 上的现成解决方案不适合我,此外,我需要一个特殊的可视化,我已经能够在 XAML 标记中实现。唯一的问题是逻辑。
一个任务
- 给定一棵元素树,每个节点都有自己的 ID 和类型。
TreeView在可视化树中显示这些节点。可以选择(活动)或不选择(不活动)节点。- 默认选择顶级节点。
- 每种类型一次只能选择一个节点。- 卡在这里
- 应始终选择恒定数量的节点,因为树中存在许多类型的节点 - 选择了这么多。
- 另外,应形成具有所选 ID 的集合。基于这个集合,我将用内容填充另一个控件。
为了可见性,这里是 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();
}
}
}
查看模型
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>
告诉我在哪里挖?如何找到先前选择的相同类型的节点,取消选择它,然后才选择当前的?

您对这个 Selected vs Active 做了一些事情,根据带有标记的良好逻辑,您需要在某处取出模型并对其进行测试。我刚刚让你的例子工作。我做了什么:
在主模型中添加了几个方法
在节点中添加了指向主模型的链接
略微修正了 Selected/Active 的逻辑
从中产生了什么
抛出我在Github上铆接的东西。但我再说一遍,我会在模型的某个地方取出这样的逻辑,否则 5 年内所有这些哈希值都会适得其反。