所有者绘制文本表格控件






4.58/5 (7投票s)
自绘文本表格控件,具有编辑、复选框、单元格合并、自动换行和可自定义外观等功能。
引言
本文介绍了一种具有编辑、复选框、单元格合并、多行文本和可自定义外观的文本表格控件。 不 支持数据绑定。
这是一个截图
背景
在我的一个项目中,我需要一个简单的文本表格/网格控件,具有最少的编辑功能,但支持自动换行和行内的单元格合并。我的领导非常不喜欢标准的DataGrid
及其外观。标准的ListView
不支持表格模式下的自动换行。经过一番研究,我找到了一些很棒的控件(例如XPTable
),但它们各自都存在一些缺陷:自动换行、滚动或速度... 当然,也有商业工具包,但我只需要它们的一小部分功能。
在制作自绘控件方面有一些经验,我决定自己制作一个。希望它对其他人有用。
使用代码
此示例展示了如何实例化控件并使用它
//
// bTable1
//
this.bTable1.BackColor = System.Drawing.Color.White;
this.bTable1.ColumnGridLines = false;
this.bTable1.DisabledColor = System.Drawing.Color.DarkGray;
this.bTable1.Dock = System.Windows.Forms.DockStyle.Fill;
this.bTable1.Font = new System.Drawing.Font("Microsoft Sans Serif",
12F, System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((byte)(204)));
this.bTable1.GridColor = System.Drawing.Color.SlateGray;
this.bTable1.HeaderBackColore = System.Drawing.Color.SlateGray;
this.bTable1.HeaderFont = new System.Drawing.Font("Arial Narrow",
11.25F, System.Drawing.FontStyle.Bold,
System.Drawing.GraphicsUnit.Point, ((byte)(204)));
this.bTable1.HeaderForeColore = System.Drawing.Color.White;
this.bTable1.HideDisabledRow = false;
this.bTable1.Location = new System.Drawing.Point(0, 0);
this.bTable1.MinimumRowHeight = 20;
this.bTable1.Name = "bTable1";
this.bTable1.RowGridLines = true;
this.bTable1.SelectedCell = null;
this.bTable1.SelectedRow = null;
this.bTable1.SelectionColor = System.Drawing.Color.AliceBlue;
this.bTable1.Size = new System.Drawing.Size(422, 470);
this.bTable1.TabIndex = 0;
this.bTable1.Text = "bTable1";
//
// Form1
//
//generate columns
BTable.Column col1 = new BTable.Column("â„–", 50);
BTable.Column col2 = new BTable.Column("Description", 200);
BTable.Column col3 = new BTable.Column("Check", 50);
BTable.Column col4 = new BTable.Column("Value", 100);
bTable1.Columns.Add(col1);
bTable1.Columns.Add(col2);
bTable1.Columns.Add(col3);
bTable1.Columns.Add(col4);
//add some data
for (int i = 1; i < 4; i++)
{
BTable.Row row1 = new BTable.Row(
new string[] { i.ToString() + " Header"});
row1.Cells[0].Font = new Font("Arial", 16);
row1.Cells[0].Editable = false;
bTable1.Rows.Add(row1);
for (int j = 1; j < 5; j++)
{
BTable.Row row2 = new BTable.Row(new string[] { i.ToString() + "." +
j.ToString(), "Some long description",
"", "Some long text here" });
row2.Cells[1].Editable = true;
row2.Cells[2].CheckBox = true;
row2.Cells[2].Checked = true;
bTable1.Rows.Add(row2);
}
}
解决方案
众所周知,表格包含单元格、行和列。我为这些对象创建了简单的类,并具有最少的属性。单元格包含值、字体、复选框、矩形和一些其他属性。矩形属性表示控件上单元格的实际位置。每次绘制控件时都会刷新它,并在用户单击控件时用于查找选定的单元格及其父行。
Row
类简单地包含单元格列表,而Column
仅具有名称和宽度。
BTable
类本身包含两个停靠的自绘自定义控件,分别由Header
和Table
类表示。Table
继承了一个可滚动控件类,可以轻松实现滚动。
在Table
控件的重写OnPaint
事件中,我使用来自命名类的数据绘制所有行和单元格。说到复选框,我决定自己绘制它们。我只是想让它们看起来更大一些,所以我从我之前的项目中获取了一些代码片段。当然,我使用双缓冲来避免闪烁,并使用SmoothingMode.AntiAlias
来使图形(尤其是复选框)看起来更好。
在Header
类的重写OnPaint
事件中,我使用渐变画笔绘制列标题,以使控件看起来更美观。
为了实现编辑,我只需在用户双击具有启用编辑属性的单元格时,在选定的单元格矩形区域中创建一个多行文本框,并在按下Enter键或文本框失去焦点时更新单元格值。
这是Table
类的重写OnPaint
事件,我们可以在其中获得最终的控件外观(除了其标题)
/// <summary>
/// Painting table
/// </summary>
/// <param name="e"></param>
protected override void OnPaint(PaintEventArgs e)
{
Pen GridPen = new Pen(BParent.GridColor);
DoubleBuffered = true;
if (AntiAliasText)
{
e.Graphics.TextRenderingHint =
System.Drawing.Text.TextRenderingHint.AntiAlias;
}
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Matrix m = new Matrix();
m.Translate(this.AutoScrollPosition.X,
this.AutoScrollPosition.Y,
MatrixOrder.Append);
e.Graphics.Transform = m;
int topx = HeaderHeight;
//Loop through all rows
foreach (Row row in BParent.Rows)
{
//check if all cells are disabled and we hide them
if (BParent.HideDisabledRow)
{
bool alldisabled = true;
foreach (Cell cell in row.Cells)
{
if (cell.Enabled)
{
alldisabled = false;
break;
}
}
if (alldisabled)
{
continue;
}
}
//Find maximum height
int rowheight = 0;
int counter = 0;
int leftx = 0;
foreach (Cell cell in row.Cells)
{
int cellwidth = BParent.Columns[counter].ColumnWidth;
if (counter == row.Cells.Count-1)
{
cellwidth = Width - leftx - 15;
}
Font drawfont = this.Font;
if (cell.Font != null)
{
drawfont = cell.Font;
}
else
{
cell.Font = drawfont;
}
int currowheight = (int)e.Graphics.MeasureString(
cell.Value.ToString(),drawfont,cellwidth).Height;
if (currowheight > rowheight)
{
rowheight = currowheight;
}
leftx += cellwidth;
counter++;
}
if (rowheight < BParent.MinimumRowHeight)
{
rowheight = BParent.MinimumRowHeight;
}
//Draw RowGridLine if needed
if (BParent.RowGridLines)
{
e.Graphics.DrawLine(GridPen, 0, topx + rowheight, Width, topx + rowheight);
}
//Generating cell rectangles
counter = 0;
leftx = 0;
foreach (Cell cell in row.Cells)
{
int cellwidth = BParent.Columns[counter].ColumnWidth;
if (counter == row.Cells.Count-1)
{
cellwidth = Width - leftx - 15;
}
//Draw ColumnGridLine if needed
if (BParent.ColumnGridLines)
{
e.Graphics.DrawLine(GridPen, leftx, topx, leftx, topx + rowheight);
}
Rectangle cellrectangle = new Rectangle(leftx, topx, cellwidth, rowheight);
cell.Rectangle = cellrectangle;
leftx += cellwidth;
counter++;
}
topx += rowheight;
}
//Painting selection background
if (BParent.SelectedRow != null)
{
foreach (Cell cell in BParent.SelectedRow.Cells)
{
e.Graphics.FillRectangle(
new SolidBrush(BParent.SelectionColor), cell.Rectangle);
}
}
//Loop through all rows, Again)
foreach (Row row in BParent.Rows)
{
//check if all cells are disabled and we hide them
if (BParent.HideDisabledRow)
{
bool alldisabled = true;
foreach (Cell cell in row.Cells)
{
if (cell.Enabled)
{
alldisabled = false;
break;
}
}
if (alldisabled)
{
continue;
}
}
//At last painting all cells
foreach (Cell cell in row.Cells)
{
Brush ForeBrush = new SolidBrush(ForeColor);
Pen ForePen = new Pen(ForeColor);
Pen CheckPen = new Pen(ForeColor,2);
if (!cell.Enabled)
{
ForeBrush = new SolidBrush(BParent.DisabledColor);
ForePen = new Pen(BParent.DisabledColor);
CheckPen = new Pen(BParent.DisabledColor,2);
}
//if cell has checkbox
if (cell.CheckBox)
{
Rectangle checkrect = new Rectangle(cell.Rectangle.X +
cell.Rectangle.Width / 2 - 7, cell.Rectangle.Y +
cell.Rectangle.Height / 2 - 7, 15, 15);
e.Graphics.DrawRectangle(ForePen, checkrect);
if (cell.Checked)
{
Point[] check =
{
new Point( 2+checkrect.X, checkrect.Y+8),
new Point( 7+checkrect.X, checkrect.Y+12),
new Point(13+checkrect.X, checkrect.Y+1),
};
e.Graphics.DrawCurve(CheckPen, check);
}
}
e.Graphics.DrawString(cell.Value.ToString(),
cell.Font, ForeBrush, cell.Rectangle);
}
}
AutoScrollMinSize = new Size(0, topx);
base.OnPaint(e);
}
注释
现在仅实现了垂直滚动,但是可以轻松添加水平滚动。在当前版本中,最后一列扩展到右侧的所有可用空间。这并非理想的情况,但目前,它符合我的要求。
某些属性和方法(例如,Value2
,SortbyValue2
)是专门为我们的工作流程实现的,对其他人可能没有用。
在很多情况下,需要实现错误处理程序。尤其是在生成列和行的时候;我稍后会添加它们。
历史
- 2011年7月4日:修复了一些错误,并向控件添加了一些新功能
- 水平滚动
- DateTime 列
- 列排序