The TableLayoutPanel, for some reason, doesn't expose the Cells bounds directly, through a Property or Method. But of course it calculates these values for internal use, since it needs to paint the borders of its Cells.
Its OnPaintBackground method is responsible for this.
When the Control needs to repaint itself, the Cells bounds are recalculated and, for each Cell, the Control raises the CellPaint event. Its TableLayoutCellPaintEventArgs argument returns both the current Cell's bounds and Column/Row coordinates.
Since this event is raised each time the TableLayoutPanel is modified/resized, we can use the CellPaint
event handler to store these references.
Here, I'm using a Dictionary<TableLayoutPanelCellPosition, Rectangle>
to store the coordinates of each Cell and map it to its bounds.
The Layout event handler sets a boolean Field that causes the Dictionary to be rebuild only when necessary (because the TableLayoutPanel's Layout has changed).
▶ The Dictionary Key is a TableLayoutPanelCellPosition because it allows a quick and simple equality comparisons. Of course you can use a different type of object, if needed.
▶ Assume the TableLayoutPanel is named tlp1
.
▶ Adding and/or removing Columns/Rows at run-time is not handled. In case it's needed, re-define the Dictionary in the Layout
event handler instead of the Form Constructor.
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;
}
}
}
To get the Cell from the current Mouse position, call GetSelectedCell()
, passing the current Pointer location, relative to the 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;
Now, to generate a Bitmap from a range of Cells, you just need to specify the starting (Left, Top) and ending (Right, Bottom) Cells and pass these values references to the TlpCellRangeToBitmap()
method (it could also be used as an extension method).
If the includeBorders
argument is set to true
, it will include the external borders of the Cells (see the visual example). Add code that checks whether the TableLayoutPanel actually has borders (BorderStyle
Property).
The Rectangle that includes the range of Cells is returned by GetCellRangeBounds()
: this method is independent and can be used for other purposes.
For example, set the Image property of a PictureBox to the generated 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));
}
This is how it works:
