在代码中设置或修改ThemeResource

时间:2015-10-26 21:46:28

标签: xaml resources themes windows-10 uwp

我的问题非常特定于Windows 10商店应用中的ThemeResources。不幸的是," classic" WPF在这里不同或不可用。

我想要为许多ui元素实现的目标:

  • 允许用户使用系统的强调色(在XAML中,这将是{ThemeResource SystemAccentColor}作为值。)
  • 允许用户改为使用自定义/固定颜色。 (我可以覆盖resourcedictionary中的SystemAccentColor键)
  • 允许在运行时在系统重音和自定义颜色之间切换(我可以使用颜色绑定而不是使用资源)

但我没有找到一个很好的解决方案来实现这一切。如果我有自己的自定义颜色的资源字典,当用户想要切换回系统的强调颜色时,我不会摆脱它。 使用我绑定的属性有一个缺点,我没有意识到用户在应用程序运行时是否更改了系统设置中的重音颜色 - 使用{ThemeResource}标记。

有关如何正确完成此操作的任何想法? 如果可以从代码中设置ThemeResource,我可以为此编写一些行为,但似乎没有。

5 个答案:

答案 0 :(得分:4)

在Windows 10中,名称" Accent Color"更改为" SystemControlHighlightAccentBrush",它是一个ThemeResource

使用它的示例

<TextBlock Foreground="{ThemeResource SystemControlHighlightAccentBrush}"
                   Text="This is a sample text" />

要覆盖它,只需在App.xaml中更改它的值

<Application.Resources>
    <SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Orange" />
</Application.Resources>

要切换,它有点困难 首先,您需要在App.xaml中为每个主题设置所有颜色

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.ThemeDictionaries>
            <ResourceDictionary x:Key="Default">
                <SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Orange" />
            </ResourceDictionary>
            <ResourceDictionary x:Key="Dark">
                <SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Green" />
            </ResourceDictionary>
            <ResourceDictionary x:Key="Light">
                <SolidColorBrush x:Key="SystemControlHighlightAccentBrush" Color="Blue" />
            </ResourceDictionary>
        </ResourceDictionary.ThemeDictionaries>
    </ResourceDictionary>
</Application.Resources>

然后,在页面或后面的代码中,设置相应的主题

<TextBlock x:Name="TestTextBlock"
               Foreground="{ThemeResource SystemControlHighlightAccentBrush}"
               RequestedTheme="Dark"
               Text="This is a sample text" />

或在C#中

TestTextBlock.RequestedTheme = ElementTheme.Dark;

答案 1 :(得分:2)

有一种方法可以在代码中设置ThemeResource ...我只在W10 Creators Update上进行了测试,因此它可能无法在旧版本上运行,但您可以创建自己的引用资源您要使用的原始ThemeResource,然后使用此资源:

<强> XAML:

<SolidColorBrush x:Key="MyBorderBrush" Color="{ThemeResource SystemAccentColor}"/>

<强> C#:

element.BorderBrush = (SolidColorBrush)Resources["MyBorderBrush"];

element的边框颜色与Windows设置中选择的重音颜色相同,即使您的应用正在运行且用户更改它,它也会发生变化。

答案 2 :(得分:1)

一旦我也面临同样的问题,我也没有找到一种以编程方式更改 ThemeResource 的方法,以便它随着手机的主题而改变。尽管如此,有一种方法可以实现您想要的功能,但是它很麻烦,并且当您想要将其实现到许多控件时可能需要大量工作。

基本思想是使用 VisualStates 更改/到 ThemeResource - 状态在xaml中定义,因此这将适用于 ThemeResources 。然后在代码中,您可以将更改调用回手机的主题值。以下是更改为主题/用户颜色的示例按钮。

<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Button Name="ColorBtn" Content="Change users color to green rom red"/>
    <local:ExtendedButton x:Name="UserBtn" Content="Change to user's theme" UserBackground="Red">
        <local:ExtendedButton.Style>
            <Style TargetType="local:ExtendedButton">
                <!--default style's setters-->
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="local:ExtendedButton">
                            <Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
                                <VisualStateManager.VisualStateGroups>
                                    <VisualStateGroup>
                                        <VisualState x:Name="ThemeColor">
                                            <Storyboard>
                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="RootGrid">
                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemColorControlAccentColor}"/>
                                                </ObjectAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualState>
                                        <VisualState x:Name="UserColor"/>
                                    </VisualStateGroup>
                                    <!--rest of default visual states-->
                                </VisualStateManager.VisualStateGroups>
                                <ContentPresenter x:Name="ContentPresenter" AutomationProperties.AccessibilityView="Raw" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" ContentTemplate="{TemplateBinding ContentTemplate}" ContentTransitions="{TemplateBinding ContentTransitions}" Content="{TemplateBinding Content}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </local:ExtendedButton.Style>
    </local:ExtendedButton>
</StackPanel>

和代码背后:

public class ExtendedButton : Button
{
    public SolidColorBrush UserBackground
    {
        get { return (SolidColorBrush)GetValue(UserBackgroundProperty); }
        set { SetValue(UserBackgroundProperty, value); }
    }

    public static readonly DependencyProperty UserBackgroundProperty =
        DependencyProperty.Register("UserBackground", typeof(SolidColorBrush), typeof(ExtendedButton),
            new PropertyMetadata(new SolidColorBrush(Colors.Red), (s, e) =>
            { if ((s as ExtendedButton).IsUserTheme) (s as ExtendedButton).Background = e.NewValue as SolidColorBrush; }));

    // we need some property to indicate if to use user's theme or phone's
    public bool IsUserTheme
    {
        get { return (bool)GetValue(IsUserThemeProperty); }
        set { SetValue(IsUserThemeProperty, value); }
    }

    public static readonly DependencyProperty IsUserThemeProperty =
        DependencyProperty.Register("IsUserTheme", typeof(bool), typeof(ExtendedButton), new PropertyMetadata(false, (s, e) =>
        {
            if ((bool)e.NewValue)
            {
                VisualStateManager.GoToState((s as ExtendedButton), "UserColor", false);
                (s as ExtendedButton).Background = (s as ExtendedButton).UserBackground;                
            }
            else VisualStateManager.GoToState((s as ExtendedButton), "ThemeColor", false);
        }));
}

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
        Random random = new Random();
        UserBtn.Click += (s, e) => UserBtn.IsUserTheme = !UserBtn.IsUserTheme; ;
        ColorBtn.Click += (s, e) => UserBtn.UserBackground = new SolidColorBrush(Color.FromArgb(0xFF, (byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255)));
    }
}

改变一种颜色还有很长的路要走,但应该有用,也许会给你一个想法。这些也是 DependencyProperties ,因此您可以根据需要使用绑定。

答案 3 :(得分:1)

我正在使用它来设置newAccentColor,直到我找到一种方法来完成它而不切换主题。这会根据强调颜色更新所有衍生画笔:

Application.Current.Resources["SystemAccentColor"] = newAccentColor;
if (Window.Current.Content is FrameworkElement fe)
{
      var requestedTheme = fe.RequestedTheme;
      fe.RequestedTheme = fe.RequestedTheme == ElementTheme.Light ? ElementTheme.Dark : ElementTheme.Light;
      fe.RequestedTheme = requestedTheme;
}

答案 4 :(得分:0)

我有一个基于几个“助手”类的解决方案。首先只是一个带有DependencyProperty Value的容器对象,可以将其绑定或设置为{ThemeResource …}

public class DependencyObjectReference<T> : DependencyObject where T : DependencyObject
{
    #region Properties
    public T Value
    {
        get { return (T)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }
    #endregion

    #region Static Data
    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register(nameof(Value),
                                    typeof(T),
                                    typeof(DependencyObjectReference<T>),
                                    new PropertyMetadata(default(T)));
    #endregion
}

接下来是解决方案的重点:一个“选择器”,其中包含一堆引用并根据索引从中进行选择:

[ContentProperty(Name = nameof(References))]
public class DependencyObjectSelector<T> : DependencyObject where T : DependencyObject
{
    #region Constructors
    public DependencyObjectSelector()
    {
        References = new DependencyObjectCollection();
    }
    #endregion

    #region Properties
    public DependencyObjectCollection References
    {
        get { return (DependencyObjectCollection)GetValue(ReferencesProperty); }
        set { SetValue(ReferencesProperty, value); }
    }

    public Int32 SelectedIndex
    {
        get { return (Int32)GetValue(SelectedIndexProperty); }
        set { SetValue(SelectedIndexProperty, value); }
    }

    public T SelectedObject
    {
        get { return (T)GetValue(SelectedObjectProperty); }
        set { SetValue(SelectedObjectProperty, value); }
    }
    #endregion

