The Control.VisibleChanged event
NOTE: This article was originally published as a post in the .NET and Memory blog (by Andreas Suurkuusk, co-founder of SciTech Software AB).
In 2001 we were working on converting our Lab Assistant project from Visual J++ to .NET (this was pre-.NET 1.0, but Visual J++ was being abandoned). Lab Assistant is an application that connects to and controls scientific instruments over the network. One requirement in Lab Assistant is that data collected from the instruments should be presented in real-time in the user interface application, and the UI should react to events (e.g. state changes) generated by the instruments. In order to implement this the user interface needs to retrieve the data to present, subscribe to remote events etc. However, a lot of different controls are used to present different types of data, and all controls will not be visible at any one time. The controls can for instance be part of a TabPage that is not being shown. Subscribing to remote events and retrieving remote data for controls that are not shown is unnecessary, can affect the performance and can cause increased network traffic. There is an easy solution to this - only retrieve data and listen to remote events while the Control viewing/editing the data is visible. So now we just needed to know when the Control becomes visible or invisible, which should be simple enough since the Control class provides a VisibleChanged event.
Unfortunately we quickly realized that the VisibleChanged event didn’t work as we expected. The Control.Visible property is a pretty special property. The documentation says:
Gets or sets a value indicating whether the control is displayed.
However, setting the Visible property to true will not cause a child control to be displayed if the parent container is invisible. This is very understandable, because that would force all parent containers to become visible as well, which would be an unexpected (and most likely unwanted) side-effect. So setting the Visible property to true means: I want this control to be displayed if (or when) all containers in the Control hierarchy are also displayed. On the other hand, the documentation is correct regarding the getter. It will only return true if all containers are also visible, thus the getter and the setter are not referring to the same concept. A better solution would be to make the Visible property read-only, and have some other property for setting/getting the wanted visibility of a specific control (this is how it is implemented in WPF, where you have a Visibility get/set property and an IsVisible read-only property).
Well, back to the issue with the VisibleChanged event. Since we’re only interested in whether the Control is actually displayed or not, getting the Visible property provides us with the information we want. We just need to be notified when the property changes. Consider the following code snippet:
panel = new Panel(); panel.VisibleChanged += new EventHandler( panel_VisibleChanged ); label = new Label(); label.VisibleChanged += new EventHandler( label_VisibleChanged ); panel.Controls.Add( label );
Assuming that panel.Visible = true and label.Visible = true, then setting:
panel.Visible = false;
will raise the panel_VisibleChanged event and panel.Visible will be false and label.Visible will be false (since the container is not visible). The important thing to note here is that the label_VisibleChanged event was NOT raised even though label.Visible changed from true to false.
panel.Visible = true;
will raise BOTH panel_VisibleChanged and label_VisibleChanged (and both panel.Visible and label.Visible will be true again).
So, when changing the Visible settings on a container Control, the VisibleChanged event for child controls will only be raised when the child becomes visible, not when it’s hidden. This renders the VisibleChanged event pretty useless for most uses that I can think of.
Investigating the Control.Visible property using Reflector (or ILDasm as I used at the time) reveals what’s causing the problem. When setting the Visible property, an internal visibility state flag is set or cleared, depending on the property value. After setting the state flag, the Control calls OnVisibleChanged, which raises the VisibleChanged event and propagates the change to child controls (via OnParentVisibleChanged), but only if the child control is Visible. This check is performed because of the fact that a child control that is not Visible, will not be affected by the visibility of the parent control. The problem is that the child control has already become invisible at this time, so the change is never propagated. Instead of checking the Visible property when propagating, the internal state flag should have been used. This is actually what happens in the OnParentVisibleChanged method. It checks whether the internal visibility flag is set, and raises the VisibleChanged event if it is, so the pre-checking before calling OnParentVisibleChanged is redundant and erroneous.
I have reported about this issue a few times in a Microsoft newgroup (the first time was before .NET 1.0 was released), but the problem still exists in .NET 2.0. It is not a serious problem, and fixing it might cause problems in programs that somehow rely on this strange behaviour.
We have written a workaround for the problem, so I have not thought about the problem for a while. Not until I helped a .NET Memory Profiler customer that had a problem with a memory leak. This leak appeared to be (at least partially) caused by the VisibleChanged problem described in this post. You can read more about this leak in the article "Memory Leak in ToolStripTextBoxControl".