一、分析
这次的需求是:有一所学校,为了方便了解学校里所有班级的情况(这里就看各位想定义哪些情况了),故需要设计出一个在界面上既能查看每个班级中每个学生的信息,又能展示每个班级的总体情况。
我分析的是:结合前面所讲述的知识点,查看每个班级用TreeView,展示总体情况用DataGrid。那就动手看看吧
二、实现
先看项目结构(注意个模块得统一:要么用core,要么用Framework)
接下来逐一讲述
(1)common模块
枚举(Enums):就一个枚举(节点类型)
public enum NodeType
{
[Description("班级")]
Grade,
[Description("学生")]
Student,
}
转换器(Converts):从枚举的值判断后返回是否显示(上代码)
public class EnumToVisibilityConvert : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Enum enumValue)
{
switch (enumValue)
{
case NodeType.Grade:
return Visibility.Collapsed;
case NodeType.Student:
return Visibility.Visible;
default:
return Visibility.Collapsed;
}
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
命令(DelegateCommand),这个就不做概述了,之前用的都是它,不知道的可以看我前面的文章
对象(Models):就一个主体对象,树结构加附属对象——学生结构(看代码,BindableObject不讲述了,跟命令一样的,前面文章有)
public abstract class BaseModel : BindableObject
{
public virtual string Name { get; set; }
public virtual Guid Guid { get; set; }
public virtual NodeType Node { get; set; } = NodeType.Grade;
}
public class TreeModel : BaseModel
{
private ObservableCollection<StudentInfo> student = new ObservableCollection<StudentInfo>();
public ObservableCollection<StudentInfo> Student
{
get => student;
set
{
student = value;
OnPropertyChanged();
}
}
}
public class StudentInfo : BaseModel
{
private string gender;
public string Gender
{
get => gender;
set
{
gender = value;
OnPropertyChanged();
}
}
private int age;
public int Age
{
get => age;
set
{
age = value;
OnPropertyChanged();
}
}
private string address;
public string Address
{
get => address;
set
{
address = value;
OnPropertyChanged();
}
}
private string number;
public string Number
{
get => number;
set
{
number = value;
OnPropertyChanged();
}
}
}
管理者(TreeViewManager):管理整个TreeView的逻辑交互(这次的封装不太好,大部分的逻辑,ViewModel那里就实现了,都没到这管理库来——哈哈哈,应该是我想偷懒,不想提取了)
public class TreeViewManager : TreeViewInterface
{
private static ObservableCollection<TreeModel> treeModels = new ObservableCollection<TreeModel>();
public static ObservableCollection<TreeModel> TreeModels
{
get => treeModels;
set
{
treeModels = value;
OnTreeModelsChanged();
}
}
public static event EventHandler TreeModelsChanged;
public static void OnTreeModelsChanged()
{
TreeModelsChanged?.Invoke(TreeModels, new EventArgs());
}
public void InitTree()
{
TreeModel treeModel = new TreeModel()
{
Guid = Guid.NewGuid(),
Name = "三年级二班",
Node = Enums.NodeType.Grade
};
StudentInfo student = new StudentInfo()
{
Name = "小红",
Guid = treeModel.Guid,
Age = 8,
Gender = "女",
Address = "天河路",
Node = Enums.NodeType.Student,
Number = "111111"
};
treeModel.Student.Add(student);
student = new StudentInfo()
{
Name = "小明",
Guid = treeModel.Guid,
Age = 8,
Gender = "男",
Address = "尧新路",
Node = Enums.NodeType.Student,
Number = "222222"
};
treeModel.Student.Add(student);
student = new StudentInfo()
{
Name = "小黑",
Guid = treeModel.Guid,
Age = 7,
Gender = "男",
Address = "水西路",
Node = Enums.NodeType.Student,
Number = "333333"
};
treeModel.Student.Add(student);
TreeModels.Add(treeModel);
}
}
(2)interface模块
就一个,目录树的接口,对应一些方法(我这就只有一个方法。。。哈哈哈)
public interface TreeViewInterface
{
void InitTree();
}
(2)VM-V模块
VM模块:一个是表格的,一个是目录树的,直接看代码吧
public class DataGridViewModel : BindableObject
{
private TreeModel treeModel;
public TreeModel TreeModel
{
get => treeModel;
set
{
treeModel = value;
OnPropertyChanged();
}
}
public DataGridViewModel(TreeModel model)
{
this.TreeModel = model;
}
}
public class TreeViewModel : BindableObject
{
private readonly TreeViewInterface @interface;
public ObservableCollection<TreeModel> TreeModels => TreeViewManager.TreeModels;
private TreeModel treeModel;
public TreeModel TreeModel
{
get => treeModel;
set
{
treeModel = value;
OnPropertyChanged();
}
}
private StudentInfo student;
public StudentInfo Student
{
get => student;
set
{
student = value;
OnPropertyChanged();
}
}
private BaseModel baseModel;
public BaseModel BaseModel
{
get => baseModel;
set
{
baseModel = value;
OnPropertyChanged();
}
}
public DelegateCommand<TreeView> SelectChangeCommand { get; private set; }
public DelegateCommand<TreeModel> OpenTableCommand { get; private set; }
public TreeViewModel(TreeViewInterface @interface)
{
this.@interface = @interface;
this.SelectChangeCommand = new DelegateCommand<TreeView>(SelectChange);
OpenTableCommand = new DelegateCommand<TreeModel>(OpenTable);
InitTree();
}
#region 命令
public void OpenTable(TreeModel model)
{
var win = new DataGridView(model);
win.Show();
}
#endregion
#region 事件
public void SelectChange(TreeView tree)
{
BaseModel = (BaseModel)tree.SelectedItem;
JudgeNode();
}
#endregion
#region 方法
private void InitTree()
{
this.@interface.InitTree();
BaseModel = TreeModels[0];
}
private void JudgeNode()
{
switch (BaseModel.Node)
{
case TreeViewChangeDataGrid.Common.Enums.NodeType.Grade:
TreeModel = (TreeModel)BaseModel;
break;
case TreeViewChangeDataGrid.Common.Enums.NodeType.Student:
Student = (StudentInfo)BaseModel;
break;
default:
break;
}
}
#endregion
V模块:一个是表格的,一个是目录树的(记得安装之前讲过的behavior的包)
a、树的界面,这次我用了之前没讲过的方法,这次树不是递归的结构,所有要么用我第一篇文章那样绑定(那方法有点老套了),要么就换一种方法(看图)
这就是新的绑定方法,跟之前很像,只是多了一个DataType,用来指定绑定的结构类型的(看代码)
<Window
x:Class="TreeViewChangeDataGrid.Views.TreeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:convert="clr-namespace:TreeViewChangeDataGrid.Common.Converts;assembly=TreeViewChangeDataGrid.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:TreeViewChangeDataGrid.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:model="clr-namespace:TreeViewChangeDataGrid.Common.Models;assembly=TreeViewChangeDataGrid.Common"
Title="MainView"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.Resources>
<convert:EnumToVisibilityConvert x:Key="enumToVisibilityConvert" />
<!-- 右键菜单 -->
<ContextMenu x:Key="Custom">
<MenuItem Header="表格查看">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding DataContext.OpenTableCommand, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
</ContextMenu>
<!-- 右键菜单 -->
<HierarchicalDataTemplate DataType="{x:Type model:TreeModel}" ItemsSource="{Binding Student}">
<Grid>
<TextBlock Text="{Binding Name}" />
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="ContextMenu" Value="{StaticResource Custom}" />
</Style>
</Grid.Style>
</Grid>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type model:StudentInfo}">
<Grid>
<TextBlock Text="{Binding Name}" />
</Grid>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TreeView x:Name="tree" ItemsSource="{Binding TreeModels}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding DataContext.SelectChangeCommand, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}" CommandParameter="{Binding ElementName=tree}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
<StackPanel
Grid.Column="1"
VerticalAlignment="Center"
Visibility="{Binding BaseModel.Node, Converter={StaticResource enumToVisibilityConvert}}">
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Label Margin="0,0,20,0" Content="姓名:" />
<TextBox
Width="100"
VerticalContentAlignment="Center"
Text="{Binding Student.Name}" />
</StackPanel>
<StackPanel
Margin="0,20"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Label Margin="0,0,20,0" Content="年龄:" />
<TextBox
Width="100"
VerticalContentAlignment="Center"
Text="{Binding Student.Age}" />
</StackPanel>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Label Margin="0,0,20,0" Content="性别:" />
<TextBox
Width="100"
VerticalContentAlignment="Center"
Text="{Binding Student.Gender}" />
</StackPanel>
<StackPanel
Margin="0,20"
HorizontalAlignment="Center"
Orientation="Horizontal">
<Label Margin="0,0,20,0" Content="地址:" />
<TextBox
Width="100"
VerticalContentAlignment="Center"
Text="{Binding Student.Address}" />
</StackPanel>
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
<Label Margin="0,0,20,0" Content="电话:" />
<TextBox
Width="100"
VerticalContentAlignment="Center"
Text="{Binding Student.Number}" />
</StackPanel>
</StackPanel>
</Grid>
</Window>
b、表格的界面,从外到里设置,一次是DataGrid、Column、Row、Cell的样式,而对于列数据的绑定,我用的是DataGridTemplateColumn,直接看代码
<Grid>
<ScrollViewer
Margin="10"
HorizontalScrollBarVisibility="Auto"
PreviewMouseWheel="PreviewMouseWheel"
VerticalScrollBarVisibility="Auto">
<DataGrid
HorizontalAlignment="Center"
AutoGenerateColumns="False"
Background="#fff"
BorderBrush="Transparent"
CanUserAddRows="False"
GridLinesVisibility="None"
HorizontalScrollBarVisibility="Hidden"
IsReadOnly="True"
ItemsSource="{Binding DataContext.TreeModel.Student, RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}}">
<!--#region 设置列的个数-->
<DataGrid.Style>
<Style TargetType="DataGrid">
<Setter Property="HeadersVisibility" Value="Column" />
</Style>
</DataGrid.Style>
<!--#endregion-->
<!--#region 数据网格的列标题样式属性-->
<DataGrid.ColumnHeaderStyle>
<!-- 样式类型:DataGridColumnHeader(数据网格列标题) -->
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Height" Value="74" />
<Setter Property="BorderThickness">
<Setter.Value>
<Thickness
Bottom="1"
Left="1"
Right="1"
Top="1" />
</Setter.Value>
</Setter>
<Setter Property="Background" Value="Orange" />
<Setter Property="BorderBrush" Value="Green" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontFamily" Value="微软雅黑" />
<Setter Property="FontWeight" Value="Black" />
<Setter Property="FontSize" Value="14" />
</Style>
</DataGrid.ColumnHeaderStyle>
<!--#endregion-->
<!--#region DataGrid的RowStyle属性-->
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background" Value="LightBlue" />
</Style>
</DataGrid.RowStyle>
<!--#endregion-->
<!--#region DataGrid的CellStyle属性(数据表格的单元格样式)-->
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness">
<Setter.Value>
<Thickness
Bottom="1"
Left="1"
Right="1" />
</Setter.Value>
</Setter>
<Setter Property="BorderBrush" Value="Green" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Opacity="0.9" Color="AntiqueWhite" />
</Setter.Value>
</Setter>
<Setter Property="Foreground" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<!--#endregion-->
<DataGrid.Columns>
<!--#region 姓名-->
<DataGridTemplateColumn Header="姓名">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel
Margin="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--#endregion-->
<!--#region 年龄-->
<DataGridTemplateColumn Header="年龄">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel
Margin="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Text="{Binding Age}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--#endregion-->
<!--#region 性别-->
<DataGridTemplateColumn Header="性别">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel
Margin="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Text="{Binding Gender}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--#endregion-->
<!--#region 地址-->
<DataGridTemplateColumn Header="地址">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel
Margin="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Text="{Binding Address}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--#endregion-->
<!--#region 地址-->
<DataGridTemplateColumn Header="电话">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel
Margin="20"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<TextBlock Text="{Binding Number}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!--#endregion-->
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
</Grid>
三、总结
总体思路是目录树显示个体信息,右键打开表格看总体信息,运行效果看图
四、补充
(1)这次的案例并不算是典型案例,我自己随意想的,只是为了扩展下思路;
(2)目录树我没有添加其他的功能,感兴趣的可以自行看我之前的文章扩展功能;
(3)这次的案例需求简单,所以实现起来简单,如果是项目上,会有动态添加新的列,添加按钮,右键菜单等等;(后面感兴趣的可以跟我交流)
(4)下次我想换个玩法了,从数据库中提取数据来渲染,不想每次的造数据,具体容我想想
(5)这次案例的源码:GitHub - TQtong/TreeViewChangeDataGrid
五、结束
这次的文章就到这了, 那么我继续闯荡江湖了,希望各位指出不足之处,