65.9K
CodeProject 正在变化。 阅读更多。
Home

所有者绘制文本表格控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.58/5 (7投票s)

2010 年 12 月 8 日

CPOL

3分钟阅读

viewsIcon

36695

downloadIcon

1133

自绘文本表格控件,具有编辑、复选框、单元格合并、自动换行和可自定义外观等功能。

引言

本文介绍了一种具有编辑、复选框、单元格合并、多行文本和可自定义外观的文本表格控件。 支持数据绑定。

这是一个截图

BTableScreen.PNG

背景

在我的一个项目中,我需要一个简单的文本表格/网格控件,具有最少的编辑功能,但支持自动换行和行内的单元格合并。我的领导非常不喜欢标准的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类本身包含两个停靠的自绘自定义控件,分别由HeaderTable类表示。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);
}

注释

现在仅实现了垂直滚动,但是可以轻松添加水平滚动。在当前版本中,最后一列扩展到右侧的所有可用空间。这并非理想的情况,但目前,它符合我的要求。

某些属性和方法(例如,Value2SortbyValue2)是专门为我们的工作流程实现的,对其他人可能没有用。

在很多情况下,需要实现错误处理程序。尤其是在生成列和行的时候;我稍后会添加它们。

历史

  • 2011年7月4日:修复了一些错误,并向控件添加了一些新功能
    • 水平滚动
    • DateTime 列
    • 列排序
© . All rights reserved.