【问题标题】:Capture image of specific area of a TableLayoutPanel捕获 TableLayoutPanel 特定区域的图像
【发布时间】:2021-07-03 10:51:38
【问题描述】:

我有一个使用TableLayoutPanel 控件的项目。
我想捕获该TableLayoutPanel 的特定区域的图像。
我有一个图像(被捕获)宽度和高度信息。我也有起始单元格索引信息,我成功捕获但是捕获的区域是错误的。

这是我的代码:

private void getImage()
{
    int totalWidth = 250 // image width size;
    int totalHeight = 200 // image height size;

    int LeftBottomCellColumnIndex = 3
    int LeftBottomCellRowIndex = 4

    int LeftTopCellColumnIndex = 3
    int LeftBottomCellRowIndex = 2 // I also have all cells index info

    Bitmap bm = new Bitmap(totalWidth, totalHeight);
    tableLayoutPanel2.DrawToBitmap(bm, new Rectangle(0, 0, totalWidth, totalHeight)); // 0,0 problem is here. I do not know what I have to put there

    string path = @"C:\Users\prdn5\TestDrawToBitmap.bmp";
    FileStream fs = new FileStream(path, FileMode.OpenOrCreate);
    bm.Save(fs, ImageFormat.Bmp);
    fs.Flush();
    fs.Close();
}

【问题讨论】:

标签: c# winforms graphics tablelayoutpanel image-capture


【解决方案1】:

由于某种原因,TableLayoutPanel 不会通过属性或方法直接公开单元格边界。但它当然会计算这些值以供内部使用,因为它需要绘制其单元格的边框。

它的OnPaintBackground 方法负责这个。
当控件需要重新绘制自身时,将重新计算单元边界,并且对于每个单元,控件都会引发CellPaint 事件。它的TableLayoutCellPaintEventArgs 参数返回当前单元格的边界和列/行坐标。
由于每次修改/调整 TableLayoutPanel 大小时都会引发此事件,因此我们可以使用 CellPaint 事件处理程序来存储这些引用。

在这里,我使用Dictionary<TableLayoutPanelCellPosition, Rectangle> 来存储每个单元格的坐标并将其映射到其边界。

Layout 事件处理程序设置了一个布尔字段,该字段导致仅在必要时重建字典(因为 TableLayoutPanel 的布局已更改)。

▶ 字典键是TableLayoutPanelCellPosition,因为它允许快速简单的相等比较。当然,如果需要,您可以使用不同类型的对象。

▶ 假设 TableLayoutPanel 被命名为 tlp1

▶ 不处理在运行时添加和/或删除列/行。如果需要,请在 Layout 事件处理程序而不是表单构造函数中重新定义 Dictionary。

public partial class SomeForm : Form
{
    Dictionary<TableLayoutPanelCellPosition, Rectangle> tlpCells = null;
    bool calcCells = false;

    public SomeForm() {
        InitializeComponent();

        // Set the DoubleBuffered property via reflection (if needed)
        var flags = BindingFlags.Instance | BindingFlags.NonPublic;
        tlp1.GetType().GetProperty("DoubleBuffered", flags).SetValue(tlp1, true);

        tlpCells = new Dictionary<TableLayoutPanelCellPosition, Rectangle>();
        for (int x = 0; x < tlp1.ColumnCount; x++) {
            for (int y = 0; y < tlp1.RowCount; y++) {
                tlpCells.Add(new TableLayoutPanelCellPosition(x, y), Rectangle.Empty);
            }
        }
    }

    private void tlp1_Layout(object sender, LayoutEventArgs e) => calcCells = true;

    private void tlp1_CellPaint(object sender, TableLayoutCellPaintEventArgs e)
    {
        if (calcCells) {
            var cellPos = new TableLayoutPanelCellPosition(e.Column, e.Row);
            tlpCells[cellPos] = e.CellBounds;

            if (cellPos.Column == tlp1.ColumnCount - 1 && 
                cellPos .Row == tlp1.RowCount - 1) calcCells = false;
        }
    }
}

