浅谈WPF之DataGrid过滤,分组,排序

使用过Excel的用户都知道,Excel可以方便的对数据进行分组,过滤,排序等操作,而在WPF中,默认提供的DataGrid只有很简单的功能,那么如何才能让我们开发的DataGrid,也像Excel一样具备丰富的客户端操作呢?今天就以一个简单的小例子,简述如何在WPF中实现DataGrid的过滤,分组,排序等功能。仅供学习分享使用,如有不足之处,还请指正。

涉及知识点

在本示例中,涉及知识点如下所示:

  1. CollectionView, CollectionView 类为实现 IEnumerable 接口的数据源提供分组和排序功能。
  2. CollectionViewSource,CollectionViewSource 类允许你从 XAML 设置 CollectionView 的属性。

注意:此两个类,是我们实现客户端过滤,分组,排序的关键。

普通绑定

1. 构建数据源

在WPF中,DataGrid的ItemSource属性用于绑定数据源,而数据源必须是实现IEnumerable接口的的列表类型,在本示例中,采用具有通知属性的列表类型ObservableCollection。当列表中元素数量发生变化时,可以实时的通知DataGrid进行刷新。

1.1 创建实体

在本示例中,为了测试,创建Student实体模型,如下所示:

public class Student
{public string No { get; set; }public string Name { get; set; }public int Age { get; set; }public bool Sex { get; set; }public string Class { get; set; }
}

 

1.2 初始化数据源列表

在本示例采用MVVM模式开发,在ViewModel中创建ObservableCollection类型的Students列表,如下所示:

using CommunityToolkit.Mvvm.ComponentModel;
using DemoDataGrid2.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace DemoDataGrid2.ViewModels
{public class TestWindowViewModel:ObservableObject{private ObservableCollection<Student> students;public ObservableCollection<Student> Students{get { return students; }set { SetProperty(ref students, value); }}public TestWindowViewModel(){var parentName = new string[5] { "张", "王", "李", "赵", "刘" };this.Students = new ObservableCollection<Student>();for (int i = 0; i < 100; i++){Student student = new Student();student.No = i.ToString().PadLeft(3, '0');student.Name = parentName[(i % 4)] + i.ToString().PadLeft(2, 'A');student.Age = 20 + (i % 5);student.Sex = i % 2 == 0 ? true : false;student.Class = $"{(i % 3)}班";this.Students.Add(student);}}}
}

 

注意:构造函数中的方法,用于创建Students列表,包含100名学生,分别对应不同的编号,姓名,年龄,性别,班级等信息。

2. 页面绑定

在ViewModel中创建数据源后,可以在Xaml中进行绑定【语法:ItemsSource="{Binding Students}"】,如下所示:

<Window x:Class="DemoDataGrid2.TestWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:DemoDataGrid2"mc:Ignorable="d"Title="DataGrid示例" Height="450" Width="800"><Grid><Grid.RowDefinitions><RowDefinition Height="Auto"></RowDefinition><RowDefinition></RowDefinition></Grid.RowDefinitions><DockPanel Grid.Row="0"></DockPanel><DataGrid Grid.Row="1" ItemsSource="{Binding Students}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" ><DataGrid.Columns><DataGridTextColumn Header="学号" Binding="{Binding No}" Width="*"></DataGridTextColumn><DataGridTextColumn Header="姓名" Binding="{Binding Name}"  Width="*"></DataGridTextColumn><DataGridTextColumn Header="年龄" Binding="{Binding Age}"  Width="*"></DataGridTextColumn><DataGridTemplateColumn Header="性别"  Width="*"><DataGridTemplateColumn.CellTemplate><DataTemplate><TextBlock x:Name="sex"><TextBlock.Style><Style TargetType="TextBlock"><Style.Triggers><DataTrigger Binding="{Binding Sex}" Value="True"><Setter Property="Text" Value="男"></Setter></DataTrigger><DataTrigger Binding="{Binding Sex}" Value="False"><Setter Property="Text" Value="女"></Setter></DataTrigger></Style.Triggers></Style></TextBlock.Style></TextBlock></DataTemplate></DataGridTemplateColumn.CellTemplate></DataGridTemplateColumn><DataGridTextColumn Header="班级" Binding="{Binding Class}"  Width="*"></DataGridTextColumn></DataGrid.Columns></DataGrid></Grid>
</Window>

 

