7

I'm working on a WinForm project and "trying" to create a TableLayoutPanel that the user can resize at runtime like the behavior of the SplitContainer. I've found some code that partially does this but it's incomplete. Can someone please help me out here?

Thanks in advance, -DA

This is the code I have so far that comes from a thread I found on CodeProject. The only thing different that I've done in my own is create a customTableLayoutPanel that inherits from TableLayoutPanel.

public partial class Form1 : Form
{
    bool resizing = false;
    TableLayoutRowStyleCollection rowStyles;
    TableLayoutColumnStyleCollection columnStyles;

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        rowStyles = tableLayoutPanel1.RowStyles;
        columnStyles = tableLayoutPanel1.ColumnStyles;
    }

    private void tableLayoutPanel1_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            resizing = true;
        }
    }

    private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e)
    {
        if (resizing)
        {
            columnStyles[0].SizeType = SizeType.Absolute;
            rowStyles[0].SizeType = SizeType.Absolute;
            rowStyles[0].Height = e.Y;
            columnStyles[0].Width = e.X;
        }
    }

    private void tableLayoutPanel1_MouseUp(object sender, MouseEventArgs e)
    {
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            resizing = false;
        }
    }
}
StaWho
  • 2,488
  • 17
  • 24
Max Eisenhardt
  • 442
  • 2
  • 9
  • 20

5 Answers5

5

Set Column SizeType and Row SizeType property as Absolute and CellBorderStyle as which ever you want rather than none after that in code write as below

public partial class Form1 : Form
{
    bool resizing = false;
    TableLayoutRowStyleCollection rowStyles;
    TableLayoutColumnStyleCollection columnStyles;
    int colindex = -1;
    int rowindex = -1;
    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        rowStyles = tableLayoutPanel1.RowStyles;
        columnStyles = tableLayoutPanel1.ColumnStyles;
    }
    private void tableLayoutPanel1_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            rowStyles = tableLayoutPanel1.RowStyles;
            columnStyles = tableLayoutPanel1.ColumnStyles;
            resizing = true;
        }
    }

    private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e)
    {
        if (!resizing)
        {
            float width = 0;
            float height = 0;
            //for rows
            for (int i = 0; i < rowStyles.Count; i++)
            {
                height += rowStyles[i].Height;
                if (e.Y > height - 3 && e.Y < height + 3)
                {
                    rowindex = i;
                    tableLayoutPanel1.Cursor = Cursors.HSplit;
                    break;
                }
                else
                {
                    rowindex = -1;
                    tableLayoutPanel1.Cursor = Cursors.Default;
                }
            }
            //for columns
            for (int i = 0; i < columnStyles.Count; i++)
            {
                width += columnStyles[i].Width;
                if (e.X > width - 3 && e.X < width + 3)
                {
                    colindex = i;
                    if (rowindex > -1)
                        tableLayoutPanel1.Cursor = Cursors.Cross;
                    else
                        tableLayoutPanel1.Cursor = Cursors.VSplit;
                    break;
                }
                else
                {
                    colindex = -1;
                    if (rowindex == -1)
                        tableLayoutPanel1.Cursor = Cursors.Default;
                }
            }
        }
        if (resizing && (colindex>-1 || rowindex > -1))
        {
            float width = e.X;
            float height = e.Y;
            if (colindex > -1)
            {
                for (int i = 0; i < colindex; i++)
                {
                    width -= columnStyles[i].Width;
                }
                columnStyles[colindex].SizeType = SizeType.Absolute;
                columnStyles[colindex].Width = width;
            }
            if (rowindex > -1)
            {
                for (int i = 0; i < rowindex; i++)
                {
                    height -= rowStyles[i].Height;
                }

                rowStyles[rowindex].SizeType = SizeType.Absolute;
                rowStyles[rowindex].Height = height;
            }
        }
    }

    private void tableLayoutPanel1_MouseUp(object sender, MouseEventArgs e)
    {
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            resizing = false;
            tableLayoutPanel1.Cursor = Cursors.Default;
        }
    }
}
Mehul Patel
  • 1,084
  • 2
  • 12
  • 28
  • This work fine if the CellBorderStyle is None. Otherwise 1 or more pixels must be added for each row/column.. – TaW Oct 05 '20 at 14:49
2

