Keeping controls centralized in a TableLayoutPanel - c#

I am starting to use C# and am trying to create a Form which will contain many different controls. In order to keep things simple, I am using a TableLayoutPanel to deal with the formatting. However, I'd like all the controls to be centralized within their respective cells. After some searching, I found this page which shows that to do so, you can merely set control.Anchor = AnchorStyles.None and the control will be centered in its cell.
This does indeed work quite well, but I've now found an odd behavior. I'm starting to build the form now, so it's completely bare bones, with a simple graph above and a single Textbox below it. Once I'm done, the graph will occupy the entire first row of the panel and all the rest of the controls will be distributed below it.
Therefore, I was going to simply set panel.SetColumnSpan(graph, 2) (in the case of two columns). That works just as expected, except that now the TextBox below is no longer centralized.
Here's the code I have so far:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Form form = new Form();
form.AutoSize = true;
form.FormBorderStyle = FormBorderStyle.FixedDialog;
Chart chart = new Chart();
chart.Anchor = AnchorStyles.None;
//...
TextBox text = new TextBox();
text.Text = "A";
text.Anchor = AnchorStyles.None;
TableLayoutPanel box = new TableLayoutPanel();
box.AutoSize = true;
box.RowCount = 2;
box.ColumnCount = 2;
box.Controls.Add(chart,0,0);
box.SetColumnSpan(chart, 2);
box.Controls.Add(text,0,1);
form.Controls.Add(box);
form.ShowDialog();
}
}
}
Here are the results with the box.SetColumnSpan commented out:
And with it active:
UPDATE:
Setting the TextBox with ColumnSpan(2) as well works, but it somewhat beats the point. For instance, if I want to have two TextBoxes on the second row, I'd want them each centered within their respective cells.
In this case, I now add a second Textbox:
TextBox text2 = new TextBox();
text2.Text = "B";
text2.Anchor = AnchorStyles.None;
And add that to the panel:
TableLayoutPanel box = new TableLayoutPanel();
box.AutoSize = true;
box.RowCount = 2;
box.ColumnCount = 2;
box.Controls.Add(chart,0,0);
box.SetColumnSpan(chart, 2);
box.Controls.Add(text,0,1);
box.Controls.Add(text2, 1, 1);
However, once again the result is unsatisfactory: each Textbox is clearly "left-justified".

UPDATE: your code is missing colum styles. Just set as this, and your're done:
this.box.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.box.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
For your textbox to be aligned in the center of form (TableLayoutPanel), set column span to 2 too. If not, depending on text box size, it is centered in first column.
this.box.ColumnCount = 2;
this.box.RowCount = 2;
this.box.Controls.Add(this.chart, 0, 0);
this.box.Controls.Add(this.text, 0, 1);
this.box.SetColumnSpan(this.chart, 2);
this.text.Anchor = System.Windows.Forms.AnchorStyles.None;
Gives
And setting this:
this.box.SetColumnSpan(this.text, 2);
And without text column span, but with to text boxes:

Related

Resizing and Collapsing label in winforms

