Load form's controls to panel in C# - c#

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.

Related

Recommended way to manipulate User Control panels into a Windows From

I just started working with Visual Studio C# and to be honest I didn't fully understand what happens when we chose to hide a form or a user control.
My intuition tells me this hide/show method is kind of "inefficient" way to get an user through all the functions of my app.
So I am asking you guys if there is another workaround to "load" user control parts in a form.
Right now my main_menu form has all the user control objects placed on the form, but hidden, and I am using buttons to show them.
Is there a better way to achieve the same result? (I was thinking of a workaround like having an empty panel where I can load the User Control - not sure if possible)
Thank you!
You can create the controls on the fly and add them to or remove them from the Controls collection. On the class level, define this field
private Control _currentPanel;
You can use a more specific type here, if you are deriving all your panels from a common base type.
Then change the panel with
// Remove previous one.
if (_currentPanel != null) {
Controls.Remove(_currentPanel);
}
// Add new one
_currentPanel = new MyNewPanel();
//TODO: possibly set the panels Docking property to Fill here.
Controls.Add(_currentPanel);
In the example I am working with the form's Controls collection; however, you might have to use the Controls collection of some container control holding the panel.

Setting the control's parent in form designer

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.

Indexing into Controls Collection on Tab Control

I have a C# Forms tab application. Each TabPage has a menu on the left (Outlook style navigation panel), and a Panel on the right for content.
If I want the content panel for tab page 0, how would I go about fetching it? I'm a bit stumped because I don't know how to index into the controls collection on a tab page. The following is underlined in red, so I believe its wrong.
Panel panel = tabControl.TabPages[0].Controls["Panel"];
EDIT: remove Window in Panel sub question. It will be moved to a separate question.
Sorry about the beginner questions. I'm a C/C++ guy with lots of MFC time, and C# UI is a bit frustrating at the moment.
foreach (Control control in tabControl1.TabPages[0].Controls)
{
// if (control.Name == "panel1")
}
You can always call this recursively on control.Controls to find a control in any hierarchy. control.Name can be used to find your specific control.
You can't show a Form, inside a Panel. You could create Custom Control where you can add your functionality and add that control to a Panel.
in order to create a new form for example you need to create a variable of what ever form that it is you want to create.
example
Form2 frm2 = new Form2();
frm2.Show();
if you want to show that form in the panel then the panel would be the Owner keep in mind the difference between Owner and Parent
please paste what ever code you have so far and we can suggest the necessary changes
Finally, how does one display a Window in a Panel? - you don't want to do that. If you want a window and a panel to share a piece of UI functionality, create a user control with all the the functionality and then you can place it in a form or in a panel.
A possibility to encapsulate complex UI content is to create a UserControl. This way you can create a reusable piece of complex UI you can basically add as a "blob" inside a form.
The reason why
Panel panel = tabControl.TabPages[0].Controls["Panel"];
is underlined red is because the Controls collection returns a Control which might be a Panel but also might be something else. So you need to cast it:
Panel panel = tabControl.TabPages[0].Controls["Panel"] as Panel;
if (panel != null)
{
// got a panel here so do something
}
Also: MSDN has some good resources - you should make use of it.

C# Winforms - Prevent a control from stealing focus when added programatically

I have a bit of an odd question.
My situation is as following:
I have a form, It contains several user controls that in turn contains either other user controls or other basic controls such as TextBox, RichTextBox and such.
As part of the logic when editing the text boxes, I create another control programatically and inform the form about it. Other controls on the form may react in turn and create more controls.
Trouble is, These controls steal focus from my control when they are created and added to the form/other controls.
Is there a way to prevent my control from losing focus while that happens?
Maybe you should include in the logic, the tab indexes first, and when you add the control, set the tab index to the last tab index + 1, your job would be easier, if you set the tab order first on the controls, and set the constant to the last tab index at design time, see here,:
private const int LAST_TAB_INDEX = 5; // an Example
private int lastTabIndex = LAST_TAB_INDEX;
private void AddControl(){
// Set up your control
Control ctl = new Control();
// ....
ctl.TabIndex = lastTabIndex;
this.Add(ctl);
this.lastTabIndex++;
}
You can see from the example how the tab index is incremented, in that way, it will prevent
the controls from stealing the focus...
Hope this helps,
Best regards,
Tom.
Controls can't accept (steal) focus unless they are visible and enabled. Have you tried creating the controls with one or both of these as false, and then turning them on when appropriate?

Dynamically Populated TableLayoutPanel Performance Degredation

