WPF一个完整的TreeView使用实例:(一)自定义控件样式+数据源绑定+动态添加父子节点
程序员文章站
2022-01-05 10:50:25
...
TreeView控件可在树结构中显示分层数据,其中的项可以展开和折叠。它可以包含多种类型的控件,如Button、Lable、Image等控件,可以通过绑定到数据源并使用HieratchicalDataTemplate对象来填充其树。可以修改默认ControlTemplate以使控件具有独特的外观。
这里举一个完整的TreeView实例来进行说明。本节主要实现自定义控件样式+数据源绑定+动态添加父子节点的功能。效果如下:
1、主窗体中加入TreeView控件
<TreeView x:Name="treeView" Background="Transparent" MinHeight="280" Width="200"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Auto"
MouseDoubleClick="TreeView_MouseDoubleClick">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ChildNodes}">
<TextBlock x:Name="showName" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,8,0,0" Height="26"
FontSize="{Binding Path=SetFontSize}" FontWeight="{Binding Path=SetFontWeight}" Text="{Binding Path=NodeName, Mode=TwoWay}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
由于我们的这个TreeView控件父子节点字体样式、背景颜色等不同,所以使用Binding的方式通过后台进行设置。
2、TreeView的Model:TreeViewNode.cs
public class TreeViewNode : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#region 属性字段
private int id;
/// <summary>
/// 节点ID
/// </summary>
public int Id
{
get
{
return id;
}
set
{
id = value;
}
}
private int parentId;
/// <summary>
/// 父节点ID
/// </summary>
public int ParentId
{
get
{
return parentId;
}
set
{
parentId = value;
}
}
private string nodeName;
/// <summary>
/// 节点名称(最多六个字符)
/// </summary>
public string NodeName
{
get
{
return nodeName;
}
set
{
nodeName = value;
if (nodeName.Length > 5)
{
//非添加项考虑字符长度
if (this.isNodeAdd != true && this.isChildNodeAdd != true)
{
nodeName = nodeName.Substring(0, 5);
}
}
OnPropertyChanged("NodeName");
}
}
private bool isChildNode;
/// <summary>
/// 是否是子节点
/// </summary>
public bool IsChildNode
{
get
{
return isChildNode;
}
set
{
isChildNode = value;
}
}
private bool isNodeAdd;
/// <summary>
/// 是否添加节点
/// </summary>
public bool IsNodeAdd
{
get
{
return isNodeAdd;
}
set
{
isNodeAdd = value;
}
}
private bool isChildNodeAdd;
/// <summary>
/// 是否是添加子节点
/// </summary>
public bool IsChildNodeAdd
{
get
{
return isChildNodeAdd;
}
set
{
isChildNodeAdd = value;
}
}
private ObservableCollection<TreeViewNode> childNodes;
/// <summary>
/// 子节点数据
/// </summary>
public ObservableCollection<TreeViewNode> ChildNodes
{
get
{
if (childNodes == null)
{
childNodes = new ObservableCollection<TreeViewNode>();
childNodes.CollectionChanged += new NotifyCollectionChangedEventHandler(OnMoreStuffChanged);
}
return childNodes;
}
set
{
childNodes = value;
}
}
private void OnMoreStuffChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
TreeViewNode stuff = (TreeViewNode)e.NewItems[0];
stuff.ParentId = this.Id;
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
TreeViewNode stuff = (TreeViewNode)e.OldItems[0];
if (stuff.ParentId == this.Id)
{
stuff.ParentId = 0;
}
}
}
#region 界面展示相关属性
//根据节点类型设置Margin
public string Margining
{
get
{
double padLeft;
if (this.isChildNode == true || this.isNodeAdd == true)
{
padLeft = 36;
}
else
{
padLeft = 10;
}
return string.Format("{0},0,0,0", padLeft);
}
}
//添加节点按钮是否展示
public Visibility ShowAddButton
{
get
{
if (this.isChildNode == false && this.isNodeAdd == true && this.isChildNodeAdd == false)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
}
//根据节点设置分隔线
public string ShowBorderThickness
{
get
{
if (this.isChildNode == false && this.isChildNodeAdd == false)
return string.Format("0,1,0,0");
else
return string.Format("0,0,0,0");
}
}
//根据子父节点设置字体大小
public int SetFontSize
{
get
{
if (this.isChildNode == true)
return 12;
else
return 14;
}
}
//根据子父节点设置字体宽度
public string SetFontWeight
{
get
{
if (this.isChildNode == true)
return "Normal";
else
return "Bold";
}
}
//根据子父节点设置字体颜色
public string SetForeground
{
get
{
if (this.isChildNode == true || this.isNodeAdd == true)
return "#999999";
else
return "#000000";
}
}
//根据子父节点设置背景颜色
public string SetBackground
{
get
{
if (this.isChildNode == true || this.isNodeAdd == true)
return "#ffffff";
else
return "#87CEEB";
}
}
//节点是否展开
public bool SetIsExpanded
{
get
{
if (this.isChildNode != true || this.isNodeAdd != true)
return false;
else
return true;
}
}
#endregion
#endregion
#region 构造函数
public TreeViewNode()
{
}
public TreeViewNode(int _id, int _parentId, bool _isChildNode, bool _isChildNodeAdd, bool _isNodeAdd, string _nodeName)
{
this.id = _id;
this.parentId = _parentId;
this.isChildNode = _isChildNode;
this.isChildNodeAdd = _isChildNodeAdd;
this.isNodeAdd = _isNodeAdd;
this.nodeName = _nodeName;
}
#endregion
}
3、TreeView样式
<!--TreeView样式-->
<Style TargetType="TreeViewItem">
<Setter Property="Cursor" Value ="Hand"></Setter>
<Setter Property="AllowDrop" Value="True"></Setter>
<Setter Property="BorderBrush" Value="#d3d3d3"></Setter>
<Setter Property="BorderThickness" Value="1"></Setter>
<Setter Property="Foreground" Value="{Binding Path=SetForeground}"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="Parent" TargetType="TreeViewItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="0" x:Name="headBorder" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{Binding Path=ShowBorderThickness}"
Tag="{Binding ElementName=PART_Header}" PreviewMouseDown="TreeView_MouseDown">
<ContentPresenter Name="PART_Header" ContentSource="Header" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{Binding Path=Margining}"/>
</Border>
<ToggleButton x:Name="toggleBtn" Grid.Row="0" Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Right" Tag="{Binding ElementName=PART_Header}" Style="{DynamicResource ToggleButtonStyle}" IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press"/>
<Image x:Name="addImg" Grid.Row="0" Width="20" Height="20" Margin="10,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Fill"
Source="Resourse/add_normal.png" Visibility="{Binding Path=ShowAddButton}"></Image>
<ItemsPresenter x:Name="ChildNode" Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="toggleBtn" Property="Visibility" Value="Hidden"/>
</Trigger>
<Trigger Property="HasItems" Value="true">
<Setter TargetName="toggleBtn" Property="Visibility" Value="Visible"/>
</Trigger>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="ChildNode" Property="Visibility" Value="Collapsed"/>
</Trigger>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="ChildNode" Property="Visibility" Value="Visible"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"/>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<Setter TargetName="headBorder" Property="Background" Value="#0000FF"/>
<Setter Property="Foreground" Value="#ffffff"/>
<Setter TargetName="addImg" Property="Source" Value="Resourse/add_press.png"/>
<Setter Property="IsExpanded" Value="{Binding Path=SetIsExpanded}"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="True"/>
<Condition Property="IsMouseOver" Value="False"/>
</MultiTrigger.Conditions>
<Setter TargetName="headBorder" Property="Background" Value="#0000FF" />
<Setter Property="Foreground" Value="#ffffff"/>
<Setter TargetName="addImg" Property="Source" Value="Resourse/add_press.png"/>
<Setter Property="IsExpanded" Value="{Binding Path=SetIsExpanded}"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="False"/>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<Setter TargetName="headBorder" Property="Background" Value="#d3d3d3"></Setter>
<Setter Property="Foreground" Value="{Binding Path=SetForeground}"/>
<Setter TargetName="addImg" Property="Source" Value="Resourse/add_normal.png"/>
<Setter Property="IsExpanded" Value="False"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="False"/>
<Condition Property="IsMouseOver" Value="False"/>
</MultiTrigger.Conditions>
<Setter TargetName="headBorder" Property="Background" Value="{Binding Path=SetBackground}" />
<Setter Property="Foreground" Value="{Binding Path=SetForeground}"/>
<Setter TargetName="addImg" Property="Source" Value="Resourse/add_normal.png"/>
<Setter Property="IsExpanded" Value="False"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--展开折叠按钮样式-->
<Style x:Key="ToggleButtonStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid SnapsToDevicePixels="True">
<Image x:Name="unfold" Source="Resourse/unfold.png" HorizontalAlignment="Left" Stretch="None"></Image>
<Image x:Name="fold" Source="Resourse/fold.png" HorizontalAlignment="Left" Stretch="None"></Image>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Visibility" TargetName="unfold" Value="Visible"/>
<Setter Property="Visibility" TargetName="fold" Value="Collapsed"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Visibility" TargetName="unfold" Value="Collapsed"/>
<Setter Property="Visibility" TargetName="fold" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
4、交互逻辑
private ObservableCollection<TreeViewNode> TreeViewNodeList = null;//数据源
private TreeViewNode TopNode = null;//顶层节点
private int TopNodeId = -1;//顶层节点编号
private int TopNodeParentId = -999;//顶层节点父节点编号
private int MaxNodeIndex = 999;//最大节点编号
public MainWindow()
{
InitializeComponent();
InitTreeView();
}
private void InitTreeView()
{
TreeViewNodeList = new ObservableCollection<TreeViewNode>();
this.treeView.ItemsSource = TreeViewNodeList;
//初始化顶层节点数据
TopNode = new TreeViewNode();
TopNode.Id = TopNodeId;
TopNode.ParentId = TopNodeParentId;
//加入添加节点项
TreeViewNode emptyNode = new TreeViewNode(MaxNodeIndex, TopNodeId, false, false, true, "双击添加新节点");
emptyNode.ChildNodes = new ObservableCollection<TreeViewNode>();
TreeViewNodeList.Add(emptyNode);
TopNode.ChildNodes.Add(emptyNode);
}
/// <summary>
/// 设置折叠状态
/// </summary>
private void TreeView_MouseDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem SelectedfItem = new TreeViewItem();
if (sender.GetType() == typeof(ToggleButton))
{
ToggleButton btn = (ToggleButton)sender;
System.Windows.Controls.ContentPresenter CP = (System.Windows.Controls.ContentPresenter)btn.Tag;
SelectedfItem = (TreeViewItem)CP.TemplatedParent;
}
else if (sender.GetType() == typeof(Border))
{
Border btn = (Border)sender;
System.Windows.Controls.ContentPresenter CP = (System.Windows.Controls.ContentPresenter)btn.Tag;
SelectedfItem = (TreeViewItem)CP.TemplatedParent;
}
else if (e.Source.GetType() == typeof(TreeViewItem))
{
SelectedfItem = (TreeViewItem)sender;
}
if (SelectedfItem == null || ((TreeViewNode)SelectedfItem.DataContext).IsChildNode || ((TreeViewNode)SelectedfItem.DataContext).IsNodeAdd)
{
SelectedfItem.IsExpanded = SelectedfItem.IsExpanded == true ? false : true;
}
}
/// <summary>
/// 双击添加节点
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TreeView_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
TreeViewNode selectNode = (TreeViewNode)this.treeView.SelectedItem;
if (selectNode == null)
return;
if (selectNode.IsNodeAdd)//双击添加节点
{
AddNode();
}
if (selectNode.IsChildNodeAdd)//双击添加子节点
{
AddAction();
}
}
/// <summary>
/// 添加节点
/// </summary>
private void AddNode()
{
TreeViewNode selectedItem = (TreeViewNode)this.treeView.SelectedItem;
int index = GetNodeIndex(TopNode.ChildNodes, selectedItem);
//添加新节点
int nodeIndex = 1;
string newNodeName = "节点" + nodeIndex;
while (!CheckNodeNameAdd(TopNode, newNodeName))
{
nodeIndex++;
newNodeName = "节点" + nodeIndex;
}
TreeViewNode newNode = new TreeViewNode(TreeViewNodeList.Count -1, -1, false, false, false, newNodeName);
//添加子节点
TreeViewNode childNode = new TreeViewNode(0, newNode.Id, true, false, false, "子节点1");
newNode.ChildNodes.Add(childNode);
//添加子节点Bottom项
TreeViewNode actionNodeEmpty = new TreeViewNode(MaxNodeIndex, newNode.Id, true, true, false, "双击添加子节点");
newNode.ChildNodes.Add(actionNodeEmpty);
TopNode.ChildNodes.Insert(index, newNode);
TreeViewNodeList.Insert(index, newNode);
}
/// <summary>
/// 添加子节点
/// </summary>
private void AddAction()
{
TreeViewNode selectedItem = (TreeViewNode)this.treeView.SelectedItem;
TreeViewNode selectParentNode = TopNode.ChildNodes[selectedItem.ParentId];
int index = GetNodeIndex(selectParentNode.ChildNodes, selectedItem);
//添加子节点
int nodeIndex = 1;
string newNodeName = "子节点" + nodeIndex;
while (!CheckNodeNameAdd(selectParentNode, newNodeName))
{
nodeIndex++;
newNodeName = "子节点" + nodeIndex;
}
TreeViewNode childNode = new TreeViewNode(selectParentNode.ChildNodes.Count - 1, selectedItem.ParentId, true, false, false, newNodeName);
selectParentNode.ChildNodes.Insert(index, childNode);
}
/// <summary>
/// 获取相应节点的编号
/// </summary>
private int GetNodeIndex(ObservableCollection<TreeViewNode> nodeList, TreeViewNode targetNode)
{
if (nodeList == null || nodeList.Count <= 0)
return 0;
for (int i = 0; i < nodeList.Count; i++)
{
if (nodeList[i].Equals(targetNode))
return i;
}
return 0;
}
/// <summary>
/// 节点重名检查
/// </summary>
private bool CheckNodeNameAdd(TreeViewNode node, string name)
{
for (int i = 0; i < node.ChildNodes.Count; i++)
{
if (node.ChildNodes[i].NodeName == name)
{
return false;
}
}
return true;
}
上一篇: WPF 自定义控件中依赖属性的简单使用
下一篇: Java8 Optional详解