first time poster, sorry if something isn't as it should be.
I'm new to Winforms and am trying to build a simple application that will display multiple features of an item (like Size, Composition, etc.). Each Characteristic has a Name, can have a Descritpion, and some can have sub-characteristics (having also a name and sometimes a descritpion).
I want to display them one under the other, with the Name of the feature on a blue background that span the whole width of the container, with the description underneath. The name will be (or have) a button (or similar) that when clicked collapse or expand the description. This must be created at run time because I don't know how many feature an object has until the user generate it.
The issues I'm running in are that either I can't span the blue background the whole width of the container (if using a FlowLayoutPanel), or I have some issue with the Description text being not the right size (either it wraps but is too big, or it doesn't wrap and then I can't see the whole text).
Some things are fixed, mainly the number of main sections (like Size, Composition, Weather, etc.), so I can prepare the skeleton before runtime.
The closest i've been to making it work gives this. This issue here is that the height of the Panel which the description label is embded in is fixed, and if I put in in Autosize, the text don't show (probably because the label is in Fill dock style). Just as information, this is what it looks like when collapsed (this is indeed what I'm looking for)
I know some library exists with collapsible panels, but I'd rather try to make it work without external libraries. Thanks in advance for any help !
This is the code that produces the results in the screenshots :
Panel SizeDescrPanel = new Panel();
SizeDescrPanel.Dock = DockStyle.Top;
//SizeDescrPanel.AutoSize = true;
SizeDescrPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
SizeDescrPanel.BackColor = Color.Bisque;
SizePanel.Controls.Add(SizeDescrPanel);
Label SizeDescrLbl = new Label();
SizeDescrLbl.Text = Lorem;
SizeDescrLbl.AutoSize = false;
SizeDescrLbl.Dock = DockStyle.Fill;
SizeDescrLbl.BackColor = Color.BurlyWood;
SizeDescrPanel.Controls.Add(SizeDescrLbl);
/*using(Graphics g = CreateGraphics())
{
SizeF size = g.MeasureString(SizeDescrLbl.Text, SizeDescrLbl.Font, SizePanel.Width);
SizeDescrPanel.Height = (int) Math.Ceiling(size.Height);
}*/
Panel SizeNamePanel = new Panel();
SizeNamePanel.Dock = DockStyle.Top;
SizeNamePanel.BackColor = Color.Cyan;
SizeNamePanel.AutoSize = true;
SizePanel.Controls.Add(SizeNamePanel);
Button SizeNameBtn = new Button();
SizeNameBtn.Text = "<Size Name> ..." + SizeDescrLbl.Height;
SizeNameBtn.TextAlign = ContentAlignment.MiddleLeft;
SizeNameBtn.FlatStyle = FlatStyle.Flat;
SizeNameBtn.AutoSize = true;
SizeNamePanel.Controls.Add(SizeNameBtn);
SizeNameBtn.Click += delegate { HideShowPanel(SizeDescrPanel); };
It,s a test project, so later I'll put that in different methods. What isn't shown here :
I have a main panel set to Fill containing everything.
The text "SIZE" is a label set to Top
Under it is another Panel (SizePanel) that is set to Top and Autosize is at True. This is the Panel inside which I'm puttin my size name and my size description. If I had a subfeature, it would be included (ideally) inside descritpion with the same configuration (button expanding/collapsing the descritpion of the SubFeature)

Fast way to add Controls to a TableLayoutPanel

I have to add a lot of Control objects to a TableLayoutPanel dynamically, what takes a significant amount of time. I also need to be able to access the controls via row- and column-index of the TableLayoutPanel and vice versa.
TableLayoutPanel.Controls has (as far as I know) 3 ways to add Control objects:
.Add(Control) - inherited, position is -1,-1 with .GetCellPosition(Control)
.Add(Control, column, row) - position and indexes are correct, but maybe a bit slow?
.AddRange (Control[]) - inherited, faster, shown position is correct (every cell is filled, if necessary columnspans are set afterwards), but position is -1,-1 with .GetCellPosition(Control)
Is there a way to combine the advantages of .Add(Control, column, row and .AddRange(Control[]), i.e. to add a lot of Controlobjects fast to a TableLayoutPanel while still being able to get the position of a Control programmatically?
EDIT to include some information from the comments:
There are up to 1000 controls added
I already use SuspendLayout() and ResumeLayout()
The TableLayoutPanel takes about 2 seconds to load. According to the profiler roughly 50% of the time is spent with adding Controls, 20% with ResumeLayout()
EDIT: MCVE
My original code is more complex, but this is an example of a TableLayoutPanel where adding the controls takes most of the time spent (2/3). I am searching for a way to speed this up.
public class FormTLPTest : Form
{
public FormTLPTest()
{
Height = 800;
Width = 800;
TableLayoutPanel tlp = new TableLayoutPanel();
tlp.Dock = DockStyle.Fill;
tlp.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
tlp.AutoScroll = true;
Controls.Add(tlp);
tlp.ColumnCount = 7;
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 20));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100.0F));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 80));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 100));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 30));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 70));
tlp.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 20));
tlp.SuspendLayout();
for (int i = 0; i<700; i++)
{
Button btn1 = new Button();
Label lb2 = new Label();
Label lb3 = new Label();
Label lb4 = new Label();
TextBox tb5 = new TextBox();
Button btn6 = new Button();
Button btn7 = new Button();
foreach (Control c in new Control[] { btn1, lb2, lb3, lb4, tb5, btn6, btn7})
{
c.Margin = new Padding();
c.Dock = DockStyle.Fill;
c.BackColor = Color.White;
}
btn1.FlatStyle = FlatStyle.Flat;
btn6.FlatStyle = FlatStyle.Flat;
btn7.FlatStyle = FlatStyle.Flat;
btn1.Text = "1";
lb2.Text = "Some longer Text - it contains information. Don't know what I should write to fill the space";
lb3.Text = "Short Text";
lb4.Text = "Short Text";
tb5.Text = "5";
btn6.Text = "Button";
btn7.Text = "+";
tlp.Controls.Add(btn1, 0, i);
tlp.Controls.Add(lb2, 1, i);
tlp.Controls.Add(lb3, 2, i);
tlp.Controls.Add(lb4, 3, i);
tlp.Controls.Add(tb5, 4, i);
tlp.Controls.Add(btn6, 5, i);
tlp.Controls.Add(btn7,6, i);
}
tlp.ResumeLayout();
}
}
Looking at the Reference Source, you can see that Control.ControlCollection.AddRange method is nothing more than a Add loop enclosed in SuspendLayout / ResumeLayout. Since your code is also enclosed with such calls, there should not be a difference in the performance.
TableLayoutPanel does two additional calls - SetRow and SetColumn, so my first thought was that they are the slow parts. However, looking at the the source code (and measuring the time with or w/o those calls), when the layout engine is suspended, their affect on the performance are negligible.
I did some additional tests with your mcve by not using TableLayoutPanel at all and just adding controls to the form itself. The conclusion is - you just have too many controls. mcve is creating 4900 controls. This is too much for WinForms (and Windows in general). After running it, my Windows almost died.
So, the add control performance cannot be improved. But that should not be your main concern. Consider switching to DataGridView or some third party data repeater control which support a lot more number of rows w/o creating significant number of controls.