I have a user control that contains a 2-column TableLayoutPanel and accepts commands to dynamically add rows to display details of an item selected in a separate control. So, the user will select a row in the other control (a DataGridView), and in the SelectedItemChanged event handler for the DataGridView I clear the detail control and then regenerate all the rows for the new selected item (which may have a totally different detail display from the previously selected item). This works great for a while. But if I keep moving from one selected item to another for quite a long time, the refreshes become VERY slow (3-5 seconds each). That makes it sound like I'm not disposing everything properly, but I can't figure out what I'm missing. Here's my code for clearing the TableLayoutPanel:
private readonly List<Control> controls;
public void Clear()
{
detailTable.Visible = false;
detailTable.SuspendLayout();
SuspendLayout();
detailTable.RowStyles.Clear();
detailTable.Controls.Clear();
DisposeAndClearControls();
detailTable.RowCount = 0;
detailTable.ColumnCount = 2;
}
private void DisposeAndClearControls()
{
foreach (Control control in controls)
{
control.Dispose();
}
controls.Clear();
}
And once I get finished loading up all the controls I want into the TableLayoutPanel for the next detail display here's what I call:
public void Render()
{
detailTable.ResumeLayout(false);
detailTable.PerformLayout();
ResumeLayout(false);
detailTable.Visible = true;
}
I'm not using anything but labels (and a TextBox very rarely) inside the TableLayoutPanel, and I add the Labels and TextBoxes to the controls list (referenced in DisposeAndClearControls()) when I create them. I tried just iterating over detailTable.Controls and disposing them that way, but it seemed to miss half the controls (determined by stepping through it in the debugger). This way I know I get them all.
I'd be interested in any suggestions to improve drawing performance, but particularly what's causing the degradation over multiple selections.
Just use a custom control that inherits from TableLayoutPanel and set the DoubleBuffered property on true, works great... especially when you dynamically add or remove rows.
public CustomLayout()
{
this.DoubleBuffered = true;
InitializeComponent();
}
I had a similar issue with TableLayout. If I used TableLayout.Controls.Clear() method, the child controls never got disposed but when I simply dropped the TableLayout without clearing it, the leak stopped. In retrospect, it's funny I used the Clear method to prevent some kind of leak.
Apparently, Clear method does not explicitly dispose of the controls (which makes sense, because the fact that you removed them from the TableLayout does not mean you are done with them) and removing the child controls from the TableLayout prevents the cleanup routine to dispose of the children when the LayoutTable itself gets disposed (it simply does not know about them anymore).
My recommendation: Delete the detailTable.Controls.Clear(); line, remove the detailTable itself from the parent's Controls collection and dispose it, then create a brand new TableLayout for the next round. Also lose the DisposeAndClearControls method entirely since you won't need it. In my experience, it worked nicely.
This way, you won't have to recycle your entire user control anymore but only the TableLayout within.
Unfortunately, the only advice I can offer is to take care of the placement of your controls yourself. In my experience the .NET TableLayoutPanel, while very useful, is leaking SOMETHING and becomes unusably slow as it grows (and it doesn't take an unreasonable number of cells to get to this point, either). This behavior can be seen in the designer as well.
I changed the containing form to just construct a new version of my user control on each selection change. It disposes the old one and constructs a new one. This seems to perform just fine. I'd originally gone with reusing just one for performance reasons anyway. Clearly that doesn't improve the performance. And the performance isn't a problem if I dispose the old one and create a new one.
Unfortunate that the TableLayoutPanel leaks like that, though.
I faced the same problem and found a good way without changing too much:
in VB.net
Dim tp As Type = tlpMyPanel.GetType().BaseType
Dim pi As Reflection.PropertyInfo = _
tp.GetProperty("DoubleBuffered", _
Reflection.BindingFlags.Instance _
Or Reflection.BindingFlags.NonPublic)
pi.SetValue(tlpMyPanel, True, Nothing)
or in C#:
Type tp = tlpMyPanel.GetType().BaseType;
System.Reflection.PropertyInfo pi =
tp.GetProperty("DoubleBuffered",
System.Reflection.BindingFlags.Instance
| System.Reflection.BindingFlags.NonPublic);
pi.SetValue(tlpMyPanel, true, null);
TableLayoutPanel.Controls.Clear() works fine for me, maybe its because i clear it from a different tab than its displayed in.
List<Control> controls = new List<Control>();
foreach (Control control in tableLayoutPanelEnderecoDetalhes.Controls)
{
controls.Add(control);
}
foreach (Control control in controls)
{
control.Dispose();
}

Categories

Resources