Joe White's Blog Life, .NET, and cats

Control.Visible... ugh. #.NET #Delphi #dgrok

Control.Visible lies.

I vaguely recalled this, actually, from some sessions in Reflector a few months ago. But it just bit me today. Not in a major way, but enough to be irritating.

I put my search tool's search options into two expandable/collapsible panels, one for selecting the directories, the other for the search options. I'm not offhand aware of any good collapsible panels for WinForms, so I just rolled my own, with one control for the header and then a panel for the contents. And I showed a little "+" or "-" glyph on the header, so it would look familiar to anyone who's used a treeview. I updated that glyph based on the Visible property of the panel, and had hooks in various places to make sure the glyph was up-to-date.

Well, this morning I noticed that, when the program came up, those glyphs were wrong. The first time I do anything to update them, they correct themselves and stay correct for the rest of the program run.

Set breakpoints. The method that updates the glyphs gets called several times during startup, and each time, the panels' Visible property is true... up until the last time, when it's false.

Now, I know I'm not changing the panels' Visible property during startup. And I know that the panels are visible once the window is displayed. So... what the heck is the deal?

At least WinForms gives me a VisibleChanged event that I can hook. So I hook it, and put in a breakpoint. Lo and behold, it does indeed fire when the panels' Visible is mysteriously changing to false. (Oddly enough, it does not fire again when their Visible is mysteriously changing back to true.) I check the stack trace, and among the code on the stack is Control.WmShowWindow.

That's when it clicks in my head: Aha, that's right. The value you set into Visible is not necessarily the value you get back out.

The MSDN docs have this to say:

Gets or sets a value indicating whether the control is displayed.

Wow, that's helpful. I can tell you from looking in Reflector, however, that what happens when you read Visible is this: it returns this.Visible && Parent.Visible && Parent.Parent.Visible && Parent.Parent.Parent.Visible && ...

That's not quite how they implement it, but it makes Visible nearly useless for nearly everything. It certainly makes it useless for what I was using it for, because there are times when it returns random values (i.e., when the window is in the process of being shown, and events are getting fired, but Form.Visible is not yet true so all of its children lie).

This is not how properties are supposed to work. I want a property to return the value I set into it. Bad WinForms! No biscuit!

Once again, this is an area where Delphi got it right. You can set TControl.Visible to True, and it will return True, even if it's on a form that's not visible yet. They have a separate property, called Showing, that has the other "is it actually visible all the way up the chain?" behavior, for those rare occasions when that's what you want. This seems much more in keeping with common sense.

Sigh. Not a big deal, once I identified the problem; I just added a couple more properties to my model object (ExpandDirectoryOptions and ExpandSearchOptions), and used them instead of reading the Visible property. Voila; problem solved.

Lesson learned: Consider the WinForms Control.Visible property to be write-only.