- 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 高度變化
該範例蠻多沒用過功能,有去了解並筆記
沒有搞清楚部分,是計算繪製區域的數學運算,未來有機會再來理解