Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design time check of markup extension arguments in WPF designer

I've written a Markup extension for WPF that allows me to do

<!-- Generic Styles -->
<Style x:Key="bold" TargetType="Label">
    <Setter Property="FontWeight" Value="ExtraBold" />
</Style>

<Style x:Key="italic" TargetType="Label">
    <Setter Property="FontStyle" Value="Italic" />
</Style>

<Style x:Key="gridHeader" TargetType="Label" 
    BasedOn="{WPF:CombiStyle bold italic }" >

It is a very usefull extension and it works great at runtime. However at design time I can't see the styles applied or that if I mistype bold and italic they might not be found as StaticResources.

Any hacks I can do to get this working?

The source code for the extension is

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;

namespace MarkupExtensions
{
    [MarkupExtensionReturnType(typeof(Style))]
    public class CombiStyleExtension : MarkupExtension
    {

        private string[] MergeStyleProviders { get; set; }

        public CombiStyleExtension(string s0)
        { 
            MergeStyleProviders = s0.Split(new[]{' '});
        }

        public override object ProvideValue(IServiceProvider
                                            serviceProvider)
        {
            return MergeStyleProviders
                .Select(x => StringToStyle(serviceProvider, x))
                .Aggregate(new Style(), RecursivelyMergeStyles);
        }

        private static Style StringToStyle(IServiceProvider serviceProvider, string x)
        {
            var style = new StaticResourceExtension() { ResourceKey = x }.ProvideValue(serviceProvider) as Style;
            if (style==null)
            {
                throw new ArgumentException("Argument could not be converted to a style");
            }
            return style;
        }

        private static Style RecursivelyMergeStyles(Style accumulator,
                                           Style next)
        {
            if (next.BasedOn != null)
            {
                RecursivelyMergeStyles(accumulator, next.BasedOn);
            }

            MergeStyle(accumulator, next);

            return accumulator;
        }

        private static void MergeStyle(Style targetStyle, Style sourceStyle)
        {
            targetStyle.TargetType = sourceStyle.TargetType;
            // Merge the Setters...
            foreach (var setter in sourceStyle.Setters)
                targetStyle.Setters.Add(setter);

            // Merge the Triggers...
            foreach (var trigger in sourceStyle.Triggers)
                targetStyle.Triggers.Add(trigger);
        }

    }
}
like image 561
bradgonesurfing Avatar asked Apr 09 '13 12:04

bradgonesurfing


1 Answers

Update: added screenshots for VS2012 (works fine) and Blend for VS2012 (works partially: base styles on BasedOn-styles are not picked up correctly for some reason).

Also checked it in VS2013 Preview and Blend for VS2013 Preview - there it works partially and exactly the same as in Blend for VS2012. Hope they fix this in release.

Works in VS2012 as good as in VS2010

In Blend for VS2012 it works partionally

The thing is that Visual Studio designer very likes when object that you try to describe in XAML has public default constructor that it use to instanciate design-time instance of that object.

I've updated a bit your CombiStyleExtension.cs class to take this into account and Visual Studio 2010 designer like it now. However Blend 4 designer still don't, sorry.

Take a look:

using System;
using System.Linq;
using System.Windows;
using System.Windows.Markup;

namespace WpfApplication7
{
    [MarkupExtensionReturnType(typeof(Style))]
    public class CombiStyleExtension : MarkupExtension
    {
        /// <summary>
        /// Set space-separated style names i.e. "size16 grey verdana".
        /// </summary>
        public string Names { private get; set; }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return Names.Split(new[] { ' ' })
                        .Select(x => Application.Current.TryFindResource(x)
                            as Style)
                        .Aggregate(new Style(), RecursivelyMergeStyles);
        }

        private static Style RecursivelyMergeStyles(Style accumulator,
                                                    Style next)
        {
            if(accumulator == null || next == null)
                return accumulator;

            if(next.BasedOn != null)
                RecursivelyMergeStyles(accumulator, next.BasedOn);

            MergeStyle(accumulator, next);

            return accumulator;
        }