要从当前鼠标位置获取单元格,请调用 GetSelectedCell(),传递当前指针位置,相对于 TableLayoutPanel:

var cell = GetSelectedCell(tlp1.PointToClient(MousePosition));
// If called from the MouseMove, MouseDown etc handlers, use the MouseEventArgs instead
var cell = GetSelectedCell(e.Location);

// [...]

private TableLayoutPanelCellPosition GetSelectedCell(Point position) 
    => tlpCells.FirstOrDefault(c => c.Value.Contains(position)).Key;

现在,要从一系列单元格生成位图,您只需指定开始(左、上)和结束(右、下)单元格并将这些值引用传递给 TlpCellRangeToBitmap() 方法(也可以用作扩展方法)。
如果 includeBorders 参数设置为 true,它将包括单元格的外部边框(参见视觉示例)。添加检查 TableLayoutPanel 是否确实有边框的代码(BorderStyle 属性)。
GetCellRangeBounds()返回包含Cells范围的Rectangle:该方法是独立的,可以用于其他目的。

例如,将 PictureBox 的 Image 属性设置为生成的 Bitmap:

var cellStart = new TableLayoutPanelCellPosition(2, 2);
var cellEnd = new TableLayoutPanelCellPosition(3, 5);

somePictureBox.Image?.Dispose();
somePictureBox.Image = TlpCellRangeToBitmap(tlp1, cellStart, cellEnd, false);

// [...]

private Bitmap TlpCellRangeToBitmap(TableLayoutPanel tlp, TableLayoutPanelCellPosition cellStart, TableLayoutPanelCellPosition cellEnd, bool includeBorders)
{
    // The 3rd parameter includes or excludes the external borders
    var selRect = GetCellRangeBounds(cellStart, cellEnd, includeBorders);
    using (var image = new Bitmap(tlp.Width + 1, tlp.Height + 1)) {
        tlp.DrawToBitmap(image, new Rectangle(Point.Empty, tlp.Size));
        return image.Clone(selRect, PixelFormat.Format32bppArgb);
    }
}

private Rectangle GetCellRangeBounds(TableLayoutPanelCellPosition start, TableLayoutPanelCellPosition end, bool extBorders)
{
    var cellStart = tlpCells[start];
    var cellEnd = tlpCells[end];
    if (extBorders) {
        cellStart.Location = new Point(cellStart.X - 1, cellStart.Y - 1);
        cellEnd.Size = new Size(cellEnd.Width + 2, cellEnd.Height + 2);
    }
    return new Rectangle(cellStart.Location, new Size(cellEnd.Right - cellStart.X, cellEnd.Bottom - cellStart.Y));
}

这就是它的工作原理:

【讨论】:

  • 首先非常感谢您付出的努力和时间。你提供的信息对我来说就像一堂课一样重要。此外,它也让我理解了这个过程。我尝试了您的代码,但有时它会给出错误“内存不足异常”。请参阅链接。 [链接] (ibb.co/gwf6DW8)。
  • 这是我的错。我找到了错误的原因。我纠正了。您的代码完美运行。我花了两天时间寻找解决方案。再次感谢你。你是救生员:)
  • 好吧,我很高兴它对您有所帮助 :) 如果您尝试在位图边界之外进行绘制,则会生成 out of memory 异常。这就是为什么有[...] new Bitmap(tlp.Width + 1, tlp.Height + 1):当包含外部边框时,正常的矩形区域宽1个像素,所以Clone()方法可能会在选择与TableLayoutPanel的边界重叠时尝试创建一个超出位图边界的选择或更多边。
  • 这对我帮助很大,我理解了主题的逻辑。再次感谢。当我选择列索引为 0 的起始单元格时出现此错误。我为此部分添加了条件'= new Point(cellStart.X - 1, cellStart.Y - 1);'。我猜如果单元格列索引为 0,cellStart.X 将为负数(-1),所以这是错误的原因。
猜你喜欢
  • 2015-12-14
  • 2014-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多