星期六, 8月 24, 2024

[C#] 自訂資料列外觀

參考官方文章 - How to: Customize the Appearance of Rows in the Windows Forms DataGridView Control 來自訂 DataGridViewRow 外觀,繪製 DataGrdiViewRow 外觀要使用 RowPrePaint 和 RowPostPaint 事件,該範例在
  • RowPrePaint 事件繪製 DataGridViewRow selection 漸層背景顏色
  • RowPostPaint 事件繪製 DataGridViewRow 跨欄位文字內容
C# Code
using System.Drawing.Drawing2D;

namespace RowPaintingSample
{
    public partial class Form1 : Form
    {
        private int _oldRowIndex { get; set; } = 0;
        private int _custom_Content_Height { get; } = 30;
        private int _hideColumnIndex { get; } = 2;
        private DataGridViewAutoSizeRowMode _defaultDataGridViewAutoSizeRowMode { get; } = DataGridViewAutoSizeRowMode.AllCellsExceptHeader;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {

            // 設定 Padding 和 Height 讓 Row 下方有空間繪製跨欄文字
            Padding newPadding = new Padding(0, 1, 0, _custom_Content_Height);
            dataGridView1.RowTemplate.DefaultCellStyle.Padding = newPadding;
            dataGridView1.RowTemplate.Height += _custom_Content_Height;

            // 顏色設定為 Transparent,才不會覆蓋自行繪製的 Selection 背景顏色
            dataGridView1.RowTemplate.DefaultCellStyle.SelectionBackColor = Color.Transparent;

            // 設定 DataGridView 相關屬性
            dataGridView1.AllowUserToAddRows = false;
            dataGridView1.CellBorderStyle = DataGridViewCellBorderStyle.None;
            dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;

            // 設定 DataGridViewColumnHeader 文字
            dataGridView1.ColumnCount = 4;
            dataGridView1.Columns[0].Name = "Recipe";
            dataGridView1.Columns[0].SortMode = DataGridViewColumnSortMode.NotSortable;
            dataGridView1.Columns[1].Name = "Category";
            dataGridView1.Columns[_hideColumnIndex].Name = "Main Ingredients";
            dataGridView1.Columns[3].Name = "Rating";

            // 該欄位資料要繪製至 DataGridViewRow 內跨欄顯示
            dataGridView1.Columns[_hideColumnIndex].Visible = false;

            // Populate the rows of the DataGridView
            string[] row1 = new string[]{
                "Meatloaf",
                "Main Dish",
                "1 lb. lean ground beef, 1/2 cup bread crumbs, 1/4 cup ketchup, 1/3 tsp onion powder, 1 clove of garlic, 1/2 pack onion soup mix, dash of your favorite BBQ Sauce",
                "****"};

            string[] row2 = new string[]{
                "Key Lime Pie",
                "Dessert",
                "lime juice, whipped cream, eggs, evaporated milk",
                "****"};

            string[] row3 = new string[]{
                "Orange-Salsa Pork Chops",
                "Main Dish",
                "pork chops, salsa, orange juice, pineapple",
                "****"};

            string[] row4 = new string[]{
                "Black Bean and Rice Salad",
                "Salad",
                "black beans, brown rice",
                "****"};

            string[] row5 = new string[]{
                "Chocolate Cheesecake",
                "Dessert",
                "cream cheese, unsweetened chocolate",
                "***"};

            string[] row6 = new string[]{
                "Black Bean Dip",
                "Appetizer",
                "black beans, sour cream, salsa, chips",
                "***"};

            object[] rows = new object[] { row1, row2, row3, row4, row5, row6 };
            foreach (string[] rowArray in rows)
                dataGridView1.Rows.Add(rowArray);

            // 自行調整 DataGridViewRow 來顯示文字內容
            dataGridView1.AutoResizeRows(DataGridViewAutoSizeRowsMode.AllCellsExceptHeaders);

            dataGridView1.RowPrePaint += dataGridView1_RowPrePaint;
            dataGridView1.RowPostPaint += dataGridView1_RowPostPaint;
            dataGridView1.ColumnWidthChanged += dataGridView1_ColumnWidthChanged;
            dataGridView1.RowHeightChanged += dataGridView1_RowHeightChanged;
            dataGridView1.CurrentCellChanged += dataGridView1_CurrentCellChanged;
        }

        // Column 寬度有變化時重新繪製
        void dataGridView1_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
        {
            var dgv = sender as DataGridView;
            dgv.Invalidate();
        }

        // Row 高度有變化時重新繪製
        void dataGridView1_CurrentCellChanged(object sender, EventArgs e)
        {
            var dgv = sender as DataGridView;

            if (_oldRowIndex != -1)
            {
                dgv.InvalidateRow(_oldRowIndex);
            }

            // CurrentCellAddress.X 代表 ColumnIndex
            // CurrentCellAddress.Y 代表 RowIndex
            _oldRowIndex = dgv.CurrentCellAddress.Y;
        }

        void dataGridView1_RowPrePaint(object sender, DataGridViewRowPrePaintEventArgs e)
        {
            var dgv = sender as DataGridView;

            // 不自動繪製 focus 的方框
            e.PaintParts &= ~DataGridViewPaintParts.Focus;

            // 判斷該是否為使用者選取的 Row
            if (e.State.HasFlag(DataGridViewElementStates.Selected) == false)
                return;

            Rectangle rowBounds = GetRowBounds(dgv, e.RowBounds);

            // 繪製 selection 背景
            using Brush backbrush = new LinearGradientBrush(rowBounds,
                    dgv.DefaultCellStyle.SelectionBackColor,
                    e.InheritedRowStyle.ForeColor,
                    LinearGradientMode.Horizontal);

            e.Graphics.FillRectangle(backbrush, rowBounds);

            // 文件說明紀錄:e.Handled 設定為 true,CellPainting 和 RowPostPaint 事件就不會觸發
            // e.Handled = true;
        }

        // 跨欄位文字內容繪製
        void dataGridView1_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
        {
            var dgv = sender as DataGridView;

            Rectangle rowBounds = GetRowBounds(dgv, e.RowBounds);

            // 在這裡使用 forebrush 進行繪圖操作
            using SolidBrush forebrush =
                e.State.HasFlag(DataGridViewElementStates.Selected)
                ? new SolidBrush(e.InheritedRowStyle.SelectionForeColor)
                : new SolidBrush(e.InheritedRowStyle.ForeColor);

            // 取得隱藏欄位文字內容,Cells[2] 是隱藏欄位
            object recipe = dgv.Rows.SharedRow(e.RowIndex).Cells[_hideColumnIndex].Value;

            if (recipe != null)
            {
                string text = recipe.ToString();

                // Calculate the bounds for the content that spans multiple
                // columns, adjusting for the horizontal scrolling position
                // and the current row height, and displaying only whole
                // lines of text.
                Rectangle textArea = rowBounds;
                textArea.X -= dataGridView1.HorizontalScrollingOffset;
                textArea.Width += dataGridView1.HorizontalScrollingOffset;
                textArea.Y += rowBounds.Height - e.InheritedRowStyle.Padding.Bottom;
                textArea.Height -= rowBounds.Height - e.InheritedRowStyle.Padding.Bottom;
                textArea.Height = (textArea.Height / e.InheritedRowStyle.Font.Height) * e.InheritedRowStyle.Font.Height;

                // Calculate the portion of the text area that needs painting.
                RectangleF clip = textArea;
                clip.Width -= dgv.RowHeadersWidth + 1 - clip.X;
                clip.X = dgv.RowHeadersWidth + 1;

                // 原本的繪製區域
                RectangleF oldClip = e.Graphics.ClipBounds;

                // 限定繪製區域
                e.Graphics.SetClip(clip);

                // 繪製跨欄位文字內容
                e.Graphics.DrawString(text, e.InheritedRowStyle.Font, forebrush, textArea);

                // 回復原本的繪製區域
                e.Graphics.SetClip(oldClip);
            }

            if (dgv.CurrentCellAddress.Y == e.RowIndex)
            {
                // 繪製 focus 時的外框                
                e.DrawFocus(rowBounds, true);
            }
        }

        private Rectangle GetRowBounds(DataGridView dgv, Rectangle rowBounds)
        {
            // 計算 Row 邊界
            return new Rectangle(
                dgv.RowHeadersWidth,
                rowBounds.Top,
                dgv.Columns.GetColumnsWidth(DataGridViewElementStates.Visible) - dgv.HorizontalScrollingOffset + 1,
                rowBounds.Height);
        }

        // Adjusts the padding when the user changes the row height so that
        // the normal cell content is fully displayed and any extra
        // height is used for the content that spans multiple columns.
        void dataGridView1_RowHeightChanged(object sender, DataGridViewRowEventArgs e)
        {
            // 計算 Row 高度
            int preferredNormalContentHeight = e.Row.GetPreferredHeight(e.Row.Index, _defaultDataGridViewAutoSizeRowMode, true) - e.Row.DefaultCellStyle.Padding.Bottom;

            // 指定新的 Padding 設定值
            Padding newPadding = e.Row.DefaultCellStyle.Padding;
            newPadding.Bottom = e.Row.Height - preferredNormalContentHeight;
            e.Row.DefaultCellStyle.Padding = newPadding;
        }

    }
}

CurrentCellChanged

該範例是在 CurrentCellChanged 事件內判斷 Row 是否有變化,因為該範例有設定 SelectionMode 為 DataGridViewSelectionMode.FullRowSelect,搭配 SelectionChanged 事件也可以達成同樣效果,改寫如下
private void dataGridView1_SelectionChanged(object sender, EventArgs e)
{
    var dgv = sender as DataGridView;
    if (dgv == null ||
        dgv.CurrentRow == null)
        return;

    dgv.InvalidateRow(dgv.CurrentRow.Index);
}

繪製 Focus

該範例在 RowPrePaint 事件特別去移除繪製 Focus,RowPostPaint 事件才又指定繪製 Focus,因為 Selection 背景漸層顏色關係,完全看不出 Focus 效果,故意把 Selection 背景漸層顏色繪製拿掉來觀察,效果如下


點擊 Cell 後繪製變化

點擊進入 Cell 後,跨欄位文字內容就消失啦

操作效果

Column 寬度變化

Row 高度變化


該範例蠻多沒用過功能,有去了解並筆記

沒有留言:

張貼留言