.NET 2
// dynamic textbox adding
myTextBox = new TextBox();
this.Controls.Add(myTextBox);
// ... some code, finally
// dynamic textbox removing
myTextBox.Dispose();
// this.Controls.Remove(myTextBox); ?? is this needed
Little explanation
Surely, if I Dispose a control I will not see it anymore, but anyway, will remain a "Nothing" in the parent controls collection?
need I also, like MSDN recommends, remove all handlers from the control?
No, you don't.
I tried it.
You can paste the following code into LINQPad:
var form = new Form();
var b = new Button();
form.Controls.Add(b);
b.Click += delegate { b.Dispose(); };
Application.Run(form);
EDIT: The control will be removed from the form's Controls collection. To demonstrate this, replace the click handler with the following:
b.Click += delegate { b.Dispose(); MessageBox.Show(form.Controls.Count.ToString());};
It will show 0.
2nd EDIT: Control.Dispose(bool disposing) contains the following code:
if (parent != null) {
parent.Controls.Remove(this);
}
EDIT:
MSDN suggests that you remove the object from the Control and then call dispose when removing an object from a collection at runtime:
http://msdn.microsoft.com/en-us/library/82785s1h%28VS.80%29.aspx
// dynamic textbox adding
myTextBox = new TextBox();
this.Controls.Add(myTextBox);
// ... some code, finally
// dynamic textbox removing
this.Controls.Remove(myTextBox);
myTextBox.Dispose();
But looking at the answer from Mat it looks as though this behavior depends on the framework being used. I think he's suggesting that when using the compact framework some controls must be Removed and also Disposed.
So Microsoft suggesting that we always remove and then dispose kind of makes sense especially if you're moving code modules to other frameworks.
MRP
After some tests, I find out that the disposed controls are automatically removed from the parent control collection.
Controls.add(myButton); //Control.Count==4
myButton.Dispose(); //Control.Count==3
UPDATE
from the control's Dispose(bool) method:
if (this.parent != null)
{
this.parent.Controls.Remove(this);
}
Further Information on Compact Framework 2 + VS2005
Designer may crash when removing a control which is derived from s.w.f.control, if it doesn't implement the following:
Dispose()
{
if(this.parent!=null){
this.parent.controls.remove(this);
}
....
}
Just keep in mind that if you have some code to iterate over your controls and do something, you would get an exception if one of these controls had been disposed. Therefore, in general I would probably recommend removing the control as good practice.
Related
What is the preferred/easiest way to find the control that is currently receiving user (keyboard) input in WinForms?
So far I have come up with the following:
public static Control FindFocusedControl(Control control)
{
var container = control as ContainerControl;
return (null != container
? FindFocusedControl(container.ActiveControl)
: control);
}
From a form, this can be called simply as (in .NET 3.5+ this could even be defined as an extension method on the form) -
var focused = FindFocusedControl(this);
Is this appropriate?
Is there a built-in method that I should be using instead?
Note that a single call to ActiveControl is not enough when hierarchies are used. Imagine:
Form
TableLayoutPanel
FlowLayoutPanel
TextBox (focused)
(formInstance).ActiveControl will return reference to TableLayoutPanel, not the TextBox (because ActiveControl seems to only be returning immediate active child in the control tree, while I'm looking for the leaf control).
If you have other calls to the Windows API already, there's no harm in using Peters solution. But I understand your worries about it and would tend to a similar solution as yours, using only the Framework functionalities. After all, the performance difference (if there is one) shouldn't be significant.
I would take a non recursive approach:
public static Control FindFocusedControl(Control control)
{
var container = control as IContainerControl;
while (container != null)
{
control = container.ActiveControl;
container = control as IContainerControl;
}
return control;
}
After searching the Internet, I found the following on George Shepherd's Windows Forms FAQ
The .Net framework libraries does not provide you an API to query for
the focused Control. You have to
invoke a windows API to do so:
[C#]
public class MyForm : Form
{
[DllImport("user32.dll", CharSet=CharSet.Auto, CallingConvention=CallingConvention.Winapi)]
internal static extern IntPtr GetFocus();
private Control GetFocusedControl()
{
Control focusedControl = null;
// To get hold of the focused control:
IntPtr focusedHandle = GetFocus();
if(focusedHandle != IntPtr.Zero)
// Note that if the focused Control is not a .Net control, then this will return null.
focusedControl = Control.FromHandle(focusedHandle);
return focusedControl;
}
}
ActiveControl on a Form or Container will return that entity's active control no matter how deeply it might be nested inside other containers.
In your example if the TextBox has Focus : then : for Form, TableLayoutPanel, and FlowLayoutPanel : the 'ActiveControl property of all of them will be the TextBox !
Some, but not all, "genuine" ContainerControl types ... like Form and UserControl ... expose Key Events (in the case of Form : only if Form.KeyPreview == true can they be used) .
Other controls which, by design, contain other controls like TableLayOutPanel, GroupBox, Panel, FlowLayoutPanel, etc. are not type ContainerControl, and they do not expose KeyEvents.
Any attempt to cast instances of objects like TextBox, FlowLayoutPanel, TableLayoutPanel directly to ContainerControl will not compile : they are not type ContainerControl.
The code in the accepted answer, and in the next answer that corrects the first answer's spelling errors, will compile/accept instances of the above as parameters because you are "downcasting" them to type 'Control by making the parameter type 'Control
But in each case the cast to ControlContainer will return null, and the passed in instance will be returned (downcasted) : essentially a no-op.
And, yes, the modified answer code will work if you pass it a "genuine" ControlContainer, like a Form instance, which is in the parent inheritance path of the ActiveControl, but you are still just wasting time duplicating the function of 'ActiveControl.
So what are "genuine" ContainerControls : check them out : MS docs for ContainerControl
Only the answer by Peter really answers the explicit question, but that answer carries the price of using interop, and 'ActiveControl will give you what you need.
Also note that every Control (container or non-container) has a Controls Collection that is never null, and that a lot of (I've never tried all of them : why would I ?) the basic WinForms control let you do "crazy stuff" like adding Controls to the ControlCollection of 'simple' controls like Button without an error.
Now if the real intent of your question was to ask how you find the outermost ContainerControl ... that is not on the Form itself ... of a regular non-container Control nested some arbitrary levels deep ... you can use some of the ideas in the answer : but the code can be greatly simplified.
Regular Controls, ContainerControls, UserControls, etc. (but not Form !) all have a 'Container property you can access to get their immediate container, but making sure you have the 'final Container in their inhertance path that's not a Form requires some code to "walk-up" the inheritance tree, which is demonstrated here.
You may also wish to check out the 'HasChildren property of 'Control which is usually useful in dealing with issues of Focus, ActiveControl, and Select in WinForms. Reviewing the difference between Select and Focus can be valuable here, and SO has some good resources on that.
Hope this helps.
Hinek's solution works well for me, except it is ContainerControl, not ControlContainer. (Just in case you were scratching your head about that red squiggly line.)
public static Control FindFocusedControl(Control control)
{
ContainerControl container = control as ContainerControl;
while (container != null)
{
control = container.ActiveControl;
container = control as ContainerControl;
}
return control;
}
If you follow ActiveControl out recursively it doesn't take you to the leaf control that has focus?
ActiveControl doesn't always work out, like with SplitContainer, ActiveControl.Focused is false.
So for a more fool proof method could do something like this:
private IEnumerable<Control> _get_all_controls(Control c)
{
return c.Controls.Cast<Control>().SelectMany(item =>
_get_all_controls(item)).Concat(c.Controls.Cast<Control>()).Where(control =>
control.Name != string.Empty);
}
var _controls = _get_all_controls(this);
foreach (Control control in _controls)
if (control.Focused)
{
Console.WriteLine(control.Name);
break;
}
I have a LinkButton that is created dynamically in Load and then added to a control that resides on the Master page. Here is the original code used to create the LinkButton:
LockUnlock = new LinkButton() { ID = "LockUnlock", Visible = false };
LockUnlock.Click += LockUnlock_Click;
Now, when I first dug into this problem I thought it might be related to the ASP.NET life cycle so I moved the code to an override OnInit, but that didn't fix it.
I then moved on to setting the ID because that's not being done. So I added this line:
LockUnlock.ID = "LockUnlock";
and I tried that in both OnInit and Load - no luck.
Then I thought, because I'm adding this to a custom control that is actually part of a ContentPlaceHolder I may need to make the ID static to get this to work, so I added this:
LockUnlock.ClientIDMode = ClientIDMode.Static;
I have only tried that in the Load, but no luck, and honestly if it's not working in Load it's almost certainly not going to change anything in OnInit - that was just my first move and really a hail Mary in a lot of ways.
Alright, so now I'm at the point where I feel like it's related to the fact that the ResourceCenter is a custom control that is added to a ContentPlaceHolder and thus the event, though it's technically hooked up, can't be fired because of the context of the class. But I'm not sure where to go from here.
Do I need to add a shared class for the click? Do I need to hookup the click in the custom control and then delegate it from there somehow?
I would prefer not to use either of those solutions, but hey, we do what we have to do! I look forward to everybody's input on this.
EDIT
The code that adds the control to the ResourceCenter looks like this:
this.ResourceCenter.AddAdminLink(LockUnlock.Visible ? LockUnlock : null);
and the code inside the ResourceCenter control that adds to its list looks like this:
if (link == null) { return; }
var wrapper = new HtmlGenericControl("li");
wrapper.Controls.Add(link);
this.AdminLinkList.Controls.Add(wrapper);
where link is what was passed into the method by the aforementioned line.
It would have to be in the init. Also, try adding the control to the control's collection, before adding the event, as in:
LockUnlock = new LinkButton() { ID = "LockUnlock", Visible = false };
Panel.Controls.Add(LockUnlock);
LockUnlock.Click += LockUnlock_Click;
FindName is broken for me :(
The object I am looking for is there. I have proof.
Here is the scenario:
ToggleButton button = (ToggleButton)sender;
Popup popup = (Popup)button.FindName("popSelectIteration");
popup is null but not always. Just sometimes. But even when it is set to null the child I am looking for is there.
I put a break point in when it was null and grabbed these two screenshots.
The is where FindName is returning null for "popSelectIteration".
But if you dig into the watch, you see that the child is there.
So what am I missing? Why does FindName not find it? As you can see from the screen shot this is not a timing issue (the FindName watch is null but the direct path is fine).
Is there a better way to find a control?
Side Note: If you are intersted in the XAML for the toggle button in question it can be found in this question: WPF - FrameworkElement - Enumerate all decendents?.
Update: I did some digging to see why this fails some times and other times it works. I have an animation that calls NameScope.SetNameScope((DependencyObject)form, new NameScope()); (Full method code here). Right after that call the FindName starts to fail.
I don't really understand that call. I think I copied and pasted the code. Anyway, I commented it out. But I would love know why this is failing.
I would guess it has to do with the difference between the visual and logical tree. The control is in the logical tree but maybe the template for this control has not been applied yet and therefore FindName won't return anything useful.
You could try to call ApplyTemplate(); on the container first.
This would also explain why it returns something sometimes.
Try
LogicalTreeHelper.FindLogicalNode(button, "popSelectIteration");
Little late to the party (and not actually answer to OP question), but
when you add elements dynamically, they are not findable by FindName.
You need to register them by calling RegisterName.
Example:
string number = GenerateNumber();
Button myButton = new Button();
myButton.Content = number;
myButton.Name = "button_" + number;
RegisterName(myButton.Name, myButton);
Panel.Children.Add(myButton);
object o = Panel.FindName(myButton.Name);
Maybe someone might find this useful.
In my experience, this happens when you add items via code-behind. I've found that you can fool FindName() (or the animation framework) via name scopes. That is, when you create your control, you do
NameScope.GetNameScope(yourContainer).RegisterName("name of your control", yourControlInstance);
For this to work reliably, though, you must make sure that you unregister the name:
NameScope.GetNameScope(yourContainer).UnregisterName("name of your control");
Posting this for future reference.
I have meet the same question now, but I use the method like so:
#region Override - OnApplyTemplate
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.PART_ListViewLeft = GetTemplateChild(cPART_ListViewLeft) as ListView;
this.PART_ListViewCenter = GetTemplateChild(cPART_ListViewCenter) as ListView;
this.PART_ListViewRight = GetTemplateChild(cPART_ListViewRight) as ListView;
this.PART_GridViewLeft = GetTemplateChild(cPART_GridViewLeft) as DsxGridView;
this.PART_GridViewCenter = GetTemplateChild(cPART_GridViewCenter) as DsxGridView;
this.PART_GridViewRight = GetTemplateChild(cPART_GridViewRight) as DsxGridView;
if(this.PART_ListViewLeft!=null)
this.PART_ListViewLeft .AlternationCount = this.AlternatingRowBrushes.Count;
if(this.PART_ListViewCenter!=null)
this.PART_ListViewCenter .AlternationCount = this.AlternatingRowBrushes.Count;
if(this.PART_ListViewRight!=null)
this.PART_ListViewRight .AlternationCount = this.AlternatingRowBrushes.Count;
// ApplyTempleted = true;
CreateColumnLayout();
}
#endregion
If the Control is dynamic create and of which or whose container the 'Visibility' is set to hide or Collapsed, then the code this.PART_ListViewLeft = GetTemplateChild(cPART_ListViewLeft) as ListView; will always return null, the reason is that the datatemplete has not yet been applied before OnApplyTemplate being called.
I would suggest to avoid using FindName function, based on my experience, expecially problematic when you try to find something in the DataTemplate applied to some control.
Instead , if it possible (based on your software architecture) declare Popup in XAML and
refer to it like resource or use Binding to set some Model property to it's reference.
Good luck.
Try to use button.FindResource("popSelectIteration")
ellipseStoryboard.Children.Add(myRectAnimation);
containerCanvas.Children.Add(myPath);
After you add register the controls like
RegisterName("TextBlock1", Var_TextBox);
or
RegisterName(myRectAnimation.Name,myRectAnimation);
RegisterName(myPath.Name,myPath);
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();
}
I am working in a C# winforms project and I have a user control which gets loaded upon its selection from a tool-strip menu. I have a dictionary lookup set to occur upon form load of the user control for other functionality. Also, when I close the user control I am just using the ".Hide();" method. I noticed that when I load the user control the first time everything is fine, but when I close it and choose to open it again the 2nd time it creates a new instance of the object thus throwing off my dictionary lookup. Therefore, I wrote some code in an attempt to fix the issue.
What I need to do is to somehow say that if an instance of the user control already exists, do not create a new instance of that object. Instead, just make the user control visible again. Therefore I have written code in an attempt to accomplish this purpose. When I select the item the first time, everything is fine. When I hide the user control and try to re-open it again nothing happens.
The following is the code I have written for this purpose which occurs upon the selection of the item from the tool-strip menu:
if (Controls.ContainsKey("CheckAvailUserControl"))
{
Controls["CheckAvailUserControl"].Dock = DockStyle.Fill;
Controls["CheckAvailUserControl"].Visible = true;
Controls["CheckAvailUserControl"].Show();
Controls["CheckAvailUserControl"].Refresh();
}
else
{
UserControl checkAvailUserControlLoad = new CheckAvailUserControl();
Controls.Add(checkAvailUserControlLoad);
checkAvailUserControlLoad.Dock = DockStyle.Fill;
checkAvailUserControlLoad.Visible = true;
checkAvailUserControlLoad.Show();
}
When I trace through my code in the debugger it is in fact hitting the right parts of the above if/else statement. Its just not displaying the user control on the screen the 2nd time I attempt to load it.
The question is: How do I get the user control to load correctly after I close it, then select it from the tool-strip menu again?
I think that Controls.ContainsKey(...) is always returning false, because you never assigned a name to your control when you created it.
If, when you create the control, you say
//...
checkAvailUserControlLoad.Name = "Something"
//...
Controls.Add(checkAvailUserControlLoad);
then
Controls.ContainsKey("Something")
will return true, and you'll be able to re-use the control by using Controls["Something"]
Here you go:
private void button_Click(object sender, EventArgs e)
{
// pass in the containing panel
LoadControl<MyControls.MyControl>(panelContainer);
}
void LoadControl<T>(Panel panel) where T : Control, new()
{
T _Control = GetControl<T>(panel);
if (_Control == null)
{
_Control = new T();
_Control.Dock = DockStyle.Fill;
panel.Controls.Add(_Control);
}
_Control.BringToFront();
}
T GetControl<T>(Panel panel) where T : Control
{
Type _Type = typeof(T);
String _Name = _Type.ToString();
if (!panel.Controls.ContainsKey(_Name))
return null;
T _Control = panel.Controls[_Name] as T;
return _Control;
}
This could work, but I think it's a little bit backwards: you're throwing new code at a problem that could be solved instead by moving your old code.
Instead, think about how the events work in your form. I bet that if you move your creating code to a slightly different event, or detect when the event is fired later and ignore those, you could fix the problem in a much nicer way.