ComboBox.Item.AddRange(string[]) VS. ComboBox.DataSource = string[]

So, I have two options:
aComboBox.Item.AddRange(stringArray);
aComboBox.SelectedItem = stringFromStringArray;
and
aComboBox.DataSource = stringArray;
aComboBox.SelectedItem = stringFromStringArray;
Now, the first one is waaaaay slower when it comes to initialization (about 5-6 times). It does set the selected item properly, but still, it's very slow so I decided to go with the second one.
But, if I use the second one, the Items array within the aComboBox is not yet set when the second command is executed, so the selected item is the one at index 1, instead the one specified.
The question is, how do I get the performance of the second one with the functionality of the first one?
EDIT:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ComboBoxTest
{
class MainWindow : Form
{
string[] list = new string[1548];
TableLayoutPanel panel = new TableLayoutPanel();
public MainWindow() : base()
{
Height = 2000;
Width = 1000;
Random rand = new Random();
for (int i = 0; i < 1547; i++)
{
list[i] = rand.Next().ToString();
}
list[1547] = 5.ToString();
Button button = new Button();
button.Text = "Press me";
button.Click += Button_Click;
panel.Controls.Add(button, 0, 0);
panel.Height = 2000;
panel.Width = 1000;
Controls.Add(panel);
Show();
}
private void Button_Click(object sender, EventArgs e)
{
for (int i = 0; i < 36; i++)
{
ComboBox box = new ComboBox();
box.DataSource = list; //box.Items.AddRange(list);
box.SelectedItem = 5.ToString();
panel.Controls.Add(box, 0, i+1);
}
}
}
}
I reproduced the problem with this program. If you change it to addRange(), it will take a lot more time, but it will set the item.
Try adding a breakpoint to the SelectedItem and then look at the ComboBox.
If you set the one, the other will be null (DataSource vs. Items). The ComboBox seems to look into Items to check if the string exists within the list, and that's why it fails with DataSource method.
Bonus question: Why are all ComboBoxes working as one (try changing the value)?
The question is, how do I get the performance of the second one with
the functionality of the first one?
If you want it to work properly, you can move the line box.SelectedItem = 5.ToString(); to the line after adding boxes to panel.
When you use DataSource for your combo box, setting SelectedItem works only if your combo box exists on your form.
I'm not sure about performance, but sure about functionality.
Bonus question: Why are all ComboBoxes working as one (try changing
the value)?
Because they are bound to the same DataSource. In fact they are using a single BindingManagerBase.
You can use different BindingSource for them. Also you can bind them to list.ToList().
Use teh DataSource method and use the FindString method to find the index of your desired selected text:
string[] arrString = {"hello", "how r u", "fine" };
comboBox1.DataSource = arrString;
comboBox1.SelectedIndex=comboBox1.FindString("fine");
MessageBox.Show(comboBox1.SelectedIndex.ToString());

