gpt4 book ai didi

wpf - 自定义 TreeView 以允许多选

转载 作者:行者123 更新时间:2023-12-03 06:41:40 29 4
gpt4 key购买 nike

内置的 WPF TreeView 控件不允许像 ListBox 那样进行多项选择。如何自定义 TreeView 以允许多重选择而不重写它。

最佳答案

我对 SoMoS 实现有一个变体,它使用在基本 TreeView 控件的派生中声明的附加属性来跟踪 TreeViewItems 的选择状态。这可以保持对 TreeViewItem 元素本身的选择跟踪,而不是对 TreeView 呈现的模型对象进行跟踪。

这是新的 TreeView 类派生。

using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls;
using System.Collections;
using System.Collections.Generic;

namespace MultiSelectTreeViewDemo
{
public sealed class MultiSelectTreeView : TreeView
{
#region Fields

// Used in shift selections
private TreeViewItem _lastItemSelected;

#endregion Fields
#region Dependency Properties

public static readonly DependencyProperty IsItemSelectedProperty =
DependencyProperty.RegisterAttached("IsItemSelected", typeof(bool), typeof(MultiSelectTreeView));

public static void SetIsItemSelected(UIElement element, bool value)
{
element.SetValue(IsItemSelectedProperty, value);
}
public static bool GetIsItemSelected(UIElement element)
{
return (bool)element.GetValue(IsItemSelectedProperty);
}

#endregion Dependency Properties
#region Properties

private static bool IsCtrlPressed
{
get { return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); }
}
private static bool IsShiftPressed
{
get { return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift); }
}

public IList SelectedItems
{
get
{
var selectedTreeViewItems = GetTreeViewItems(this, true).Where(GetIsItemSelected);
var selectedModelItems = selectedTreeViewItems.Select(treeViewItem => treeViewItem.Header);

return selectedModelItems.ToList();
}
}

#endregion Properties
#region Event Handlers

protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);

// If clicking on a tree branch expander...
if (e.OriginalSource is Shape || e.OriginalSource is Grid || e.OriginalSource is Border)
return;

var item = GetTreeViewItemClicked((FrameworkElement)e.OriginalSource);
if (item != null) SelectedItemChangedInternal(item);
}

#endregion Event Handlers
#region Utility Methods

private void SelectedItemChangedInternal(TreeViewItem tvItem)
{
// Clear all previous selected item states if ctrl is NOT being held down
if (!IsCtrlPressed)
{
var items = GetTreeViewItems(this, true);
foreach (var treeViewItem in items)
SetIsItemSelected(treeViewItem, false);
}

// Is this an item range selection?
if (IsShiftPressed && _lastItemSelected != null)
{
var items = GetTreeViewItemRange(_lastItemSelected, tvItem);
if (items.Count > 0)
{
foreach (var treeViewItem in items)
SetIsItemSelected(treeViewItem, true);

_lastItemSelected = items.Last();
}
}
// Otherwise, individual selection
else
{
SetIsItemSelected(tvItem, true);
_lastItemSelected = tvItem;
}
}
private static TreeViewItem GetTreeViewItemClicked(DependencyObject sender)
{
while (sender != null && !(sender is TreeViewItem))
sender = VisualTreeHelper.GetParent(sender);
return sender as TreeViewItem;
}
private static List<TreeViewItem> GetTreeViewItems(ItemsControl parentItem, bool includeCollapsedItems, List<TreeViewItem> itemList = null)
{
if (itemList == null)
itemList = new List<TreeViewItem>();

for (var index = 0; index < parentItem.Items.Count; index++)
{
var tvItem = parentItem.ItemContainerGenerator.ContainerFromIndex(index) as TreeViewItem;
if (tvItem == null) continue;

itemList.Add(tvItem);
if (includeCollapsedItems || tvItem.IsExpanded)
GetTreeViewItems(tvItem, includeCollapsedItems, itemList);
}
return itemList;
}
private List<TreeViewItem> GetTreeViewItemRange(TreeViewItem start, TreeViewItem end)
{
var items = GetTreeViewItems(this, false);

var startIndex = items.IndexOf(start);
var endIndex = items.IndexOf(end);
var rangeStart = startIndex > endIndex || startIndex == -1 ? endIndex : startIndex;
var rangeCount = startIndex > endIndex ? startIndex - endIndex + 1 : endIndex - startIndex + 1;

if (startIndex == -1 && endIndex == -1)
rangeCount = 0;

else if (startIndex == -1 || endIndex == -1)
rangeCount = 1;

return rangeCount > 0 ? items.GetRange(rangeStart, rangeCount) : new List<TreeViewItem>();
}

#endregion Utility Methods
}
}

这是 XAML。请注意,最重要的部分是将使用单一“IsSelected”属性的两个触发器替换为 MultiSelectTreeViewItemStyle 中新的“IsItemSelected”附加属性,以实现视觉状态。

