4

基于DevExpress的GridControl实现的一些界面处理功能 - 伍华聪

 1 year ago
source link: https://www.cnblogs.com/wuhuacong/p/17401374.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

DevExpress的GridControl控件能够提供很多强大的操作,其视图GridView能够通过各种设置,呈现出多种复杂的界面效果,本篇随笔探讨一些常见的GridControl控件及其GridView的视图操作处理,以便在大家也需要的时候作为一个参考的代码。其中设计一些常见的操作,如合并单元格、汇总统计、复制粘贴行、导入数据处理、导出Excel、导出PDF等、打印GridView视图、内置插入及保存数据处理等等。

1、合并单元格

 有时候,需要把多行紧挨着的单元格内容进行合并展示,这样有助于界面的美观及查看便利,如下界面所示。

8867-20230515120540605-203582766.png

 或者这样的界面,都是类似的效果。

8867-20230515120613709-1945796207.png

合并的处理比较简单,只需要设置允许合并,以及实现合并的处理事件代码即可。 

    //允许合并
    this.gridView1.OptionsView.AllowCellMerge = true;

然后实现的事件代码如下所示。

        private List<string> mergeFields = new List<string> { "订单号", "客户订单号", "客户编码", "客户名称", "组合编码", "组合名称"};
        private void GridView1_CellMerge(object sender, CellMergeEventArgs e)
        {
            var fieldName = e.Column.FieldName;
            if(mergeFields.Contains(fieldName))
            {
                var view = sender as GridView;
                if(view != null)
                {
                    // 获取当前单元格的值
                    var cellValue = view.GetRowCellValue(e.RowHandle1, fieldName);
                    // 获取前一行相同列的单元格的值
                    var prevCellValue = view.GetRowCellValue(e.RowHandle2, fieldName);

                    if (e.RowHandle2 >= 0 && cellValue != null && cellValue.Equals(prevCellValue))
                    {
                        // 合并单元格
                        e.Merge = true;
                        e.Handled = true;
                    }
                }
            }
            else
            {
                e.Merge = false;
                e.Handled = true;
            }
        }

我们可以根据自己的业务需要,设置一些合并的字段,放在列表中即可。呈现的界面效果类似下面所示。

8867-20230515121614948-880467894.png

这里值得注意的时候,如果我们需要把列表设置为具有复选框的记录显示模式,方便勾选来进行其他操作,那么就需要取消合并的功能。 

 this.gridView1.OptionsView.AllowCellMerge = false;

这样才可以进行展示复选框的处理。

8867-20230515123215916-1550797950.png

2、汇总统计处理

汇总的处理,也是类似的操作,需要设置显示底部的面板,以及设置好汇总列的处理信息。

 

8867-20230515122301238-383717896.png

 为了更方便的设置统计信息,我们可以建立一个扩展函数的方式,来实现统计行的信息处理,如下扩展方法所示。

/// <summary>
/// 设置统计列内容
/// </summary>
/// <param name="gridView">GridView对象</param>
/// <param name="fieldName">统计字段</param>
/// <param name="summaryItemType">统计类型</param>
/// <param name="prefix">显示前缀</param>
public static void SetSummaryColumn(this GridView gridView, string fieldName, SummaryItemType summaryItemType = SummaryItemType.Sum,
    string prefix = "")
{
    if (!gridView.OptionsView.ShowFooter)
    {
        gridView.OptionsView.ShowFooter = true;
    }

    if (gridView.Columns.Count > 0)
    {
        gridView.Columns[fieldName].SummaryItem.FieldName = fieldName;
        gridView.Columns[fieldName].SummaryItem.DisplayFormat = gridView.Columns[fieldName].DisplayFormat.FormatString;
        gridView.Columns[fieldName].SummaryItem.SummaryType = summaryItemType;
        gridView.Columns[fieldName].SummaryItem.DisplayFormat = prefix + "{0}";
    }
}

如果我们需要在创建统计行的时候,请空它之前的记录信息,那么也可以增加多一个扩展函数来处理清空统计信息,如下所示。

/// <summary>
/// 清空统计项目
/// </summary>
/// <param name="gridView"></param>
public static void ClearSummaryColumns(this GridView gridView)
{
    gridView.OptionsView.ShowFooter = false;
    foreach (GridColumn column in gridView.Columns)
    {
        if (column.SummaryItem != null)
        {
            column.SummaryItem.Collection.Clear();
        }
    }
}

最终我们在界面上创建统计信息的代码如下所示。

    //添加统计行
    gridView1.ClearSummaryColumns();
    gridView1.SetSummaryColumn("订单量", DevExpress.Data.SummaryItemType.Sum);
    gridView1.SetSummaryColumn("完成数量", DevExpress.Data.SummaryItemType.Sum);

最终的界面效果如下所示

8867-20230515123030532-2095657527.png

3、复制粘贴行及导入处理

有时候为了更加便捷的对记录信息进行复制或者粘贴到GridView列表中进行处理,那么需要设置相关的GridView的属性,让它能够允许复制并设置复制的信息,这样的格式化后,就可以用于粘贴到记事本或者粘贴到新增记录模式下的GridView视图中了。

我们只需要设置其中的OptionsClipboard中的某些属性,如下代码所示。

//实现选择复制到新的行中
view.OptionsClipboard.AllowCopy = DefaultBoolean.True;  //允许复制
view.OptionsClipboard.CopyColumnHeaders = DefaultBoolean.False;  //是否复制表头
view.OptionsClipboard.PasteMode = DevExpress.Export.PasteMode.Append; //粘贴模式
view.OptionsClipboard.ClipboardMode = DevExpress.Export.ClipboardMode.Formatted;//格式化模式

然后,如果我们的GridView是设置到编辑模式的话,设置下面的代码,让它可以新增粘贴的记录。

view.OptionsBehavior.Editable = true;
view.OptionsBehavior.ReadOnly = false;
view.OptionsBehavior.AllowAddRows = true;

设置好这些,我们如果需要从剪切板中粘贴记录过来,那么只需要简单的饿调用下即可。

view.PasteFromClipboard();//从剪切板中复制记录过来

如果需要从Excel里面导入到记录表里面我们只需要读取Excel里面的记录,然后设置到当前的列表中即可。

var fileDialog = new OpenFileDialog();
fileDialog.Title = "导入xcel";
fileDialog.Filter = "Excel文件(*.xls)|*.xls";
var dialogResult = fileDialog.ShowDialog(this);
if (dialogResult == DialogResult.OK)
{
    ExcelImporter.ImportExcel(fileDialog.FileName, currentView);
}

4、内置的导出Excel处理及导出PDF操作

GridView本身控件提供了ExportToXls、ExportToXlsx两个方法,可以直接把数据导出为Excel文件,可以指定常规的xls或者xlsx的格式。

如下代码所示。

var fileDialog = new SaveFileDialog();
fileDialog.Title = "导出Excel";
fileDialog.Filter = "Excel文件(*.xls)|*.xls";

var dialogResult = fileDialog.ShowDialog(this);
if (dialogResult == DialogResult.OK)
{
    var options = new XlsExportOptions();
    options.TextExportMode = TextExportMode.Text; //修改绑定数据的格式为文本
    view.ExportToXls(fileDialog.FileName);
    MessageDxUtil.ShowTips("导出Excel成功!");

    if (openExcel && File.Exists(fileDialog.FileName))
    {
        System.Diagnostics.Process.Start(fileDialog.FileName);
    }
}

当然,我们也可以利用第三方控件Aspose.Cell或者NPOI、Myxls的控件进行Excel的导出操作,那样也可以提供更多通用的控制处理。

自定义格式的报表导出,可以是一个典型的图文并茂的统计报表,类似样式如下所示。