    #region Event Handlers
    private void Evt_OnVectorChangedReferences(IObservableVector<DependencyObject> sender, IVectorChangedEventArgs args)
    {
        UpdateSelectedObject();
    }
    #endregion

    #region Private Implementation Methods
    private void UpdateSelectedObject()
    {
        if  (
            References != null
            &&
            SelectedIndex >= 0
            &&
            SelectedIndex < References.Count
            &&
            References[SelectedIndex] is DependencyObjectReference<T>
            )
        {
            BindingOperations.SetBinding
            (
                this,
                SelectedObjectProperty,
                new Binding
                {
                    Source = References[SelectedIndex],
                    Path = new PropertyPath(nameof(DependencyObjectReference<T>.Value))
                }
            );
        }
        else
        {
            ClearValue(SelectedObjectProperty);
        }
    }

    private void OnReferencesPropertyChanged(DependencyObjectCollection oldValue, DependencyObjectCollection newValue)
    {
        if (oldValue != null)
            oldValue.VectorChanged -= Evt_OnVectorChangedReferences;

        if (newValue != null)
            newValue.VectorChanged += Evt_OnVectorChangedReferences;
    }

    private static void ReferencesPropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs args)
    {
        DependencyObjectSelector<T>     _this = (DependencyObjectSelector<T>)dobj;

        _this.OnReferencesPropertyChanged(args.OldValue as DependencyObjectCollection, args.NewValue as DependencyObjectCollection);
    }

    private static void SelectedIndexPropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs args)
    {
        DependencyObjectSelector<T>     _this = (DependencyObjectSelector<T>)dobj;

        _this.UpdateSelectedObject();
    }
    #endregion

    #region Static Data
    public static readonly DependencyProperty ReferencesProperty =
        DependencyProperty.Register(nameof(References),
                                    typeof(DependencyObjectCollection),
                                    typeof(DependencyObjectSelector<T>),
                                    new PropertyMetadata(null, ReferencesPropertyChanged));

    public static readonly DependencyProperty SelectedIndexProperty =
        DependencyProperty.Register(nameof(SelectedIndex),
                                    typeof(Int32),
                                    typeof(DependencyObjectSelector<T>),
                                    new PropertyMetadata(-1, SelectedIndexPropertyChanged));

    public static readonly DependencyProperty SelectedObjectProperty =
        DependencyProperty.Register(nameof(SelectedObject),
                                    typeof(T),
                                    typeof(DependencyObjectSelector<T>),
                                    new PropertyMetadata(default(T)));
    #endregion
}

如您所见,此类包含引用的集合,并将其SelectedObject属性绑定到适当引用的Value。 当SelectedIndex更改时,以及引用集合本身更改时,此绑定也会更新。

这些类显然无法在XAML中使用,因为它们由类型T(必须从DependencyObject派生)进行参数化。但是, 将它们分类的一个简单问题:

public sealed class BrushReference : DependencyObjectReference<Brush>
{
}
public sealed class BrushSelector : DependencyObjectSelector<Brush>
{
}

现在的诀窍是将BrushSelector放在一些可访问的ResourceDictionary(例如您的Page的{​​{1}})中,然后进行绑定 为其Resources属性:

SelectedObject

请注意,在XAML中定义<Page.Resources> <mynamespace:BrushSelector x:Key="MyBrushSelector" SelectedIndex="{x:Bind Path=MyViewModel.MyBrushIndex, Mode=OneWay}"> <mynamespace:BrushReference Value="{ThemeResource SystemControlForegroundAccentColor}"/> <mynamespace:BrushReference Value="{ThemeResource SystemControlForegroundBaseHighBrush}"/> <mynamespace:BrushReference Value="Red"/> <mynamespace:BrushReference Value="Wheat"/> </mynamespace:BrushSelector> </Page.Resources> <!-- ... --> <TextBlock Text="..." Foreground="{Binding Source={StaticResource MyBrushSelector}, Path=SelectedObject}" /> 时不必指定<DependencyObjectCollection>,因为 选择器类上的BrushSelector属性。

其他一些注释-首先,我希望将[ContentProperty]设为只读SelectedObject,因为它永远都不应 由选择器外部的标记或代码设置,但UWP尚不支持。其次,DependencyProperty属性必须 类型为References且属性本身必须为DependencyObjectCollection,否则主题更改不会传播 正确地。最后,您甚至可以使用自己的主题资源,如果您的应用未指定明确的主题,则在更改时 Windows控制面板中的主题(例如,浅色->深色,反之亦然),这些颜色也会更新。