読者です 読者をやめる 読者になる 読者になる

がりらぼ

WindowsRuntimeの応援ブログ

WinRT:デフォルトのシステムリソースカラーを変更してアプリのテーマカラーを一括変更

WindowsRuntime

WindowsRuntimeのXAMLでは多くのコントロールがデフォルトで紫色のテーマカラーを使っています。

ProgressBarとかSliderとかの色が紫になってるアレです。

f:id:garicchi:20150107061837p:plain

これを変更するためにはControlTemplateを変更したりすれば良いのですがいちいちすべてのコントロールのControlTemplateを変更していてはめんどくさいです。

というわけで紫っぽいテーマカラーを一括変更するクラスを作りました。

使い方

こんなクラスをAttachedTheme.csとしてプロジェクトにコピペして追加しましょう。

using System;
using System.Collections.Generic;
using System.Text;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;

namespace Theme
{
    public class ThemeBrush
    {
        public string Name { get; set; }
        public int Level { get; set; }
    }
    public class AttachedTheme
    {
        //Level is light or deep or normal color level
        private static List<ThemeBrush> _systemBrushes = new List<ThemeBrush>()
        {
            new ThemeBrush{Name="ComboBoxItemSelectedBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ComboBoxItemSelectedPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="ComboBoxSelectedBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ComboBoxSelectedPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="HyperlinkForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="HyperlinkPointerOverForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="HyperlinkPressedForegroundBrush",Level=1},
            new ThemeBrush{Name="IMECandidateSelectedBackgroundThemeBrush",Level=2},
            new ThemeBrush{Name="ListBoxItemSelectedBackgroundThemeBrush",Level=2},
            new ThemeBrush{Name="ListBoxItemSelectedPointerOverBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ListViewItemDragBackgroundThemeBrush",Level=2},
            new ThemeBrush{Name="ListViewItemSelectedBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ListViewItemSelectedPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="ListViewItemSelectedPointerOverBorderThemeBrush",Level=0},
            new ThemeBrush{Name="ProgressBarForegroundThemeBrush",Level=1},
            new ThemeBrush{Name="ProgressBarIndeterminateForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="SearchBoxButtonBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="SearchBoxButtonPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="SearchBoxHitHighlightForegroundThemeBrush",Level=1},
            new ThemeBrush{Name="SearchBoxHitHighlightSelectedForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="SliderTrackDecreaseBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="SliderTrackDecreasePointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="SliderTrackDecreasePressedBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="TextSelectionHighlightColorThemeBrush",Level=2},
            new ThemeBrush{Name="ToggleSwitchCurtainBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ToggleSwitchCurtainPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="ToggleSwitchCurtainPressedBackgroundThemeBrush",Level=0},
        };

        public static SolidColorBrush GetThemeBrush(DependencyObject obj)
        {
            return (SolidColorBrush)obj.GetValue(ThemeBrushProperty);
        }

        public static void SetThemeBrush(DependencyObject obj, SolidColorBrush value)
        {
            obj.SetValue(ThemeBrushProperty, value);
        }

        // Using a DependencyProperty as the backing store for ThemeBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ThemeBrushProperty =
            DependencyProperty.RegisterAttached("ThemeBrush", typeof(SolidColorBrush), typeof(AttachedTheme), new PropertyMetadata(null, (s, e) =>
            {
                RewriteSystemResource(GetThemeBrush(s));
            }));



        public static SolidColorBrush GetLightThemeBrush(DependencyObject obj)
        {
            return (SolidColorBrush)obj.GetValue(LightThemeBrushProperty);
        }

        public static void SetLightThemeBrush(DependencyObject obj, SolidColorBrush value)
        {
            obj.SetValue(LightThemeBrushProperty, value);
        }

        // Using a DependencyProperty as the backing store for LightThemeBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty LightThemeBrushProperty =
            DependencyProperty.RegisterAttached("LightThemeBrush", typeof(SolidColorBrush), typeof(AttachedTheme), new PropertyMetadata(null, (s, e) =>
            {
                RewriteSystemResource(GetLightThemeBrush(s),0);
            }));



        public static SolidColorBrush GetNormalThemeBrush(DependencyObject obj)
        {
            return (SolidColorBrush)obj.GetValue(NormalThemeBrushProperty);
        }

        public static void SetNormalThemeBrush(DependencyObject obj, SolidColorBrush value)
        {
            obj.SetValue(NormalThemeBrushProperty, value);
        }

        // Using a DependencyProperty as the backing store for NormalTheme.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty NormalThemeBrushProperty =
            DependencyProperty.RegisterAttached("NormalThemeBrush", typeof(SolidColorBrush), typeof(AttachedTheme), new PropertyMetadata(null, (s, e) =>
            {
                RewriteSystemResource(GetNormalThemeBrush(s), 1);
            }));




        public static SolidColorBrush GetDeepThemeBrush(DependencyObject obj)
        {
            return (SolidColorBrush)obj.GetValue(DeepThemeBrushProperty);
        }

        public static void SetDeepThemeBrush(DependencyObject obj, SolidColorBrush value)
        {
            obj.SetValue(DeepThemeBrushProperty, value);
        }

        // Using a DependencyProperty as the backing store for DeepThemeBrush.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DeepThemeBrushProperty =
            DependencyProperty.RegisterAttached("DeepThemeBrush", typeof(SolidColorBrush), typeof(AttachedTheme), new PropertyMetadata(null, (s, e) =>
            {
                RewriteSystemResource(GetDeepThemeBrush(s), 2);
            }));



        public static void RewriteSystemResource(SolidColorBrush themeBrush,int level)
        {
            if (themeBrush != null)
            {
                foreach (ThemeBrush tb in _systemBrushes)
                {
                    if (tb.Level == level)
                    {
                        Application.Current.Resources[tb.Name] = themeBrush;
                    }
                }
            }
        }

        public static void RewriteSystemResource(SolidColorBrush themeBrush)
        {
            if (themeBrush != null)
            {
                foreach (ThemeBrush tb in _systemBrushes)
                {
                    SolidColorBrush applyBrush = themeBrush;
                    Application.Current.Resources[tb.Name] = applyBrush;
                }
            }
        }


    }

}

あとはAppクラスのOnLaunchedでRewriteSystemResourceメソッドを呼ぶだけです。*1

Theme.AttachedTheme.RewriteSystemResource(new SolidColorBrush(Color.FromArgb(255,54,104,87)));

f:id:garicchi:20150107062549p:plain

これでさまざまなコントロールに使われている紫の色が一括変更できました。

f:id:garicchi:20150107062624p:plain

XAMLから使う

AttachedThemeクラスはstaticメソッドを呼ぶだけでC#から一行でテーマカラーを変更できますが添付プロパティを提供しているのでXAMLからも呼び出せます。

XAMLから呼び出すと色を #366857 こんな感じのHTML記法?で指定できるというメリットがあります。

こんな感じで最初に呼ばれるPageの属性にxmlnsでTheme名前空間を指定し、AttachedThemeのThemeBrushプロパティを指定します。

xmlns:t="using:Theme"
t:AttachedTheme.ThemeBrush="#365768"

f:id:garicchi:20150107063009p:plain

濃度レベルを分ける

デフォルトで用意されている紫の色も、濃さが微妙に違います。   薄い、普通、濃いの3段階指定できるようにしたので3つ指定すると濃さによって色を指定できます。

LightThemeBrush、NormalThemeBrush、DeepThemeBrushがあります。

f:id:garicchi:20150107063329p:plain

これでアプリのテーマカラーを一括で指定できるようになりました。

しくみ

AttachedThemeクラスのしくみとしては、システムリソースをちまちま変更する方法でテーマカラー一括変更を実現しています。

コントロールの紫色は、システムリソースで指定されているため、このシステムリソースを変更してやれば色を変えることができるわけです。

f:id:garicchi:20150107063655p:plain

システムリソースで紫色のものを濃さを3段階で主観評価したらこんな感じになりました。

ComboBoxItemSelectedBackgroundThemeBrush 普通
ComboBoxItemSelectedPointerOverBackgroundThemeBrush 薄い
ComboBoxSelectedBackgroundThemeBrush 普通
ComboBoxSelectedPointerOverBackgroundThemeBrush 薄い
HyperlinkForegroundThemeBrush 薄い
HyperlinkPointerOverForegroundThemeBrush 薄い
HyperlinkPressedForegroundBrush 普通
IMECandidateSelectedBackgroundThemeBrush 濃い
ListBoxItemSelectedBackgroundThemeBrush 濃い
ListBoxItemSelectedPointerOverBackgroundThemeBrush 普通
ListViewItemDragBackgroundThemeBrush 濃い
ListViewItemSelectedBackgroundThemeBrush 普通
ListViewItemSelectedPointerOverBackgroundThemeBrush 薄い
ListViewItemSelectedPointerOverBorderThemeBrush 薄い
ProgressBarForegroundThemeBrush 普通
ProgressBarIndeterminateForegroundThemeBrush 薄い
SearchBoxButtonBackgroundThemeBrush 普通
SearchBoxButtonPointerOverBackgroundThemeBrush 薄い
SearchBoxHitHighlightForegroundThemeBrush 普通
SearchBoxHitHighlightSelectedForegroundThemeBrush 薄い
SliderTrackDecreaseBackgroundThemeBrush 普通
SliderTrackDecreasePointerOverBackgroundThemeBrush 薄い
SliderTrackDecreasePressedBackgroundThemeBrush 薄い
TextSelectionHighlightColorThemeBrush 濃い
ToggleSwitchCurtainBackgroundThemeBrush 普通
ToggleSwitchCurtainPointerOverBackgroundThemeBrush 薄い
ToggleSwitchCurtainPressedBackgroundThemeBrush 薄い

そして薄い=0、普通=1、濃い=2で値をつけ、Listに入れます。

private static List<ThemeBrush> _systemBrushes = new List<ThemeBrush>()
        {
            new ThemeBrush{Name="ComboBoxItemSelectedBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ComboBoxItemSelectedPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="ComboBoxSelectedBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ComboBoxSelectedPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="HyperlinkForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="HyperlinkPointerOverForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="HyperlinkPressedForegroundBrush",Level=1},
            new ThemeBrush{Name="IMECandidateSelectedBackgroundThemeBrush",Level=2},
            new ThemeBrush{Name="ListBoxItemSelectedBackgroundThemeBrush",Level=2},
            new ThemeBrush{Name="ListBoxItemSelectedPointerOverBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ListViewItemDragBackgroundThemeBrush",Level=2},
            new ThemeBrush{Name="ListViewItemSelectedBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ListViewItemSelectedPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="ListViewItemSelectedPointerOverBorderThemeBrush",Level=0},
            new ThemeBrush{Name="ProgressBarForegroundThemeBrush",Level=1},
            new ThemeBrush{Name="ProgressBarIndeterminateForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="SearchBoxButtonBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="SearchBoxButtonPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="SearchBoxHitHighlightForegroundThemeBrush",Level=1},
            new ThemeBrush{Name="SearchBoxHitHighlightSelectedForegroundThemeBrush",Level=0},
            new ThemeBrush{Name="SliderTrackDecreaseBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="SliderTrackDecreasePointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="SliderTrackDecreasePressedBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="TextSelectionHighlightColorThemeBrush",Level=2},
            new ThemeBrush{Name="ToggleSwitchCurtainBackgroundThemeBrush",Level=1},
            new ThemeBrush{Name="ToggleSwitchCurtainPointerOverBackgroundThemeBrush",Level=0},
            new ThemeBrush{Name="ToggleSwitchCurtainPressedBackgroundThemeBrush",Level=0},
        };

これをforeachで回してちまちまリソースカラーを変更する処理をAttachedThemeクラスでは行っています。

*1:Appクラスのコンストラクタでは失敗します