Skip to content

Instantly share code, notes, and snippets.

@KOZ60
Last active November 7, 2024 23:17
Show Gist options
  • Save KOZ60/bf6a44c8c97dd1dc8a01b7a6a5113799 to your computer and use it in GitHub Desktop.
Save KOZ60/bf6a44c8c97dd1dc8a01b7a6a5113799 to your computer and use it in GitHub Desktop.
Five things should do when using DataGridView
  1. Enable double buffering
  2. Commit a value when selected in ComboBox/CheckBox
  3. Preventing and canceling drawing with BeginUpdate/EndUpdate
  4. When the ReadOnly property is changed to false, all columns from becoming editable
  5. 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);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment