有关WPF中DataGrid控件的基础应用总结
基础说明
DataGrid是WPF提供的基础控件,它可以非常轻松的呈现出一张表格,本文章会按照从易到难的顺序依次将DataGrid的使用方法进行解说,除了MSDN上给出的最基本的例子之外,给出了三个比较常见的在真实使用场景下使用的例子,这三个例子已经基本覆盖了我们能够遇到的大部分使用场景了。
基础实例:MSDN上,使用DataGrid绑定一个数据模型
在MSDN上,可以非常轻松的找到有关于对DataGrid控件进行数据绑定的方法,微软提供的绑定方法是这样的:
MSDN源地址点击这里查看
执行步骤:
1、有一个已经定义好的类型:
public class Customer
{public string FirstName { get; set; }public string LastName { get; set; }public Uri Email { get; set; }public bool IsMember { get; set; }
}
2、编写一个XAML的DataGrid控件
在这里,DataGrid关联数据比较灵活,就不一一列举了,下边贴出的代码只使用了其中一种关联方式:
- 可以像例子里一样:先将ItemsSource使用Binding空出path属性,然后使用DataContext指定数据源
- 也可以在前端不指定ItemsSource,然后直接在C#代码中指定ItemsSource:this.CustomerDataGrid.ItemsSource = list;
- 也可以直接在XAML前端直接指定容器
<DataGrid Name="DG1" ItemsSource="{Binding}" AutoGenerateColumns="False" ><DataGrid.Columns><DataGridTextColumn Header="Column1" Binding="{Binding FirstName}"/><DataGridTextColumn Header="Column2" Binding="{Binding LastName}" /><DataGridHyperlinkColumn Header="Column3" Binding="{Binding Email}" /><DataGridCheckBoxColumn Header="Column4" Binding="{Binding IsMember}" /></DataGrid.Columns>
</DataGrid>
3、通过DataContext关联容器和DataGrid控件实例
ObservableCollection<Customer> list = InitCustomerData.InitData(); // 向容器中添加数据
this.CustomerDataGrid.DataContext = list;
最终,通过DataContext将类型实例的容器与DataGrid的实例进行关联,效果如下,就可以在DataGrid当中显示出来(我这个截图稍微有点出入,多了个我做实验的Column5):
但是,这个用法有以下几个缺点:
- DataGrid内部只有DataGrid预设的几种数据类型,没有办法扩展
- 只有显示功能,没有对DataGrid中的数据有任何操作
- DataGrid数据结构在前端被写死,如果存在很多张表的话,就需要对应数量的前端代码
注:以上代码的实例可以在WPF项目下的主界面逻辑处理InitaListToDataGridColumn函数当中实现(源代码地址在文章最后给出)
实际应用
应用实例1:通过数据模板扩展表格单元格的显示内容
在应用实例1当中,解决了基础实例当中的两个问题:
- DataGrid可以填充任意的数据
- DataGrid可以通过模板添加按钮等带有事件的控件来控制单元格
实现的方法是使用“数据模板(DataTemplate)”来填充DataGrid的单元格,这个数据模型可以是多种多样的,比如:
- Image控件显示图片
- 一个TextBlock加button按钮的组合
- 等等~~~
执行步骤:
1、前端使用DataTemplate定义一个单元格要显示的控件内容
在这里,可以看到DataGrid控件当中的前两列,编号和时间戳仍然是基础的使用方法,而第三列的消息内容,则需要有更多的功能,比如一个点击事件,这个点击事件可以查看消息更详细的内容,所以我为这个单元格设计了一个TextBlock控件显示消息的简略内容,以及一个按钮,这个按钮点击后,可以显示这个消息的详细内容
这个数据模板需要使用x:Type关联一个类型作为它的模型,Text显示内容可以是这个类型的其中一个属性,而按钮的点击事件则可以直接调用这个类型的成员函数或回调函数
<!-- 一个数据模板,使用在后边DataGrid的最后一个单元格 -->
<Border.Resources><!-- 这个数据模板使用MessageModel这个类型作为数据模型 --><DataTemplate x:Key="cellEditingTemplate" DataType="{x:Type model:MessageModel}"><Grid><Grid.ColumnDefinitions><ColumnDefinition/><ColumnDefinition Width="15"/></Grid.ColumnDefinitions><!-- 控件类型是一个TextBlock和一个Button控件的组合 --><TextBlock Grid.Column="0" Text="{Binding m_content}"/><Button Grid.Column="1" Content="..." Command="{Binding ShowCommand}"/></Grid></DataTemplate><DataTemplate x:Key="cellDropBox" DataType="{x:Type model:MessageModel}"><ComboBox ItemsSource="{Binding m_content_detail}" SelectedIndex="0" /></DataTemplate>
</Border.Resources><!-- 表格,用来显示MessageVM中所有的消息,使用ItemsSource指定数据源 -->
<DataGrid AutoGenerateColumns="False" x:Name="MsgDataGrid" ItemsSource="{Binding Path=.}" BeginningEdit="MsgDataGrid_BeginningEdit" ><DataGrid.Columns><DataGridTextColumn Header="编号" Binding="{Binding m_No}"/><DataGridTextColumn Header="时间戳" Binding="{Binding m_time}"/><!-- 自定义的数据模板添加到单元格的最后 --><DataGridTemplateColumn Header="消息内容" CellTemplate="{StaticResource ResourceKey = cellEditingTemplate}" CellEditingTemplate="{StaticResource ResourceKey = cellEditingTemplate}"/><DataGridTemplateColumn Header="消息详情" CellTemplate="{StaticResource ResourceKey=cellDropBox}"/></DataGrid.Columns>
</DataGrid>
2、定义一个与DataTemlate相关联的类型
在前端WPF代码中,DataTemplate当中,指定了类型MessageModel为这个模板的模型,并且,模板当中的第一个TextBlock使用了类型中的属性m_content,第二个Button按钮使用了ICommand回调函数,同时,这个MessageModel还是DataGrid另外两列的数据模板
public class MessageModel
{/// <summary>/// 可触发的命令:显示消息内容;/// </summary>private ICommand mShowCommand;public ICommand ShowCommand{get{if (mShowCommand == null){mShowCommand = new RelayCommand(() =>{ExecuteAction();},() => CanExecuteFunc());}return mShowCommand;}}private bool CanExecuteFunc(){return true;}private void ExecuteAction(){Console.WriteLine("Content is" + this.m_content);foreach(var iter in this.m_content_detail){Console.WriteLine("Content Detail is " + iter);}}/// <summary>/// 消息编号;/// </summary>private string No;public string m_No{get { return No; }set { No = value; }}/// <summary>/// 时间戳;/// </summary>private DateTime time;public DateTime m_time{get { return time; }set { time = value; }}/// <summary>/// 消息内容A;/// </summary>private string content;public string m_content{get { return content; }set { content = value; }}private List<string> content_detail;public List<string> m_content_detail{get { return content_detail; }set { m_content_detail = value; }}/// <summary>/// 消息的源IP地址;/// </summary>private string source;public string m_source{get { return source; }set { source = value; }}/// <summary>/// 消息的目的IP地址;/// </summary>private string dest;public string m_dest{get { return dest; }set { dest = value; }}
}public class RelayCommand : ICommand
{private Action mExecuteAction; // 执行命令;private Func<bool> mCanExecuteFunc; // 命令是否可以执行;public RelayCommand(Action executeAction, Func<bool> canExecuteFunc){mExecuteAction = executeAction;mCanExecuteFunc = canExecuteFunc;}public bool CanExecute(object parameter){return mCanExecuteFunc.Invoke();}public void Execute(object parameter){mExecuteAction.Invoke();}public event EventHandler CanExecuteChanged;}
3、定义一个与DataGrid控件直接交互的VM层,用来控制控件的显示内容
这种设计思路是MVVM框架的思路,可以比较好的解耦前端WPF层(View层)与后端数据模型(Model层),在VM层(ViewModel),专门用一个List或者ObservableCollection容器保存想要在前端DataGrid中显示的容器列表
public class MessageVM
{// 存放所有消息内容的地方;private volatile ObservableCollection<MessageModel> m_messagelist;public ObservableCollection<MessageModel> messagelist{get{return m_messagelist;}set{m_messagelist = value;}}public MessageVM(){messagelist = new ObservableCollection<MessageModel>();}
}
4、在主程序当中,将VM层与对应的DataGrid控件进行DataContext关联
这个就是处理逻辑了,没什么好说的,用这个处理逻辑为VM层添加一些实验数据,以便显示在前端的DataGrid当中
public MessageVM m_MessageVM = new MessageVM(); // MessageVM层,用来显示MessageModel的;
this.MsgDataGrid.DataContext = m_MessageVM.messagelist;// 使用一个线程更新DataGrid控件
Task a = new Task(()=>
{int temp = 0;while(true){temp = temp + 1;Dispatcher.Invoke(DispatcherPriority.Normal, (Action)delegate{m_MessageVM.messagelist.Add(new MessageModel(){m_No = temp.ToString(),m_time = DateTime.Now,m_content = "content",m_source = "172.27.0.1",m_dest = "172.27.0.2"});});Thread.Sleep(1233);}
});a.Start();
最终,呈现的效果如下,在点击消息内容单元格内的"…"按钮后,既可以触发查看内容的详细信息
这个方法的主要核心点就在于使用了DataTemplate数据模板,这个数据模板可以很好的帮助你在DataGrid表的单元格内放置任意你想要的形态,但这样做也会带来一些问题:
- 如果只是简单的编辑表格,或者对表格本身进行操作,DataTemplate就无能为力了:
- 比如编辑单元格
- 比如鼠标滑过表格或单元格时,有悬浮窗提示信息
- 等等这样针对表格,某个单元格本身,而不是针对某个单元格中的内容操作
- 这个表格仍然没有解决前端代码被写死,Model和View必须一一对应的问题
应用实例2:使用DataGrid自带的事件对表格进行操作
以上那种形式,是通过自定义单元格来解决大部分对表格操作的需求,但是,如同上例,简单操作单元格的话,用鼠标或者控件本身事件就好了,在每个单元格内都增加一个按钮也比较难看,况且自定义DataTemplate针对整张表格的操作也时无法实现。所以大部分的基本的操作,利用DataGrid本身自带的事件实现就好了
执行步骤:
1、在前端定义一个表格
这里注意一点:将SelectionUnit=“Cell” SelectionMode="Single"这两个属性设定后,就可以对某个单元格进行操作了,默认情况下,点击DataGrid后,默认是选中整行的
<!-- 对DataGrid进行更加丰富的操作,比如单元格编辑事件等等 -->
<Border BorderBrush="Black" BorderThickness="1" Grid.Row="3" Margin="5,5,5,5"><DataGrid x:Name="CustomerDataGrid_AddEvent" ItemsSource="{Binding Path = .}" AutoGenerateColumns="False" SelectionUnit="Cell" SelectionMode="Single"><DataGrid.Columns><DataGridTextColumn Header="Column1" Binding="{Binding column1.name}"/><DataGridHyperlinkColumn Header="Column2" Binding="{Binding column2.name}" /><DataGridCheckBoxColumn Header="Column3" Binding="{Binding column3.name}" /></DataGrid.Columns></DataGrid>
</Border>
2、 为这张表格的单元格定义一个数据类型
为了能够让单元格中具有更多的操作行为,我为这个单元格单独定义了类型,在C#后台填充单元格的时候,直接填充类型即可,然后在WPF前端,就可以访问这个类型对应的某个属性显示在DataGrid控件当中了,就如同第一步{Binding column2.name}这样,实则它访问的就是GridCell类型的name
public class GridCell
{public string name { get; set; }public void EditingCalback(){Console.WriteLine("GridCell Editing Callback:" + name);}
}
3、为这张表格定义一个VM数据层
这个VM数据层就是为了保存这张表所有要呈现的数据,及其操作表格时对应的函数逻辑
public class DataGridWithEvent
{public GridCell column1 { get; set; } // 向单元格填写自定义个类型;public GridCell column2 { get; set; } // 向单元格填写自定义个类型;public GridCell column3 { get; set; } // 向单元格填写自定义个类型;// 当表格控件被编辑时,会调用单元格自身实例对应的函数;public void JudegePropertyCall_CellEditing(string colHeader){switch(colHeader){case "Column1":this.column1.EditingCalback();break;case "Column2":this.column2.EditingCalback();break;case "Column3":this.column3.EditingCalback();break;default:break;}}}
4、最后在逻辑处理代码中填充数据和对应的事件
ObservableCollection<DataGridWithEvent> list = InitDataGridWithEventData.InitData();
this.CustomerDataGrid_AddEvent.DataContext = list;// 以下是表格事件;
this.CustomerDataGrid_AddEvent.BeginningEdit += CustomerDataGrid_AddEvent_BeginningEdit; // 事件一:单元格开始编辑事件;
this.CustomerDataGrid_AddEvent.SelectionChanged += CustomerDataGrid_AddEvent_SelectionChanged; // 事件二:单元格选择出现变化时;
this.CustomerDataGrid_AddEvent.GotFocus += CustomerDataGrid_AddEvent_GotFocus; // 事件三:DataGrid表格点击单元格获取焦点时;// 以下是鼠标事件;
this.CustomerDataGrid_AddEvent.MouseMove += CustomerDataGrid_AddEvent_MouseMove; // 事件四:鼠标移动到某个单元格上时触发(实验函数增加了鼠标拖动效果);
this.CustomerDataGrid_AddEvent.GotMouseCapture += CustomerDataGrid_AddEvent_GotMouseCapture; // 事件五:使用这个事件事件鼠标拖拽更加稳定;this.CustomerDataGrid_AddEvent.MouseLeftButtonDown += CustomerDataGrid_AddEvent_MouseLeftButtonDown; // 事件六:鼠标左键点击事件,这个事件只针对DataGrid整个表格;
this.CustomerDataGrid_AddEvent.MouseEnter += CustomerDataGrid_AddEvent_MouseEnter; // 事件七:鼠标进入整个表格时触发,且只触发一次;// 另一个元素接收鼠标拖拽事件;
this.ReceiveDataLabel.AllowDrop = true;
this.ReceiveDataLabel.Drop += ReceiveDataLabel_Drop;
列出比较常用的事件:
由于MSDN上边,有关于事件列表中的描述都比较晦涩难懂,所以在此列出一些比较常见的
表格操作系列事件(未完待续)
鼠标操作系列事件
- MouseMove
- 当鼠标进入到DataGrid控件真实填充过的单元格后,鼠标每移动到某个元素的时候,都会触发这个事件
- 带有两个入参,几个比较重要的可以获取的参数:
- object sender:DataGrid本身
- MouseEventArgs e:e.OriginalSource is DataGridCell
- 获取表格当中的元素类型:(e.OriginalSource as DataGridCell).DataContext
- GotMouseCapture
- 当鼠标进入到DataGrid控件真实填充过的单元格后,鼠标每点击某个单元格的时候,就会触发这个事件
- 带有两个入参,几个比较重要的可以获取的参数:
- object sender:DataGrid本身
- e.OriginalSource is DataGrid
- 获取表格当中的元素类型:foreach (var iter in (e.OriginalSource as DataGrid).SelectedCells)
以上两个鼠标事件的作用区域见下图:即有真实填充数据的区域
应用场景1:
MouseMove和GotMouseCapture可以用来实现鼠标拖拽事件
使用DragDrop.DoDragDrop就可以实现鼠标拖拽的起点,使用[控件名称].Drop += [funcCallback]实现接收鼠标拖拽的事件。具体可参见GitHub中的源码
- MouseEnter
- 当鼠标进入到整个DataGrid表格的时候,会触发一次,且不会重复触发,直到鼠标离开DataGrid控件后,再次进入之后,才会再次触发
- 带有两个入参,三个可以获取的比较重要的参数:
- sender:DataGrid本身
- e.Source is DataGrid本身
- e.OriginalSource is DataGrid本身
MouseEnter鼠标事件的作用区域为DataGrid整表,见下图:
- MouseLeftButtonDown
- 当鼠标在DataGrid没有填充数据的单元格处,发生了鼠标左键按下的时候触发的事件
- 带有两个参数,两个可以获取的比较重要的参数:
- sender is DataGrid本身
- e.Source is DataGrid本身
MouseLeftButtonDown鼠标事件的作用区域为DataGrid没有填充单元格的区域,见下图:
应用实例3:通过动态类型实现一个动态DataGrid表格(即可以填充任何数据类型)
以上两种实例的做法的核心思想是:每一个DataGrid都对应一个数据模型,这样做的好处就是操作数据模型就相当于操作DataGrid表格了,但是这样做有一个缺陷,如果数据模型增加到一定的数量,比如几千个,这样你就需要在前台声明几千个表格,后台代码也需要有对应几千个数据模型,再进一步,表格的内容如果无法确定是动态加载的,这样的方法就不具备任何可行性了
解决问题的思路:
实现这个的思路其实非常淳朴简单,使用dynamic动态类型,在运行时动态的加载表格的ViewModel层。这样一来,我们就可以在运行时为某一个DataGrid动态的生成与其对应的类型了。具体Dynamic类型在此就不展开说了,可参见MSDN
执行步骤:
1、声明一个动态类型的VM层
这个VM层中保存了所有生成一个类型的数据,包括:
- Properties:属性名称和属性类型的对应关系
- key:类型中属性的名字
- value:类型中属性的类型的实例
- colName_Property:列名和属性名的对应关系
- key:DataGrid表的列名称
- value:Property的属性名称
- AddProperty:动态方法,用于动态给Properties和ColName_Property添加对应关系
public class DyDataDridModel : DynamicObject
{// 用来保存这个动态类型的所有属性;// string为属性的名字;// object为属性的值(同时也包含了类型);Dictionary<string, object> Properties = new Dictionary<string, object>();// 用来保存中文列名与属性的对应关系;Dictionary<string, string> ColName_Property = new Dictionary<string, string>();// 为动态类型动态添加成员;public override bool TrySetMember(SetMemberBinder binder, object value){if (!Properties.Keys.Contains(binder.Name)){Properties.Add(binder.Name, value);}return true;}// 为动态类型动态添加方法;public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result){// 可以通过调用方法的手段添加属性;if (binder.Name == "AddProperty" && binder.CallInfo.ArgumentCount == 3){string name = args[0] as string;if (name == null){//throw new ArgumentException("name"); result = null;return false;}// 向属性列表添加属性及其值;object value = args[1];Properties.Add(name, value);// 添加列名与属性列表的映射关系;string column_name = args[2] as string;ColName_Property.Add(column_name, name);result = value;return true;}if(binder.Name == "JudgePropertyName_StartEditing" && binder.CallInfo.ArgumentCount == 1){string columnname = args[0] as string;if(columnname == null){result = null;return false;}// 在当前列名于属性列表中查找,看是否有匹配项;if(ColName_Property.ContainsKey(columnname)){string key = ColName_Property[columnname];if(Properties.ContainsKey(key)){object property = Properties[key];}}else{}}return base.TryInvokeMember(binder, args, out result);}// 获取属性;public override bool TryGetMember(GetMemberBinder binder, out object result){return Properties.TryGetValue(binder.Name, out result);}}
2、在前端声明一个没有任何列定义的DataGrid表格:
<DataGrid AutoGenerateColumns="False" x:Name="MsgDataGrid_AutoGenCol"/>
3、在控制类中,可以使用DyDataDridModel动态添加一个类及其属性
动态为DyDataDridModel添加多个属性以及对应的值,这些属性就相当于表格中的列,值就相当于每行对应的数据,下边的例子就增加了一行数据(Grid上文中已经列出过了,这里使用同样的Gridcell)
// 支持动态添加内容的类型
dynamic model = new DyDataDridModel();// 向单元格内添加内容,这里是添加了一整行内容;
model.AddProperty("property2", new GridCell() { name = "343" }, "列2");
model.AddProperty("property0", new GridCell() { name = "123" }, "列0");
model.AddProperty("property1", new GridCell() { name = "321" }, "列1");
list.Add(model);// 定义每一列显示的内容以及Binding的对象
for (int i = 0; i <= 2; i++)
{DataGridTextColumn column = new DataGridTextColumn();column.Header = "列" + i;column.Binding = new Binding("property" + i + ".name");this.MsgDataGrid_AutoGenCol.Columns.Add(column);
}
this.MsgDataGrid_AutoGenCol.ItemsSource = list;
把上边的语句代码翻译回方式一或方式二的模式,是这个样子的:
// VM层模型
class VModel
{public GridCell property2;public GridCell property0;public GridCell property1;
}// 数据模型
public class GridCell
{public string name { get; set; }public void EditingCalback(){}
}<!--view层-->
<DataGrid x:Name="CustomerDataGrid" ItemsSource="{Binding Path = .}" AutoGenerateColumns="False" ><DataGrid.Columns><DataGridTextColumn Header="列2" Binding="{Binding property2.name}"/><DataGridTextColumn Header="列0" Binding="{Binding property0.name}" /><DataGridHyperlinkColumn Header="列1" Binding="{Binding property1.name}" /></DataGrid.Columns>
</DataGrid>// 逻辑层:
ObservableCollection<VModel> list = new ObservableCollection<VModel>();
list.add(new GridCell(){ property2.name = "343",property0.name = "123",property1.name = "321"
});
CustomerDataGrid.Datacontext = list;
动态加载表的最终效果如下:
以上,就是目前所有我对WPF的DataGrid控件使用的归纳,总结下来,需要掌握的知识点并不难。如果想要对DataGrid进行灵活应用的话,需要对MVVM架构有一个大致的认识,并且对动态类型有一定的了解,熟悉Python的同学,对动态类型认识起来,肯定就轻松很多了。
另外,以上三种实现方式,都可以在我的一个项目中找到:
https://github.com/visiontrail/CSharpKnowledge
本文有关DataGrid的内容都在WPF项目当中
有关WPF中DataGrid控件的基础应用总结相关推荐
- WPF中DataGrid控件
WPF中DataGrid控件的个别属性使用 //设置不可自动拉伸宽度dataGrid.CanUserResizeColumns = false;//第一列不可见dataGrid.HeadersVisi ...
- WPF 中DataGrid控件显示和设置数据
一.基础知识 1.DataGrid控件:用来显示数据的控件,从对象集合中获取信息并在具有行和单元格的网格中显示信息.每行和单独的对象相对应,并且每列和对象的某个属性相对应. 2.在DataGrid 中 ...
- 详解WPF 4 DataGrid控件的基本功能
分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow 详解WPF 4 D ...
- WPF之DataGrid控件使用
WPF之DataGrid控件使用 一.DataGrid简介 二.DataGrid数据源绑定 三.DataGrid单元格编辑触发事件 四.DataGrid增加删除行 五.DataGrid数据触发器 参考 ...
- WPF 自定义DataGrid控件样式
WPF 自定义DataGrid控件样式 样式一: 样式代码: <!--DataGrid样式--><Style TargetType="DataGrid">& ...
- WPF中一个控件绑定另一个控件的属性
原文:WPF中一个控件绑定另一个控件的属性 如同一个Grid中的一个按钮根据另一个按钮的显示与否作出不同的响应: 绑定的时候通过ElementName来指定控件 <Grid Margin=&qu ...
- wpf中使用控件时,最好给控件取一个好的名字
wpf中使用控件时,最好给控件取一个好的名字, 比如按钮A可以 取名为 btnA 这样使用会是编程清晰.
- VB.net数据库编程中DataGrid控件的使用技巧
VB.net数据库编程中DataGrid控件的使用技巧 如何用同一个DataGrid显示不同的数据表:如何用DataGrid显示主表/明细表的内容:如何用DataGrid分页显示或编辑数据表的记录.这 ...
- WPF中通过控件Margin属性设置控件位置
WPF中通过控件Margin属性设置控件位置 一.Margin属性简介 二.Margin在cs文件中定义 三.Margin设置控件位置 四.参考文档 一.Margin属性简介 在使用WPF进行页面设计 ...
最新文章
- 【杂】LaTeX中一些符号的输入方法
- 一文读懂图像局部特征点检测算法
- python列表不包含哪个内置函数_python 列表的推导器和内置函数
- python实现排序算法_python实现·十大排序算法之插入排序(Insertion Sort)
- Application log save debug - how log data is persisted to database table
- Java中的方法(形参及实参)return返回类型
- Task类的简单介绍
- 解决OpenCV编译时./bin: error while loading shared libraries: libopencv_highgui.so.3.2: cannot open的问题
- 拓端tecdat|R语言使用特征工程泰坦尼克号数据分析应用案例
- ASCII码值转化十六进制,十进制数;十六进制字符值转十进制,ASCII码值;
- 吴恩达深度学习笔记 最全最详细!这一篇足够了!
- ADC SFDR无杂散动态范围
- Zune WIFI无线同步教程
- coherence-based label propagation over time series for accelerated active learning
- 【OpenCV】计算两幅图片视觉差
- 【算法】什么是OJ系统?
- PostMan中文插件支持8.12.2
- 计算机word怎么录制宏,Word 2013中录制宏的方法
- 计算机自动维护有用吗,Win10怎么开启自动维护功能?系统自动维护有什么作用?...
- HNOI 2018 滚粗记