04150703-9f4e2cca500e499680a5aec8323db81a.png

如我在随笔《使用Aspose.Cell控件实现Excel高难度报表的生成(一)》、《使用Aspose.Cell控件实现Excel高难度报表的生成(二)》、《利用Aspose.Word控件和Aspose.Cell控件,实现Word文档和Excel文档的模板化导出》中介绍过相关的处理方式,一般我们使用封装好的方法,通用的导出Excel内容即可,有时候我们直接利用分页控件进行封装,导出常规的Excel文档。

AsposeExcelTools.DataTableToExcel2(table, (String)e.Argument, out outError);

导出Excel数据的效果如下所示。

041100046202193.png

5、打印当前GridView视图

GridView本身也提供了直接打印的操作方法,如果对一些简单的表格,可以直接使用它进行打印当前视图处理。

 currentView.GridControl.ShowRibbonPrintPreview();

这样的打印效果,呈现出一个Ribbon的报表预览界面,然后直接在上面进行定制打印的格式。

8867-20230515145322551-1195945591.png

 或者我们也可以在生成打印预览的时候,指定更多的定制信息,如下界面所示。

private void menu_PrintFixColumn_Click(object sender, EventArgs e)
{
    this.winGridViewPager1.gridView1.OptionsPrint.EnableAppearanceEvenRow = true;

    using (PrintableComponentLink link = new PrintableComponentLink(new PrintingSystem()))
    {
        link.Component = this.winGridViewPager1.gridControl1;
        link.Landscape = true;
        link.PaperKind = System.Drawing.Printing.PaperKind.A3;
        link.CreateMarginalHeaderArea += new CreateAreaEventHandler(Link_CreateMarginalHeaderArea);
        link.CreateDocument();
        link.ShowPreview();
    }
}
private void Link_CreateMarginalHeaderArea(object sender, CreateAreaEventArgs e)
{
    string title = this.AppInfo.AppUnit + " -- " + "备件信息报表";
    PageInfoBrick brick = e.Graph.DrawPageInfo(PageInfo.None, title, Color.DarkBlue,
       new RectangleF(0, 0, 100, 21), BorderSide.None);

    brick.LineAlignment = BrickAlignment.Center;
    brick.Alignment = BrickAlignment.Center;
    brick.AutoWidth = true;
    brick.Font = new System.Drawing.Font("宋体", 11f, FontStyle.Bold);
}

类似的打印预览的界面效果如下所示。

8867-20230515150219210-1012963683.png

 当然我们也可以利用第三方控件的打印处理来实现更多的效果,不过内置的GridView打印操作,基本上也能满足大多数的要求了。

6、弹出GridView自定义菜单

 GridView的右键菜单,可以用ContextMenuStrip的常规性菜单控件来定义,我分页控件中就是采用这样的方式,设置比较简单,只需要设置GridCtrol控件的ContextMenuStrip属性即可,如下代码所示。

 this.gridControl1.ContextMenuStrip = this.contextMenuStrip1;

并且通过ContextMenuStrip的Opening事件,可以对它进行一定的设置禁用/可用的处理。

 this.contextMenuStrip1.Opening += new CancelEventHandler(contextMenuStrip1_Opening);

private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
    this.menu_Add.Visible = (this.OnAddNew != null && this.ShowAddMenu);
    this.menu_Delete.Visible = (this.OnDeleteSelected != null && this.ShowDeleteMenu);
    this.menu_Edit.Visible = (this.OnEditSelected != null && this.ShowEditMenu);
    this.menu_Refresh.Visible = (this.OnRefresh != null);
}

这样就可以对GridView的右键进行绑定及权限的设置处理,类似下面的界面效果所示。

8867-20230515151130614-149313274.png

