【Dev】DevExpress应用
一、大纲
最近使用DevExpress做C/S开发碰到了一些问题,在解决问题的同时在这里做一下记录,下面列出涉及到技术点
- Dev框架下GridControl与GridView
- 使用模板列动态替换GridView的指定列
- GridView分组并去掉列名的前缀
- FPT服务器文件预览与下载
- 单元格添加按钮并添加自定义点击事件
- GridView数据导出到Excel
- 向Word模板中写数据
二、Dev框架下的GridControl和GridView
1.GridControl和GridView的关系
Dev框架下GridControl负责操作数据,GridView负责展示数据,GridControl是GridView的容器,一个GridControl可以容纳多个GridView,在GridView中的任何数据操作都不会影响到GridControl中的源数据,即当我们将GridControl中数据重新刷入GridView时,GridView中数据操作会被清除,所以如果我们有要在本地展示的数据则需要在GridControl刷数据进入GridView的时候重新再刷入一次本地数据。
2.GridControl输入数据到GridView的原理
GridControl的数据刷入GridView的操作由Dev框架执行,我们需要做的只是把数据绑定到GridControl.DataSource中即可。
只有当GridView中存在与GridControl数据源对应列时,GridControl才能将对应列的数据刷入GridView的对应列中,这里的对应列指的是GridView中列的FiledName
的值与GridControl数据源的列名相同,且大小写敏感。
在GridView的列属性中有三个极为重要的属性:
Name:列在程序中操作的标识符,类似变量名,对列的操作都由它来引用,如:修改colfilename列的列宽
1
colfilename.Width = 300;
ColumnEdit:用于挂载模板列的属性,可以将列动态的替换为其他类型的控件,例子中是将列挂载了一个多行编辑框,这样就可以在单元格中显示多行内容
FieldName:FieldName属性是列与GridControl数据源对应的标志,如果想要将GridControl数据源中某一列的数据刷入当前列,那么当前列的FieldName的取值必须和数据源中对应列的列名一致,并且FielName也是用来获取表格数据的标识,如:
1
gridView_FileViewer.GetFocusedDataRow()["path"]//取所选行的path列单元格的数据
3.向GridView存在而GridControl中不存在的列刷入数据
有的时候为了展示需要,我们需要在GridView中增加新列刷入自己的数据,而新增列在GridControl的数据源中没有与之对应的列,即在数据源中没有对应的字段(这里的数据源通常情况下指的就是数据库中的表),此时我们就需要在GridControl.Datasource中动态地添加一列来与新增列对应。
为什么要要在GridControl.Datasource中动态地添加一列呢?
这可能是由GridControl和GridView的内部机制影响的,当一列在GridView中存在而GridControl中不存在时,我们是无法向此列写入数据的,即使数据是来自本地而不是数据库,并且编译器会报错:
如果我们要向GridView存在而GridControl中不存在的列刷入数据,那么我们必须在GridControl的DataSource中动态的加列,下面是示例代码:
1 |
|
三、使用模板列动态替换GridView中的指定列
有的时候为了保密需要,在数据库中部分字段会用编码标识,如:人名使用编码标识,张三对应编码001,但是在表格中展示的时候应该显示人名而不是编码,此时我们就需要用到模板列的动态替换。
直接上代码:
1 |
|
这是通过代码添加动态的添加模板列,同时我们也可以在列属性中的ColumnEdit属性中静态的添加模板列。
四、GridView分组并去掉列名的前缀
1.分组
GridView分组只需要在需要分组的列的属性中将GroupIndex属性值由“-1”改为0即可,如果需要二级分组则在需要分组的列的属性中将GroupIndex属性值由“-1”改为1,以此类推需要三级分组则改为2。
2.去掉列名前缀
分完组后如果不做修改我们加载数据之后表格是这样的:
有时我们不需要显示列名前缀,这时我们需要修改GridView的GroupFormat属性修改为{1},GroupFormat属性的默认值是{0}: [#image]{1} {2}
,其中
- {0}显示列标题
- [#image]显示图片
- {1}显示列的内容值
- {2}显示列的摘要
设置好之后,效果是这样的:
四、FTP文件预览与下载
直接上代码,解释都放注释上了:
1 |
|
这里挑几个比较重要的函数讲解
RequestFile
1 |
|
OpenFileInWindows
1 |
|
当关闭文件时清楚临时文件夹的内容
1 |
|
这里我使用的是最简单的直接删除临时文件夹的暴力删除法,这样做会有一个问题就是,在程序删除文件夹的时候,可能预览文件的进程还没有被系统杀死或有其他的进程占用了目录中文件,这都会导致目录删除失败而抛出异常,我的解决方案是在删除目录之前等待500ms,等待系统将预览文件的进程杀死后在删除文件夹,但是如果是其他的进程占用了目录,则需要手动结束进程才能继续删除临时文件夹,我的解决方案是,如果有其他进程占用了目录,则本次本次临时缓冲区先不删除,等下次有机会再删除。所以这里的try-catch不是用来抛出异常的,而是用来推出函数的。
当然比较理想的删除方法是遍历整个目录中文件和子文件夹,依次删除目录下文件和子文件夹,有被其他进程占用的文件暂时不删除。这样就可以只留下被占用的文件,而不是整个目录。
五、单元格添加按钮并添加自定义点击事件
有时我们需要向某一列的单元格添加点击事件,甚至向某一个单元格添加点击事件,这时我们就需要向单元格添加按钮了。
1.向单元格添加简单的点击事件
如果我们只想在某一单元格添加简单的点击事件
1 |
|
这时我们需要用到RowCellClick事件,RowCellClick事件在鼠标点击单元格时触发,然后我们只需要判定鼠标点击是哪一行哪一列,就可以实现某一个单元格的点击事件了。当然我们也可以通过添加按钮来实现。
2.向单元格添加复杂点击事件
如果我们想向单元格添加一系列复杂的点击事件,如在某一单元格内做文件的上传,预览,下载,删除等操作,这时我们就需要借助模板列了,使用模板列是无法只向某一个单元添加点击事件的,因为模板列挂载的是一整列。
我需要用到模板列RepositoryItemButtonEdit
,我可以在列属性里静态挂载,也可以在代码中动态挂载,重要的是我们需要用到RepositoryItemButtonEdit
属性里的Buttons
属性,向Buttons属性里添加元素。
光是添加按钮单元中还是看不到按钮的,我们还需要将每个按钮的Kind属性设置为Glyph,这样我们才能在单元格中看到按钮
添加完按钮就可以向对应按钮添加点击事件了,我们可以发现在列属性里找不到事件,所以我们需要在代码中为按钮添加点击事件,这时我们需要用到repositoryItemButtonEdit.Buttons[0].Click
,其中repositoryItemButtonEdit是模板列的名字,Buttons[0]是第一个按钮的引用,我们只需要向Click事件添加我们想要执行的函数即可。
六、GridView数据导出到Excel
GridView的数据要导出到Excel有很多种方法,可以最直接的就是遍历GridView,然后将数据写入Excel,这算是比较麻烦的做法了,事实上Dev已经提供了一些便捷的方法。
1.GetAllFilteredAndSortedRows()方法
Dev提供了一个GridView.DataController.GetAllFilteredAndSortedRows()
方法,可以用于提取GridView当前数据,在筛选排序等操作之后更改了的数据也可以提取。
但是,GridView类中的DataController对象在VS中被隐藏了,即通过提示器是找不到GDataController对象的的,只能通过手写调用。
GetAllFilteredAndSortedRows()方法返回的是一个IList泛型列表,数据写入Excel一就要自己手动写入,写入方法:
1 |
|
其中有一点需要格外注意,在使用Import函数时需要引用DevExpress.Docs
程序集,因为Import函数在这个程序集里,Dev在DevExpress.Docs程序集里给Worksheet的父类ExternalWorksheet写了扩展,也就是扩展了Import函数等,其中DevExpress.Docs程序集和DevExpress.Spreadsheet程序集的命名空间是一样的,如果没有搞清楚这一点很容易产生玄学问题🥴
2.GridView.Export()方法
最简单的方法就是使用Dev官方提供的导出方法GridView.Export().
Dev已经在GridView中添加了官方的Export方法,支持多种导出格式:
- Xls
- Xlsx
- Html
- Mht
- Text
- Rtf
- Csv
- Image
- Docx
同时提供三种重载:
使用方法也很简单:
1 |
|
- view.Export是dev自带的导出方法,在导出文件后dev会自动调用系统对此文件的默认打开应用来打开文件,当然dev也提供内置的预览方法,这在下一节导出word模板中使用。
七、向Word模板中写入数据
1.载入word模板文件
向word模板中写入数据我这里主要使用的是RichEditControl类,RichEditControl类提供海量的富文本API接口,这里主要讲解使用到的API。
首先打开word文档,RichEditControl类提供RichEditControl.LoadDocument(string path)方法加载文档,RichEditControl类也提供多个LoadDocument函数的重载给予各种文件的加载形式,我这里使用的是直接通过文件路径加载文档。LoadDocument函数支持DOC、DOCX、RTP、HTM、HTML、MHT、XML和EPUB类型的文档,可以自动检测文档类型。
1 |
|
将文档载入内存之后就可以通过RichEditControl.Document.Text属性查看文档内容了,也可以通过RichEditControl.Document.Text属性判断文档是否加载成功。
1 |
|
2.向word模板的指定位置写入数据
向word模板的指定位置写入数据主要使用Word的书签和域,我这里使用的是书签,在word中想要插入数据的地方添加一个书签即可,如:
我想要在生产号、型号和图号后面的单元格写入数据,那么我只需要在这些单元中添加书签即可,添加书签的步骤:
graph LR;
将光标定位到要添加的书签的位置-->插入-->书签-->添加一个书签名-->添加
添加完书签时候在word上是看不到的,但是把光标定位到书签所在的位置处,插入书签时会自定定位到所插入的书签名。
然后即可通过 Document.Replace(DocumentRange range,string text)函数来向书签所在位置插入数据了,其中DocumentRange类型的参数需要通过Document.Bookmarks[string bookmarks].Range来将字符串类型的书签标志转换为DocumentRange类型的可用书签标志。
如:我要在生产号、型号和图号后面的单元格写入数据,那么我需要分别在这些单元格中插入书签sch
、xh
、th
,然后通过下面代码即可向word模板中写入数据
1 |
|
原理就是书签提供了一个占位符,而dev则通过搜索匹配的占位符,将指定数据替换掉占位符。
3.向word模板中的表格插入新行并写入内容
向word模板中的表格插入新行则稍微复杂一些。主要步骤如下:
首先word文档中要有一个模板表格
需要在要插入表的位置添加书签table
遍历word文档中所有的表再遍历每一个表中所有的单元格,查找到书签所在的单元格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public TableCell GetTableCell(Document document)
{
//遍历文档中所有的表
foreach (Table table in document.Tables)
{
int row = 0, col = 0;
bool ok = false;
TableCell retCell = null;
//遍历表格中所有的单元格
table.ForEachCell((cell, rowIndex, columnIndex) =>
{
if (cell.Range.Contains(document.Bookmarks["table"].Range.Start))
{
row = rowIndex;
col = columnIndex;
retCell = cell;
ok = true;
}
});
if (ok)
{
return retCell;
}
}Table.ForEachCell(TableCellProcessorDelegate cellProcessor)函数传入的是一个委托。这里使用的是匿名方法
在指定单元格后新增行
可以使用Document.Tables[int index].Rows.Append()函数在表的最后追加行,或使用 Document.Tables[int index].Rows.InsertAfter(int rowIndex)函数在指定行之后插入行。其中Document.Tables[int index].Rows.Append()中index(表的索引)可以通过 Document.Tables.IndexOf(Table table)函数获取,而table又可以同通过上一步查找到的TableCell对象retCell.Table属性获取。
1
2
3
4
5
6
7
8
9
10
11
12RichEditControl richEdit = new RichEditControl();
richEdit.LoadDocument("C:/a.doc");
Document doc = rich.Document;
TableCell cell = GetTableCell(doc);
doc.BeginUpdate();
int index = doc.Tables.IndexOf(cell.Table);
doc.Tables[index].Rows.Append();//或者
//doc.Table[index].Rows.InsertAfter(cell.Row.Index - 1);
//获取指定单元格的占位符范围
DocumentRange range = doc.Tables[index].Rows[cell.Row.Index].Cells[cell.Index].ContentRange;
doc.Replace()
doc.EndUpdate();
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!