微軟動作真是快,本來想寫WP8.1RT系列,結果剛整理了一點就出Win10 UAP了。不過還好RT到Win10的差別還不算太大。前兩天參加了Win10開發極客秀,雖然沒獲獎,不過在韋恩卑鄙的幫助下順利將澎湃新聞WP8.1版升級到了Win10UAP,使用了一些新的特性,最近爭取有時間慢慢把一些東西總結一下。
今天先說一下如何在Win10 UAP中切換主題模式。
切換日間、夜間主題模式這個功能我從WP8就實現了,並封裝成了一個庫,用在我所有的WP8的app里。到了WP8.1因為系統主題樣式都改了,又重寫了一遍。還沒來得及整理寫blog,Win10的樣式又改了……吐槽不完啊簡直。不過思路都是一樣的,現在以Win10版本為例總結一下。
UAP的樣式和以前的版本基本一樣,都是一些類似css的東西,我們通過覆蓋系統的style,就可以實現自己的主題樣式。首先找到UAP的style的位置:
C:\Program Files (x86)\Windows Kits\10\Include\10.0.10069.0\winrt\xaml\design\themeresources.xaml
2015-12-29 update:
升級10586后,該文件的地址在:
C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.10586.0\Generic
里面有generic.xaml和themeresources.xaml
打開這個文件,可以看到里面存放的是系統默認的主題樣式。
順便說一下,WP8 和WP8.1 的默認主題樣式也可以找到,位置不一樣。
一、新建UAP項目
新建個UAP項目。因為我習慣用MVVM-Sidekick來做,所以以后都會基於這個框架來做,順便給@韋恩卑鄙 做下廣告^_^
這個例子就叫ThemeDemo。確定。
等待框架程序創建完成。
可見現在Win10 UAP的項目只有一個,與WP8.1時代分別為PC和手機新建項目的方式已經不同了。這樣可以更方便。
二、添加默認主題
用VS2015RC打開剛才找到的系統默認樣式文件,如圖:
這樣看比較亂,把代碼折疊一下:
這樣看就清楚了,包括了Default、Light和高對比對三種主題樣式,和各種style、字體、字體大小、各種Color和Brush、控件的style等等,現在我們需要提取出來進行修改。
在項目中添加一個名為CustomTheme的文件夾。
一般只需要對Dark和Light分別處理即可,比如我想Dark的背景色不是純黑,Light的背景色不是純白等等。
新建兩個xaml文件,命名為ThemeResourcesDark.xaml和ThemeResourcesLight.xaml,根節點這樣寫:
然后把系統樣式文件里有關Color和Brush的部分復制過來,Default對應Dark,Light對應Light。
控件的style另外建個文件CustomStyleResources.xaml,把控件的style復制過來,因為不同主題下控件只是背景色不同,margin、padding這些屬性都是一致的。
我還添加了一套FlatUI的顏色資源,FlatUIColorsResources.xaml,里面存放了各種Flat風格的Color和Brush來方便使用。
三、引入自定義樣式資源
打開App.xaml,添加以下代碼:
控件的style和主題的style都引入進來了,順便說一下,控件模板等東西不要往App.xaml里堆,多了顯得太亂,應該都統一放到資源文件里進行管理。
四、修改自定義樣式
現在運行程序,樣子是默認的,還是白底黑字。因為RequestedTheme="Light"。
現在我們修改個背景色看看。打開MainPage.xaml 可以看到以下代碼:
也就是說,根Grid的背景色名字是ApplicationPageBackgroundThemeBrush。
然后去ThemeResourcesLight.xaml文件里找這個資源,改一下顏色:
修改的顏色最好加個注釋。
然后跑一下:
好了背景色已經變了,不再是純白了。然后可以繼續改前景色、Dark主題的背景色、前景色……。
五、在程序中切換主題
UAP的主題是通過RequestedTheme來設置的,可以在頁面中綁定一個屬性來實現切換。
打開MainPage_Model.cs,添加一個屬性,輸入代碼段propvm按Tab
/// <summary> ///當前主題 /// </summary> public ElementTheme CurrentTheme { get { return _CurrentThemeLocator(this).Value; } set { _CurrentThemeLocator(this).SetValueAndTryNotify(value); } } #region Property ElementTheme CurrentTheme Setup protected Property<ElementTheme> _CurrentTheme = new Property<ElementTheme> { LocatorFunc = _CurrentThemeLocator }; static Func<BindableBase, ValueContainer<ElementTheme>> _CurrentThemeLocator = RegisterContainerLocator<ElementTheme>("CurrentTheme", model => model.Initialize("CurrentTheme", ref model._CurrentTheme, ref _CurrentThemeLocator, _CurrentThemeDefaultValueFactory)); static Func<ElementTheme> _CurrentThemeDefaultValueFactory = () => { return ElementTheme.Default; }; #endregion |
在初始化VM的時候,給其賦值:
添加一個Command,輸入propcmd按Tab
/// <summary> ///切換日間夜間模式 /// </summary> public CommandModel<ReactiveCommand, String> CommandSetCustomTheme { get { return _CommandSetCustomThemeLocator(this).Value; } set { _CommandSetCustomThemeLocator(this).SetValueAndTryNotify(value); } } #region Property CommandModel<ReactiveCommand, String> CommandSetCustomTheme Setup protected Property<CommandModel<ReactiveCommand, String>> _CommandSetCustomTheme = new Property<CommandModel<ReactiveCommand, String>> { LocatorFunc = _CommandSetCustomThemeLocator }; static Func<BindableBase, ValueContainer<CommandModel<ReactiveCommand, String>>> _CommandSetCustomThemeLocator = RegisterContainerLocator<CommandModel<ReactiveCommand, String>>("CommandSetCustomTheme", model => model.Initialize("CommandSetCustomTheme", ref model._CommandSetCustomTheme, ref _CommandSetCustomThemeLocator, _CommandSetCustomThemeDefaultValueFactory)); static Func<BindableBase, CommandModel<ReactiveCommand, String>> _CommandSetCustomThemeDefaultValueFactory = model => { var resource = "SetCustomTheme"; // Command resource var commandId = "SetCustomTheme"; var vm = CastToCurrentType(model); var cmd = new ReactiveCommand(canExecute: true) { ViewModel = model }; //New Command Core cmd .DoExecuteUIBusyTask( vm, async e => { //Todo: Add SetCustomTheme logic here, or await MVVMSidekick.Utilities.TaskExHelper.Yield(); if (vm.CurrentTheme == ElementTheme.Dark || vm.CurrentTheme == ElementTheme.Default) { vm.CurrentTheme = ElementTheme.Light; } else { vm.CurrentTheme = ElementTheme.Dark; } } ) .DoNotifyDefaultEventRouter(vm, commandId) .Subscribe() .DisposeWith(vm);
var cmdmdl = cmd.CreateCommandModel(resource); cmdmdl.ListenToIsUIBusy(model: vm, canExecuteWhenBusy: false); return cmdmdl; }; #endregion |
ElementTheme和ApplicationTheme是不同的,后者是App的屬性,前者可以應用於UIElement。所以可以把這個屬性綁定到根Grid上。
在頁面中加一個Button,Content設置為"切換主題",然后把Command綁定到CommandSetCustomTheme上
跑一下看看:
可以順利切換了,而且背景色和前景色也變成了我們設置的樣子,不是純黑純白了。
其實TextBlock和Button並沒有設置背景色前景色,都是繼承系統的,所以不用特別設置。其他的控件也一樣,如果需要特殊處理就自己添加樣式即可。
六、設置控件的style
順便說一下CustomStyleResources.xaml的作用,我建議把控件的樣式寫在這里,覆蓋系統默認的。比如可以把所有的Grid都更改一下背景色,就可以在這里改,或者改全局Pivot的頭部margin之類的。
忘了最后附上源碼:
鏈接: http://pan.baidu.com/s/1mgFvXos 密碼: wnck