Style BasedOn的DynamicResource

时间:2012-02-28 21:48:28

标签: wpf styles

构建一个具有自定义“高对比度”主题的应用程序,供户外使用,可在运行时切换打开和关闭。这可以通过合并和取消合并包含以下样式的资源字典来正常工作......

<Style x:Key="{x:Type MenuItem}" TargetType="{x:Type MenuItem}">
    <Setter Property="OverridesDefaultStyle" Value="true"/>
    <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
    <Setter Property="Template" Value="{StaticResource Theme_MenuItemTemplate}"/>
</Style>

当使用menuitem未指定样式时,此功能非常有用。尽管在许多情况下这是不现实的,因为没有方法可以绑定没有样式的ItemsSource生成的子项。例如:

<ContextMenu.ItemContainerStyle>
    <Style TargetType="MenuItem">
        <Setter Property="Header" Value="{Binding Path=Name}"/>
        <Setter Property="IsCheckable" Value="True"/>
        <Setter Property="IsChecked" Value="{Binding Path=Checked}"/>
        <EventSetter Event="Checked" Handler="HistoryItem_Checked"/>
    </Style>
</ContextMenu.ItemContainerStyle>

StackOverflow上的其他帖子说你只需要这样做......

<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">
    <!-- Your overrides -->
</Style>

但这对我的情况不起作用,因为我的BasedOn可以并且将在运行时更改(当然,您不能在BasedOn属性上使用DynamicResource扩展)。在我的应用程序中执行此操作当前会导致控件在加载控件时覆盖其样式,而其他控件在没有重新加载的情况下正确切换。

所以我的问题......

有没有办法让DynamicResource扩展为BasedOn工作,还是我可以实现另一种方法/ hack来实现这一点?

4 个答案:

答案 0 :(得分:7)

最后使用DynamicResouce找出了Style.BasedOn AttachedDependencyProperty的解决方案。

以下是ItemsControl.ItemContainerStyle的修正(可轻松修改以更改FrameworkElement.Style

public class DynamicContainerStyle
{
    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(DynamicContainerStyle), new UIPropertyMetadata(DynamicContainerStyle.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(DynamicContainerStyle), new UIPropertyMetadata(DynamicContainerStyle.StylesChanged));

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

        var Element = (System.Windows.Controls.ItemsControl)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.ItemContainerStyle = 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;
    }
}

这是一个例子......

<!-- xmlns:ap points to the namespace where DynamicContainerStyle class lives -->
<MenuItem Header="Recent" 
    ItemsSource="{Binding Path=RecentFiles}"
    IsEnabled="{Binding RelativeSource={RelativeSource Self}, Path=HasItems}"
    ap:DynamicContainerStyle.BaseStyle="{DynamicResource {x:Type MenuItem}}">
    <ap:DynamicContainerStyle.DerivedStyle>
        <Style TargetType="MenuItem">
            <EventSetter Event="Click"  Handler="RecentFile_Clicked"/>
        </Style>
    </ap:DynamicContainerStyle.DerivedStyle>
    <MenuItem.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}"/>
        </DataTemplate>
    </MenuItem.ItemTemplate>
</MenuItem>

这是一个修改后的版本,在我对另一篇文章的回答中设置了FrameworkElement.StyleSetting a local implicit style different from theme-style / alternative to BasedOn DynamicResource

答案 1 :(得分:3)

我对NtscCobalts的答案略有改进:

    #region Type-specific function (FrameworkElement)
    private static void StylesChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
    {
        var mergedStyles = GetMergedStyles<FrameworkElement>(target, GetBaseStyle(target), GetDerivedStyle(target)); // NOTE: change type on copy

        var element = (FrameworkElement)target; // NOTE: change type on copy

        element.Style = mergedStyles;
    }
    #endregion Type-specific function (FrameworkElement)


    #region Reused-function 
    public static Style GetMergedStyles<T>(DependencyObject target, Style baseStyle, Style derivedStyle) where T : DependencyObject
    {
        if (!(target is T)) throw new InvalidCastException("Target must be " + typeof(T));

        if (derivedStyle == null) return baseStyle;
        if (baseStyle == null) return derivedStyle;

        var newStyle = new Style { BasedOn = baseStyle, TargetType = derivedStyle.TargetType };
        foreach (var setter in derivedStyle.Setters) newStyle.Setters.Add(setter);
        foreach (var trigger in derivedStyle.Triggers) newStyle.Triggers.Add(trigger);
        return newStyle;

    }
    #endregion Reused-function

您可以看到,在创建新样式时,可以简单地设置基本样式。这样,基本样式的基本样式将自动在层次结构中。

答案 2 :(得分:2)

随着时间的流逝,我变得更加明智,如果更改主要是颜色(使用边框扩展也应该起作用),我想提供另一种选择来处理不同的外观。

创建所需的颜色作为资源,并为控件创建样式时,将颜色用作DynamicResources,而不是StaticResource,这就是整个窍门。

要更改“主题”时,只需加载/覆盖颜色资源。由于具有动态绑定,因此样式将自动更新/使用最接近的资源定义。

类似地,如果在某个区域中您希望事物看起来有所不同,只需在父控件的“资源”中设置新值即可设置/覆盖颜色资源。

我希望这对其他== 0的人有用。

答案 3 :(得分:1)

您的样式应位于UIElement.Resources标记中。这可以动态清除和重新填充。

我做了类似但不像这样复杂的事情:

MobileApp.Get().Resources.MergedDictionaries.Clear();

Uri uri = new Uri("/Resources/DayModeButton.xaml", UriKind.Relative);
ResourceDictionary resDict = Application.LoadComponent(uri) as ResourceDictionary;

resDict["SelectedColor"] = selectedColor; //change an attribute of resdict

MobileApp.Get().Resources.MergedDictionaries.Add(resDict);