Control not being cleaned up after I add it to a form

  • Thread starter Thread starter Guest
  • Start date Start date
G

Guest

Hi, I am using Team Foundation Server's unit testing features and I have
discovered something that is causing me a bit of a problem: please take a
look at the following test code:

public void UnloadAnyControlTest()
{
Form form = new Form();

Panel panel = new Panel();

WeakReference panelRef = new WeakReference(panel);

panel.Dock = DockStyle.Fill;

// Below are the two lines of code which, if commented out,
// will permit this test to pass. Otherwise, it fails.
form.Controls.Add(panel);
form.Controls.Remove(panel);

GC.Collect();
GC.WaitForPendingFinalizers();

Assert.IsNotNull(panelRef.Target);

panel = null;

GC.Collect();
GC.WaitForPendingFinalizers();

Assert.IsNull(panelRef.Target);
}

Basically, I have some user controls that I developed and I was writing some
test code to ensure that they were properly cleaned up when I unloaded them.
I found that it wasn't happening, and I wrote the above test to see if I
could figure out what was going on.

Basically, if I create a panel and create a weak reference to it, then null
out (destroy) the only other reference I have to the panel, the garbage
collector will clean up the panel. If, however, in between there I add the
panel to a form and then remove it again, the panel will not be cleaned up!

Does anyone know why?

Can anyone suggest a solution or shed some light on this issue?

Thanks in advance,

-John
 
John,

This problem (that now I'm confident to say it's a bug in WindowsForms)
bugged me since I saw your post. I was running your sample and I was getting
the same wrong results over and over again and couldn't figure out why. I
spent hours digging with the reflector in the windows forms code and
everything looked OK; there was no leaking references to the panel, so it
shouldn't have happened.

Finally I created my own test application and all of a sudden it was working
coorectly. Then I compared yours sample with mine and I found that the
difference was that I didn't set the panel's Dock property. That was it! If
the panel is docked when removed, something keeps reference to it; if is not
docked it gets GC-ed correctly. Knowing where to look at, I finaly found the
reason. The layout engine caches in each controls the bounds of all docked
child controls. The thing is that it does that using the control as a key in
a dictionary. The problem is that when you remove the docked panel nothing
clears the cache and the cotrol is still referenced as a key in the
dictionary.

There are two workarounds that I found:

Workaround 1:
Before removing the panel form the form set the panel's Dock to None and
then remove it.

Workaround2:
I found that the cache is cleared during layout, thus the solution is to
call form's PerformLayout method after removing the panel.

In your sample code add:
form.PerformLayout()
right after the line : panel = null;

Sometimes the layout is triggered by itself e.g. if there are other docked
controls, but in your case there is none.
 
Hello,

Wow, thank you so much for taking the time to look into this! I just came
in Monday morning and this is some good news for me =) I will try out your
suggestion, but based on the thoroughness of your post, I have no doubt that
it will work.

Thanks again.

-John
 
Back
Top