Tuesday, May 27, 2008

A Major Silverlight PITA and Two Annoying 3.0 Limitations

Pardon my rant, but the thing I currently hate most about Silverlight (besides copious XML) is the Visibility property. Any sane framework would implement Visibility as a Boolean. Not Silverlight though. It’s creators in undoubted infinite wisdom implemented it as an enumeration. The values of the enumeration? There are two: Visible and Collapsed. Hmmm.

Of course this causes superfluous verbosity in common everyday code:

button1.Visibility = makeVisible ? Visibility.Visible : Visibility.Collapsed;

Or worse when things get a little more complex:

// don't display the panel if its button's aren't visible
panel1.Visibility = !(button1.Visibility == Visibility.Visible && button2.Visibility == Visibility.Visible) ? Visibility.Visible : Visibility.Collapsed;

Clearly this was done to keep Silverlight compatible with the Windows Presentation Foundation (WPF) which has three values in its enumeration property (Visible, Hidden, and Collapsed). But that’s just as ridiculous. Why WPF couldn’t use two properties, Visible (Boolean) and NotVisibleBehavior (enumeration) is beyond me.

It’s ok though, because .Net 3.0 gave me a cure to any Framework shortcomings: Extension Methods. A syntactic sugar cure for all my bitterness:

public static void SetVisible(this FrameworkElement element, bool visible) {
    element.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
}

public static bool IsVisible(this Visibility visibility) {
    return visibility == Visibility.Visible;
}

Fantastic, now my "complex" example becomes:

// don't display the panel if its button's aren't visible
panel1.SetVisible(!(button1.IsVisible() && button2.IsVisible()));

Still not quite as nice as a Boolean visible property, but certainly doable.

3.0 Limitation #1, By Ref Extension Methods

But wait. Isn’t it best practice in Silverlight to use binding for these types of things? Separation of logic from presentation and all. So I should do:

<StackPanel Visibility="{Binding IsPanelVisible}">

And then:

public class DisplayStuff : INotifyPropertyChanged {
    public
Visibility IsPanelVisible { get; private set; }

    public void UpdateStatus(bool makeVisible) {
        IsPanelVisible = makeVisible ? Visibility.Visible : Visibility.Collapsed;
        // make sure to notify the control that the property has changed
        PropertyChanged(this, new PropertyChangedEventArgs("IsPanelVisible"));
    }
}

And we can set the DataContext of some parent element to an instance of DisplayStuff and all the children including our panel magically databind. That’s cool, but the ugliness is back (well, not as bad since I removed the buttons to simply the example, but you can pretend). This is because we extended FrameworkElement not Visibility. No problem, just extend Visibility right?

public static void SetVisible(this Visibility visibility, bool visible) {
    visibility = visible ? Visibility.Visible : Visibility.Collapsed;
}

Except this doesn’t work. Can you spot the problem?

It compiles. It runs. But the value of IsPanelVisible never changes. Oh yea, C# is pass by value by default. And now the .Net Framework 3.0 limitation. This isn’t possible:

public static void SetVisible(this ref Visibility visibility, bool visible) {

You get "The parameter modifier 'ref' cannot be used with 'this'." Grr.

Limitation #2, By Ref Automatic Properties

Ok, so remove “this”, and go back to C# 2.0 helper functions which extension methods are syntactic sugar for anyway:

public static void SetVisible(ref Visibility visibility, bool visible) {

And now our class can do:

ExtensionMethods.SetVisible(ref IsPanelVisible, makePanelVisible);

Right? Not so fast I’m afraid. Compile error. “A property or indexer may not be passed as an out or ref parameter”. And I guess this is reasonable. You can’t pass the address of a function, which is what a property is in the background. So you should pass the private variable that backs the property.

Except that I don’t have one! I used an automatic property. And .Net doesn’t let me access the private variable backing the automatic property. So I’m stuck!

And this is .Net 3.0 limitation #2. Automatic properties are wonderful until you try to do much with them. Why couldn’t the framework notice that I’m using an automatic property and pass the variable that I can’t access by ref to my function?

And now I find myself back in a .Net 2.0 world because all the features I like so much in 3.0 are more sugar than substance.

Conclusion

Allowing automatic properties to pass by reference or allowing access to the private member behind them would be nice. Allowing extension methods to change the instance they extend would be nice. But ultimately none of this would be a problem if Visible had been implemented as a Boolean. The way every other framework in the world does. </Complaining>

9 comments:

Judah Himango said...

Booleans as method parameters are frowned upon. I believe FxCop will report them as a violation.

The reason is the ambiguity in the call

DoSomething(true, false, true)

is not very readable.

However, booleans as properties are allowed because they are very readable. They must have decided to use an enum for the sake of compatibility with WPF.

Lee Richardson said...

Right they did it for compatibility with WPF, but why did WPF do it this way?

Re: Booleans as method parameters, I guess I can agree that they aren't readable, but unfortunately we don't have extension properties yet, only extension methods. Maybe in C# 4.0 :).

Matthew said...

According to MSDN:
http://msdn.microsoft.com/en-us/library/system.windows.uielement.visibility(VS.95).aspx


It says:
"The reason that Visibility uses an enumeration rather than a simple Boolean is that this area of the Silverlight object model is modeled on the Windows Presentation Foundation (WPF) Visibility property, which uses a three-state model for Visibility of Visible/Hidden/Collapsed. In the WPF model, Hidden denotes a visibility state where the object should not render, but should still occupy space in a WPF layout. Silverlight version 1.0 does not support a Hidden visibility state, but still uses the Visibility enumeration values that remain (Visible, Collapsed). If you are importing XAML UI definitions from WPF, you might need to change the cases where a Visibility is declared as Hidden in order to use that XAML in Silverlight."


So there is your answer :)

Bart said...

@Judah,

If a developer codes like this:
DoSomething(true, false, true)

they need to be shot :) j/k...but a better practice would be passing descriptive variables:

bool isPostBack = true;
DoSomething(isPostBack);

...eliminates the readability aspect of the code. PLUS enumerations have their PITA aspects :)

Andrey said...

Are the IValueConverters supported in Silverlight? If yes, then you can implement BooleanToVisiblityConverter yourself if it is not in there.

Lee Richardson said...

Andrey, you are the man! Yes they do implement IValueConverters, it just never occurred to me to use one in that context. That would be an awesome solution. Cool, I'll try it and post how it goes.

Lee Richardson said...

Matthew, yes I know they did this for compatibility with WPF, but *as I mentioned in the article* it would be FAR better to have a Visible property (not method) and a NotVisibleBehavior property (perhaps enumeration) that describes whether the object takes up space when it isn't visible.

ellenair said...

uh...hi. :) i need help..can you help me with my assignment?
its..uh.. C programming..will you please help me? :(

ellenair said...

can you please right me a recursive function called CountEvenASCII that takes in a string parameter str and return the number of characters with even ASCII codes.???

i need help :(