Scaling WinForms TableLayoutPanel - c#

I have an issue with TableLayoutPanel, which fills an userControl (DockStyle.Fill). I face a trouble, when this control is being resized, I need all cells to change their size, but only last row and last column changes size (so whole tableLayoutPanel fills the control). I change this controler size using Bounds property.
let's say I wrote following code:
// creating tableLayoutPanel:
private void createTableLayoutPanel(int count)
{
tableLayoutPanel = new TableLayoutPanel();
tableLayoutPanel.MouseClick += new MouseEventHandler(this.observe_MouseClick);
tableLayoutPanel.AutoSize = true;
tableLayoutPanel.ColumnCount = 3;
tableLayoutPanel.RowCount = count;
tableLayoutPanel.Dock = DockStyle.Fill;
tableLayoutPanel.AutoSize = true;
this.Controls.Add(tableLayoutPanel);
}
// resizing:
private void OnMouseWheel(MouseEventArgs e)
{
this.Bounds = new Rectangle(this.Location.X, this.Location.Y, (int)(this.Width*newScale),(int)(this.Height*newScale));
}
Thanks for any help.

For each row, add an item to the TableLayoutPanel's RowStyles collection with SizeType = SizeType.Percent and Height = 100 / tableLayoutPanel.RowCount. Do the same with the ColumnStyles collection.

Related

Auto resize to fill all multi panels

How to size all panels as fill in form1 window without changing panels size? I searched on Google. very difficult to find. That why i want to help. like in below Thanks.
If 3 panels this will be resized:
4 Panels:
5 Panels:
9 Panels:
You can use a FlowLayoutPanel. Insert in your form and set Dock=Fill in the designer. Add this const to your form:
private const int PanelSize = 200;
In the constructor:
this.flowLayoutPanel1.Resize += this.OnFlowLayoutPanel1_Resize;
this.OnFlowLayoutPanel1_Resize(this.flowLayoutPanel1, EventArgs.Empty);
And use this method to create/destroy the panels:
private void OnFlowLayoutPanel1_Resize(object sender, EventArgs e)
{
// At least, one panel
var columns = Math.Max(1, this.flowLayoutPanel1.Width / (double)PanelSize);
var rows = Math.Max(1, this.flowLayoutPanel1.Height / (double)PanelSize);
var panelsCount = this.flowLayoutPanel1.Controls.Count;
var requiredPanelsCount = rows * columns;
var diff = requiredPanelsCount - panelsCount;
if (diff > 0)
{
// We need more panels: create it
for (int i = 0; i < diff; i++)
{
var panel = new Panel
{
Size = new Size(PanelSize, PanelSize),
TabIndex = this.flowLayoutPanel1.Controls.Count,
BackColor = GetBackColor()
};
this.flowLayoutPanel1.Controls.Add(panel);
}
}
else
{
// Remove unneeded panels
for (int i = 0; i < diff; i++)
{
this.flowLayoutPanel1.Controls.RemoveAt(
this.flowLayoutPanel1.Controls.Count - 1);
}
}
}
I have used this method to set a color:
private Color GetBackColor()
{
var colors = new[]
{
Color.Black, Color.Red, Color.Green, Color.Blue, Color.White
};
var index = this.flowLayoutPanel1.Controls.Count % colors.Length;
return colors[index];
}
Adapt it to your needs.
If you need fill entire form, you must work with a variable PanelSize. Use a minimum and maximum size for the panels instead a fixed value.
I'm creating/removing Panels to fill entire space. If you only need fixed panels in the form, you don't need the resize event. The FlowLayoutControl do what you need.

Resize TableLayoutPanel rows only as cells are dynamically populated