以下两点需要注意:

  1. 在本示例中,性别为bool类型,要转换成汉字,用到了DataTrigger。
  2. DataGrid的列可以自动生成,也可以手动创建,可以通过AutoGenerateColumns="False"来设置。

3. 普通绑定示例

普通绑定示例截图,如下所示:

DataGrid过滤

在DataGrid中,实现客户端过滤,且不需要重新初始化数据源,则需要用到CollectionViewSource。

1. 定义资源及绑定

将CollectionViewSource定义成一种资源,并将资源的Source属性绑定到数据源,再将DataGrid中的ItemSource绑定到此资源,然后就可以在过滤时对资源进行过滤。

定义资源如下所示:

<Window.Resources><CollectionViewSource x:Key="cvStudents" Source="{Binding Students}"></CollectionViewSource>
</Window.Resources>

 

DataGrid绑定资源【语法:ItemsSource="{Binding Source={StaticResource cvStudents}}"】,如下所示:

<DataGrid x:Name="dgStudents" Grid.Row="1" ItemsSource="{Binding Source={StaticResource cvStudents}}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" ><DataGrid.Columns><DataGridTextColumn Header="学号" Binding="{Binding No}" Width="*"></DataGridTextColumn><DataGridTextColumn Header="姓名" Binding="{Binding Name}"  Width="*"></DataGridTextColumn><DataGridTextColumn Header="年龄" Binding="{Binding Age}"  Width="*"></DataGridTextColumn><DataGridTemplateColumn Header="性别"  Width="*"><DataGridTemplateColumn.CellTemplate><DataTemplate><TextBlock x:Name="sex"><TextBlock.Style><Style TargetType="TextBlock"><Style.Triggers><DataTrigger Binding="{Binding Sex}" Value="True"><Setter Property="Text" Value="男"></Setter></DataTrigger><DataTrigger Binding="{Binding Sex}" Value="False"><Setter Property="Text" Value="女"></Setter></DataTrigger></Style.Triggers></Style></TextBlock.Style></TextBlock></DataTemplate></DataGridTemplateColumn.CellTemplate></DataGridTemplateColumn><DataGridTextColumn Header="班级" Binding="{Binding Class}"  Width="*"></DataGridTextColumn></DataGrid.Columns>
</DataGrid>

 

2. 过滤条件

在本示例中,以性别为过滤条件,当点击过滤条件时,触发过滤命令,如下所示:

<DockPanel Grid.Row="0" Margin="5"><TextBlock Text="筛选条件:"></TextBlock><TextBlock Text="性别:"></TextBlock><CheckBox Content="男" IsChecked="{Binding FilterM.IsMaleChecked}" Command="{Binding FiterSexCheckedCommand}"></CheckBox><CheckBox Content="女" IsChecked="{Binding FilterM.IsFemaleChecked}" Command="{Binding FiterSexCheckedCommand}"></CheckBox>
</DockPanel>

 

当用户点击时,触发Command绑定的命令,如下所示:

private ICommand fiterSexCheckedCommand;public ICommand FiterSexCheckedCommand
{get{if (fiterSexCheckedCommand == null){fiterSexCheckedCommand = new RelayCommand<object>(FilterSexChecked);}return fiterSexCheckedCommand;}
}private void FilterSexChecked(object obj)
{if (this.dataGrid != null){ICollectionView cvs = CollectionViewSource.GetDefaultView(this.dataGrid.ItemsSource);if (cvs != null && cvs.CanFilter){cvs.Filter = (object obj) =>{bool flag = true;bool flag1 = true;bool flag2 = true;var student = obj as Student;if (!FilterM.IsMaleChecked){flag1 = student.Sex != true;}if (!FilterM.IsFemaleChecked){flag2 = student.Sex != false;}flag = flag1 && flag2;return flag;};}}
}

 