另请注意,我没有将新的 TreeView 控件聚合到 UserControl 中。

<Window 
x:Class="MultiSelectTreeViewDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiSelectTreeViewDemo"
Title="MultiSelect TreeView Demo" Height="350" Width="525">

<Window.Resources>
<local:DemoViewModel x:Key="ViewModel"/>
<Style x:Key="TreeViewItemFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF1BBBFA"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="Transparent"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF262626"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF595959"/>
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="Transparent"/>
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF989898"/>
<Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Padding="5,5,5,5" Width="16">
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
<Path.RenderTransform>
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
</Path.RenderTransform>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="RenderTransform" TargetName="ExpandPath">
<Setter.Value>
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
</Setter.Value>
</Setter>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsChecked" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="MultiSelectTreeViewItemStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="1,0,0,0"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ToggleButton
x:Name="Expander"
ClickMode="Press"
IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border
x:Name="Bd"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Grid.Column="1"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="true">
<ContentPresenter
x:Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<ItemsPresenter
x:Name="ItemsHost"
Grid.ColumnSpan="2"
Grid.Column="1"
Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
</Trigger>
<!--Trigger Property="IsSelected" Value="true"-->
<Trigger Property="local:MultiSelectTreeView.IsItemSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<!--Condition Property="IsSelected" Value="true"/-->
<Condition Property="local:MultiSelectTreeView.IsItemSelected" Value="true"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="VirtualizingPanel.IsVirtualizing" Value="true">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>

<Grid
Background="WhiteSmoke"
DataContext="{DynamicResource ViewModel}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<local:MultiSelectTreeView
x:Name="multiSelectTreeView"
ItemContainerStyle="{StaticResource MultiSelectTreeViewItemStyle}"
ItemsSource="{Binding FoodGroups}">
<local:MultiSelectTreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Grid>
<TextBlock FontSize="14" Text="{Binding Name}"/>
</Grid>
</HierarchicalDataTemplate>
</local:MultiSelectTreeView.ItemTemplate>
</local:MultiSelectTreeView>
<Button
Grid.Row="1"
Margin="0,10"
Padding="20,2"
HorizontalAlignment="Center"
Content="Get Selections"
Click="GetSelectionsButton_OnClick"/>
</Grid>
</Window>

这是一个驱动它的俗气 View 模型(用于演示目的)。

using System.Collections.ObjectModel;

namespace MultiSelectTreeViewDemo
{
public sealed class DemoViewModel
{
public ObservableCollection<FoodItem> FoodGroups { get; set; }

public DemoViewModel()
{
var redMeat = new FoodItem { Name = "Reds" };
redMeat.Add(new FoodItem { Name = "Beef" });
redMeat.Add(new FoodItem { Name = "Buffalo" });
redMeat.Add(new FoodItem { Name = "Lamb" });

var whiteMeat = new FoodItem { Name = "Whites" };
whiteMeat.Add(new FoodItem { Name = "Chicken" });
whiteMeat.Add(new FoodItem { Name = "Duck" });
whiteMeat.Add(new FoodItem { Name = "Pork" });
var meats = new FoodItem { Name = "Meats", Children = { redMeat, whiteMeat } };

var veggies = new FoodItem { Name = "Vegetables" };
veggies.Add(new FoodItem { Name = "Potato" });
veggies.Add(new FoodItem { Name = "Corn" });
veggies.Add(new FoodItem { Name = "Spinach" });

var fruits = new FoodItem { Name = "Fruits" };
fruits.Add(new FoodItem { Name = "Apple" });
fruits.Add(new FoodItem { Name = "Orange" });
fruits.Add(new FoodItem { Name = "Pear" });

FoodGroups = new ObservableCollection<FoodItem> { meats, veggies, fruits };
}
}
public sealed class FoodItem
{
public string Name { get; set; }
public ObservableCollection<FoodItem> Children { get; set; }

public FoodItem()
{
Children = new ObservableCollection<FoodItem>();
}
public void Add(FoodItem item)
{
Children.Add(item);
}
}
}

这是 MainWindow 代码隐藏中的按钮单击处理程序,用于显示 MessageBox 中的选择。

    private void GetSelectionsButton_OnClick(object sender, RoutedEventArgs e)
{
var selectedMesg = "";
var selectedItems = multiSelectTreeView.SelectedItems;

if (selectedItems.Count > 0)
{
selectedMesg = selectedItems.Cast<FoodItem>()
.Where(modelItem => modelItem != null)
.Aggregate(selectedMesg, (current, modelItem) => current + modelItem.Name + Environment.NewLine);
}
else
selectedMesg = "No selected items!";

MessageBox.Show(selectedMesg, "MultiSelect TreeView Demo", MessageBoxButton.OK);
}

希望这有帮助。

关于wpf - 自定义 TreeView 以允许多选,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/459375/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com