I improved the code of MD's answer.
Instead of listening to MouseEvents of the TableLayoutPanel I listen to those of the Controls. Here is the code for resizable rows:

    TableLayoutPanel tlp;
    bool resizing;
    int rowindex = -1;
    int nextHeight;

    private void control_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            resizing = true;
        }
    }

    private void control_MouseMove(object sender, MouseEventArgs e)
    {
        Control c = (Control)sender;
        if (!resizing)
        {
            rowindex = -1;
            tlp.Cursor = Cursors.Default;

            if (e.Y <= 3)
            {
                rowindex = tlp.GetPositionFromControl(c).Row - 1;
                if (rowindex >= 0)
                    tlp.Cursor = Cursors.HSplit;
            }
            if (c.Height - e.Y <= 3)
            {
                rowindex = tlp.GetPositionFromControl(c).Row;
                if (rowindex < tlp.RowStyles.Count)
                    tlp.Cursor = Cursors.HSplit;
            }
        }
        if (resizing && rowindex > -1)
        {
            nextHeight = e.Y;
        }
    }

    private void control_MouseUp(object sender, MouseEventArgs e)
    {
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            if (nextHeight > 0)
                tlp.RowStyles[rowindex].Height = nextHeight;
            resizing = false;
        }
    }
Breeze
  • 2,010
  • 2
  • 32
  • 43
1

My answer is for using the mouse to resize only the columns of the tablelayoutpanel but can be easily updated to do the same for rows. I'd rather use a splitpanel for the rows of course, but still. In the code I cater for 4 columns but extending it to more or less is easy enough. The cursurToDefault event will be added to the mousemove-event of the container controls inside the tablelayoutpanel.

 private int beamMoving;
 private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e)
    {
        this.Cursor = Cursors.VSplit;
        if (e.Button == System.Windows.Forms.MouseButtons.Left)
        {
            int beam0 = (int)tableLayoutPanel1.ColumnStyles[0].Width;
            int beam1 = (int)tableLayoutPanel1.ColumnStyles[0].Width + (int)tableLayoutPanel1.ColumnStyles[1].Width;
            int beam2 = (int)tableLayoutPanel1.ColumnStyles[0].Width + (int)tableLayoutPanel1.ColumnStyles[1].Width + (int)tableLayoutPanel1.ColumnStyles[2].Width;

            switch (beamMoving)
            {
                case 0:
                    {
                        if (e.X > 0)
                        {
                            tableLayoutPanel1.ColumnStyles[0].Width = e.X;
                        }
                        break;
                    }
                case 1:
                    {
                        if (e.X - beam0 > 0)
                        {
                            tableLayoutPanel1.ColumnStyles[1].Width = e.X - beam0;
                        }
                        break;
                    }
                case 2:
                    {
                        if (e.X - beam1 > 0)
                        {
                            tableLayoutPanel1.ColumnStyles[2].Width = e.X - beam1;
                        }
                        break;
                    }
            }
        }
    }
    private void cursorToDefault(object sender, MouseEventArgs e)
    {
        this.Cursor = Cursors.Default;
    }

    private void tableLayoutPanel1_MouseDown(object sender, MouseEventArgs e)
    {
        int beam0 = (int)tableLayoutPanel1.ColumnStyles[0].Width;
        int beam1 = (int)tableLayoutPanel1.ColumnStyles[0].Width + (int)tableLayoutPanel1.ColumnStyles[1].Width;
        int beam2 = (int)tableLayoutPanel1.ColumnStyles[0].Width + (int)tableLayoutPanel1.ColumnStyles[1].Width + (int)tableLayoutPanel1.ColumnStyles[2].Width;
        if (e.X + 20 > beam0 && e.X - 20 < beam1)
        {
            beamMoving = 0;
        }
        if (e.X + 20 > beam1 && e.X - 20 < beam2)
        {
            beamMoving = 1;
        }
        if (e.X + 20 > beam2 && e.X - 20 < tableLayoutPanel1.Width)
        {
            beamMoving = 2;
        }                    
    }
0

Wrote A Module based on the original code

This uses a dictionary and 4 event handlers for multiple tablelayoutpanel to have their rows and columns re-sized.

There is some code in there for catching moving things off the screen but not much.

Read the comments in the code for more information

Module modResizeableTableLayoutPanel

Private otlpdic As Dictionary(Of String, Integer()) = Nothing


 ''' <summary>
 ''' Adds the needed handlers for resizing the rows and columns of multiple
 ''' tablelayoutpanel uses the tag property
 ''' also requires primary controls in the cells to have a margin of 6... this is
 ''' a hack to detect when the mouse
 ''' is no longer over a divider and not over a control in the cell
 ''' All rows and columns require absolute positioning
 ''' </summary>
 ''' <param name="otlp">tablelayoutpanel</param>
 ''' <remarks></remarks>
