November, 2007

  • Cloudy in Seattle

    Don't do that in the WPF Designer (Cider)!

    • 3 Comments

    One of the challenges of building a designer after the framework has shipped (as is the case with Cider) is that the designer isn't always able to support all of the coding patterns that develop before the tools become available.

    This is not a new problem.  Brian Pepin wrote a similar article to this one back in 2004 about Windows Forms.

    This post will discuss some of those patterns that are valid at runtime but are not supported by Cider.

    Code Behind in Controls

    Cider, like Blend is fundamentally a XAML designer.  We load up XAML files.  When you write code behind for a particular control, that code behind may or may not be run at design time.

    If the code behind is in the constructor of a control that is hosted by a parent in the Cider designer, it will be run when that control is loaded onto the design surface.  For instance, if I create a UserControl named MyUserControl and place it on a Window, the constructor for MyUserControl will be run when that Window is loaded in Cider.

    However, if I am designing MyUserControl in Cider, because that type is being modified and created at that time, we don't instantiate MyUserControl -- which in turn means that none of the code behind for MyUserControl will be run.

    By the same logic, the code behind for any Window that is being designed will also never be run.

    Additionally, if you ever load XAML that binds to custom properties for a type being designed (i.e. Window is being designed or MyUserControl is being designed), those bindings will fail because again, the instance that is on the designer is the base class (Window, UserControl) of the type you are designing and not the actual type.

    For example:

    <Window x:Class="DontDoThis.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300" Name="MainWindow">
        <Grid>
            <!--Notice how I can't bind to a Custom property on Window1 since the CustomInt property doesn't exist at design time-->
            <TextBox Text="{Binding ElementName=MainWindow, Path=CustomInt}" Height="29" Margin="55,28,77,0" VerticalAlignment="Top" />
        </Grid>
    </Window>

    Base Classes

    As I've alluded to above, when we design a type, we instantiate its base class and show that in the designer.  What that means is that the base class for any type that you wish to show in the designer must be concrete (i.e. not abstract) and also have a public default constructor.

    That is, if I derive a class AbstractDerivedWindow from Window and then derive ConcreteDerivedWindow from AbstractDerivedWindow and try to load up and design ConcreteDerivedWindow, we will fail since we try to instantiate AbstractDerivedWindow, which we cannot.

    More concretely:

    <loc:AbstractDerivedWindow x:Class="DontDoThis.ConcreteDerivedWindow" . . . > . . . </loc:AbstractDerivedWindow>

    will not load in the designer with the error: Type 'AbstractDerivedWindow' is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter.

    Setting Property Values in Code Behind 

    For compiled controls, for example a UserControl on a Window, that control is instantiated XAML + code which means that the code behind will run for that UserControl when it is on the design surface.

    If the code behind for that UserControl makes property changes, our model will not pick that up... which will result in the model and the designer getting out of sync with each other.

    In some situations, that isn't so bad, consider the following:

        public partial class UserControl2 : UserControl
        {
            public UserControl2()
            {
                InitializeComponent();
                Background = Brushes.Blue;
            }

    Cider will look at the properties that are set on a control after it's constructor has run and treat those values as the default values regardless of whether or not they are truly the default value as per the Dependency Property definition, set by the XAML for that control (i.e. UserControl2.xaml) or set programmatically as above.

    When I instantiate UserControl2 above on Window1.xaml, the Background property is set to Blue.  If I set the Background property locally:

    <loc:UserControl2 Background="Cyan" /> 

    It changes as expected and when I delete the Background attribute in the XAML (<loc:UserControl2/>) the Background goes back to Blue.

    That said, there are situations like the following where our designer will not match the runtime values because the properties are being set programmatically and our model doesn't pick it up.

        public class CustomControl1 : Button
        {
            String property_One;

            public String Property_One
            {
                get
                {
                    return property_One;
                }
                set
                {
                    SolidColorBrush backgroundSolidColorBrush = System.Windows.Media.Brushes.Firebrick;
                    SolidColorBrush foregroundSolidColorBrush = System.Windows.Media.Brushes.White;
                    Background = backgroundSolidColorBrush;
                    Foreground = foregroundSolidColorBrush;
                    property_One = value;
                }
            }
        }

    And this control is instanced on a Window as follows:

    <custom:CustomControl1 Margin="11,36,11,124" Property_One="One"/>

    In this case, due to some internal "shadow property/design mode value provider" magic we do in the designer, the Background and Foreground properties are set to Firebrick and White respectively at runtime but not in the designer.

    It's also important to note that the designer does not make calls to property accessor methods (get/set) for Dependency Properties.

    Finally

    Along the same lines as what I discuss here and some of my other "why doesn't my XAML load" posts, Jim Galysn has written a great post about troubleshooting designer load failures.

  • Cloudy in Seattle

    Visual Studio 2008 has shipped!

    • 0 Comments

    This is a pretty exciting day as Visual Studio 2008 has shipped.  This is the first product I've shipped with Microsoft which makes it a milestone for me.  I think back to what feels like yesterday (2 years ago) when I started on Project Cider and all we had was a half working XAML previewer. 

    Since I represented my team in the Developer Division shiproom, late last week I got to sign these big 6 foot Visual Studio boxes and received a t-shirt of all of the best shiproom quotes.  I was happy to see I got quoted twice!

    • "We have no crud in our database" (referring to our bug database)
    • "This bug is best illustrated by watching the video" (I created a video of a bug repro to support my arguments to get an approval to check in a fix for that bug)

     It really has been a great experience for me and I've learned a lot over the past two years.  I really look forward to working on our next release.

     Here is Soma's announcement:

Page 1 of 1 (2 items)