虽然利用ContextMenuStrip的传统菜单条,可以很好、方便的实现右键菜单的处理,不过缺点是样式没有随着DevExpress本身的效果变化,如果需要追求一样的样式体验,那么可以考虑使用DevExpress的PopupMenu控件来承载菜单或者Ribbon的一些按钮操作。

 PopupMenu控件可以指定Ribbon窗体控件,然后它们右键菜单和Ribbon的按钮集合同样的出现和隐藏。

8867-20230515151559042-537370618.png

然后在设计模式下设计对应的菜单项目集合。

8867-20230515152035931-1744063914.png

在界面设计好Ribbon的按钮和菜单对象的按钮后,我们可以为菜单绑定对应的GridControl事件处理,让它结合GridControl的右键事件出现右键菜单。

this.gridControl.MouseUp += GridControl_MouseUp;

显示右键菜单的事件代码如下所示。

private void GridControl_MouseUp(object sender, MouseEventArgs e)
{
    try
    {
        if (e.Button == MouseButtons.Right)
        {
            var view = gridControl.DefaultView as GridView;
            var info = view.CalcHitInfo(e.Location);
            if (info.InRowCell)
            {
                popupGridMenu.ShowPopup(gridControl.PointToScreen(e.Location));
            }
        }
    }
    catch (Exception ex)
    {
        MessageDxUtil.ShowError(ex.Message);
    }
}
8867-20230515152424701-1803827596.png

7、直接新增保存的处理

之前在随笔《在DevExpress程序中使用GridView直接录入数据的时候,增加列表选择的功能 》 、《在DevExpress程序中使用Winform分页控件直接录入数据并保存》分别介绍了两种不同方式的数据直接在GridView列表中处理的方式,本质上两者是一致的,都是利用GridView本身的一些事件进行操作,实现更加方便的数据录入体验。

我们一般通过 InitNewRow 、ValidateRow 、CellValueChanged来处理数据的录入操作,如下详细操作的界面代码所示。