I have a TableLayoutPanel, ug_degrees, with 3 columns and 1 row. Each cell gets dynamically populated with another TableLayoutPanel, degreePanel, containing 1 label and 1 textbox.
I need to get my layout to look something like this:
Right now my layout looks like this:
I'm at a loss for why I have those giant gaps between the cells, and why the row will not expand to fill its contents (a label & a textbox). I have tried to set the whole TableLayoutPanel's autosize property to true, but the columns get resized, even if I set only the rows' sizetype to autosize as well.
The properties behind the table shown are default. All non-default properties are customized in C# below.
// Dynamically load undergraduate degrees
int row = 0;
for (int i = 0; i < degrees.undergraduate.Count; i++) {
// Create and populate panel for each degree
TableLayoutPanel degreePanel = new TableLayoutPanel();
degreePanel.ColumnCount = 1;
degreePanel.RowCount = 2;
degreePanel.AutoSize = true;
foreach (RowStyle style in degreePanel.RowStyles) {
style.SizeType = SizeType.AutoSize;
}
degreePanel.BorderStyle = BorderStyle.FixedSingle;
//degreePanel.Margin = new Padding(0);
Label degTitle = new Label();
degTitle.Text = degrees.undergraduate[i].title;
degTitle.Dock = DockStyle.Fill;
TextBox degDesc = new TextBox();
degDesc.ReadOnly = true;
degDesc.Multiline = true;
degDesc.Dock = DockStyle.Fill;
degDesc.Text = degrees.undergraduate[i].description;
SizeF size = degDesc.CreateGraphics()
.MeasureString(degDesc.Text,
degDesc.Font,
degDesc.Width,
new StringFormat(0));
degDesc.Height = (int)size.Height;
degreePanel.Controls.Add(degTitle, 0, 0);
degreePanel.Controls.Add(degDesc, 0, 1);
ug_degrees.Controls.Add(degreePanel, i, row);
// Resize rows and columns (only after adding controls)
foreach (RowStyle style in ug_degrees.RowStyles) {
style.SizeType = SizeType.AutoSize;
}
// Jump to next row if current row is full
if ((i+1) % 3 == 0) {
row++;
}
Add degreePanel.Dock = DockStyle.Fill

How can a scrollable area retrieve the size of what is being displayed?

In my application I've got the following situation:
I've got a Windows Form with a Tab Control with several tabs. Each tab contains arbitrary content which is added by other classes upon startup or during runtime.
I want to set up the tabs in a way that scrollbars appear automatically as soon as the Form is too small for the tab's panel to display everything.
What I've tried so far is setting the tab page's AutoScroll = true and setting the AutoScrollMinSize property to the size of the panel.
This did not work as expected as the panel's Size always seems to be (200, 100) independent of its contents.
I've created a small example application (code below) which demonstrates the issue. If you resize the form, you'll see that scroll bars only appear if the Form gets smaller than the panel (default size of (200, 100)) rather than the text box in the panel (size of 300, 150). If you set AutoScrollMinSize manually (uncomment line 34), it behaves as expected.
The question is: How can the tab page retrieve the actual size of what is displayed in it?
I could probably recurse through all controls and try calculating the size myself - but this feels really bad.
PS: Please do not suggest setting the size of the panel to the size of the label, as the actual panels are much more complex than that. ;-)
Code:
Simply create an Application in Visual Studio and override Program.cs with the following code:
using System;
using System.Windows.Forms;
namespace ScrollbarTest
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var sampleForm = CreateSampleForm();
Application.Run(sampleForm);
}
private static Form CreateSampleForm()
{
var sampleForm = new Form() { };
var tabControl = new TabControl() { Dock = DockStyle.Fill };
var tabPage = new TabPage("Test") { AutoScroll = true };
sampleForm.Controls.Add(tabControl);
tabControl.TabPages.Add(tabPage);
var samplePanel = CreateSamplePanel();
tabPage.Controls.Add(samplePanel);
// this does not provide the right size
tabPage.AutoScrollMinSize = samplePanel.Size;
// uncomment this to make it work
//tabPage.AutoScrollMinSize = new System.Drawing.Size(300, 150);
return sampleForm;
}
private static Control CreateSamplePanel()
{
// As an example, create a panel with a text box with a fixed size.
var samplePanel = new Panel() { Dock = DockStyle.Fill };
var sampleSize = new System.Drawing.Size(300, 150);
var textBox = new TextBox()
{
Dock = DockStyle.Fill,
MinimumSize = sampleSize,
MaximumSize = sampleSize,
Size = sampleSize
};
samplePanel.Controls.Add(textBox);
return samplePanel;
}
}
}
The samplePanel.Size returns (200,100). In your CreateSamplePanel method, if you set samplePanel.MinimumSize = sampleSize; then your code will work.
Panels don't calculate their size properties (e.g. Size, MinimumSize, PreferredSize) based on their child controls. You will have to subclass Panel and provide that behavior. Even TableLayoutPanel and FlowLayoutPanel don't correctly calculate the PreferredSize property, which is surprising. At the very least, normally you override the GetPreferredSize(Size proposedSize) method, and optionally have the MinimumSize property return the PreferredSize property.
It's worth noting that DockStyle.Fill and MinimumSize are at odds with each other. TabPage controls are inherently DockStyle.Fill mode, which is why you have to set the AutoScrollMinSize property.
Edit: Isn't there any existing function which retrieves the total required size of a list of controls (recursively), e.g. through their X/Y and Size?
It's up to the host container itself (e.g. TableLayoutPanel) to calculate its PreferredSize correctly because only it knows the exact details of how its layout is performed.
You can set the AutoSize property to true and then hope that GetPreferredSize(...)/PreferredSize calculates the right size. For TableLayoutPanel, I recall there was a case where it wasn't calculating correctly and I had to subclass it and override the GetPreferredSize(...) method. GetPreferredSize(...) won't be called unless AutoSize is true.
If you're talking about a plain Panel or UserControl, by default these use the WYSIWYG LayoutEngine, and do not calculate the PreferredSize. You could subclass and then calculate maximum control.X + control.Width and same thing for height, and use that as the preferred size.
First try setting AutoSize to true and see if that works for you. If not, you might have to override the GetPreferredSize(...) method. Here is a crude example:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var sampleForm = new Form() { AutoScroll = true };
var panel = new MyPanel() { AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink, BackColor = Color.LightYellow };
for (int i = 0; i < 6; i++) {
for (int j = 0; j < 3; j++) {
Button b = new Button { Text = "Button" + panel.Controls.Count, AutoSize = true };
b.Click += delegate {
MessageBox.Show("Preferred Size: " + panel.PreferredSize);
};
panel.Controls.Add(b, j, i);
}
}
sampleForm.Controls.Add(panel);
Application.Run(sampleForm);
}
private class MyPanel : TableLayoutPanel {
public override Size MinimumSize {
get {
return PreferredSize;
}
set {
}
}
public override Size GetPreferredSize(Size proposedSize) {
Size s = new Size();
int[] harr = new int[100];//this.RowCount];
int[] warr = new int[100];//this.ColumnCount];
foreach (Control c in this.Controls) {
var cell = this.GetPositionFromControl(c);
var ps = c.PreferredSize;
Padding m = c.Margin;
int w = ps.Width + m.Horizontal;
int h = ps.Height + m.Vertical;
if (w > warr[cell.Column])
warr[cell.Column] = w;
if (h > harr[cell.Row])
harr[cell.Row] = h;
}
foreach (int w in warr)
s.Width += w;
foreach (int h in harr)
s.Height += h;
return s;
}
}

