- Enable double buffering
- Commit a value when selected in ComboBox/CheckBox
- Preventing and canceling drawing with BeginUpdate/EndUpdate
- When the ReadOnly property is changed to false, all columns from becoming editable
- Clipboard Operations (Clear/Paste)
using System;
using System.Text;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[DesignerCategory("code")]
public class DataGridViewEx : DataGridView
{
// ========================================================
// #1 Enable double buffering
// ========================================================
public DataGridViewEx() {
DoubleBuffered = true;
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void OnMouseDown(MouseEventArgs e) {
base.OnMouseDown(e);
//
// #1.1 When MouseDown on ColumnHeader or RowHeader, guide line appear, so turn off double buffering.
//
var hittestInfo = HitTest(e.X, e.Y);
switch (hittestInfo.Type) {
case DataGridViewHitTestType.ColumnHeader:
case DataGridViewHitTestType.RowHeader:
DoubleBuffered = false;
break;
}
}
protected override void OnMouseUp(MouseEventArgs e) {
base.OnMouseUp(e);
//
// #1.2 When MouseUp turn on double buffering.
//
DoubleBuffered = true;
}
// ========================================================
// #2 Commit a value when selected in ComboBox/CheckBox
// ========================================================
protected override void OnCurrentCellDirtyStateChanged(EventArgs e) {
base.OnCurrentCellDirtyStateChanged(e);
if (IsCurrentCellDirty) {
switch (CurrentCell.OwningColumn) {
case DataGridViewCheckBoxColumn _:
case DataGridViewComboBoxColumn _:
CommitEdit(DataGridViewDataErrorContexts.Commit);
break;
}
}
}
// ========================================================
// #3 Preventing and canceling drawing with BeginUpdate/EndUpdate
// ========================================================
private int beginUpdateCount = 0;
public void BeginUpdate() {
if (beginUpdateCount > 0) {
beginUpdateCount++;
return;
}
beginUpdateCount++;
// #3.1 If a MessageBox or other process occurs between BeginUpdate and EndUpdate,
// drawing will resume.
BeginInvoke((Action<bool>)EndUpdate, true);
SetRedraw(false);
}
public void EndUpdate() {
EndUpdate(false);
}
public void EndUpdate(bool force) {
if (force || beginUpdateCount == 1) {
beginUpdateCount = 0;
SetRedraw(true);
} else if (beginUpdateCount > 0) {
beginUpdateCount--;
}
}
// Suppression events
protected override bool CanRaiseEvents {
get {
return beginUpdateCount == 0;
}
}
private const int WM_SETREDRAW = 0x000B;
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
private void SetRedraw(bool value) {
if (value) {
SendMessage(Handle, WM_SETREDRAW, 1, 0);
Invalidate();
} else {
SendMessage(Handle, WM_SETREDRAW, 0, 0);
}
}
// ========================================================
// #4 When the ReadOnly property is changed to false, all columns from becoming editable
// ========================================================
private bool readOnly = false;
private readonly List<DataGridViewColumn> notReadOnlyColumns
= new List<DataGridViewColumn>();
public new bool ReadOnly {
get {
return readOnly;
}
set {
if (readOnly != value) {
SetupColumns(value);
readOnly = value;
}
}
}
private void SetupColumns(bool newReadOnly) {
BeginUpdate();
try {
if (newReadOnly) {
notReadOnlyColumns.Clear();
foreach (DataGridViewColumn column in Columns) {
if (!column.ReadOnly) {
notReadOnlyColumns.Add(column);
column.ReadOnly = true;
}
}
} else {
foreach (DataGridViewColumn column in notReadOnlyColumns) {
column.ReadOnly = false;
}
notReadOnlyColumns.Clear();
}
} finally {
EndUpdate();
}
}
// ========================================================
// #5 Clipboard Operations (Clear/Paste)
// ========================================================
private const char CTRL_V = '\u0016';
protected override void OnKeyDown(KeyEventArgs e) {
base.OnKeyDown(e);
switch (e.KeyData) {
case Keys.Delete:
BeginUpdate();
OnClear();
EndUpdate();
e.SuppressKeyPress = true;
e.Handled = true;
break;
}
}
protected virtual void OnClear() {
foreach (DataGridViewCell c in SelectedCells) {
if (IsPastableCell(c)) {
CurrentCell = c;
c.Value = null;
if (IsCurrentCellInEditMode) {
EndEdit();
BeginEdit(true);
}
}
}
}
protected override void OnKeyPress(KeyPressEventArgs e) {
base.OnKeyPress(e);
switch (e.KeyChar) {
case CTRL_V:
if (Clipboard.ContainsText()) {
BeginUpdate();
OnPaste();
EndUpdate();
}
e.Handled = true;
break;
}
}
protected virtual void OnPaste() {
var tmp = GetSelectedRectangle();
if (!tmp.HasValue) return;
var rectangle = tmp.Value;
var buffer = Clipboard.GetText();
buffer = buffer.Replace("\r\n", "\r");
buffer = buffer.Replace("\n", "\r");
var clipRows = buffer.Split('\r');
var clipRowIndex = 0;
var maxRowIndex = (rectangle.Top == rectangle.Bottom) ?
(AllowUserToAddRows ? int.MaxValue : RowCount - 1) :
rectangle.Bottom;
var rowIndex = rectangle.Top;
while (rowIndex <= maxRowIndex && clipRowIndex < clipRows.Length) {
var clipColumns = clipRows[clipRowIndex].Split('\t');
var clipColumnIndex = 0;
var maxColumnIndex = (rectangle.Left == rectangle.Right) ?
ColumnCount - 1 : rectangle.Right;
var columnIndex = rectangle.Left;
while (columnIndex <= maxColumnIndex && clipColumnIndex < clipColumns.Length) {
PasteCell(columnIndex, rowIndex, clipColumns[clipColumnIndex]);
columnIndex++;
clipColumnIndex++;
}
rowIndex++;
clipRowIndex++;
}
}
public override DataObject GetClipboardContent() {
var tmp = GetSelectedRectangle();
if (!tmp.HasValue) {
return null;
}
var rectangle = tmp.Value;
var clipRows = new StringBuilder();
var clipColumns = new StringBuilder();
for (int rowIndex = rectangle.Top; rowIndex <= rectangle.Bottom; rowIndex++) {
clipRows.Append('\r');
clipColumns.Clear();
for (int columnIndex = rectangle.Left; columnIndex <= rectangle.Right; columnIndex++) {
clipColumns.Append('\t');
if (IsItemCell(columnIndex, rowIndex)) {
var value = this[columnIndex, rowIndex].Value;
if (value != null) {
clipColumns.Append(value.ToString());
}
}
}
if (clipColumns.Length > 0) {
clipRows.Append(clipColumns.ToString(1, clipColumns.Length - 1));
}
}
if (clipRows.Length > 0) {
var dataObject = new DataObject();
dataObject.SetData(clipRows.ToString(1, clipRows.Length - 1));
return dataObject;
}
return null;
}
public bool PasteCell(int columnIndex, int rowIndex, string value) {
var cell = this[columnIndex, rowIndex];
if (IsPastableCell(cell)) {
return PasteCell(cell, value);
} else {
return false;
}
}
protected bool IsPastableCell(DataGridViewCell cell) {
if (!IsItemCell(cell)) return false;
if (cell.ReadOnly || cell.OwningRow.ReadOnly ||
cell.OwningColumn.ReadOnly || ReadOnly) return false;
if (!cell.Visible) return false;
switch (cell.OwningColumn) {
case DataGridViewTextBoxColumn _:
case DataGridViewCheckBoxColumn _:
case DataGridViewComboBoxColumn _:
break;
default:
return false;
}
var cnv = GetTypeConverter(cell);
if (!cnv.CanConvertFrom(typeof(string))) return false;
return true;
}
protected TypeConverter GetTypeConverter(DataGridViewCell cell) {
var valueType = cell.OwningColumn.ValueType;
if (valueType == null) {
valueType = typeof(string);
}
return TypeDescriptor.GetConverter(valueType);
}
protected bool IsItemCell(DataGridViewCell cell) {
return IsItemCell(cell.ColumnIndex, cell.RowIndex);
}
public bool IsItemCell(int columnIndex, int rowIndex) {
return columnIndex >= 0 && rowIndex >= 0 &&
columnIndex < ColumnCount && rowIndex < RowCount;
}
protected bool PasteCell(DataGridViewCell cell, string value) {
try {
CurrentCell = cell;
var cnv = GetTypeConverter(cell);
var obj = cnv.ConvertFrom(value);
if (cell.OwningRow.IsNewRow) {
// Force Commit NewRow
BeginEdit(true);
NotifyCurrentCellDirty(true);
CommitEdit(DataGridViewDataErrorContexts.Commit);
EndEdit();
}
cell.Value = obj;
if (IsCurrentCellInEditMode) {
EndEdit();
BeginEdit(true);
}
return true;
} catch {
return false;
}
}
protected virtual Rectangle? GetSelectedRectangle(DataGridViewSelectedCellCollection selectedCells) {
if (selectedCells.Count == 0) {
return null;
}
var left = int.MaxValue;
var top = int.MaxValue;
var right = int.MinValue;
var bottom = int.MinValue;
foreach (DataGridViewCell c in selectedCells) {
if (left > c.ColumnIndex) left = c.ColumnIndex;
if (right < c.ColumnIndex) right = c.ColumnIndex;
if (top > c.RowIndex) top = c.RowIndex;
if (bottom < c.RowIndex) bottom = c.RowIndex;
}
return Rectangle.FromLTRB(left, top, right, bottom);
}
public virtual Rectangle? GetSelectedRectangle() {
return GetSelectedRectangle(SelectedCells);
}
}