6

A resource extension I've been using for a few years now stopped working at design time in a new .Net 4 project with the following error:

Markup extension 'StaticResourceExtension' requires 'IXamlSchemaContextProvider' be implemented in the IServiceProvider for ProvideValue.

The relevant method from the extension is the following:

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        Style resultStyle = new Style();
        foreach (string currentResourceKey in resourceKeys)
        {
            object key = currentResourceKey;
            if (currentResourceKey == ".")
            {
                IProvideValueTarget service = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
                key = service.TargetObject.GetType();
            }
            Style currentStyle = new StaticResourceExtension(key).ProvideValue(serviceProvider) as Style;
            if (currentStyle == null)
                throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
            resultStyle.Merge(currentStyle);
        }
        return resultStyle;
    }

Presumably, the compiler is giving the error because when I call currentStyle = new StaticResourceExtension(key).ProvideValue(serviceProvider);, the serviceProvider I'm passing along is missing IXamlSchemaContextProvider information. Don't know where it would come from though, I don't even know how the service provider for the markup extension gets set in the first place, I just use it like this:

<Style x:Key="ReadOnlyTextCell" TargetType="{x:Type TextBlock}" BasedOn="{util:MultiStyle ReadOnlyCell TextCell}"/>


The full code for the extension is here:

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

/* MultiStyleExtension - used to merge multiple existing styles.
 *  
 * Example:
    <Window xmlns:local="clr-namespace:FlagstoneRe.WPF.Utilities.UI">
        <Window.Resources>
            <Style x:Key="ButtonStyle" TargetType="Button">
                <Setter Property="Width" Value="120" />
                <Setter Property="Height" Value="25" />
                <Setter Property="FontSize" Value="12" />
            </Style>
            <Style x:Key="GreenButtonStyle" TargetType="Button">
                <Setter Property="Foreground" Value="Green" />
            </Style>
            <Style x:Key="RedButtonStyle" TargetType="Button">
                <Setter Property="Foreground" Value="Red" />
            </Style>
            <Style x:Key="BoldButtonStyle" TargetType="Button">
                <Setter Property="FontWeight" Value="Bold" />
            </Style>
        </Window.Resources>

        <Button Style="{local:MultiStyle ButtonStyle GreenButtonStyle}" Content="Green Button" />
        <Button Style="{local:MultiStyle ButtonStyle RedButtonStyle}" Content="Red Button" />
        <Button Style="{local:MultiStyle ButtonStyle GreenButtonStyle BoldButtonStyle}" Content="green, bold button" />
        <Button Style="{local:MultiStyle ButtonStyle RedButtonStyle BoldButtonStyle}" Content="red, bold button" />

 * Notice how the syntax is just like using multiple CSS classes.
 * The current default style for a type can be merged using the "." syntax:

        <Button Style="{local:MultiStyle . GreenButtonStyle BoldButtonStyle}" Content="Bold Green Button" />

 */

namespace FlagstoneRe.WPF.Utilities.UI
{
    [MarkupExtensionReturnType(typeof(Style))]
    public class MultiStyleExtension : MarkupExtension
    {
        private string[] resourceKeys;

        /// <summary>
        /// Public constructor.
        /// </summary>
        /// <param name="inputResourceKeys">The constructor input should be a string consisting of one or more style names separated by spaces.</param>
        public MultiStyleExtension(string inputResourceKeys)
        {
            if (inputResourceKeys == null)
                throw new ArgumentNullException("inputResourceKeys");
            this.resourceKeys = inputResourceKeys.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            if (this.resourceKeys.Length == 0)
                throw new ArgumentException("No input resource keys specified.");
        }

