背景
DataGridView控件的列是支持TextBoxColumn、ComboBoxColumn等类型的,就是DataGridView的单元格进入编辑模式的时候就会出现对应的控件,但是有些业务场景是需要在一个单元格进入编辑状态时需要多个控件组合完成业务需求,就无法直接只用特定类型的Column来实现了。如下图:
实现思路
这种需求就不能使用ComboBoxColumn的方式来实现了,也不需要进入编辑状态,可以通过在DataGridView的Controls中添加需要的控件来实现。
关键代码
DataGridViewHelper.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.ComponentModel;
using System.Windows.Forms;
namespace DXApplication1.Helper2
{
using Wongoing.Entity;
/// <summary>
/// 网格控件辅助类
/// </summary>
public class DataGridViewHelper
{
#region 字段定义
private static int RowHeight = 30; //保留默认行号
private static int LastVerticalScrollingOffset = 0; //保留最后垂直滚动条的位置
private static int LastHorizontalScrollingOffset = 0; //保留最后水平滚动条的位置
#endregion
#region 设置网格控件样式
/// <summary>
/// 设置网格控件样式
/// </summary>
/// <param name="dataGridView">网格控件对象</param>
public static void SetStyle(DataGridView dataGridView)
{
#region 启用双缓冲解决大数据闪烁问题
Type dgvType = dataGridView.GetType();
PropertyInfo pi = dgvType.GetProperty("DoubleBuffered", BindingFlags.Instance | BindingFlags.NonPublic);
pi.SetValue(dataGridView, true, null);
#endregion
#region 只读、尾行、删除、排序、行高、列宽
dataGridView.ReadOnly = true; //设置为只读
dataGridView.AllowUserToAddRows = false; //禁用自动添加尾行
dataGridView.AllowUserToDeleteRows = false; //禁用删除
dataGridView.AllowUserToOrderColumns = false; //禁用排序
dataGridView.AllowUserToResizeRows = false; //禁用调整行高
dataGridView.AllowUserToResizeColumns = false; //禁用调整列宽
dataGridView.AutoGenerateColumns = false; //禁用自动生成列
dataGridView.RowTemplate.Height = RowHeight; //行高
#endregion
#region 影响性能
dataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing; //禁用调整列宽
dataGridView.RowHeadersWidthSizeMode = DataGridViewRowHeadersWidthSizeMode.DisableResizing; //禁用调整行高
dataGridView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.None; //禁用列自动调整尺寸
dataGridView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; //禁用行高自动调整
#endregion
#region 网格线、选择模式
//dataGridView.CellBorderStyle = DataGridViewCellBorderStyle.None; //禁用网格线
//dataGridView.EditMode = DataGridViewEditMode.EditProgrammatically;
dataGridView.RowHeadersVisible = false; //隐藏列头
dataGridView.SelectionMode = DataGridViewSelectionMode.FullRowSelect; //整行选中
dataGridView.MultiSelect = false; //禁用选中多行
#endregion
#region 虚模式
//dataGridView.VirtualMode = true; //开启虚模式
#endregion
dataGridView.BackgroundColor = System.Drawing.Color.White; //设置控件背景色
dataGridView.DefaultCellStyle.SelectionBackColor = System.Drawing.Color.YellowGreen; //设置选中行的高亮背景色
}
#endregion
#region 设置列
/// <summary>
/// 设置列
/// </summary>
/// <param name="dataGridView">网格控件对象</param>
public static void SetColumns(DataGridView dataGridView)
{
dataGridView.Columns.Clear();
List<DataGridViewColumn> colList = new List<DataGridViewColumn>();
DataGridViewTextBoxColumn col = null;
#region 步号
col = new DataGridViewTextBoxColumn();
col.ReadOnly = true;
col.Name = "colStepNo";
col.DataPropertyName = "StepNo";
col.HeaderText = "步号";
col.Width = 60;
colList.Add(col);
#endregion
#region 控制模式
col = new DataGridViewTextBoxColumn();
col.ReadOnly = true;
col.Name = "colControlMode";
col.DataPropertyName = "ControlMode";
col.HeaderText = "控制模式";
col.Width = 400;
colList.Add(col);
#endregion
#region 跳转条件
col = new DataGridViewTextBoxColumn();
col.ReadOnly = true;
col.Name = "colGotoCondition";
col.DataPropertyName = "GotoCondition";
col.HeaderText = "跳转条件";
col.Width = 300;
colList.Add(col);
#endregion
#region 记录条件
col = new DataGridViewTextBoxColumn();
col.ReadOnly = true;
col.Name = "colRecordCondition";
col.DataPropertyName = "RecordCondition";
col.HeaderText = "记录条件";
col.Width = 280;
colList.Add(col);
#endregion
#region 电流量程
col = new DataGridViewTextBoxColumn();
col.ReadOnly = true;
col.Name = "colCurrentRange";
col.DataPropertyName = "CurrentRange";
col.HeaderText = "电流量程";
col.Width = 80;
colList.Add(col);
#endregion
#region 延迟
col = new DataGridViewTextBoxColumn();
col.ReadOnly = true;
col.Name = "colDelay";
col.DataPropertyName = "Delay";
col.HeaderText = "延迟";
col.Width = 60;
colList.Add(col);
#endregion
dataGridView.Columns.AddRange(colList.ToArray());
//if (dataGridView.Columns.Count < DataConfigHelper.Instance.MaxItemCount)
//{
// //如果网格控件的列数比最大要显示的数据项数少,则增加列
// for (int i = dataGridView.Columns.Count; i < DataConfigHelper.Instance.MaxItemCount; i++)
// {
// col = new DataGridViewTextBoxColumn();
// col.ReadOnly = true;
// colList.Add(col);
// }
// dataGridView.Columns.AddRange(colList.ToArray());
//}
//else
//{
// //如果网格控件的列数比最大要显示的数据项数多,则移除列
// int maxItemCount = DataConfigHelper.Instance.MaxItemCount;
// while(dataGridView.Columns.Count > maxItemCount)
// {
// dataGridView.Columns.RemoveAt(dataGridView.Columns.Count - 1);
// }
//}
}
#endregion
#region 设置列和列头
///// <summary>
///// 设置列和列头
///// </summary>
///// <param name="dataGridView">网格控件对象</param>
///// <param name="type">列头对应的类型</param>
//public static void SetColumnAndHeader(DataGridView dataGridView, Type type)
//{
// List<DataItem> dataItems = DataConfigHelper.Instance[type];
// if (dataItems != null)
// {
// SetColumns(dataGridView);
// IOrderedEnumerable<DataItem> orderItems = dataItems.Where(p => p.IsDisplay == true).OrderBy(p => p.DisplayIndex);
// DataItem[] items = orderItems.ToArray();
// for (int i = 0; i < dataGridView.Columns.Count; i++)
// {
// DataGridViewTextBoxColumn col = dataGridView.Columns[i] as DataGridViewTextBoxColumn;
// if (i < items.Length)
// {
// DataItem dataItem = items[i];
// col.Width = dataItem.DisplayWidth;
// col.DisplayIndex = i;
// dataItem.DisplayIndex = i;
// col.DataPropertyName = dataItem.DataPropertyName;
// col.HeaderText = dataItem.DisplayName;
// }
// else
// {
// col.HeaderText = String.Empty;
// }
// }
// }
//}
#endregion
#region 为网格控件注册事件
/// <summary>
/// 为网格控件注册事件
/// </summary>
/// <param name="dataGridView">网格控件对象</param>
public static void RegisterEvent(DataGridView dataGridView)
{
dataGridView.DataBindingComplete -= dataGridView_DataBindingComplete;
dataGridView.DataBindingComplete += dataGridView_DataBindingComplete;
dataGridView.CellMouseClick -= dataGridView_CellMouseClick;
dataGridView.CellMouseClick += dataGridView_CellMouseClick;
dataGridView.CellLeave -= dataGridView_CellLeave;
dataGridView.CellLeave += dataGridView_CellLeave;
dataGridView.CurrentCellChanged -= dataGridView_CurrentCellChanged;
dataGridView.CurrentCellChanged += dataGridView_CurrentCellChanged;
dataGridView.Scroll -= dataGridView_Scroll;
dataGridView.Scroll += dataGridView_Scroll;
dataGridView.MouseClick -= dataGridView_MouseClick;
dataGridView.MouseClick += dataGridView_MouseClick;
}
#endregion
#region 事件处理
#region 数据绑定完毕事件处理,设置奇偶背景色及当前行选中状态
private static void dataGridView_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
#region 0、条件验证
DataGridView dataGridView = sender as DataGridView;
if (dataGridView == null)
{
return;
}
#endregion
#region 设置奇偶行背景色
for (int i = 0; i < dataGridView.Rows.Count; i++)
{
if (i % 2 == 0)
{
dataGridView.Rows[i].DefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(204, 255, 204);
}
else
{
dataGridView.Rows[i].DefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(255, 255, 204);
}
}
#endregion
#region 取消选中行
if (dataGridView.CurrentRow != null)
{
dataGridView.CurrentRow.Selected = false;
}
#endregion
}
#endregion
#region 单元格点击事件处理,编辑状态时,把对应的编辑控件添加到对应的位置
/// <summary>
/// 单元格鼠标点击事件
/// </summary>
/// <param name="sender">事件源对象</param>
/// <param name="e">事件对象</param>
private static void dataGridView_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
Console.WriteLine("dataGridView_CellMouseClick");
#region 0、条件验证
if (Common.Global.Instance.CurrEditorMode != Wongoing.Entity.Enum.TaskEditorMode.Edit)
{
return;
}
DataGridView dataGridView = sender as DataGridView;
if (dataGridView == null)
{
return;
}
if (e.RowIndex < 0 || e.RowIndex >= dataGridView.RowCount)
{
return;
}
if (e.ColumnIndex == 0)
{
return;
}
#endregion
Console.WriteLine("dataGridView_CellMouseClick Controls = " + dataGridView.Controls.Count);
#region 1、还原行高
for (int i = 0; i < dataGridView.Rows.Count; i++)
{
if (dataGridView.Rows[i].Height != dataGridView.RowTemplate.Height)
{
dataGridView.Rows[i].Height = dataGridView.RowTemplate.Height;
}
}
#endregion
#region 2、添加编辑控件
System.Drawing.Rectangle rect = dataGridView.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, true);
#region 2.1 控制模式
if (dataGridView.Columns[e.ColumnIndex].DataPropertyName == "ControlMode")
{
if (!dataGridView.Controls.ContainsKey(TaskEditorlHelper.Instance.ControlMode.MainEditItem.Name))
{
Control ctl = TaskEditorlHelper.CreateComponent(TaskEditorlHelper.Instance.ControlMode.MainEditItem);
if (ctl != null)
{
int x = rect.Left + 5;
int y = rect.Top + (rect.Height - ctl.Height) / 2 - 1;
ctl.Location = new System.Drawing.Point(x, y);
if (ctl is ComboBox)
{
ComboBox cbControlMode = ctl as ComboBox;
cbControlMode.DataSource = DataSourceHelper.Instance.ControlModeData;
cbControlMode.SelectedIndexChanged += cbControlMode_SelectedIndexChanged;
}
dataGridView.Controls.Add(ctl);
}
}
}
#endregion
#region 2.2 跳转条件
#endregion
#region 2.3 记录条件
#endregion
#region 2.4 电流量程
#endregion
#endregion
#region 3、保存当前垂直滚动条和水平滚动条的位置
LastVerticalScrollingOffset = dataGridView.VerticalScrollingOffset; //保存当前垂直滚动条的位置
LastHorizontalScrollingOffset = dataGridView.HorizontalScrollingOffset; //保存当前水平滚动条的位置
#endregion
#region 4、设置选中行
if (dataGridView.CurrentRow != null)
{
dataGridView.CurrentRow.Selected = true;
}
#endregion
Console.WriteLine("dataGridView_CellMouseClick Controls = " + dataGridView.Controls.Count);
}
/// <summary>
/// 选择控制模式事件处理
/// </summary>
/// <param name="sender">事件源</param>
/// <param name="e">事件参数</param>
private static void cbControlMode_SelectedIndexChanged(object sender, EventArgs e)
{
Console.WriteLine("cbControlMode_SelectedIndexChanged...");
int maxHeight = 0;
ComboBox cbControlMode = sender as ComboBox;
if (cbControlMode != null)
{
if (cbControlMode.Parent != null && cbControlMode.Parent is DataGridView)
{
Console.WriteLine("cbControlMode_SelectedIndexChanged Controls = " + cbControlMode.Parent.Controls.Count);
DataGridView dataGridView = cbControlMode.Parent as DataGridView;
if (dataGridView.CurrentRow != null)
{
maxHeight = dataGridView.CurrentRow.Height;
}
if (cbControlMode.SelectedItem is DataItem)
{
DataItem selectItem = cbControlMode.SelectedItem as DataItem;
if (selectItem != null)
{
Console.WriteLine(selectItem.DataPropertyName);
#region 移除之前的选项控件
foreach (string key in TaskEditorlHelper.Instance.ControlMode.DicOptionItems.Keys)
{
foreach (EditControlItem item in TaskEditorlHelper.Instance.ControlMode.DicOptionItems[key])
{
cbControlMode.Parent.Controls.RemoveByKey(item.Name);
}
}
#endregion
#region 新增对应的选项控件
if (TaskEditorlHelper.Instance.ControlMode.DicOptionItems.ContainsKey(selectItem.DataPropertyName))
{
List<EditControlItem> controlItems = TaskEditorlHelper.Instance.ControlMode.DicOptionItems[selectItem.DataPropertyName];
int x = cbControlMode.Location.X + cbControlMode.Width;
int y = cbControlMode.Location.Y;
for (int i = 0; i < controlItems.Count; i++)
{
Control c = TaskEditorlHelper.CreateComponent(controlItems[i]);
if (c.Height > maxHeight)
{
maxHeight = c.Height;
}
x += 5;
c.Location = new System.Drawing.Point(x, y);
cbControlMode.Parent.Controls.Add(c);
x += c.Width;
}
#region 修改行高
if (dataGridView.CurrentRow != null)
{
if (maxHeight > dataGridView.CurrentRow.Height)
{
dataGridView.CurrentRow.Height = maxHeight;
}
}
#endregion
#region 重新定位Label控件
foreach (Control c in cbControlMode.Parent.Controls)
{
if (c is Label)
{
c.Top = c.Top - (dataGridView.RowTemplate.Height - cbControlMode.Height) / 2 + (maxHeight - c.Height) / 2;
}
}
#endregion
}
else
{
Console.WriteLine(String.Format("The ControlMode DicOptionItems Keys not Conains the [{0}]", selectItem.DataPropertyName));
}
#endregion
}
}
Console.WriteLine("cbControlMode_SelectedIndexChanged Controls = " + cbControlMode.Parent.Controls.Count);
}
}
}
#endregion
#region 当前单元格离开事件,从编辑控件中获取数据并填充值单元格
private static void dataGridView_CellLeave(object sender, DataGridViewCellEventArgs e)
{
Console.WriteLine("dataGridView_CellLeave");
#region 0、条件验证
if (Common.Global.Instance.CurrEditorMode != Wongoing.Entity.Enum.TaskEditorMode.Edit)
{
return;
}
DataGridView dataGridView = sender as DataGridView;
if (dataGridView == null)
{
return;
}
#endregion
#region 控件取值
StringBuilder sbValue = new StringBuilder();
#region 2.1 控制模式
if (dataGridView.Columns[e.ColumnIndex].DataPropertyName == "ControlMode")
{
if (dataGridView.Controls.ContainsKey(TaskEditorlHelper.Instance.ControlMode.MainEditItem.Name))
{
Control ctl = dataGridView.Controls.Find(TaskEditorlHelper.Instance.ControlMode.MainEditItem.Name, true).FirstOrDefault();
if (ctl is ComboBox)
{
ComboBox cbControlMode = ctl as ComboBox;
if (cbControlMode.SelectedItem is Wongoing.Entity.DataItem)
{
Wongoing.Entity.DataItem item = cbControlMode.SelectedItem as Wongoing.Entity.DataItem;
sbValue.Append(item.DisplayName).Append(",").Append(item.DataPropertyName);
if (TaskEditorlHelper.Instance.ControlMode.DicOptionItems.ContainsKey(item.DataPropertyName))
{
foreach (EditControlItem controlItem in TaskEditorlHelper.Instance.ControlMode.DicOptionItems[item.DataPropertyName])
{
ctl = dataGridView.Controls.Find(controlItem.Name, true).FirstOrDefault();
if (ctl is TextBox || ctl is Label)
{
PropertyInfo pi = ctl.GetType().GetProperty("Text");
if (pi != null)
{
sbValue.Append(",").Append(pi.GetValue(ctl, null));
}
}
}
}
}
}
}
}
dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = sbValue.ToString();
#endregion
#region 跳转条件
#endregion
#region 记录条件
#endregion
#region 电流量程
#endregion
#endregion
}
#endregion
#region 当前单元格更改事件处理,移除编辑控件
private static void dataGridView_CurrentCellChanged(object sender, EventArgs e)
{
Console.WriteLine("CurrentCellChanged");
#region 0、条件验证
if (Common.Global.Instance.CurrEditorMode != Wongoing.Entity.Enum.TaskEditorMode.Edit)
{
return;
}
DataGridView dataGridView = sender as DataGridView;
if (dataGridView == null)
{
return;
}
#endregion
Console.WriteLine("dataGridView_CurrentCellChanged Controls = " + dataGridView.Controls.Count);
#region 2、移除控件
dataGridView.Controls.Clear();
dataGridView.Controls.Clear();
#endregion
#region 3、取消选中行
if (dataGridView.CurrentRow != null)
{
dataGridView.CurrentRow.Selected = false;
}
#endregion
Console.WriteLine("dataGridView_CurrentCellChanged Controls = " + dataGridView.Controls.Count);
}
#endregion
#region 滚动条滚动事件处理,对应的编辑控件要相应的位置移动
private static void dataGridView_Scroll(object sender, ScrollEventArgs e)
{
Console.WriteLine("Scroll..");
#region 0、条件验证
if (Common.Global.Instance.CurrEditorMode != Wongoing.Entity.Enum.TaskEditorMode.Edit)
{
return;
}
DataGridView dataGridView = sender as DataGridView;
if (dataGridView == null)
{
return;
}
#endregion
Console.WriteLine("FirstDisplayedScrollingRowIndex = " + dataGridView.FirstDisplayedScrollingRowIndex + ", CurrRowIndex = " + dataGridView.CurrentCell.RowIndex);
#region 1、控件根据滚动条的滚动值移动
foreach (Control ctl in dataGridView.Controls)
{
ctl.Left -= dataGridView.HorizontalScrollingOffset - LastHorizontalScrollingOffset;
ctl.Top -= dataGridView.VerticalScrollingOffset - LastVerticalScrollingOffset;
if (dataGridView.FirstDisplayedScrollingRowIndex > dataGridView.CurrentCell.RowIndex)
{
ctl.Visible = false;
}
else
{
ctl.Visible = true;
}
}
#endregion
#region 2、保存当前垂直滚动条和水平滚动条的位置
LastVerticalScrollingOffset = dataGridView.VerticalScrollingOffset; //保存当前垂直滚动条的位置
LastHorizontalScrollingOffset = dataGridView.HorizontalScrollingOffset; //保存当前水平滚动条的位置
#endregion
Console.WriteLine("HorizontalScrollingOffset = " + dataGridView.HorizontalScrollingOffset + ", VerticalScrollingOffset = " + dataGridView.VerticalScrollingOffset);
}
#endregion
#region 右键点击列弹出上下文菜单
private static void dataGridView_MouseClick(object sender, MouseEventArgs e)
{
#region 0、条件验证
if (Common.Global.Instance.CurrEditorMode != Wongoing.Entity.Enum.TaskEditorMode.Edit && e.Button == MouseButtons.Right)
{
return;
}
if (e.Button != MouseButtons.Right)
{
return;
}
DataGridView dataGridView = sender as DataGridView;
if (dataGridView == null)
{
return;
}
#endregion
#region 1、构建上下文菜单
ContextMenu contextMenu = new ContextMenu();
MenuItem mnuAddTaskStep = new MenuItem();
mnuAddTaskStep.Text = "追加工步";
contextMenu.MenuItems.Add(mnuAddTaskStep);
MenuItem mnuDelTaskStep = new MenuItem();
mnuDelTaskStep.Text = "删除工步";
contextMenu.MenuItems.Add(mnuDelTaskStep);
contextMenu.Show(dataGridView, new System.Drawing.Point(e.X, e.Y));
#endregion
}
#endregion
#endregion
}
}
窗体代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using DevExpress.XtraEditors;
namespace DXApplication1
{
using Wongoing.Entity;
using Helper2;
using Wongoing.Basic;
public partial class XtraForm1 : DevExpress.XtraEditors.XtraForm
{
private List<TaskStep> _data = null;
public XtraForm1()
{
InitializeComponent();
this._data = this.GetTaskStepData();
}
private void XtraForm1_Load(object sender, EventArgs e)
{
DataGridViewHelper.RegisterEvent(this.dataGridView1);
DataGridViewHelper.SetStyle(this.dataGridView1);
DataGridViewHelper.SetColumns(this.dataGridView1);
this.dataGridView1.DataSource = this._data;
}
public List<TaskStep> GetTaskStepData()
{
List<TaskStep> lst = new List<TaskStep>();
TaskStep ts = null;
for (int i = 0; i < 20; i++)
{
ts = new TaskStep();
lst.Add(ts);
}
return lst;
}
private void button2_Click(object sender, EventArgs e)
{
Wongoing.Entity.ControlModeEditor editor = new ControlModeEditor();
Wongoing.Basic.SerializeHandler.SerializeXml<Wongoing.Entity.ControlModeEditor>(editor, @"e:\editor.xml");
editor = Wongoing.Basic.SerializeHandler.DeserializeXml<Wongoing.Entity.ControlModeEditor>(@"e:\editor.xml");
}
}
}