I create controls dynamically (checkBoxes, listBox, trackBar...) on a Form, and i put menuStrip using the editor. I'd like to remove all the controls, and i'd like to keep the menu.
When i use this line:
this.Controls.Clear();
it removes the menu as well as the controls, however as i understood, menu items are not controls, they are on the form directly(i can see them if i write "this" and press a dot).
i tried to cycle over the controls and removed only if the type was one of the controls, but some control stayed while some are removed. I cycled using controls.Count. I tried to put the whole cycle to another while() and exit if Controls.Count > 1 like this:
while( this.Controls.Count > 1 )
{
for (int i = 0; i < this.Controls.Count; i++ )
{
if ((this.Controls[i].GetType() != typeof(MenuStrip)) )
{
this.Controls.RemoveAt(i);
}
}
}
It removes the controls and leave the menu alone, but the items that are disappears doesn't disapper in the same time, but some time later, i guess it's becouse the while runs more than one time.
My questions:
1. Why can't it remove all the control at once, while i iterate over the whole thing using controls.count as the upper bound.
2. What's the point of menuStrip as control while toolStripMenuItmes are not control.
This is the classic mistake of modifying the collection that you are iterating. Most collection classes generate an exception when you do that, unfortunately ControlCollection doesn't. It just misbehaves, you'll skip the next control after the one you remove. There's another nasty bug, the Controls.RemoveAt() method doesn't dispose the control you remove, it leaks forever.
Avoid the iteration bug by iterating backwards. And properly dispose, like this:
for (int i = this.Controls.Count-1; i >= 0; i--) {
if (this.Controls[i].GetType() != typeof(MenuStrip)) {
this.Controls[i].Dispose();
}
}
Related
I have a WPF Project, Main window contains several grids.
After creating Textboxes and -blocks in a loop I add them to a grid using:
grid1.Children.Add(textbox1);
grid2.Children.Add(textbox2);
...
grid20.Children.Add(textbox20);
Is it somehow possible to replace this piece of code with a loop too?
If you have many of these grid/textboxes it might be wiser to wrap them in another control as Yaho Cho suggested in his comment..
I might be stating the obvious, but if you place the grids and textboxes in a list (i.e. create them in code and add them to the container in code) you can loop them.
List<TextBox> tl = new List<TextBox>(){ ... };
List<Grid> gl = new List<Grid>(){ ... }
for (i=0; i<20; i++)
{
gl[i].Children.Add(tl[i]);
container.Children.Add(gl[i]);
}
So I have a listview with a hierarchical data template containing signal graph.
<HierarchicalDataTemplate
DataType="{x:Type ViewModels:BusViewModel}"
ItemsSource ="{Binding Path = bits}"
>
<Components:SignalGraph
x:Name="signal_graph"
/>
If I remove an item from itemslist, the signalgraph remains and is still hooked onto the redraw event, so I'm having redraw events for items that are not on screen.
My first instinct was to go and change VirtualizingStackPanel.VirtualizationMode="Standard"
so as to be certain that the container wasn't being reused, but that is not enough to stop the redraws.
However, I am merely using the virtualizing tile panel from here:
http://blogs.msdn.com/b/dancre/archive/2006/02/16/implementing-a-virtualizingpanel-part-4-the-goods.aspx
and I don't think it implements recycling. It looks to just be using generator's remove and generatenext methods rather than the recycle method. So I'm rather confused as to why the generated objects are not being disposed of correctly. When I look in the cleanupItems method of the panel
private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
{
UIElementCollection children = this.InternalChildren;
IItemContainerGenerator generator = this.ItemContainerGenerator;
for (int i = children.Count - 1; i >= 0; i--)
{
GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
int itemIndex = generator.IndexFromGeneratorPosition(childGeneratorPos);
if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
{
generator.Remove(childGeneratorPos, 1);
RemoveInternalChildRange(i, 1);
}
}
}
I find that the panel's internal children does get reduced to 1, so I think that WPF is supposed to be taking care of most of this for me. I therefore am of the opinion that I probably need to implement IDisposable or something along these lines to ensure that the control is destroyed and all event handlers are detached.
How do I properly dispose of items when removing it from the observable collection that belongs to the listview's itemssource?
You are on the right track. Calling generator.Remove does remove the container from generators cache but if you have events or handlers remaining between the item and container the container will not get collected by GC.
Therefore release all the handlers and wpf will take care of removing container from memory, actually GC will remove it. Like you mentioned in first sentence you seem to have some drawing event. If you do not release that event, the container will not get finalized.
So simply release all your custom logic you have in there and you should do fine.
I just hooked onto the unloaded event and deattached the event handler, and now the code works fine. I am guessing behind the scenes the itemscontrol tries to destroy the object if there are no references to it or something
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 like 10 items in a ContextMenuStrip. I have an Opening Event handler which decides which of the 10 items will be visible. Based on application logic I may hide many of them by setting Visible = false and Enabled = false;
What happens is in the case that 6/10 are displayed. Their will be an area 4 menu items height of blank space the first time I click the strip. If I right click to make it appear the second time it shows up where it should be. So, the strip basically has the position calculated for a 10 item strip when in reality it's only a 6 item strip.
How do I force it to recalculate?
Here are some of the things I've tried:
contextMenuStrip1.Refresh();
contextMenuStrip1.Update();
contextMenuStrip1.PerformLayout();
contextMenuStrip1.AutoSize = true;
ToolStripItem tempItem = contextMenuStrip1.Items.Add("temp");
contextMenuStrip1.Items.Remove(tempItem);
contextMenuStrip1.Refresh();
contextMenuStrip1.Update();
contextMenuStrip1.Invalidate();
I've found that
AutoSize = false;
AutoSize = true;
after all item manipulations works. Haven't found any other solution.
In my case, I add items on opening (dynamic list of windows), and sometimes item caption was longer than the menu.
Have you tried using Invalidate()?
From the MSDN:
Invalidates the entire surface of the control and causes the control
to be redrawn. (Inherited from Control.)
Perhaps you need to use then WM_PAINT message, I have found that sometimes you need to use SendMessage (Interop) with Winforms.
http://msdn.microsoft.com/en-us/library/windows/desktop/dd145213(v=vs.85).aspx
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();
}