星期一, 7月 11, 2022

[C#] DataGridView - CalendarColumn

根據官方文章 - How to: Host Controls in Windows Forms DataGridView Cells 來學習如何把 DateTimePicker 放進 DataGridViewColumn 內,基本上把文章內的 Code 直接 copy 出來就可以直接建立出 CalendarColumn 並使用,該內容重點分別為
  • 實作 IDataGridViewEditingControl  interface
  • DataGridViewCell
  • DataGridViewColumn

IDataGridViewEditingControl

建立 CalendarEditingControl 繼承 DateTimePicker 並實作IDataGridViewEditingControl

IDataGridViewEditingControl.EditingControlWantsInput 重點文字
The EditingControlWantsInputKey method is called by the DataGridView. The DataGridView will pass in true for dataGridViewWantsInputKey when it can process the keyData. If the editing control can let the DataGridView handle the keyData, EditingControlWantsInputKey should return false when dataGridViewWantsInputKey is true. Other implementations of EditingControlWantsInputKey may ignore a dataGridViewWantsInputKey value of true and handle the keyData in the editing control.
Some situations require that cell contents reposition when the value changes. For example, cell contents may need to reposition when a cell wraps text and the contents become larger.


public class CalendarEditingControl : DateTimePicker, IDataGridViewEditingControl
{
    /// <summary>
    /// Gets or sets the DataGridView that contains the cell.
    /// </summary>
    public DataGridView EditingControlDataGridView { get; set; }

    /// <summary>
    /// Gets or sets the index of the hosting cell's parent row.
    /// </summary>
    public int EditingControlRowIndex { get; set; }

    /// <summary>
    /// Gets or sets a value indicating whether the value of the editing control differs from the value of the hosting cell.
    /// </summary>
    public bool EditingControlValueChanged { get; set; }

    public CalendarEditingControl()
    {
        // 原本是以短日期格式來顯示,故意設定為常日期格式來顯示,凸顯 EditingControl 和 Cell 可以有設定不同值
        Format = DateTimePickerFormat.Long;
    }

    /// <summary>
    /// Determines whether the specified key is a regular input key that the editing control should process or a special key that the DataGridView should process.
    /// </summary>
    public bool EditingControlWantsInputKey(
        Keys key, 
        bool dataGridViewWantsInputKey)
    {
        // Let the DateTimePicker handle the keys listed.
        switch (key & Keys.KeyCode) 
        {
            case Keys.Left:
            case Keys.Up:
            case Keys.Down:
            case Keys.Right:
            case Keys.Home:
            case Keys.End:
            case Keys.PageDown:
            case Keys.PageUp:
               return true;
            default:
               return !dataGridViewWantsInputKey;
        }
    }

    /// <summary>
    /// Gets or sets a value indicating whether the cell contents need to be repositioned whenever the value changes.
    /// </summary>
    public bool RepositionEditingControlOnValueChange
    {
        get
        {
            return false;
        }
    }


    /// <summary>
    /// Gets the cursor used when the mouse pointer is over the EditingPanel but not over the editing control.
    /// </summary>
    public Cursor EditingPanelCursor
    {
        get
        {
            return base.Cursor;
        }
    }

    /// <summary>
    /// 自行覆寫,不是 IDataGridViewEditingControl 的,控制項值變化都必須做通知
    /// </summary>
    /// <param name="eventargs"></param>    
    protected override void OnValueChanged(EventArgs eventargs)
    {
        // Notify the DataGridView that the contents of the cell have changed.
        EditingControlValueChanged = true;
        EditingControlDataGridView.NotifyCurrentCellDirty(true);
        base.OnValueChanged(eventargs);
    }

    /// <summary>
    /// Changes the control's user interface (UI) to be consistent with the specified cell style.
    /// </summary>
    public void ApplyCellStyleToEditingControl(DataGridViewCellStyle dataGridViewCellStyle)
    {
        // 特別設定文字來凸顯 EditingControl 和 Cell 可以有設定不同值
        Font = new Font("微軟正黑體", 20, FontStyle.Bold);
        CalendarForeColor = dataGridViewCellStyle.ForeColor;
        CalendarMonthBackground = dataGridViewCellStyle.BackColor;
    }

    /// <summary>
    /// Prepares the currently selected cell for editing.
    /// </summary>
    public void PrepareEditingControlForEdit(bool selectAll)
    {
        // No preparation needs to be done.
    }

    #region EditingControlFormattedValue 相關
    
    /// <summary>
    /// Retrieves the formatted value of the cell.
    /// </summary>
    public object GetEditingControlFormattedValue(DataGridViewDataErrorContexts context)
    {
        return EditingControlFormattedValue;
    }

    /// <summary>
    /// Gets or sets the formatted value of the cell being modified by the editor.
    /// </summary>
    public object EditingControlFormattedValue
    {
        get
        {
            return Value.ToShortDateString();
        }
        set
        {
            // 必須檢查日期字串是否可以轉為 DateTime,無法轉換就以 DateTime.Now 為預設值
            if (value is string)
            {
                if (DateTime.TryParse(value.ToString(), out DateTime dt))
                {
                    Value = dt;
                }
                else
                {
                    Value = DateTime.Now;
                }
            }
        }
    } 
    
    #endregion
}

DataGridViewCell

直接繼承 DataGridViewTextBoxCell 來使用
public class CalendarCell : DataGridViewTextBoxCell
{
    public CalendarCell()
        : base()
    {
        // Use the short date format.
        Style.Format = "d";
    }

    /// <summary>
    /// Attaches and initializes the hosted editing control.
    /// </summary>
    public override void InitializeEditingControl(
        int rowIndex,
        object initialFormattedValue,
        DataGridViewCellStyle dataGridViewCellStyle)
    {
        // Set the value of the editing control to the current cell value.
        base.InitializeEditingControl(rowIndex, initialFormattedValue, dataGridViewCellStyle);

        // CalendarEditingControl 是一個衍生自 DateTimePicker 類別的使用者自訂類別。
        CalendarEditingControl ctl = DataGridView.EditingControl as CalendarEditingControl;
       
        // Use the default row value when Value property is null.
        if (Value == null)
        {
            ctl.Value = (DateTime)DefaultNewRowValue;
        }
        else
        {
            // 編輯狀態 DateTimePicker 初始值
            ctl.Value = (DateTime)Value;
        }
    }

    public override Type EditType
    {
        get
        {
            // Return the type of the editing control that CalendarCell uses.
            return typeof(CalendarEditingControl);
        }
    }

    public override Type ValueType
    {
        get
        {
            // Return the type of the value that CalendarCell contains.
            return typeof(DateTime);
        }
    }
   
    public override object DefaultNewRowValue
    {
        get
        {
            // Use the current date and time as the default value.
            return DateTime.Now;
        }
    }
}

DataGridViewColumn
public class CalendarColumn : DataGridViewColumn
{
    public CalendarColumn() : base(new CalendarCell())
    {

    }

    public override DataGridViewCell CellTemplate
    {
        get
        {
            return base.CellTemplate;
        }
        set
        {
            // Ensure that the cell used for the template is a CalendarCell. 
            if (value != null &&
                !value.GetType().IsAssignableFrom(typeof(CalendarCell)))
                throw new InvalidCastException("Must be a CalendarCell");

            base.CellTemplate = value;
        }
    }
}

DateTimePicker 顏色變化

Code 內在 ApplyCellStyleToEditingControl() 有對 DateTimePicker 顏色進行變化,但不會有效果,官方文件 - DateTimePicker.CalendarMonthBackground 上有說明
Starting with Windows Vista and depending on the theme, setting this property might not change the appearance of the calendar. For example, if Windows is set to use the Aero theme, setting this property has no effect. This is because an updated version of the calendar is rendered with an appearance that is derived at run time from the current operating system theme. If you want to use this property and enable the earlier version of the calendar, you can disable visual styles for your application. Disabling visual styles might affect the appearance and behavior of other controls in your application. To disable visual styles in Visual Basic, open the Project Designer and uncheck the Enable XP visual styles check box. To disable visual styles in C#, open Program.cs and comment out Application.EnableVisualStyles();
基本上是 DateTimePickcer 限制,該點在該筆記 - [C#] MonthCalendar 記錄過

設定畫面

Column 選擇內多出 CalendarColumn 選項

  [C#] DataGridView - CalendarColumn-1

點擊 cell 後就會出現 DateTimePicker,下圖中可以看見刻意變化設定
  • 字型變成粗體
  • 日期為長日期格式
[C#] DataGridView - CalendarColumn-2


DataGridView.NotifyCurrentCellDirty()

在 OnValueChanged() 內有呼叫 NotifyCurrentCellDirty(),DataTimePicker 資料才會可以回到 cell 去,下圖為模擬沒有呼叫 NotifyCurrentCellDirty() 的情況

沒有留言:

張貼留言