Marcel Lorenz
Marcel Lorenz

Reputation: 453

Type key for style not found

I created a custom user control called FlatButton deriving from Button:

XAML:

<Button
    x:Class="Program.GUI.Controls.FlatButton"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:GUIControls="clr-namespace:Program.GUI.Controls"
    mc:Ignorable="d">
    <Button.Resources>
        <ResourceDictionary>
            <Style x:Key="{x:Type GUIControls:FlatButton}" TargetType="{x:Type GUIControls:FlatButton}" BasedOn="{StaticResource ResourceKey={x:Type Button}}">
                <Setter Property="OverridesDefaultStyle" Value="True"/>
                <Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True"/>
                <Setter Property="BorderThickness" Value="0"/>
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type GUIControls:FlatButton}">
                            <Grid x:Name="LayoutRoot" Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}">
                                <TextBlock
                                    x:Name="Text"
                                    Text="{TemplateBinding Content}"
                                    Foreground="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"
                                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                            </Grid>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter TargetName="LayoutRoot" Property="Background" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HoverBackground}"/>
                                    <Setter TargetName="Text" Property="Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=HoverForeground}"/>
                                </Trigger>
                                <Trigger Property="IsMouseCaptured" Value="True">
                                    <Setter TargetName="LayoutRoot" Property="Background" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ClickBackground}"/>
                                    <Setter TargetName="Text" Property="Foreground" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ClickForeground}"/>
                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </ResourceDictionary>
    </Button.Resources>
</Button>

CS:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace Program.GUI.Controls {
    /// <summary>
    /// Interaction logic for FlatButton.xaml
    /// </summary>
    public partial class FlatButton : Button {
        public static readonly DependencyProperty HoverBackgroundProperty = DependencyProperty.Register(
            "HoverBackground",
            typeof(Brush),
            typeof(FlatButton)
        );

        public static readonly DependencyProperty HoverForegroundProperty = DependencyProperty.Register(
            "HoverForeground",
            typeof(Brush),
            typeof(FlatButton)
        );

        public static readonly DependencyProperty ClickBackgroundProperty = DependencyProperty.Register(
            "ClickBackground",
            typeof(Brush),
            typeof(FlatButton)
        );

        public static readonly DependencyProperty ClickForegroundProperty = DependencyProperty.Register(
            "ClickForeground",
            typeof(Brush),
            typeof(FlatButton)
        );

        public Brush HoverBackground { get; set; }
        public Brush HoverForeground { get; set; }

        public Brush ClickBackground { get; set; }
        public Brush ClickForeground { get; set; }

        static FlatButton() {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(FlatButton), new FrameworkPropertyMetadata(typeof(FlatButton)));
        }

        public FlatButton() {
            this.InitializeComponent();
        }
    }
}

The Problem is when I try to create an extending style the base style is not found:

MainWindow XAML:

<Style x:Key="DefaultDarkButtonThemeStyle" TargetType="{x:Type GUIControls:FlatButton}" BasedOn="{StaticResource {x:Type GUIControls:FlatButton}}">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="Foreground" Value="{DynamicResource ColorTextPrimaryDark}"/>
    <Setter Property="HoverBackground" Value="{DynamicResource ColorPrimaryDarkLight}"/>
    <Setter Property="HoverForeground" Value="{DynamicResource ColorTextPrimaryDarkLight}"/>
    <Setter Property="ClickBackground" Value="{DynamicResource ColorPrimaryDarkLightLight}"/>
    <Setter Property="ClickForeground" Value="{DynamicResource ColorTextPrimaryDarkLightLight}"/>
</Style>

Exception:

System.Windows.Markup.XamlParseException: ''Provide value on 'System.Windows.Markup.StaticResourceHolder' threw an exception.' Line number '45' and line position '47'.'

Inner Exception
Exception: Cannot find resource named 'Program.GUI.Controls.FlatButton'. Resource names are case sensitive.

Upvotes: 1

Views: 328

Answers (1)

thatguy
thatguy

Reputation: 22089

If you develop a custom control like FlatButton, you should create a separate style that either resides in a theme resource dictionary (e.g. Themes/Generic.xaml) if you develop a custom control library or any other dedicated resource dictionary. Regardless of which path you choose, you can merge the corresponding resource dictionary into the application resources or any other resource dictionary on a lower scope in order to make the style accessible within that scope (apply it automatically if it is implicit or creating styles based on it, whatever).

Your partial XAML definition is the usual approach for creating UserControls, but not for custom controls. The style for FlatButton in the resources of the Button markup can only be resolved within the scope of this local resource dictionary, which means the FlatButton itself or down its visual tree. Look at the static resource lookup behavior to understand how the style is being resovled and why it fails.

  1. The lookup process checks for the requested key within the resource dictionary defined by the element that sets the property.

  2. The lookup process then traverses the logical tree upward to the parent element and its resource dictionary. This process continues until the root element is reached.

  3. App resources are checked. App resources are those resources within the resource dictionary that is defined by the Application object for your WPF app.

Consequently, when your DefaultDarkButtonThemeStyle is created in your main window and tries to resolve the GUIControls:FlatButton base style, it does not find it, because it is not defined within the window resources or anywhere up the visual tree or the application resources.

However, if you simply created a theme or regular resource dictionary that contained the style and included it in the window or application resource, it would be resolved successfully.

For more information on creating custom controls, including how to set up styles and resource dictionaries, you can refer to Control authoring overview.

Upvotes: 1

Related Questions