        private static void MergeStyle(Style targetStyle, Style sourceStyle)
        {
            if(targetStyle == null || sourceStyle == null)
            {
                return;
            }

            targetStyle.TargetType = sourceStyle.TargetType;

            // Merge the Setters...
            foreach(var setter in sourceStyle.Setters)
                targetStyle.Setters.Add(setter);

            // Merge the Triggers...
            foreach(var trigger in sourceStyle.Triggers)
                targetStyle.Triggers.Add(trigger);
        }
    }
}

Usage of this markup extension changed also just a bit. How it was:

BasedOn="{WPF:CombiStyle bold italic }"

and how it now:

BasedOn="{WPF:CombiStyle Names='bold italic'}"

And just to save some time for you here is a bit of xaml to copy-paste-run-and-watch:

MainWindow.xaml:

<Window x:Class="WpfApplication7.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:WPF="clr-namespace:WpfApplication7"
        Title="MainWindow" Height="350" Width="569">
    <Window.Resources>
        <!-- Did not managed to make the type-level style work -->
        <!-- from app.xaml, so put it here. Just in case. -->
        <Style TargetType="{x:Type Label}"
                  BasedOn="{WPF:CombiStyle Names='size16 grey verdana'}" />
    </Window.Resources>
    <StackPanel>
        <Label Content="Type-level: size16 + grey + verdana" />
        <Label Content="'h1': size24 + royalBlue" Style="{DynamicResource h1}" />
        <Label Content="'warning': size24 + yellow + bold + shadow"
                   Style="{DynamicResource warning}" />
        <Label Content="Inline: size12 + italic"
                   Style="{WPF:CombiStyle Names='size12 italic'}" />
        <Label Content="Inline: size16 + bold + italic + red"
                   Style="{WPF:CombiStyle Names='size16 bold italic red'}" />
        <Label Content="Inline: size24 + green"
                   Style="{WPF:CombiStyle Names='size24 green'}" />
    </StackPanel>
</Window>

App.xaml:

<Application x:Class="WpfApplication7.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:WPF="clr-namespace:WpfApplication7"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <!-- Sizes -->
        <Style x:Key="size12" TargetType="Label">
            <Setter Property="FontSize" Value="12" />
        </Style>
        <Style x:Key="size16" TargetType="Label">
            <Setter Property="FontSize" Value="16" />
        </Style>
        <Style x:Key="size24" TargetType="Label">
            <Setter Property="FontSize" Value="24" />
        </Style>
        <!-- Bold/Italic -->    
        <Style x:Key="bold" TargetType="Label">
            <Setter Property="FontWeight" Value="ExtraBold" />
        </Style>
        <Style x:Key="italic" TargetType="Label">
            <Setter Property="FontStyle" Value="Italic" />
        </Style>
        <!-- Colors --> 
        <Style x:Key="grey" TargetType="Label">
            <Setter Property="Foreground" Value="#333333" />
        </Style>
        <Style x:Key="royalBlue" TargetType="Label">
            <Setter Property="Foreground" Value="RoyalBlue" />
        </Style>
        <Style x:Key="green" TargetType="Label">
            <Setter Property="Foreground" Value="Green" />
        </Style>
        <Style x:Key="yellow" TargetType="Label">
            <Setter Property="Foreground" Value="Yellow" />
        </Style>
        <Style x:Key="red" TargetType="Label">
            <Setter Property="Foreground" Value="#D00000" />
        </Style>
        <!-- Fonts -->  
        <Style x:Key="verdana" TargetType="Label">
            <Setter Property="FontFamily" Value="Verdana" />
        </Style>
        <!-- Effects -->
        <Style x:Key="shadow" TargetType="Label">
            <Setter Property="Effect">
                <Setter.Value>
                    <DropShadowEffect ShadowDepth="0" />
                </Setter.Value>
            </Setter>
        </Style>
        <!-- Predefined Combinations -->
        <Style x:Key="h1" TargetType="{x:Type Label}"
               BasedOn="{WPF:CombiStyle Names='size24 royalBlue'}" />
        <Style x:Key="warning" TargetType="{x:Type Label}"
               BasedOn="{WPF:CombiStyle Names='size24 yellow bold shadow'}">
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="BorderBrush" Value="Yellow" />
        </Style>
    </Application.Resources>
</Application>

Enjoy ;)

like image 136
Sevenate Avatar answered Sep 23 '22 23:09

Sevenate