private void RegisterEvent()
{
    var grd = this.gridControl1;
    var grv = this.gridView1;
    grv.InitGridView(GridType.NewItem, false, EditorShowMode.MouseDownFocused, "");
    //创建显示的列
    grv.CreateColumn("ItemNo", "备件编号", 120).CreateButtonEdit().ButtonClick += (s, e) =>
    {
        #region 选取备件信息,返回后赋值当前记录
        if (grv.GetFocusedRow() == null)
        {
            grv.AddNewRow();//一定要增加
        }
        FrmSelectItemDetail dlg = new FrmSelectItemDetail();
        dlg.WareHouse = this.txtWareHouse.Text;
        if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
        {
            var info = dlg.ItemDetailInfo;
            if (info != null)
            {
                grv.SetFocusedRowCellValue("ItemNo", info.ItemNo);
                grv.SetFocusedRowCellValue("ItemName", info.ItemName);
                grv.SetFocusedRowCellValue("ItemBigType", info.ItemBigType);
                grv.SetFocusedRowCellValue("ItemType", info.ItemType);
                grv.SetFocusedRowCellValue("MapNo", info.MapNo);
                grv.SetFocusedRowCellValue("Specification", info.Specification);
                grv.SetFocusedRowCellValue("Unit", info.Unit);
                grv.SetFocusedRowCellValue("Price", info.Price);
                grv.SetFocusedRowCellValue("Material", info.Material);
                grv.SetFocusedRowCellValue("Source", info.Source);
                grv.SetFocusedRowCellValue("StoragePos", info.StoragePos);
                grv.SetFocusedRowCellValue("UsagePos", info.UsagePos);
                grv.SetFocusedRowCellValue("WareHouse", info.WareHouse);
                grv.SetFocusedRowCellValue("Dept", info.Dept);
                grv.SetFocusedRowCellValue("Quantity", 1);//默认数量为1
            }
        }
        #endregion
    };
    grv.CreateColumn("ItemName", "备件名称", 120);
    grv.CreateColumn("Quantity", "数量").CreateSpinEdit();
    grv.CreateColumn("ItemBigType", "备件属类", 120);
    grv.CreateColumn("ItemType", "备件类别", 120);
    grv.CreateColumn("MapNo", "图号");
    grv.CreateColumn("Specification", "规格型号", 120);
    grv.CreateColumn("Unit", "单位");
    grv.CreateColumn("Price", "单价");
    grv.CreateColumn("Amount", "金额");
    grv.CreateColumn("Material", "材质", 120);
    grv.CreateColumn("Source", "来源", 120);
    grv.CreateColumn("StoragePos", "库位", 120);
    grv.CreateColumn("UsagePos", "使用位置", 120);
    grv.CreateColumn("WareHouse", "所属库房", 120);
    grv.CreateColumn("Dept", "所属部门", 120);

    //设置部分字段不可修改
    var readonlyFields = "ItemName,ItemBigType,ItemType,MapNo,Specification,Unit,Price,Amount,Material,Source,UsagePos,WareHouse,Dept";
    grv.SetColumnsReadOnly(readonlyFields);

    //绑定数据源,否则无法新增存储
    var list = new List<ItemDetailInfo>();
    var dt = DataTableHelper.ConvertToDataTable<ItemDetailInfo>(list);

    //同时增加两列在实体类属性里没有的列
    dt.Columns.Add(new DataColumn("Quantity", typeof(int)));
    dt.Columns.Add(new DataColumn("Amount", typeof(decimal)));
    grd.DataSource = dt;

    grv.InitNewRow += delegate(object sender, InitNewRowEventArgs e)
    {
        //如果是GUID的主键,可以初始化,以及赋值明细记录的父ID等操作
        //GridView gridView = grd.FocusedView as GridView;
        //gridView.SetFocusedRowCellValue("ID", Guid.NewGuid().ToString());
    };
    grv.ValidateRow += delegate(object sender, ValidateRowEventArgs e)
    {
        //校验一些不能为空的字段
        var result = grd.ValidateRowNull(e, new string[]
        {
            "ItemNo",
            "ItemName",
            "Quantity"
        });
    };
    grv.CellValueChanged += (object sender, CellValueChangedEventArgs e) =>
    {
        //根据数量计算金额
        if (e.Column.FieldName == "Quantity" && e.Value != null)
        {
            var Price = string.Concat(grv.GetFocusedRowCellValue("Price")).ToDecimal();
            var Quantity = string.Concat(e.Value).ToDecimal();
            grv.SetFocusedRowCellValue("Amount", Price * Quantity);
        }
    };
    grv.RowCellStyle += (object sender, RowCellStyleEventArgs e) =>
    {
        //设置特殊颜色标志
        if (e.Column.FieldName == "Quantity" )
        {
            e.Appearance.BackColor = Color.Moccasin;
            e.Appearance.ForeColor = Color.Red;
        }
    };    
}

而如果需要结合删除的功能,那么可以增加对RowDeleted的事件处理。

    //行删除操作
    grv.OptionsBehavior.AllowDeleteRows = DefaultBoolean.True;
    grv.RowDeleted += (s, ee) =>
    {
        //同时移除价格列表
        var info = ee.Row as OrderInfo;
        if(info != null)
        {
            this.OrderInfos.Remove(info);
            for (int i = 0; i < this.gridView2.RowCount; i++)
            {
                var code = (string)this.gridView2.GetRowCellValue(i, "产品编码");
                if(info.产品编码 == code)
                {
                    this.gridView2.DeleteRow(i);
                }
            }
        }
    };
    grv.KeyDown += (s, ee) =>
    {
        if (ee.KeyCode == Keys.Delete)
        {
            gridView1.DeleteSelectedRows();
            ee.Handled = true;
        }
    };

以上就是在实际项目中,常用到的GridControl和GridView的常规处理方法,用好这些控件的处理,可以极大程度的提高用户的界面体验。

当然可能还有很多常用的方法或者处理方式,等待大家的进一步挖掘和分享。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK