[Code Index] by Mike Marynowski

programming for fun and work

WPF Visibility Binding with Design Time Control (and more!)

Designers don't want to worry about how to modify view models when designing a UI. They just want to show and hide elements to see what they look like, and occasionally test out a few different values to make sure it still looks correct. The current methods are too complicated to setup or to cumbersome to work with for simple tasks.

Looking for something simple and easy that we can throw at designers, I tried to do this with an attached property. I was hoping it would work something like this:

<Grid Visibility="{Binding RootObject, Converter={StaticResource NullToVisibilityConverter}}" data:DesignVisibility.Value="Visible">
    <TextBlock Background="Red" Text="Testing visibility" />
</Grid>

This was a good idea in theory and I still can't explain why it didn't work consistently. Here is what the changed value handler for DesignVisibility.Value looked like:

private static void OnValueChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
    if (!DesignerProperties.GetIsInDesignMode(target))
        return;

    var element = target as UIElement;

    if (element == null)
        throw new ArgumentException("Target must be of type UIElement.", "sender");

    target.SetValue(UIElement.VisibilityProperty, e.NewValue);
}

It was buggy, like when you first opened the designer it would still collapse elements that had been set to Visible until you changed the value to either Collapsed or Hidden and then back again to Visibility. Sometimes it wouldn't work at all. I figured the binding might resolve after the attached property is set and overwrite it, so I tried various hacks like adding an asynchronous delay and then setting it again to force it, but nothing I tried worked.

Determined to create an acceptable solution, I tried another approach: creating a new markup extension that can accept a design time value to use instead of a binding or other hard coded value. This has the additional advantage of being easily extendable to support more than just the Visibility property, which we did. The result can be used like this:

<Grid Visibility="{data:Value {Binding RootObject, Converter={StaticResource NullToVisibilityConverter}}, DesignValue=Visible}">
    <TextBlock Background="Red" Text="Testing visibility" />
</Grid>

If you don't set DesignValue, it still returns the binding. If you set DesignValue, it overrides the binding while in the WPF designer. Works like a charm, and designers can easily fiddle with the values they see in their designer without worrying about the data model. Works for all property types including doubles like Width and Height, strings like Text, and of course enums like Visibility. It also works for hard-coded values or values set with other markup extensions like {x:Static}:

<TextBlock Text="AnimatedText" Width="{data:Value 0, DesignValue=200}" />
<TextBlock Text="{data:Value {x:Static local:Application.Name}, DesignValue=Fantastico 9001}" />

I have found this particularly useful for more advanced custom controls that use Storyboards and/or code behind to manipulate elements and expect them to be hard-coded to an initial state. Designers can fiddle around with the various states of the controls without worrying about messing up their run-time behavior.

Here is the complete class, the code is fairly self explanatory:

    public class ValueExtension : MarkupExtension
    {
        public object DesignValue { get; set; } = DependencyProperty.UnsetValue;
        
        [ConstructorArgument("value")]
        public object Value { get; set; } = DependencyProperty.UnsetValue;
                
        public ValueExtension() { }

        public ValueExtension(object value)
        {
            Value = value;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
            var property = provideValueTarget.TargetProperty as DependencyProperty;
            var target = provideValueTarget.TargetObject as DependencyObject;

            if (target == null || property == null)
                return this;

            object value = DesignerProperties.GetIsInDesignMode(target) && DesignValue != DependencyProperty.UnsetValue ? DesignValue : Value;

            if (value == DependencyProperty.UnsetValue || value == null)
                return value;

            if (value is MarkupExtension)
                return ((MarkupExtension)value).ProvideValue(serviceProvider);
            
            if (property.PropertyType.IsInstanceOfType(value))
                return value;
            
            return TypeDescriptor.GetConverter(property.PropertyType).ConvertFrom(value);
        }
    }