        /// <summary>
        /// Returns a style that merges all styles with the keys specified in the constructor.
        /// </summary>
        /// <param name="serviceProvider">The service provider for this markup extension.</param>
        /// <returns>A style that merges all styles with the keys specified in the constructor.</returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            Style resultStyle = new Style();
            foreach (string currentResourceKey in resourceKeys)
            {
                object key = currentResourceKey;
                if (currentResourceKey == ".")
                {
                    IProvideValueTarget service = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
                    key = service.TargetObject.GetType();
                }
                Style currentStyle = new StaticResourceExtension(key).ProvideValue(serviceProvider) as Style;
                if (currentStyle == null)
                    throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
                resultStyle.Merge(currentStyle);
            }
            return resultStyle;
        }
    }

    public static class MultiStyleMethods
    {
        /// <summary>
        /// Merges the two styles passed as parameters. The first style will be modified to include any 
        /// information present in the second. If there are collisions, the second style takes priority.
        /// </summary>
        /// <param name="style1">First style to merge, which will be modified to include information from the second one.</param>
        /// <param name="style2">Second style to merge.</param>
        public static void Merge(this Style style1, Style style2)
        {
            if(style1 == null)
                throw new ArgumentNullException("style1");
            if(style2 == null)
                throw new ArgumentNullException("style2");
            if(style1.TargetType.IsAssignableFrom(style2.TargetType))
                style1.TargetType = style2.TargetType;
            if(style2.BasedOn != null)
                Merge(style1, style2.BasedOn);
            foreach(SetterBase currentSetter in style2.Setters)
                style1.Setters.Add(currentSetter);
            foreach(TriggerBase currentTrigger in style2.Triggers)
                style1.Triggers.Add(currentTrigger);
            // This code is only needed when using DynamicResources.
            foreach(object key in style2.Resources.Keys)
                style1.Resources[key] = style2.Resources[key];
        }
    }
}
Alain
  • 26,663
  • 20
  • 114
  • 184

4 Answers4

3

Instead of using StaticResourceExtension to get the style from static resources, you can instead get the style from the current application' static resources which does the same thing.

Replace the problematic line:

Style currentStyle = new StaticResourceExtension(key).ProvideValue(serviceProvider) as Style;

with:

Style currentStyle = Application.Current.TryFindResource(key) as Style;

(Side note: see my other answer on why to use TryFindResource(key) instead of Resource[key])

Community
  • 1
  • 1
Josh Bowden
  • 5,735
  • 2
  • 25
  • 36
2

I'm running into the same issue. Some additional information:

At runtime, the IServiceProvider value is of type "MS.Internal.Xaml.ServiceProviderContext".

In the Visual Studio Xaml Designer, the IServiceProvider value is of type "Microsoft.Expression.DesignModel.Core.InstanceBuilderOperations.InstanceBuilderServiceProvider".

Clearly, VS2010 is using classes from Expression Blend to provide better design-time behavior than VS2008 had - but at a price, because the Expression Blend classes don't have all the same information that the actual run-time system does.

MORE INFO: I tried inserting my own class that implements both IServiceProvider and IXamlSchemaContextProvider. Some calls get passed to the original IServiceProvider, and I provide a dummy (empty) XamlSchemaContext when requested. But I still get the same error.

Something inside WPF is encapsulating my IServiceProvider with another IServiceProvider type - but one that fails to implement IXamlSchemaContextProvider. I have no further ideas on how to resolve this.

Jeff B
  • 1,856
  • 2
  • 17
  • 28
  • Closing this question because I've given up on trying to test anything at design time, given that it almost never looks the same as at run time. You put a lot of effort into diagnosing this issue so you deserve the points. – Alain Aug 10 '12 at 18:39
1

I had the same problem with VS2010 Designer, in order to solve it you have to modify that method:

public override object ProvideValue(IServiceProvider serviceProvider)
{
    /* if in design-mode return null */
    if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(new DependencyObject()))
        return null;

    Style resultStyle = new Style();
    foreach (string currentResourceKey in resourceKeys)
    {
        object key = currentResourceKey;
        if (currentResourceKey == ".")
        {
            IProvideValueTarget service = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
            key = service.TargetObject.GetType();
        }
        Style currentStyle = new StaticResourceExtension(key).ProvideValue(serviceProvider) as Style;
        if (currentStyle == null)
            throw new InvalidOperationException("Could not find style with resource key " + currentResourceKey + ".");
        resultStyle.Merge(currentStyle);
    }
    return resultStyle;
}
pr0gg3r
  • 4,254
  • 1
  • 36
  • 27
  • The other answer already proposed disabling the extension during design time. Since my problem is that the extension doesn't work during design time, disabling it is just another way of making it not work, and therefore doesn't solve the problem. I want a solution that makes the extension *work* during design time. – Alain Nov 30 '12 at 17:16
0

This happens because ProvideValue throws an exception at design time, but does not do at run time. So the Visual Studio designer can not get the value from the extension.

The problem line is

Style currentStyle = new StaticResourceExtension(key).ProvideValue(serviceProvider) as Style;

Visual Studio does not provides the correct value for serviceProvider parameter so an exception is thrown.

Use NativeApi.IsInDesignMode property to disable some of the code at design time.

Dmitry Tashkinov
  • 1,976
  • 19
  • 16
  • 1
    Disabling that line in design mode defeats the purpose of the extension - which is to merge two styles. Without that line, the current style cannot be retrieved to merge with the second one. That and I already identified the problem line in my question, so no points. – Alain Mar 07 '12 at 13:49