How to set different forecolor to single label?

lblOperationType.Text = "Text";
Label l1 = new Label();
int len = lblOperationType.Text.Length - 1;
string b = Convert.ToString(lblOperationType.Text.ToCharArray()[0]);
string a = lblOperationType.Text.Substring(1, len);
l1.Text = (b);
l1.ForeColor = Color.Red;
lblOperationType.Text = l1.Text + a;
Is this correct code? I have to make a single label Text like 1st Letter should be red in color.
No, in general (i.e. without custom rendering yourself) a label only has a single foreground colour. Assuming this is Windows Forms, it sounds like you might want a RichTextBox instead - that allows multiple colours, fonts etc within a single control.
As an example:
using System;
using System.Drawing;
using System.Windows.Forms;
class Test
{
static void Main()
{
var rtb = new RichTextBox {
Text = "Test",
ReadOnly = true
};
rtb.Select(1, 3);
rtb.SelectionColor = Color.Red;
rtb.DeselectAll();
var form = new Form { Controls = { rtb } };
Application.Run(form);
}
}
That's not terribly nice code - it would be better to set the Rtf property directly, with the control codes necessary to set the colour, but I'm having a tricky time getting the exact format of the RTF right.

C# Label not visible in GroupBox

I have a loop that is supposed to go through DataTable and for each row create a new GroupBox, set it's text to value from one column, in that GroupBox I want to put a Label with Text similar to another column in the table.
This is just part of the code!
for (int i = 0; i < tab.Rows.Count; i++)
{
lblbox[i] = new GroupBox();
lblbox[i].Text = tab.Rows[i]["text"].ToString();
lblbox[i].Name = "box no " + i.ToString();
lblbox[i].Visible = true;
this.Controls.Add(lblbox[i]);
lblbox[i].Location = new Point(5, 55 * i);
lblbox[i].Height = 50;
lblbox[i].SendToBack();
importancelbl[i] = new Label();
importancelbl[i].Text = "Importance: " + tab.Rows[i]["importance"].ToString();
importancelbl[i].Name = "implbl" + i.ToString();
importancelbl[i].Visible = true;
lblbox[i].Controls.Add(importancelbl[i]);
importancelbl[i].BringToFront();
Point locP = new Point();
locP.X = lblbox[i].Location.X + 5;
locP.Y = lblbox[i].Location.Y + 15;
importancelbl[i].Location = locP;
}
When i run the code it creates three (I have three rows in my table) GroupBoxes correctly and creates all the labels, but only first label is visible in its Groupbox. When I add those labels to the Form and not to the GroupBox, all of them are visible, but I want them to be in boxes...
I've tried pretty much everything and I'm still very confused (espacially by the behavior of the first label). I know the mistake is probably obvious and stupid, but I just can't find it!
Control.Location is relative to its parent, so set Location for the label to (5, 15).
locP.X = 5;
locP.Y = 15;
My guess is that they are somehow overlapping and making each other disappear somehow.
Could you try posting pictures of the form when it works and when it doesn't? Also add all your code?
Try to preform adding
lblbox[i].Controls.Add(importancelbl[i]);
this.Controls.Add(lblbox[i]);
after setting all your properties

Categories