【自定义样式】TreeView 的应用
TreeView展开所有节点
<TreeView.ItemContainerStyle> <Style TargetType="{x:Type TreeViewItem}"> <Setter Property="IsExpanded" Value="True"></Setter> </Style> </TreeView.ItemContainerStyle>
注意:使用ItemContainerStyle 需要注意,如果集合控件的每一项是通过绑定生成的,则ItemContainerStyle对每一项Item都起作用,如果集合控件的项是通过直接在XAML中添加的,则ItemContainerStyle只对集合控件的第一层Item起作用。在绑定数据源的的前提下,TreeView在父子节点只能应用相同的控件种类。如下图,HierarchicalDataTemplate中的控件被同时应用到了父节点和子节点。
TreeViewItem控件模板
<TreeView.ItemContainerStyle> <Style TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}"> <Setter Property="HorizontalContentAlignment" Value="Center" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="TreeViewItem"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition /> </Grid.RowDefinitions> <!-- Note that the following do not work, but I believe the top 2 should?! --> <!-- Background 虽然没效果,但是必须写,不写Background 就没有 IsMouseOver效果--> <Border x:Name="Bd" CornerRadius="4" HorizontalAlignment="Stretch" BorderThickness="{TemplateBinding Border.BorderThickness}" Background="White" BorderBrush="{TemplateBinding Border.BorderBrush}" Padding="{TemplateBinding Control.Padding}" SnapsToDevicePixels="True" > <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <ToggleButton Grid.Column="0" Margin="{Binding Path=DataContext, Converter= {StaticResource ResourceKey={x:Static converter:ConvertersTypes.IndentConverter }},RelativeSource={RelativeSource TemplatedParent}}" IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press" Name="Expander"> <ToggleButton.Style> <Style TargetType="ToggleButton"> <Setter Property="UIElement.Focusable" Value="false" /> <Setter Property="FrameworkElement.Width" Value="16" /> <Setter Property="FrameworkElement.Height" Value="16" /> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate TargetType="ToggleButton"> <Border Padding="5,5,5,5" Background="#00FFFFFF" Width="16" Height="16"> <Path Fill="#00FFFFFF" Stroke="#FF989898" Name="ExpandPath"> <Path.Data> <PathGeometry Figures="M0,0L0,6L6,0z" /> </Path.Data> <Path.RenderTransform> <RotateTransform Angle="135" CenterX="3" CenterY="3" /> </Path.RenderTransform> </Path> </Border> <ControlTemplate.Triggers> <Trigger Property="UIElement.IsMouseOver" Value="True"> <Setter TargetName="ExpandPath" Property="Shape.Stroke" Value="#FF1BBBFA" /> <Setter TargetName="ExpandPath" Property="Shape.Fill" Value="#00FFFFFF" /> </Trigger> <Trigger Property="ToggleButton.IsChecked" Value="True"> <Setter TargetName="ExpandPath" Property="UIElement.RenderTransform"> <Setter.Value> <RotateTransform Angle="180" CenterX="3" CenterY="3" /> </Setter.Value> </Setter> <Setter TargetName="ExpandPath" Property="Shape.Fill" Value="#FF595959" /> <Setter TargetName="ExpandPath" Property="Shape.Stroke" Value="#FF262626" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </ToggleButton.Style> </ToggleButton> <!--整个HierarchicalDataTemplate下的Grid会插入到 TreeViewItem控件模板的ContentPresenter中--> <ContentPresenter x:Name="PART_Header" Content="{TemplateBinding HeaderedContentControl.Header}" ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}" ContentStringFormat="{TemplateBinding HeaderedItemsControl.HeaderStringFormat}" ContentTemplateSelector="{TemplateBinding HeaderedItemsControl.HeaderTemplateSelector}" ContentSource="Header" Grid.Column="1" HorizontalAlignment="Stretch" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> </Grid> </Border> <ItemsPresenter x:Name="ItemsHost" Grid.Column="1" Grid.Row="1" /> </Grid> <ControlTemplate.Triggers> <Trigger Property="TreeViewItem.IsExpanded" Value="False"> <Setter TargetName="ItemsHost" Property="UIElement.Visibility" Value="Collapsed" /> </Trigger> <Trigger Property="ItemsControl.HasItems" Value="False"> <Setter TargetName="Expander" Property="UIElement.Visibility" Value="Hidden" /> </Trigger> <Trigger Property="TreeViewItem.IsSelected" Value="True"> <Setter TargetName="Bd" Property="Panel.Background" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" /> <Setter Property="TextElement.Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}" /> </Trigger> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="TreeViewItem.IsSelected" Value="True" /> <Condition Property="Selector.IsSelectionActive" Value="False" /> </MultiTrigger.Conditions> <Setter TargetName="Bd" Property="Panel.Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" /> <Setter Property="TextElement.Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" /> </MultiTrigger> <Trigger Property="UIElement.IsEnabled" Value="False"> <Setter Property="TextElement.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </TreeView.ItemContainerStyle>
TreeViewItem数据模板
对应的xaml代码如下:
<HierarchicalDataTemplate DataType="{x:Type model:QALibrary}" ItemsSource="{Binding Path=ChildQALibrarys}"> <Grid Height="28" > <Grid.ColumnDefinitions> <ColumnDefinition MinWidth="20" Width="auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <Path Margin="7" Grid.Column="0" Fill="#1DA1E4" Stretch="Uniform" Data="{Binding FileType, Converter={StaticResource ResourceKey={x:Static converter:ConvertersTypes.FileTypeConverter }}}"/> <!--Textbox LostFocus 结束重命名--> <TextBox Name="displayNodeName_txt" Grid.Column="1" Style="{DynamicResource DirectoryTextBoxStyle}" HorizontalAlignment="Left" FontSize="12" VerticalAlignment="Center" IsEnabled="{Binding IsReadOnly, Converter={StaticResource ResourceKey={x:Static converter:ConvertersTypes.IsReadOnlyToEnableConverter } }}" BorderBrush="Transparent" IsReadOnly="{Binding IsReadOnly}" Text="{Binding QALibraryName, Mode=TwoWay}"> <b:Interaction.Triggers> <b:EventTrigger EventName="LostFocus"> <b:InvokeCommandAction Command="{Binding DataContext.EndEditCommand,RelativeSource={RelativeSource AncestorType=TreeView, Mode=FindAncestor } }" /> </b:EventTrigger> </b:Interaction.Triggers> </TextBox> </Grid> </HierarchicalDataTemplate>
HierarchicalDataTemplate是能够帮助层级控件显示层级数据的模板,一般多用于MenuItem和TreeViewItem,也可自己实现层级数据结构。
换种写法
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type model:QALibrary}" ItemsSource="{Binding Path=ChildQALibrarys}">
<Grid Height="28" >
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="20" Width="auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Path Margin="7" Grid.Column="0" Fill="#1DA1E4" Stretch="Uniform" Data="{Binding FileType, Converter={StaticResource ResourceKey={x:Static converter:ConvertersTypes.FileTypeConverter }}}"/>
<!--Textbox LostFocus 结束重命名-->
<TextBox Name="displayNodeName_txt" Grid.Column="1" Style="{DynamicResource DirectoryTextBoxStyle}" HorizontalAlignment="Left" FontSize="12" VerticalAlignment="Center" IsEnabled="{Binding IsReadOnly, Converter={StaticResource ResourceKey={x:Static converter:ConvertersTypes.IsReadOnlyToEnableConverter } }}" BorderBrush="Transparent" IsReadOnly="{Binding IsReadOnly}" Text="{Binding QALibraryName, Mode=TwoWay}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="LostFocus">
<b:InvokeCommandAction Command="{Binding DataContext.EndEditCommand,RelativeSource={RelativeSource AncestorType=TreeView, Mode=FindAncestor } }" />
</b:EventTrigger>
</b:Interaction.Triggers>
</TextBox>
</Grid>
</HierarchicalDataTemplate>
</TreeView.Resources>
整个HierarchicalDataTemplate下的Grid会插入到 TreeViewItem控件模板的ContentPresenter中。如果这个Grid下的内容不是占据全行的话,TreeViewItem模板中的Border,依旧会有一些空白。
TreeViewItem多模板
<Window x:Class="WpfTutorialSamples.TreeView_control.TreeViewMultipleTemplatesSample" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:self="clr-namespace:WpfTutorialSamples.TreeView_control" Title="TreeViewMultipleTemplatesSample" Height="200" Width="250"> <Grid Margin="10"> <TreeView Name="trvFamilies"> <TreeView.Resources> <HierarchicalDataTemplate DataType="{x:Type self:Family}" ItemsSource="{Binding Members}"> <StackPanel Orientation="Horizontal"> <Image Source="/WpfTutorialSamples;component/Images/group.png" Margin="0,0,5,0" /> <TextBlock Text="{Binding Name}" /> <TextBlock Text=" [" Foreground="Blue" /> <TextBlock Text="{Binding Members.Count}" Foreground="Blue" /> <TextBlock Text="]" Foreground="Blue" /> </StackPanel> </HierarchicalDataTemplate> <DataTemplate DataType="{x:Type self:FamilyMember}"> <StackPanel Orientation="Horizontal"> <Image Source="/WpfTutorialSamples;component/Images/user.png" Margin="0,0,5,0" /> <TextBlock Text="{Binding Name}" /> <TextBlock Text=" (" Foreground="Green" /> <TextBlock Text="{Binding Age}" Foreground="Green" /> <TextBlock Text=" years)" Foreground="Green" /> </StackPanel> </DataTemplate> </TreeView.Resources> </TreeView> </Grid> </Window>
using System; using System.Collections.Generic; using System.Windows; using System.Collections.ObjectModel; namespace WpfTutorialSamples.TreeView_control { public partial class TreeViewMultipleTemplatesSample : Window { public TreeViewMultipleTemplatesSample() { InitializeComponent(); List<Family> families = new List<Family>(); Family family1 = new Family() { Name = "The Doe's" }; family1.Members.Add(new FamilyMember() { Name = "John Doe", Age = 42 }); family1.Members.Add(new FamilyMember() { Name = "Jane Doe", Age = 39 }); family1.Members.Add(new FamilyMember() { Name = "Sammy Doe", Age = 13 }); families.Add(family1); Family family2 = new Family() { Name = "The Moe's" }; family2.Members.Add(new FamilyMember() { Name = "Mark Moe", Age = 31 }); family2.Members.Add(new FamilyMember() { Name = "Norma Moe", Age = 28 }); families.Add(family2); trvFamilies.ItemsSource = families; } } public class Family { public Family() { this.Members = new ObservableCollection<FamilyMember>(); } public string Name { get; set; } public ObservableCollection<FamilyMember> Members { get; set; } } public class FamilyMember { public string Name { get; set; } public int Age { get; set; } } }
TreeView的节点数据类型
/// <summary> /// 要绑定到Tree 结构所有要设计成链表结构 /// 要具备INotifyPropertyChanged,重命名TreeNode时候用到 /// </summary> [Table("QALibrary")] public class QALibrary:INotifyPropertyChanged { [Key] [Required] [Column(Order = 1)] public int QALibraryID { get; set; } [DefaultValue(0)] [Required] public int QALibraryParentID { get; set; } private string? qALibraryName; [DefaultValue("未命名")] public string? QALibraryName { get { return qALibraryName; } set { qALibraryName = value; NotifyPropertyChanged(); } } public FileType FileType { get; set; } //由递归调用生成 [NotMapped] public ObservableCollection<QALibrary> ChildQALibrarys { get; set; } //由递归调用生成,主要用于遍历 [NotMapped] public QALibrary? QALibraryParent { get; set; } //isReadOnly 设置节点是否可以编辑 ,默认不可编辑的 private bool? isReadOnly = true; [NotMapped] public bool? IsReadOnly { get { return isReadOnly; } set { isReadOnly = value; NotifyPropertyChanged(); } } public event PropertyChangedEventHandler? PropertyChanged; public DateTime CreateDateTime { get; set; } public int? InTotal { get; set; } public QALibrary() { ChildQALibrarys = new ObservableCollection<QALibrary>(); } private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
对应的xaml
<TreeView Name="chapterTree" Grid.Column="0"> <TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type model:QALibrary}" ItemsSource="{Binding Path=ChildQALibrarys}">
<Grid Height="28" >
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="20" Width="auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Path Margin="7" Grid.Column="0" Fill="#1DA1E4" Stretch="Uniform" Data="{Binding FileType, Converter={StaticResource ResourceKey={x:Static converter:ConvertersTypes.FileTypeConverter }}}"/>
<!--Textbox LostFocus 结束重命名-->
<TextBox Name="displayNodeName_txt" Grid.Column="1" Style="{DynamicResource DirectoryTextBoxStyle}" HorizontalAlignment="Left" FontSize="12" VerticalAlignment="Center" IsEnabled="{Binding IsReadOnly, Converter={StaticResource ResourceKey={x:Static converter:ConvertersTypes.IsReadOnlyToEnableConverter } }}" BorderBrush="Transparent" IsReadOnly="{Binding IsReadOnly}" Text="{Binding QALibraryName, Mode=TwoWay}">
<b:Interaction.Triggers>
<b:EventTrigger EventName="LostFocus">
<b:InvokeCommandAction Command="{Binding DataContext.EndEditCommand,RelativeSource={RelativeSource AncestorType=TreeView, Mode=FindAncestor } }" />
</b:EventTrigger>
</b:Interaction.Triggers>
</TextBox>
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate> </TreeView>
TreeView 右建菜单
Xaml代码如下:
<TreeView.ContextMenu> <ContextMenu> <!--实际上,ContextMenu用的是自己的一套datacontext,可视树上根本找不到TreeView。用PlacementTarget.SelectedItem就可以得到这个TreeView的SelectedItem了--> <MenuItem Header="重命名" Command="{Binding ReNameCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ContextMenu}}}" /> <MenuItem Header="删除" Command="{Binding RemoveCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ContextMenu}}}" /> <MenuItem Header="新建文件" Command="{Binding AddFileCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ContextMenu}}}" /> <MenuItem Header="新建文件夹" Command="{Binding AddFolderCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ContextMenu}}}" /> <!--分隔符--> <Separator Margin="0,5,20,5"></Separator> <MenuItem Header="新建文件[根目录]" Command="{Binding AddFileInRootCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ContextMenu}}}" /> <MenuItem Header="新建文件夹[根目录]" Command="{Binding AddFolderInRootCommand}" CommandParameter="{Binding PlacementTarget.SelectedItem, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type ContextMenu}}}" /> </ContextMenu> </TreeView.ContextMenu>
注意:实际上,ContextMenu用的是自己的一套datacontext,可视树上根本找不到TreeView。用PlacementTarget.SelectedItem就可以得到这个TreeView的SelectedItem了
WPF中使用后台代码来控制TreeView的选择项(SelectedItem)以及展开节点操作
首先为TreeView控件制作一个Style:
<Style x:Key="LibraryTreeViewItemStyle" TargetType="{x:Type TreeViewItem}" <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" /> <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> <Setter Property="FontWeight" Value="Normal" /> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="FontWeight" Value="Bold" /> </Trigger> </Style.Triggers> </Style> <TreeView ItemsSource="{Bind Path=YourCollection}" ItemContainerStyle="{StaticRecource LibraryTreeViewItemStyle}" ItemTemplate={StaticResource YourHierarchicalDataTemplate} />
最后,只要在绑定的数据类中提供相应的属性就可以,例如:
private bool m_bIsSelected = false; public bool IsSelected { get { return m_bIsSelected; } set { m_bIsSelected = value; OnPropertyChanged(new PropertyChangedEventArgs("IsSelected")); } }
现在只要操作数据类的这个属性就可以控制树视图的选择项了。
自定义ContextMenu样式
ListView自动生成序号
1)新建一个自定义IndexConverter类,实现接口IValueConverter。
public class IndexConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { ListViewItem item = (ListViewItem)value; ListView listView = ItemsControl.ItemsControlFromItemContainer(item) as ListView; int index = listView.ItemContainerGenerator.IndexFromContainer(item) + 1; return index.ToString(); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
2)在xmal中添加资源
<UserControl.Resources> <local:IndexConverter x:Key="MyIndexConverter"/> </UserControl.Resources>
3)在listview中添加序号绑定。
<ListView x:Name="Inspect_listview" SelectedIndex="{Binding SampleIndex}" Background="#FF1E1E20" ItemsSource="{Binding InspectItemList}" ItemContainerStyle="{StaticResource ListViewItemStyle}" FontSize="16" Foreground="White"> <ListView.View> <GridView> <!- 这句为绑定序号-> <GridViewColumn Header="No" Width="40" DisplayMemberBinding ="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Converter={StaticResource MyIndexConverter}}" /> <GridViewColumn Header="Serial No" Width="60" DisplayMemberBinding ="{Binding SerialNo}" /> <GridViewColumn Header="Begin/End Time" Width="120" DisplayMemberBinding ="{Binding BeginEndTime}" /> <GridViewColumn Header="Machine" Width="80" DisplayMemberBinding ="{Binding MachineResult}" /> <GridViewColumn Header="Defect" Width="100" DisplayMemberBinding ="{Binding Defect}" /> <GridViewColumn Header="Review" Width="90" DisplayMemberBinding ="{Binding }" /> </GridView> </ListView.View> </ListView>
TreeNode 缩进
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <!--子节点缩进--> <ToggleButton Grid.Column="0" Margin="{Binding Converter= {StaticResource ResourceKey={x:Static converter:ConvertersTypes.IndentConverter }},RelativeSource={RelativeSource TemplatedParent}}" IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press" Name="Expander"> <ToggleButton.Style> <Style TargetType="ToggleButton"> <Setter Property="UIElement.Focusable" Value="false" /> <Setter Property="FrameworkElement.Width" Value="16" /> <Setter Property="FrameworkElement.Height" Value="16" /> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate TargetType="ToggleButton"> <Border Padding="5,5,5,5" Background="#00FFFFFF" Width="16" Height="16"> <Path Fill="#00FFFFFF" Stroke="#FF989898" Name="ExpandPath"> <Path.Data> <PathGeometry Figures="M0,0L0,6L6,0z" /> </Path.Data> <Path.RenderTransform> <RotateTransform Angle="135" CenterX="3" CenterY="3" /> </Path.RenderTransform> </Path> </Border> <ControlTemplate.Triggers> <Trigger Property="UIElement.IsMouseOver" Value="True"> <Setter TargetName="ExpandPath" Property="Shape.Stroke" Value="#FF1BBBFA" /> <Setter TargetName="ExpandPath" Property="Shape.Fill" Value="#00FFFFFF" /> </Trigger> <Trigger Property="ToggleButton.IsChecked" Value="True"> <Setter TargetName="ExpandPath" Property="UIElement.RenderTransform"> <Setter.Value> <RotateTransform Angle="180" CenterX="3" CenterY="3" /> </Setter.Value> </Setter> <Setter TargetName="ExpandPath" Property="Shape.Fill" Value="#FF595959" /> <Setter TargetName="ExpandPath" Property="Shape.Stroke" Value="#FF262626" /> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </ToggleButton.Style> </ToggleButton> <ContentPresenter x:Name="PART_Header" Content="{TemplateBinding HeaderedContentControl.Header}" ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}" ContentStringFormat="{TemplateBinding HeaderedItemsControl.HeaderStringFormat}" ContentTemplateSelector="{TemplateBinding HeaderedItemsControl.HeaderTemplateSelector}" ContentSource="Header" Grid.Column="1" HorizontalAlignment="Stretch" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> </Grid>
缩进的转换器
public class IndentConverter : IValueConverter { public int Indent { get; set; } = 15; public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var item = value as TreeViewItem; if (item == null) return new Thickness(0); int level = this.GetLevels(item); return new Thickness(Indent * level, 0, 0, 0); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } // 获取 当前元素的在TreeView中的层级 public int GetLevels(TreeViewItem item) { int level = 0; Type tree = typeof(TreeView); // 判断节点所在的层级 QALibrary? elem = item.DataContext as QALibrary; while (elem != null && elem.GetType() != tree) { level++; elem = elem.QALibraryParent as QALibrary; } return level; } }
编程是个人爱好