c# Relative position of elements in Windows Form

I try to create simple app with 2 columns using SpliterContainer and control panel with buttons. And I would like that on every screen it will look good. That's why I decided to use relative position of elements.
I read documentation and different forums, but I get something strange. Second column of splitter doesn't appear at all.
Please, can you help me find the reason of that problem?
private void Form1_Load(object sender, EventArgs e)
{
WindowState = FormWindowState.Maximized;
int screenWidth = Screen.PrimaryScreen.Bounds.Width;
int screenHeight = Screen.PrimaryScreen.Bounds.Height;
//set form size
this.Size = new Size(screenWidth, screenHeight);
//set button panel size
const double percentOfHeightPanel = 0.05;
int heightOfPanelButton = Convert.ToInt32(screenHeight * percentOfHeightPanel);
this.panel_button.Size = new System.Drawing.Size(screenWidth, heightOfPanelButton);
this.panel_button.Location = new Point(0, 0);
//set splitContainer size
int widthOfContainer = Convert.ToInt32(0.5 * screenWidth);
int heightOfContainers = Convert.ToInt32(screenHeight * (0.95));
splitContainer1.Panel1.MinimumSize = new Size(widthOfContainer, heightOfContainers);
splitContainer1.Panel2.MinimumSize = new Size(widthOfContainer, heightOfContainers);
splitContainer1.Location = new Point(0, heightOfPanelButton);
//this.splitContainer1.Panel2MinSize = screenWidth - widthOfContainer;
//set textBox size
this.textBox1.Multiline = true;
this.textBox1.Location = new Point(0, heightOfPanelButton);
this.textBox1.MinimumSize = new System.Drawing.Size(widthOfContainer, heightOfContainers);
this.textBox2.Multiline = true;
this.textBox2.Location = new Point(widthOfContainer, heightOfPanelButton);
this.textBox1.MinimumSize = new System.Drawing.Size(widthOfContainer, heightOfContainers);
}
If you want two have two splitter panels of the same size set
splitContainer1.SplitterDistance =
(splitContainer1.Width - splitContainer1.SplitterWidth) / 2;
Then set
splitContainer1.IsSplitterFixed = true;
You can set these two properties manually at design time. The user will then not be able to resize the panels and the panels will automatically resize to be of same size.
Consider using a TableLayoutPanel instead.
If further, the two sides should look the same, place your controls on a UserControl and place two instances of them into the two panels with a docked property set to Fill.

