次の方法で共有


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

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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://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.

Comments

  • Anonymous
    November 25, 2007
    The comment has been removed

  • Anonymous
    November 26, 2007
    In this case, you are trying to access the app.config for your exe however at design time you are running in Visual Studio and thus the app.config is for Visual Studio and will not contain the keys you are looking for. You can use DesignerProperties.GetIsInDesignMode() to determine that you are at design time and not access the app.config file.

  • Anonymous
    April 17, 2008
    With WPF, designers and developers can really work closely now