注意:通过CollectionViewSource.GetDefaultView(this.dataGrid.ItemsSource)方法获取具有过滤功能的CollectionView类对象,然后再对Filter进行委托即可。

其中FilterM是在ViewModel中声明的FilterConditionM类型的属性。

private FilterConditionM filterM;public FilterConditionM FilterM
{get { return filterM; }set { SetProperty(ref filterM, value); }
}

 

 FilterConditionM是封装的过滤条件模型类, 如下所示:

namespace DemoDataGrid2.Models
{public class FilterConditionM:ObservableObject{private bool isMaleChecked;public bool IsMaleChecked{get { return isMaleChecked; }set { SetProperty(ref isMaleChecked , value); }}private bool isFemaleChecked;public bool IsFemaleChecked{get { return isFemaleChecked; }set { SetProperty(ref isFemaleChecked, value); }}}
}

 

3. 过滤功能示例

具备过滤功能的示例截图,如下所示:

DataGrid分组

在WPF中,实现DataGrid的分组,也是通过CollectionViewSource来实现。

1. 设置分组列

有两种方式可以设置分组

1.1 XAML中设置

在XAML中,通过设置CollectionViewSource的GroupDescriptions属性,来设置具体分组的列属性,如下所示:

<CollectionViewSource x:Key="cvStudents" Source="{Binding Students}"><CollectionViewSource.GroupDescriptions><PropertyGroupDescription PropertyName="Class"/><PropertyGroupDescription PropertyName="Sex"/></CollectionViewSource.GroupDescriptions>
</CollectionViewSource>

 

1.2 后台代码设置

在ViewModel中设置CollectionView的GroupDescriptions属性,如下所示:

ICollectionView cvTasks = CollectionViewSource.GetDefaultView(this.dataGrid.ItemsSource);
if (cvTasks != null && cvTasks.CanGroup == true)
{cvTasks.GroupDescriptions.Clear();cvTasks.GroupDescriptions.Add(new PropertyGroupDescription("Class"));cvTasks.GroupDescriptions.Add(new PropertyGroupDescription("Sex"));
}

 

2. 设置分组样式

在WPF中,通过设置DataGrid的GroupStyle属性来改变分组样式,如下所示:

<DataGrid x:Name="dgStudents" Grid.Row="1" ItemsSource="{Binding Source={StaticResource cvStudents}}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" ><DataGrid.GroupStyle><!-- 第一层分组 --><GroupStyle><GroupStyle.ContainerStyle><Style TargetType="{x:Type GroupItem}"><Setter Property="Margin" Value="0,0,0,5"/><Setter Property="Template"><Setter.Value><ControlTemplate TargetType="{x:Type GroupItem}"><Expander IsExpanded="True" Background="LightGray" BorderBrush="#FF002255" Foreground="DarkBlue" BorderThickness="1"><Expander.Header><DockPanel><TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5"/><TextBlock FontWeight="Bold" Text=" 班 , " VerticalAlignment="Center"></TextBlock><TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}" VerticalAlignment="Center"/><TextBlock FontWeight="Bold" Text=" 名学生"  VerticalAlignment="Center"></TextBlock></DockPanel></Expander.Header><Expander.Content><ItemsPresenter /></Expander.Content></Expander></ControlTemplate></Setter.Value></Setter></Style></GroupStyle.ContainerStyle></GroupStyle><!-- 第二层及之后的分组 --><GroupStyle><GroupStyle.HeaderTemplate><DataTemplate><DockPanel Background="LightGoldenrodYellow"><TextBlock Foreground="Blue" Margin="30,0,0,0" Width="30"><TextBlock.Style><Style TargetType="TextBlock"><Style.Triggers><DataTrigger Binding="{Binding Path=Name}" Value="True"><Setter Property="Text" Value="男"></Setter></DataTrigger><DataTrigger Binding="{Binding Path=Name}" Value="False"><Setter Property="Text" Value="女"></Setter></DataTrigger></Style.Triggers></Style></TextBlock.Style></TextBlock><TextBlock Text="{Binding Path=ItemCount}" Foreground="Blue"/></DockPanel></DataTemplate></GroupStyle.HeaderTemplate></GroupStyle></DataGrid.GroupStyle><DataGrid.Columns><DataGridTextColumn Header="学号" Binding="{Binding No}" Width="*"></DataGridTextColumn><DataGridTextColumn Header="姓名" Binding="{Binding Name}"  Width="*"></DataGridTextColumn><DataGridTextColumn Header="年龄" Binding="{Binding Age}"  Width="*"></DataGridTextColumn><DataGridTemplateColumn Header="性别"  Width="*"><DataGridTemplateColumn.CellTemplate><DataTemplate><TextBlock x:Name="sex"><TextBlock.Style><Style TargetType="TextBlock"><Style.Triggers><DataTrigger Binding="{Binding Sex}" Value="True"><Setter Property="Text" Value="男"></Setter></DataTrigger><DataTrigger Binding="{Binding Sex}" Value="False"><Setter Property="Text" Value="女"></Setter></DataTrigger></Style.Triggers></Style></TextBlock.Style></TextBlock></DataTemplate></DataGridTemplateColumn.CellTemplate></DataGridTemplateColumn><DataGridTextColumn Header="班级" Binding="{Binding Class}"  Width="*"></DataGridTextColumn></DataGrid.Columns>
</DataGrid>

 

