Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting a local implicit style different from theme-style / alternative to BasedOn DynamicResource

Tags:

styles

wpf

xaml

imagine a wpf-application where I can dynamically change the theme. I do this by swapping out ResourceDictionaries at the Application-resource level. The theme-resourcedictionaries have implicit styles defined for TextBox and the like.

Now I have a part in my application where textboxes should have this specific style "NonDefaultTextBoxStyle" and not the application wide implicit one.

I would love to do this (using DynamicResource because the theme can be changed during runtime):

<StackPanel>
    <StackPanel.Resources>
        <Style TargetType="TextBox" BasedOn="{DynamicResource NonDefaultTextBoxStyle}"/>
    </StackPanel.Resources>
    <TextBox .../>
    <TextBox .../>
    <TextBox .../>
</StackPanel>

instead of having to do this:

<StackPanel>
    <TextBox Style="{DynamicResource NonDefaultTextBoxStyle}" .../>
    <TextBox Style="{DynamicResource NonDefaultTextBoxStyle}" .../>
    <TextBox Style="{DynamicResource NonDefaultTextBoxStyle}" .../>
</StackPanel>

Now to simplify this, I had this idea of setting an inheritable attached property on the StackPanel that would set a specified style on every descendent textbox.

Is this a good idea? Are there simpler ways? Am I missing something?

this pretty much boils down to: What is an alternative to BasedOn="{DynamicResource ...} in a style?

like image 780
Markus Hütter Avatar asked Dec 22 '11 12:12

Markus Hütter


People also ask

What is an implicit style?

An implicit style is one that's used by all controls of the same TargetType, without requiring each control to reference the style.

How to add style in XAML?

Put simply, where you declare a style affects where the style can be applied. For example, if you declare the style in the root element of your app definition XAML file, the style can be used anywhere in your app. If you declare the style in one of the app's XAML files, the style can be used only in that XAML file.


1 Answers

I had the exact same problem but for ItemsContainerStyle... so I did pretty much what you said and wrote an AttachedProperty that would allow a dynamic resource for the BasedOn.

DynamicResource for Style BasedOn

Here is the solution modified for your situation:

public class DynamicStyle
{
    public static Style GetBaseStyle(DependencyObject obj)
    {
        return (Style)obj.GetValue(BaseStyleProperty);
    }

    public static void SetBaseStyle(DependencyObject obj, Style value)
    {
        obj.SetValue(BaseStyleProperty, value);
    }

    // Using a DependencyProperty as the backing store for BaseStyle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty BaseStyleProperty =
        DependencyProperty.RegisterAttached("BaseStyle", typeof(Style), typeof(DynamicStyle), new UIPropertyMetadata(DynamicStyle.StylesChanged));

    public static Style GetDerivedStyle(DependencyObject obj)
    {
        return (Style)obj.GetValue(DerivedStyleProperty);
    }

    public static void SetDerivedStyle(DependencyObject obj, Style value)
    {
        obj.SetValue(DerivedStyleProperty, value);
    }

    // Using a DependencyProperty as the backing store for DerivedStyle.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DerivedStyleProperty =
        DependencyProperty.RegisterAttached("DerivedStyle", typeof(Style), typeof(DynamicStyle), new UIPropertyMetadata(DynamicStyle.StylesChanged));

    private static void StylesChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        if (!typeof(FrameworkElement).IsAssignableFrom(target.GetType()))
            throw new InvalidCastException("Target must be FrameworkElement");

        var Element = (FrameworkElement)target;

        var Styles = new List<Style>();

        var BaseStyle = GetBaseStyle(target);

        if (BaseStyle != null)
            Styles.Add(BaseStyle);

        var DerivedStyle = GetDerivedStyle(target);

        if (DerivedStyle != null)
            Styles.Add(DerivedStyle);

        Element.Style = MergeStyles(Styles);
    }

    private static Style MergeStyles(ICollection<Style> Styles)
    {
        var NewStyle = new Style();

        foreach (var Style in Styles)
        {
            foreach (var Setter in Style.Setters)
                NewStyle.Setters.Add(Setter);

            foreach (var Trigger in Style.Triggers)
                NewStyle.Triggers.Add(Trigger);
        }

        return NewStyle;
    }
}

And here is an example of its use:

<!-- xmlns:ap points to the namespace where DynamicStyle class lives -->
<Button ap:DynamicStyle.BaseStyle="{DynamicResource {x:Type Button}}">
    <ap:DynamicStyle.DerivedStyle>
        <Style TargetType="Button">
            <Style.Triggers>
                <MultiDataTrigger>
                    <MultiDataTrigger.Conditions>
                        <Condition Binding="{Binding Path=FirstButtonWarning}" Value="True"/>
                        <Condition Binding="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled}" Value="True"/>
                    </MultiDataTrigger.Conditions>
                    <MultiDataTrigger.Setters>
                        <Setter Property="Background" Value="{DynamicResource WarningBackground}"/>
                        <Setter Property="Foreground" Value="{DynamicResource WarningForeground}"/>
                    </MultiDataTrigger.Setters>
                </MultiDataTrigger>
            </Style.Triggers>
        </Style>
    </ap:DynamicStyle.DerivedStyle>
    <TextBlock Text="Button that changes background and foreground when warning is active"/>
</Button>
like image 177
NtscCobalt Avatar answered Nov 15 '22 09:11

NtscCobalt