Fast way to add Controls to a TableLayoutPanel - c#

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.

Related

Dynamic button-C#

i have buttons this buttons for categories i want when click in this buttons i want to get rows from database products table and get two data product name and price and set it those values into dynamic buttons in windows application notes i have set location properities for button such like flowLayoutPanel
i have this code
private void btnhotdrink_Click_1(object sender, EventArgs e)
{
//int StartPositition = 100;
//int EndPosition = 10;
DataTable dt = new DataTable();
dt =clsprdcat.GET_HOTDRINK_CATEGORY();
for(int i=0; i < dt.Rows.Count; i++)
{
for (int s=0; s < dt.Columns[4]; s++)
{
Button l = addbuttons(i,s);
flowLayoutPanel1.Controls.Add(l);
//EndPosition +=70;
}
}
}
Button addbuttons(int i)
{
Button l = new Button();
l.Name = "Name" + l.ToString();
l.Text = "label" + l.ToString();
l.ForeColor = Color.White;
l.BackColor = Color.Green;
l.Font = new Font("Serif", 24, FontStyle.Bold);
l.Width = 170;
l.Height = 80;
//l.Location = new Point(start, end);
l.TextAlign = ContentAlignment.MiddleCenter;
l.Margin = new Padding(5);
return l;
}
How Can i Do this
All Buttons are inherently dynamically created at runtime. The Windows Forms Designer works via partial classes: You work on one part. The designer on the other (designer.cs). And at compile time the Designers code is executed by calling "InitializeComponents()" in the Constructor. It can only do exactly the same things you can do.
If you need help with creating buttons yourself, you can always look into the Designer part. Just do take care not to change anything in that part. This System only works because the designer is exclusively writing in the file and compilation/loading of a Project is prone to seizing up if you did manual changes.
As you wrote it right now, this code will create dt.Rows.Count buttons at the same locaiton. Usually the Button creation code it needs access to the loop variable, to adapt the positions of each consequtive element.. Personally I prefer to leave positioning of Elements as much to automatics as possible. These designs simply adapt better to changes in resolution and window size.

Dynamically added labels disappear in runtime

I add labels to my form programmatically, but they disappear, except last one. I'm sure that given location to them is appropriate. But when the second label appears, first disappears, or when third label appears, second disappears.
Here is my code:
Label[] lenlab = new Label[255];
Label lab = new Label();
lab.Font = new Font("Microsoft Sans Serif", 10, FontStyle.Bold);
lab.ForeColor = Color.White;
lab.BackColor = Color.Transparent;
lab.AutoSize = true;
lenlab[1] = lab;
lenlab[1].Location = new Point(50, panel1.Location.Y + panel1.Height + 20);
lenlab[1].Text = c[1];
this.Controls.Add(lenlab[1]);
for (int i = 2; i < c.Count; i++)
{
lenlab[i] = lab;
lenlab[i].Location = new Point(lenlab[i - 1].Location.X + lenlab[i -1].Width + 40, lenlab[i - 1].Location.Y);
lenlab[i].Text = " + " + c[i];
this.Controls.Add(lenlab[i]);
}
This line is causing every position in your array to have a reference to the same Label you created originally, outside the loop, which means all you're doing is changing the position and text of the same Label inside your loop.
lenlab[i] = lab;
The behavior you're seeing is due to the fact that you can only add a particular control to this.Controls once, so the effect is that you see the same label changing position.
Here's the portion of the Add() method that checks whether the control you're adding already has a parent, and if it does, then it removes it from it's current parent before adding it to the new one. So every time you call this.Controls.Add() with the same Label, it removes it from the Form and then adds it again.
// Remove the new control from its old parent (if any)
if (value.parent != null) {
value.parent.Controls.Remove(value);
}
Instead, create a new Label inside your for loop:
lenlab[i] = new Label();
There are controls that can help you layout controls without the need to calculate a new position each time. In particular, read up on the FlowLayoutPanel and TableLayoutPanel classes.
What you are doing there is basically create one Label, change it several times, and attach it several times to the page. What you end up having on the page is the last version of the Label being added once, which is the expected behavior.
If you want to add several labels, you need to new each of them.

Keeping controls centralized in a TableLayoutPanel

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:

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

Listview background drawing problem C# Winform

I have a little problem with a Listview.
I can load it with listview items fine, but when I set the background color it doesn't draw the color all the way to the left side of the row [The listViewItems are loaded with ListViewSubItems to make a grid view, only the first column shows the error]. There is a a narrow strip that doesn't paint. The width of that strip is approximately the same as a row header would be if I had a row header.
If you have a thought on what can be done to make the background draw I'd love to hear it.
Now just to try a new idea, I'm offering a ten vote bounty for the first solution that still has me using this awful construct of a mess of a pseudo grid view. [I love legacy code.]
Edit:
Here is a sample that exhibits the problem.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ListView lv = new ListView();
lv.Dock = System.Windows.Forms.DockStyle.Fill;
lv.FullRowSelect = true;
lv.GridLines = true;
lv.HideSelection = false;
lv.Location = new System.Drawing.Point(0, 0);
lv.TabIndex = 0;
lv.View = System.Windows.Forms.View.Details;
lv.AllowColumnReorder = true;
this.Controls.Add(lv);
lv.MultiSelect = true;
ColumnHeader ch = new ColumnHeader();
ch.Name = "Foo";
ch.Text = "Foo";
ch.Width = 40;
ch.TextAlign = HorizontalAlignment.Left;
lv.Columns.Add(ch);
ColumnHeader ch2 = new ColumnHeader();
ch.Name = "Bar";
ch.Text = "Bar";
ch.Width = 40;
ch.TextAlign = HorizontalAlignment.Left;
lv.Columns.Add(ch2);
lv.BeginUpdate();
for (int i = 0; i < 3; i++)
{
ListViewItem lvi = new ListViewItem("1", "2");
lvi.BackColor = Color.Black;
lvi.ForeColor = Color.White;
lv.Items.Add(lvi);
}
lv.EndUpdate();
}
}
Ah! I see now :}
You want hacky? I present unto you the following:
...
lv.OwnerDraw = true;
lv.DrawItem += new DrawListViewItemEventHandler( lv_DrawItem );
...
void lv_DrawItem( object sender, DrawListViewItemEventArgs e )
{
Rectangle foo = e.Bounds;
foo.Offset( -10, 0 );
e.Graphics.FillRectangle( new SolidBrush( e.Item.BackColor ), foo );
e.DrawDefault = true;
}
For a more inventive - and no less hacky - approach, you could try utilising the background image of the ListView ;)
(Prior to the Edit...)
I've just tried setting the BackColor on a System.Windows.Forms.ListView, and the color is applied across the control just fine (with and without images).
Are you doing any Custom Painting at all?
Ok I'm adding some additional solution notes. If you use the solution above you also need to insert a draw handler for the column headers, otherwise they won't paint. The selected item rectangle also looks funny so you'll want to check for that in the lv_DrawItem function and implement a similar solution. Remeber that highlighting is chosen at the system level and not in you application.
Better ListView (and free Better ListView Express) allows setting background image with various alignment settings (centered, tiled, stretched, fit, snap to border/corner). Alpha transparency is also supported:

Categories