DataGrid排序

在WPF中,实现DataGrid的排序,也是通过CollectionViewSource来实现。

1. 设置排序列

有两种方式可以设置DataGrid排序列,如下所示:

1.1 XAML中设置

通过设置CollectionViewSource的SortDescriptions属性,设置排序列和排序方向。如下所示:

<CollectionViewSource x:Key="cvStudents" Source="{Binding Students}"><CollectionViewSource.SortDescriptions><scm:SortDescription PropertyName="No" Direction="Ascending"/><scm:SortDescription PropertyName="Age" Direction="Descending"/></CollectionViewSource.SortDescriptions>
</CollectionViewSource>

 

 其中scm是新定义的命名空间【xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"】

1.2 后台代码设置

在ViewModel中通过后台代码设置,同样也需要引入对应的命名空间,如下所示:

ICollectionView cvTasks = CollectionViewSource.GetDefaultView(this.dataGrid.ItemsSource);
if (cvTasks != null && cvTasks.CanSort == true)
{cvTasks.SortDescriptions.Clear();cvTasks.SortDescriptions.Add(new SortDescription("No", ListSortDirection.Ascending));cvTasks.SortDescriptions.Add(new SortDescription("Age", ListSortDirection.Ascending));
}

 

DataGrid整体示例

具备过滤,分组,排序的示例截图,如下所示:

参考文献

1. 官方文档:https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/controls/how-to-group-sort-and-filter-data-in-the-datagrid-control?view=netframeworkdesktop-4.8

以上就是【浅谈WPF之DataGrid过滤,分组,排序】的全部内容,希望能够一起学习,共同进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/563543.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机组成原理第六章难,计算机组成原理第六章答案

第6章 计算机的运算方法2. 已知X0.a1a2a3a4a5a6(ai为0或1)&#xff0c;讨论下列几种情况时ai各取何值。 (1)X (2)X (3)121 811X4161&#xff0c;只要a11&#xff0c;a2~a6不全为0即可。 2解&#xff1a; (1)若要X(2)若要X (3)若要1&#xff0c;只要a1~a3不全为0即可。 811 X &a…

职高学计算机走单招是,职高学生不用愁了,还有机会上本科,走“单招”或是最佳途径!...

原标题&#xff1a;职高学生不用愁了&#xff0c;还有机会上本科&#xff0c;走“单招”或是最佳途径&#xff01;职高学生不用愁了&#xff0c;也有机会上“本科”&#xff0c;走“单招”或是最佳途径随着教育制度的不断改革&#xff0c;中考也成为了人生的分水岭之一&#xf…

css3 filter url,CSS3 filter(滤镜) 属性

