Wednesday, March 4, 2009

Further Thoughts on WPF and XAML

I started to play with WPF and XAML a couple weeks back, and wrote a post about my first thoughts. Since then, I've been working through my first full-fledged WPF app, and I've figured out a ton more that I didn't understand at the time: both about the true strengths of WPF, and the things I still find really annoying. So some more thoughts:

  • The ability to easily modify templated controls is amazing. You can do things in minutes with a ListBox in XAML that would have taken months of tedious coding in WinForms.
  • The WPF designer in Visual Studio sucks. It gives you virtually no help in troubleshooting errors, it's slow, and among many other complaints, it is of no help whatsoever when you're trying to layout a DataTemplate. I've been reduced to numerous long, painful cycles of, "change/compile/run/look/swear/change again".
  • The WPF data-binding model is very powerful, but it's very subtle, difficult to get right, and very easy to get wrong. If anyone at Microsoft actually believes the marketing-speak that it's "simple" in addition to powerful, they need to have their head examined. For instance, I guess it's kinda cool that you can set bind properties on one control to another control, and can trigger behaviors when those properties change: but I can do this in a very straightforward way in C#, and in contrast, the XAML syntax for doing it is really annoying. If you didn't know XAML pretty darned well, would you have any idea what the code below is doing? (Hint: it displays a particular control when its parent ListBoxItem is selected.)
  • <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}, AncestorLevel=1}, Path=IsSelected}" Value="True">     <Setter Property="Visibility" Value="Visible" TargetName="pledgeDetailsDockPanel" /> DataTrigger>

  • XAML desperately needs some debugging aids. Since it's a declarative and not a procedural language, it doesn't really make sense to "step through" code, but something like FireBug for XAML instead of HTML would make a ton of difference when you're trying to figure out why a particular {Binding} isn't working the way you expect. (Which, in my limited experience, is fairly close to always.)
  • Contrary to the raves I've read elsewhere, I'm convinced that XAML is an ugly, clunky language. Granted, there are certain things that it allows you to express with great ease: instantiating a control, for instance, and setting its properties. But if you have to interact with outside objects or even other controls in any but the most basic manner, things get ugly very quickly. If you've ever worked with an ObjectDataSource, especially if you had to pass it dynamic method parameters, you know what I mean. Compared to XAML in these instances, C++ is a model of clarity, and perl is a model of readability.
  • Here's an example of what I mean on that last point. Let's say that I've got a simple WPF app that converts Fahrenheit to Celsius, by calling the method ToCelsius() on a Fahrenheit class.

    In C# code-behind, I can get this to work with two lines of code:

    private void txtFahrenheit_TextChanged(object sender, TextChangedEventArgs e) {     Fahrenheit temp = new Fahrenheit(txtFahrenheit.Text);     lblCelsius.Content = temp.ToCelsius(); }

    Here's how you get those two lines of code to work in XAML (with all of the purely formatting attributes and tags removed):

    <Grid.Resources>     <ObjectDataProvider ObjectType="{x:Type local:Fahrenheit}" x:Key="fahrenheit">         <ObjectDataProvider.ConstructorParameters>             <system:String>system:String>         ObjectDataProvider.ConstructorParameters>     ObjectDataProvider>     <ObjectDataProvider ObjectInstance="{StaticResource fahrenheit}" MethodName="ToCelsius" x:Key="toCelsius" /> Grid.Resources> <TextBox>     <TextBox.Text>         <Binding Source="{StaticResource fahrenheit}">             <Binding.Path>ConstructorParameters[0]Binding.Path>             <Binding.BindsDirectlyToSource>TrueBinding.BindsDirectlyToSource>             <Binding.UpdateSourceTrigger>PropertyChangedBinding.UpdateSourceTrigger>         Binding>     TextBox.Text> TextBox> <Label>     <Label.Content>         <Binding Source="{StaticResource toCelsius}" />     Label.Content> Label>

    Those two lines of very simple, very comprehensible code have expanded into something like 16 new lines of very dense, intricate, complicated XML. Oh, and have I mentioned that you can't debug those 16 lines of code? And if you look closely, you'll notice that there's a nice ugly hack in there, where I do a sort of reverse-binding, with the Fahrenheit textbox as the target of a binding to the first parameter in the Fahrenheit constructor. You have to do this because the ObjectDataProvider is inexplicably not a DependencyObject, so you can't bind any of its properties to the properties of a control: you have to do it the other way around, and bind a control's properties to the ObjectDataProvider.

    Sigh.

    XAML and WPF still needs a bit of work.