Public Sub InitializeResizeableTableLayOutPanel(ByRef otlp As TableLayoutPanel)
    'create has if needed
    Call CreateTableLayoutPanelHash()

    'creat hash entry for TableLayoutPanel
    Call CreateHashEntryForTableLayoutPanel(otlp)

    'sets the border style to something that helps the mouse cursor detect.
    otlp.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single

    'hack to set margins on sub controls
    For Each con In otlp.Controls
        Select Case con.GetType
            Case GetType(FlowLayoutPanel)
                Dim obj As FlowLayoutPanel = DirectCast(con, FlowLayoutPanel)
                obj.Margin = New Padding(6)
            Case GetType(DataGridView)
                Dim obj As DataGridView = DirectCast(con, DataGridView)
                obj.Margin = New Padding(6)
            Case GetType(Button)
                Dim obj As Button = DirectCast(con, Button)
                obj.Margin = New Padding(6)
            Case GetType(GroupBox)
                Dim obj As GroupBox = DirectCast(con, GroupBox)
                obj.Margin = New Padding(6)
        End Select
    Next

    'add event handlers
    AddHandler otlp.MouseDown, AddressOf tlp_MouseDown
    AddHandler otlp.MouseUp, AddressOf tlp_MouseUp
    AddHandler otlp.MouseMove, AddressOf tlp_MouseMove
    AddHandler otlp.MouseLeave, AddressOf tlp_MouseLeave
End Sub

''' <summary>
''' Clear the dictionary/hashtable that keeps track of needed variables to control the resizing 
''' </summary>
''' <remarks></remarks>
Public Sub ResetResizeableTableLayOutPanelData()
    otlpdic.Clear()
    otlpdic = Nothing
    Call CreateTableLayoutPanelHash()
End Sub

Private Sub CreateTableLayoutPanelHash()
    If otlpdic Is Nothing Then
         otlpdic = New Dictionary(Of String, Integer())
    End If
End Sub

Private Sub CreateHashEntryForTableLayoutPanel(ByRef otlp As TableLayoutPanel)
    If Not otlp.Tag Is Nothing AndAlso otlpdic.ContainsKey(otlp.Tag) Then

    Else
        Dim tmpguid As Guid = Guid.NewGuid

        otlp.Tag = tmpguid.ToString

        otlpdic.Add(otlp.Tag, {0, -1, -1})
    End If
End Sub

Public Sub tlp_MouseDown(sender As Object, e As System.Windows.Forms.MouseEventArgs)
    If e.Button = System.Windows.Forms.MouseButtons.Left Then
        Dim otlp As TableLayoutPanel = DirectCast(sender, TableLayoutPanel)
        Call CreateHashEntryForTableLayoutPanel(otlp)
        Dim tmpdata As Integer() = otlpdic.Item(otlp.Tag)
        tmpdata(0) = -1

    End If
End Sub


Public Sub tlp_MouseMove(sender As Object, e As System.Windows.Forms.MouseEventArgs)
    Dim otlp As TableLayoutPanel = DirectCast(sender, TableLayoutPanel)
    Call CreateHashEntryForTableLayoutPanel(otlp)
    Dim tmpdata As Integer() = otlpdic.Item(otlp.Tag)

    'Dim rowStyles = 
        'columnStyles = TableLayoutPanel1.ColumnStyles

    If Not tmpdata(0) Then

        Dim width As Double = 0
        Dim height As Double = 0
        'for rows
        For i As Integer = 0 To otlp.RowStyles.Count - 1

            height += otlp.RowStyles(i).Height

            If (e.Y > height - 2 And e.Y < height + 2) Then

                tmpdata(1) = i
                otlp.Parent.Cursor = Cursors.HSplit
                Exit For
            Else
                tmpdata(1) = -1
                otlp.Parent.Cursor = Cursors.Default
            End If
        Next
        'for columns
        For i As Integer = 0 To otlp.ColumnStyles.Count - 1

            width += otlp.ColumnStyles(i).Width
            If e.X > width - 2 And e.X < width + 2 Then

                tmpdata(2) = i
                If (tmpdata(1) > -1) Then
                    otlp.Parent.Cursor = Cursors.Cross
                Else
                    otlp.Parent.Cursor = Cursors.VSplit
                    Exit For
                End If
            Else

                tmpdata(2) = -1
                If (tmpdata(1) = -1) Then
                    otlp.Parent.Cursor = Cursors.Default
                End If
            End If
        Next
    End If
    If (tmpdata(0) And (tmpdata(2) > -1 Or tmpdata(1) > -1)) Then

        Dim Width As Double = e.X
        Dim Height As Double = e.Y

        If e.X > otlp.Width - 100 Then
            Exit Sub
        End If

        If e.Y > otlp.Height - 100 Then
            Exit Sub
        End If



        If (tmpdata(2) > -1) Then
            For i As Integer = 0 To tmpdata(2) - 1

                Width -= otlp.ColumnStyles(i).Width
            Next
            otlp.ColumnStyles(tmpdata(2)).SizeType = SizeType.Absolute


            If Width < 10 Then
                Width = 10
            End If

            If Width > otlp.Width - 100 Then
                Width = otlp.Width - 100
            End If



            otlp.ColumnStyles(tmpdata(2)).Width = Width
        End If
        If (tmpdata(1) > -1) Then

            For i As Integer = 0 To tmpdata(1) - 1

                Height -= otlp.RowStyles(i).Height
            Next
            otlp.RowStyles(tmpdata(1)).SizeType = SizeType.Absolute

            If Height < 10 Then
                Height = 10
            End If

            If Height > otlp.Height - 100 Then
                Height = otlp.Height - 100
            End If


            otlp.RowStyles(tmpdata(1)).Height = Height
        End If
    End If