CSS3 filter(滤镜) 属性实例修改所有图片的颜色为黑白 (100% 灰度):img {-webkit-filter: grayscale(100%); /* Chrome, Safari, Opera */filter: grayscale(100%);}定义和使用filter 属性定义了元素(通常是)的可视效果(例如&#xff1a;模糊与饱和度)。默认值:none继承:no动画…

云联惠身份认证得多久_【转发扩散】你完成认证了吗?老来网APP也可以刷脸认证哦!...

为缓解民生山西认证压力&#xff0c;提高认证效率与认证率&#xff0c;山西省社保局推出“老来网”认证手机app&#xff0c;请未认证人员以及民生山西无法认证人员&#xff0c;采用此认证方式。附&#xff1a;民生山西APP认证流程老来网APP具体操作流程如下&#xff1a;一、 老…

区域显示触发_Nature Communications:地幔数据显示可氧化的火山气体的减少可能触发了大氧化事件...

Nature Communications&#xff1a;地幔数据显示可氧化的火山气体的减少可能触发了大氧化事件包括人类在内的需氧生物之所以能在地球上繁盛兴旺&#xff0c;主要得益于大气中大量O2的存在&#xff0c;但在地球整个历史的大部分时间内O2含量水平并不高。地质记录中的硫同位素非质…

华三ap设置无线服务器,H3C无线控制器典型配置案例集(V5)-6W113

本文档介绍AP自动注册配置举例。2 配置前提本文档不严格与具体软、硬件版本对应&#xff0c;如果使用过程中与产品实际情况有差异&#xff0c;请参考相关产品手册&#xff0c;或以设备实际情况为准。本文档假设您已了解自动AP功能。如图1所示&#xff0c;AC作为DHCP服务器为AP…

仪表盘怎么调 铃木uy125摩托车_平时市区骑行,摩托车链条多久保养一次?

链条因为前期成本低&#xff0c;后期维护容易&#xff0c;所以成为了现在的大多数摩托车的传动装置。但它也有自身的弊端&#xff0c;比如需要频繁的保养&#xff0c;如若不然就会有掉链和异响的可能&#xff0c;那么市区骑行的摩托车应该多久保养一次链条呢?我们还是先来看一…

过磅系统更换服务器,无人值守过磅系统改造方案

智能无人值守过磅系统改造方案以前,汽车运输的物料计量工作采用汽车衡仪表显示计量数据&#xff0c;司磅员手工填写单据、报表,这就造成了管理上的许多问题如:安全性差、操作繁琐、作弊、统计错误、数据不能共享等&#xff0c;给企业造成较大损失&#xff0c;不能满足生产经营的…

怎样把文件传到华为云服务器,如何把文件传到云服务器上

如何把文件传到云服务器上 内容精选换一换MongoDB官网提供了针对不同操作系统的客户端安装包&#xff0c;其二进制安装包下载页面链接为&#xff1a;https://www.mongodb.com/download-center#community。本章节以RedHat/CentOS 7.0和MongoDB 3.4.24为例&#xff0c;介绍如何获…

指数函数中x的取值范围_谨记!高考数学中容易出错的几个地方

高考生想要取得好的数学成绩必须要认真复习&#xff0c;在复习的时候大家要掌握一些内容&#xff0c;这样能使大家取得事半功倍的学习效果&#xff0c;下面小更为大家带来2019高考数学易犯的72个低级错误这篇内容&#xff0c;希望高考生能够认真阅读。1.集合中元素的特征认识不…

正在从“vetur”获取代码操作_长时间运行 io.Reader 和 io.Writer 操作测算进度和估算剩余时间...

每当我们在使用类似 io.Copy 和 ioutil.ReadAll 的工具时&#xff0c;比如我们正在从 http.Response 主体读入或者上传一个文件&#xff0c;我们会发现这些方法将一直堵塞&#xff0c;直到整个过程完成&#xff0c;哪怕耗时数十分钟甚至是小时——而且我们没有办法来查看进度&a…

挖掘城市ip_抖in杭州嘉年华,原来城市营销还能这么玩!

作者 | 汤木森来源 | TOP营销(ID&#xff1a;TOP_MKT)在《看不见的城市》中&#xff0c;卡尔维诺曾写下这样的语句&#xff1a;“每到一个新城市&#xff0c;旅行者就会发现一段自己未曾经历的过去。”重庆的穿楼轻轨、西安的摔碗酒、厦门的土耳其冰激凌……这些场景在抖音走红…

搭建微服务_快速搭建 SpringCloud 微服务开发环境的脚手架

本文作者&#xff1a;HelloGitHub-秦人本文适合有 SpringBoot 和 SpringCloud 基础知识的人群&#xff0c;跟着本文可使用和快速搭建 SpringCloud 项目。HelloGitHub 推出的《讲解开源项目》系列&#xff0c;今天给大家带来一款基于 SpringCloud2.1 的微服务开发脚手开源项目—…

getresourceasstream方法_【设计模式】第三篇:一篇搞定工厂模式【简单工厂、工厂方法模式、抽象工厂模式】...

文章系列目录(持续更新中):【设计模式】第一篇&#xff1a;概述、耦合、UML、七大原则&#xff0c;详细分析总结(基于Java)【设计模式】第二篇&#xff1a;单例模式的几种实现And反射对其的破坏一 为什么要用工厂模式之前讲解 Spring 的依赖注入的文章时&#xff0c;我们就已经…

gis根据行政区计算栅格数据计算_亚马逊fba运费根据什么计算?怎么计算?

亚马逊FBA一般有如下费用(仅做参考)&#xff1a;1、亚马逊头程(本地发货到FBA仓库的费用)2、订单处理费(1USD / Order)3、拣货打包费(1USD / Pcs)4、重量处理费(0.46USD&#xff0c;这个以1磅一下的large standard size非媒介产品为例&#xff0c;这个是以重量等级的不同从而产…

华硕z97不识别m2固态_华硕H110T +i3 8100T 组装黑苹果Mac mini安装教程

最近组装了一台黑苹果主机&#xff0c;从硬件&#xff0c;到系统安装&#xff0c;驱动安装费了很大的精力&#xff0c;写这篇文章记录下来&#xff0c;希望对像我一样需求的网友有用。目前家里是网通200M的宽带&#xff0c;我喜欢挂pt下一些电影&#xff0c;怎么来播放这些电影…

vue封装websocket_有关WebSocket必须了解的知识

一、前言最近之前时间正好在学习java知识&#xff0c;所以自个想找个小项目练练手&#xff0c;由于之前的ssm系统已经跑了也有大半年了&#xff0c;虽然稀烂&#xff0c;但是功能还是勉强做到了&#xff0c;所以这次准备重构ssm系统&#xff0c;改名为postCode系统(至于为什么前…

list选取多个元素 python_【幼儿园级】0基础学python一本通(上)——AI未来系列1...

在未来面前&#xff0c;每个人都是学生江海升月明&#xff0c;天涯共此时&#xff0c;关注江时&#xff01;引子本篇为AI未来系列第一篇。为什么要研究学习python&#xff1f;python是未来屠龙的屠龙宝刀&#xff0c;再辅助以我们的高中数学基础(足够用的屠龙术)&#xff0c;小…

用polt3画曲面_用SolidWorks建模一个:防滑板曲面造型

防滑板曲面造型2020年4月点底部提取码&#xff1a;eo9z此图用SolidWorks2019版建模&#xff0c;用KeyShot 9.0 渲染(上面两张图) 。SW原文件在今日文件夹里。建模过程1.在上视基准面上画草图。2.曲面拉伸&#xff0c;反向&#xff1a;5 。3.新建基准面&#xff0c;距离上视基准…

lntellijidea怎么创建文件_DBC文件到底是个啥

本文首发自公众号“汽车技术馆”在之前的一篇文章中给大家分享了一些CAN的基本知识&#xff0c;比如CAN通讯是个啥&#xff0c;CAN通讯的机制以及CAN通讯的帧结构等等&#xff0c;相信读过这篇文章的朋友应该都有了一个初步的认识&#xff0c;如果还没有看过的朋友可以在读本文…