wpfvisual-studioxamlcontenttemplate

VsBrush doesn't work in WPF ResourceDictionary


tl;dr

Problem description

First of all, I'm new to WPF and I have little to now idea what I'm doing...

I'm developing a Visual Studio 2013 Extension in which I create a custom Tool Window. Since the WPF controls don't inherit themes from parent (i.e. Visual Studio main window), you have to to it manually.

I read that I should use the Microsoft.VisualStudio.Shell.VsBrushes or the Microsoft.VisualStudio.PlatformUI.EnvironmentColors classes as they define the Visual Studio shell theme specific constants. (See example here.) This works all fine as long as I use this within a Style tag.

However, Microsoft's Menu and MenuItem ControlTemplate Example explains how to do a proper MenyItem template using <ContentTemplate>. The problem is, that neither the VsBrush nor the EnvironmentColors don't work within the <ContentTemplate>. Either there is a generic exception complaining when I set the color for <GradintStop> (no details, what the problem is), or the UI just hangs, doesn't even load. In this later case when I break the application, I always en up in MS.Win32.UnsafeNativeMethods.GetMessageW() function.

Questions

  1. Could someone please explain what I'm doing wrong and why I can't use the VsBrushes and EnvironmentColors classes in my <ContentTemplate>?
  2. How can I properly style my Visual Studio Extension using the suggested <ContentTemplate> format?

Attempts To Solve The Issue

  1. I checked in the constructor of the whole package, that the color constants of the VsBrushes class can be fetched. Only after the constructor does the UI hang, so the VsBrushes values must be initialized by the time the XAML is processed.
  2. As pointed out above, without the use of <ControlTemplate> the constants can be used.
  3. Investigated the exception: it was thrown when the parser tried to interpret the Color property of the <GradientStop> tag. No explanation what exactly failed. (After a while -- as the code changed a bit -- the exception ceased and the UI hang started.)
  4. If I change the LinearGradientBrush to SolidColorBrush the problem still persists (of course the exception is slightly different this time): 'Set property 'System.Windows.Media.SolidColorBrush.Color' threw an exception.
  5. The problem is not <MenuItem> specific. It can be reproduced with <Button> and I guess with any arbitrary WPF control.

Source

Here's the code I use for defining my style for MenuItems:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:vsShell="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.12.0"
                    xmlns:vsUI="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.12.0">
    <Color x:Key="MyColor">#FFFFFF00</Color>
    <Style x:Key="{x:Type Menu}" TargetType="{x:Type Menu}">
        <Setter Property="OverridesDefaultStyle" Value="True" />
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Menu}">
                    <Border BorderThickness="1">
                        <Border.BorderBrush>
                            <LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
                                <LinearGradientBrush.GradientStops>
                                    <GradientStopCollection>
                                        <GradientStop Color="{DynamicResource MyColor}" Offset="0.0" />
                                        <GradientStop Color="{DynamicResource {x:Static SystemColors.ActiveBorderColorKey}}" Offset="0.1" />
                                        <GradientStop Color="#FF00FF00" Offset="0.5" />
                                        <!-- The following 3 do not work -->
                                        <GradientStop Color="{DynamicResource {x:Static vsUI:EnvironmentColors.AccentBorderBrushKey}}" Offset="0.8" />
                                        <GradientStop Color="{DynamicResource {x:Static vsShell:VsBrushes.AccentBorderKey}}" Offset="0.8" />
                                        <GradientStop Color="{DynamicResource VsBrush.AccentBorder}" Offset="1.0" />
                                    </GradientStopCollection>
                                </LinearGradientBrush.GradientStops>
                            </LinearGradientBrush>
                        </Border.BorderBrush>
                        <Border.Background>
                            <LinearGradientBrush EndPoint="0,0.5" StartPoint="1,0.5">
                                <GradientStop Color="Blue" Offset="0" />
                                <GradientStop Color="Blue" Offset="1" />
                            </LinearGradientBrush>
                        </Border.Background>
                        <StackPanel ClipToBounds="True" Orientation="Horizontal" IsItemsHost="True" />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Call Stack

This is where the process "hangs":

[Managed to Native Transition]  
>   WindowsBase.dll!MS.Win32.UnsafeNativeMethods.GetMessageW(ref System.Windows.Interop.MSG msg, System.Runtime.InteropServices.HandleRef hWnd, int uMsgFilterMin, int uMsgFilterMax) Line 566  C#
WindowsBase.dll!System.Windows.Threading.Dispatcher.GetMessage(ref System.Windows.Interop.MSG msg, System.IntPtr hwnd, int minMessage, int maxMessage) Line 391 C#
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame) Line 979  C#
WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) Line 961  C#
WindowsBase.dll!System.Windows.Threading.Dispatcher.Run() Line 1059 C#
Microsoft.VisualStudio.Shell.12.0.dll!Microsoft.Internal.VisualStudio.PlatformUI.BackgroundDispatcher.ThreadProc(object arg)    Unknown
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart_Context(object state)    Unknown
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Unknown
mscorlib.dll!System.Threading.ThreadHelper.ThreadStart(object obj)  Unknown

Solution

  • Resolution

    All these following 3 lines cause compilation error, because the compiler expects a Color but I gave it a Brush instead.

    <GradientStop Color="{DynamicResource {x:Static vsUI:EnvironmentColors.AccentBorderBrushKey}}" Offset="0.8" />
    <GradientStop Color="{DynamicResource {x:Static vsShell:VsBrushes.AccentBorderKey}}" Offset="0.8" />
    <GradientStop Color="{DynamicResource VsBrush.AccentBorder}" Offset="1.0" />
    

    I should have use <GradientStop Color="{DynamicResource {x:Static vsUI:EnvironmentColors.AccentBorderColorKey}}" Offset="0.8" /> instead. (Notice that I use AccentBorderColorKey instead of AccentBorderBrushKey.)

    XAML is parsing string and tries to interpret that which leads to a simple fact: XAML is typeless (everything is a string). Since the error message ('Set property 'System.Windows.Media.LinearGradientBrush.Color' threw an exception.) is not really talkative, it doesn't really help you understand what you did wrong.

    Lesson learned

    Try to check the type manually if the compiler doesn't do it for you (or doesn't tell you that it is what causes the problem.)