End Sub


Public Sub tlp_MouseLeave(sender As Object, e As System.EventArgs)
    Dim otlp As TableLayoutPanel = DirectCast(sender, TableLayoutPanel)
    otlp.Parent.Cursor = Cursors.Default
End Sub

Public Sub tlp_MouseUp(sender As Object, e As System.Windows.Forms.MouseEventArgs)
    If e.Button = System.Windows.Forms.MouseButtons.Left Then
        Dim otlp As TableLayoutPanel = DirectCast(sender, TableLayoutPanel)
        Dim tmpdata As Integer() = otlpdic.Item(otlp.Tag)
        tmpdata(0) = 0
        otlp.Parent.Cursor = Cursors.Default
    End If
End Sub
End Module
0

I modified this a little bit, the original code was not lining up with the borders. I also wanted my columns to auto size by default when the application loads. I used tableLayoutPanel1.GetRowHeights() and tableLayoutPanel1.GetColumnWidths() which I found from https://stackoverflow.com/a/51993386/13729116 which Jazimov explained that this is hidden from Intellisense because it is not suppose to work with controls that span columns. I tested this and it does work with a weird bit that you can move the column hidden by the spanned control in the padding areas around the control. I am sure you could work around this if you wanted to.

 private void tableLayoutPanel1_MouseMove(object sender, MouseEventArgs e)
    {            
        if (!resizing)
        {
            float width = 0;
            float height = 0;
            int[] rowHeights = tableLayoutPanel1.GetRowHeights();
            int[] colWidths = tableLayoutPanel1.GetColumnWidths();
            //for rows
            for (int i = 0; i < rowHeights.Count(); i++)
            {
                height += rowHeights[i];
                if (e.Y > height - 3 && e.Y < height + 3)
                {
                    rowindex = i;
                    tableLayoutPanel1.Cursor = Cursors.HSplit;
                    break;
                }
                else
                {
                    rowindex = -1;
                    tableLayoutPanel1.Cursor = Cursors.Default;
                }
            }
            //for columns
            for (int i = 0; i < colWidths.Count(); i++)
            {
                width += colWidths[i];
                if (e.X > width - 3 && e.X < width + 3)
                {
                    colindex = i;
                    if (rowindex > -1)
                        tableLayoutPanel1.Cursor = Cursors.Cross;
                    else
                        tableLayoutPanel1.Cursor = Cursors.VSplit;
                    break;
                }
                else
                {
                    colindex = -1;
                    if (rowindex == -1)
                        tableLayoutPanel1.Cursor = Cursors.Default;
                }
            }
        }
        if (resizing && (colindex > -1 || rowindex > -1))
        {
            float width = e.X;
            float height = e.Y;
            int[] rowHeights = tableLayoutPanel1.GetRowHeights();
            int[] colWidths = tableLayoutPanel1.GetColumnWidths();
            if (colindex > -1)
            {
                for (int i = 0; i < colindex; i++)
                {
                    width -= colWidths[i];
                }
                columnStyles[colindex].SizeType = SizeType.Absolute;
                columnStyles[colindex].Width = width;
            }
            if (rowindex > -1)
            {
                for (int i = 0; i < rowindex; i++)
                {
                    height -= rowHeights[i];
                }

                rowStyles[rowindex].SizeType = SizeType.Absolute;
                rowStyles[rowindex].Height = height;
            }
        }
    }
Hack.Sign
  • 9
  • 2