I've a UserControl containing a FlowLayoutPanel. A lot of this control instances are needed to be used in a nested form on the Form. I found it out that only 15 nested instances can be created! So I decided to check a more simple model of my control out. The model consist of a GroupBox that contains a Panel:
Control parent = this;
for (int groupIndex = 0; groupIndex < 100; groupIndex++)
{
GroupBox grp = new GroupBox();
Panel pnl = new Panel();
pnl.Dock = DockStyle.Fill;
pnl.Parent = grp;
grp.Parent = parent;
grp.Size = new Size(parent.Width - 10, parent.Height - 10);
parent = pnl;
}
When groupIndex reaches to 24, it encounters the Error creating window handle exception. What's the reason for and how to overcome it?
Thanks
This a duplicate of the following question:
Control Nesting Limits in WinForms
To answer your question, Windows has a limit of 50 nested controls (source).
Your sample creates a group box and nests a panel inside of it, and you do this 24 times before the crash occurs. If you account for the form itself, you are hitting that limit.
The limit is enforced when the controls are drawn. From my testing, I was able to create and nest many hundreds of controls, but when I added the top level control of that nest to a visible control, it still crashed.
Related
This MSDN article offers some good advice about manually customising the form designer to improve performance:
Reduce the number of method and property calls on controls during startup. For example, Controls.Bounds is a better option than calls to Control.Location and Control.Size.
Create the form from the top down. In nested control hierarchies, set the parent property of containers (using the above rule) before adding controls to the container. As in the BigForm application, the panels had their parent property set to the form before the 40 controls were connected to the panel. If further containers exist lower in the hierarchy, the same changes should be applied.
I have followed the 1st bit of advice, replacing:
this.MyControl.Location = new System.Drawing.Point(5, 5);
this.MyControl.Size = new System.Drawing.Size(630, 90);
with:
this.MyControl.Bounds = new System.Drawing.Rectangle(5, 5, 630, 90);
This resulted in a super 20% (about 200ms) speed-up on one form. I'm trying to follow the 2nd bit of advice and not quite sure how to proceed. The Designer.cs file contains code like this:
this.Controls.Add(this.pnlHeader);
but not the code I was expecting (according to the example):
this.pnHeader.Parent = this; // Not in the Designer
The code this.Controls.Add(this.pnlHeader); appears at the bottom of InitializeComponent. Is the advice suggesting moving the code to the top or something else entirely?
EDIT #2
i found this msdn article which explain the issue as following:
Another method for improving performance is to initialize the controls
in the control tree top-down. For example, if you have a panel control
with many controls in it, create the panel first, and then add the
controls to the panel. Also, setting the parent property of the
control instead of adding to the Controls collection can improve
performance.
For example, consider adding a textbox to a panel's control collection:
Before optimization:
// Create a new panel and textbox control
Panel panel1 = new Panel();
TextBox textBox1 = new TextBox();
// Set the Text property of the TextBox control
textBox1.Text = "My Text";
// Add the TextBox to the Panel's control collection
panel1.Controls.Add(this.textBox1);
// Add the Panel to the Form's control collection
this.Controls.Add(panel1);
//... subsequent controls
Optimizing this code snippet using the top-down and parenting techniques results in the following snippet:
After optimization:
// Create a new panel and textbox control
Panel panel1 = new Panel();
TextBox textBox1 = new TextBox();
// set parents from top to down
this.panel1.Parent = this;
this.textBox1.Parent = this.panel1;
// Set properties of child control (cause repainting only once)
textBox1.Text = "My Text";
//... subsequent controls
This can make a big difference with a deeply nested control hierarchy.
Optimizing the code in the InitializeComponent method by creating
the controls top-down and re-parenting them resulted in a performance
improvement of about 50% over the default Forms Designer generated
code!
Following on from the answer from S.Serp here are some observations to be aware of:
Replacing Location and Size with Bounds resulted in an average boost of ~15% for form loading.
Replacing Controls.Add with Parent resulted in a further boost of ~5-10% (for an impressive total of ~20-25%).
As Sefe points out, manually editing the Designer.cs file is usually only appropriate where you are not making changes in the Designer very often. Any changes made in the Designer will overwrite your manual code. Be warned! This is not a disaster, you simply lose the boost. Either live with the slower form loading or redo the manual changes.
Be careful to put the Bounds call after setting Multiline = true; on TextBox controls (if enabled). If you set it before, your control will (unhelpfully) be resized to a single line.
Be careful with ensuring each child control has the parent set correctly! Open all forms in the Visual Studio Designer after manually editing InitializeComponent to see that everything is kosher...but don't edit anything otherwise your changes will be deleted.
The lines...
this.Controls.Add(this.pnlHeader);
...and...
this.pnHeader.Parent = this;
...are equivalent. The control trees they produce are identical. And you should not change the auto-generated code. Your changes will be overwritten the next time the designer writes its updates.
You usually don't have to worry about the designer-generated code. You can concentrate on your part and assume the designer is correct.
Good day all.
I have an issue with using the ListView control in C#:
I can create an Object of it and at least from what the debugger says (nothing) I can also add items and columns and all. However:
I cannot see the ListView at all.
Below the code with which I want to draw it onto an otherwise emtpy Form:
private ListView auftraegeView;
And then inside the constructor, after InitializeComponent:
auftraegeView = new ListView();
auftraegeView.View = View.Details;
auftraegeView.Width = this.Width - 12;
auftraegeView.Height = this.Height - 20;
auftraegeView.Left = 6;
auftraegeView.Top = 14;
I tried .Show() and .Refresh() methods, but to no avail.
The problem is that you are not adding your control to your form. You have created the object but now you need to show it on the form. For that you have to add it to the list of controls that need to be shown on the forms.
Some thing like
yourFormName.Controls.Add(auftraegeView );
Have tyou tried BringToFront and checked that the prorperties Visible = true?
i Now is trivial but..
counters.Add(new Counter());
foreach (Counter con in counters)
{
con.Show();
con.Top = this.Top;
con.Left = this.Left;
}
counter is a very basic UserControl I made with 3 buttons and textbox. I'm trying create a draggable Counter, I can see the counter on the list (counters) but I can't see it anywhere on screen.
I was wondering if new Counter() is enough to create the UserControl on screen.
(i mean to create this "counter" object dynamically)
Control needs to have a parent that hosts the control. The parent control can be either a form or another container control. When you want to add your control to the hosting control, add the instance into the Controls collection of the hosting control like this:
var counter = new Counter();
var form = new Form();
form.Controls.Add(counter);
form.Show();
I think user Controls need to be in some sort of container, for example a flowLayoutPanel or your instance of the Form class itself to visually exist.
You need to call CONTAINERINSTANCE.Controls.Add(con).
I want to load Form's controls to a panel in C# so the panel will show the same components as the form. I have tried this code:
foreach (Control control in (new Form2()).Controls)
{
panels[panelsCounter].Controls.Add(control);
}
But the problem is that when I'm running the program it loads only the type of control that I've added last (For example if I've been added a label and than I've added a button to the form it shows only a button, but if I add another label, it shows both of the labels, but not the button).
Please help me.
This is a classic bug, you are modifying the collection while you are iterating it. The side-effect is that only ever other control will be moved to the panel. You'll need to do this carefully, iterate the collection backwards to avoid the problem:
var formObj = new Form2(); //???
for (int ix = formObj.Controls.Count-1; ix >= 0; --ix) {
panels[panelsCounter].Controls.Add(formObj.Controls[ix]);
}
Controls are not designed to be displayed multiple times. You cannot add controls to multiple forms, or add the same control to a form multiple times. They simply weren't designed to support it.
You could go through each control and create a new control of the same type, and even copy over the values of their properties (or at least what's publicly accessible to you), effectively cloning them, but it's important that it be a different control that you add to the new panel.
I have a c# windows form application that has a similar GUI functionality as MSN. It works in the way such that only a notification window appears if there is notification which in this case I have put several buttons and other stuffs in a single panel. (is this the right way to do so?)
How do I code it such that I can use a arrayList to add similar panels to the list and use a for loop to call it out. Example would be calling 2 or 3 similar panels through the use of arraylist(?) and for them to appear below one another. (Maybe like how MSN notifications window comes up one above another.)
the code for the panel is
this.panel1.Controls.Add(this.button1);
this.panel1.Controls.Add(this.lblImage);
this.panel1.Controls.Add(this.lblName);
this.panel1.Controls.Add(this.lblLinkName);
this.panel1.Controls.Add(this.lblLinkLocation);
this.panel1.Controls.Add(this.lblLocation);
this.panel1.Location = new System.Drawing.Point(13, 134);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(506, 100);
this.panel1.TabIndex = 17;
do I have to code the for loop in the designer file or the coding file? as after I have tried to add for loop in the designer code file, the designer view sort of unable to display my UI.
I'm guessing this is what you're looking for
for(int i = 0; i < panels.length; i++){
AddPanel(panels[i], i);
}
AddPanel(System.Drawing.Point point, int tabIndex){
Panel panel = new Panel();
this.Add(panel);
panel.Controls.Add(new Button());
panel.Controls.Add(new Label("Image"));
panel.Controls.Add(new Label("Name"));
panel.Controls.Add(new Label("linkName"));
panel.Controls.Add(new Label("linkLocation"));
panel.Controls.Add(new Label("location"));
panel.Location = point;
panel.Name = "panel" + i.ToString();
panel.Size = new System.Drawing.Size(506, 100);
panel.TabIndex = tabIndex;
}
You'll need to populate the panels array with a the points you'd like your panels to be added at.
I would create a Custom Control that has the interface you want for each item. Have the Control expose properties, methods, and events that allow you to access the child controls in a constant manner.
You can create multiple instances of the custom control and add them to a List and attach event handlers to them, etc...
If you use a flow layout panel as the parent and add each instance of the custom control to it, it will automatically handle the layout for you without you having to manually position them. If the flow layout is inside a container with autoscroll set, you will have a nice scrolling list of whatever kind of Items you can dream up.
adding example
With a custom control called ListItem.
ListItem item = new ListItem();
someFlowPanel.controls.add(item);
You should probably set item's width to the width of the flow panel you add it to, and set it to anchor left and right.