Draw only outer border for TableLayoutPanel Cells

I am using the TableLayoutPanel for example if I have 3 rows and 5 columns. I want to draw only the outer border for the entire panel. By default the the panel provides CellBorderStyle which adds all side borders to all the cells available. Is there any way where we can set only outside borders?
I have provided a sample code below.
TableLayoutPanel tblPanel = new TableLayoutPanel;
tblPanel.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
Label lblName;
TextBox txtName;
Button btnAdd;
int colCnt = 0;
for(int rw =0; rw < 3; rw++)
{
lblName = new Label();
lblName.Name = "mylabel" + rw.ToString();
tblPanel.Controls.Add(lblName, colCnt, rw);
colCnt++;
txtName = new TextBox();
txtName.Name = "mytext" + rw.ToString();
tblPanel.Controls.Add(txtName, colCnt, rw);
colCnt++;
btnAdd = new Button();
btnAdd.Name = "mybutton" + rw.ToString();
tblPanel.Controls.Add(btnAdd, colCnt, rw);
colCnt = 0;
}
TableLayoutPanel does in fact support the BorderStyle property, which is what you want. For example:
tableLayoutPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
https://msdn.microsoft.com/en-us/library/system.windows.forms.tablelayoutpanel.borderstyle(v=vs.110).aspx
It is decorated with:
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
So Intellisense won't show it to you, but it is documented and it works. I have no insight into why it is non-browsable.
You'd be better off painting the cell border yourself. Something along the following lines, then customize:
public TableForm() {
InitializeComponent();
this.tableLayoutPanel.CellPaint += tableLayoutPanel_CellPaint;
}
private void tableLayoutPanel_CellPaint(object sender, TableLayoutCellPaintEventArgs e) {
var topLeft = e.CellBounds.Location;
var topRight = new Point(e.CellBounds.Right, e.CellBounds.Top);
e.Graphics.DrawLine(Pens.Black, topLeft, topRight);
}
At design-time:
At runtime:
You can achieve by changing the property CellBorderStyle to Single or desired selection.
Property Change :
Sample :
TableLayOutPanel itself does not support a property for border except CellBorderStyle which is not what you want.
I suggest you to put your TableLayOutPanel into a Panel control and set Dock property of your TableLayOutPanel to Fill.
Then Set BorderStyle of Panel to what you want (FixedSingle or Fixed3D)
public TestForm()
{
InitializeComponent();
tableLayoutPanel.Paint += tableLayoutPanel_Paint;
}
private void tableLayoutPanel_Paint(object sender, PaintEventArgs e){
e.Graphics.DrawRectangle(new Pen(Color.Blue), e.ClipRectangle);
}

Categories