標籤:www. ems 元素 erp NPU 資源檔 img 欄位 initial
WPF Modern UI 主題更換原理一 . 如何更換主題?
二 . 程式碼分析
代碼路徑 : FirstFloor.ModernUI.App / Content / SettingsAppearance.xaml
1.關鍵 XAML 代碼
<ComboBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Themes}" SelectedItem="{Binding SelectedTheme, Mode=TwoWay}" DisplayMemberPath="DisplayName" VerticalAlignment="Center" Margin="0,0,0,4" />
形如 Property = "{ Binding fieldname }" 這種格式的綁定,都是將控制項屬性綁定到當前 使用者控制項 DataContext 屬性的對象中的。
那我們再開啟 SettingsAppearance.cs 檔案看一看後台代碼:
public partial class SettingsAppearance : UserControl{ public SettingsAppearance() { InitializeComponent(); // a simple view model for appearance configuration this.DataContext = new SettingsAppearanceViewModel(); }}
很顯然,控制項的 DataContext 屬性引用到了 new SettingsAppearanceViewModel();
那再F12進去分析一下 SettingsAppearanceViewModel 這個類有哪些相關的東西:
2. Themes
首先看一下 Themes 這個屬性 ,它是一個 LinkCollection 對象,並返回了 themes 欄位:
public LinkCollection Themes{ get { return this.themes; }}
LinkCollection 繼承自 ObservableCollection<Link> ,百度一下 ObservableCollection這個類,就知道它其實也是用來實現資料繫結的,當集合內的元素髮生變化時,集合就會通知外部調用者,此處不多贅述。
此外,SettingsAppearance 類的建構函式中向 themes 添加了一些主題,這裡就不貼代碼了。
3. SelectedTheme
public Link SelectedTheme{ get { return this.selectedTheme; } set { if (this.selectedTheme != value) { this.selectedTheme = value; OnPropertyChanged("SelectedTheme"); // and update the actual theme AppearanceManager.Current.ThemeSource = value.Source; } }}
當 SelectedTheme 更改時,set 方法會更改當前的主題源。
對 ThemeSource 這個屬性一直F12下去,會找到一個方法
SetThemeSource:
private void SetThemeSource(Uri source, bool useThemeAccentColor){ if (source == null) { throw new ArgumentNullException("source"); } var oldThemeDict = GetThemeDictionary(); var dictionaries = Application.Current.Resources.MergedDictionaries; var themeDict = new ResourceDictionary { Source = source }; // if theme defines an accent color, use it var accentColor = themeDict[KeyAccentColor] as Color?; if (accentColor.HasValue) { // remove from the theme dictionary and apply globally if useThemeAccentColor is true themeDict.Remove(KeyAccentColor); if (useThemeAccentColor) { ApplyAccentColor(accentColor.Value); } } // add new before removing old theme to avoid dynamicresource not found warnings dictionaries.Add(themeDict); // remove old theme if (oldThemeDict != null) { dictionaries.Remove(oldThemeDict); } OnPropertyChanged("ThemeSource");}
看一下第一個函數 GetThemeDictionary:
private ResourceDictionary GetThemeDictionary(){ // determine the current theme by looking at the app resources and return the first dictionary having the resource key ‘WindowBackground‘ defined. return (from dict in Application.Current.Resources.MergedDictionaries where dict.Contains("WindowBackground") select dict).FirstOrDefault();}
這個函數使用 LINQ 從 App.xaml 定義的 MergedDictionaries 中搜尋主題資源
看一下 App.xaml 裡面的代碼:
<Application x:Class="FirstFloor.ModernUI.App.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.xaml" /> <ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.Light.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources></Application>
這裡面預定義了兩個資源,去相應的地方找到這兩個資源檔,其中 ModernUI.Light.xaml 內有大量包含 "WindowBackground" 字串的 Key ,那這個顯然就是佈景主題資源檔案了。
接下來的邏輯就比較好理解了:調整 AccentColor ,加入新的主題,移除舊的主題,最後通知屬性更改。
4. 動畫
那主題更改的漸層動畫效果是在哪裡觸發的呢?
看一下 MainWindon 的基類 MorderWindow , 這裡面監聽了 AppearanceManager.Current.PropertyChanged 事件:
/// <summary>/// Initializes a new instance of the <see cref="ModernWindow"/> class./// </summary>public ModernWindow(){ // 其他代碼 ... // listen for theme changes AppearanceManager.Current.PropertyChanged += OnAppearanceManagerPropertyChanged;}... private void OnAppearanceManagerPropertyChanged(object sender, PropertyChangedEventArgs e){ // start background animation if theme has changed if (e.PropertyName == "ThemeSource" && this.backgroundAnimation != null) { this.backgroundAnimation.Begin(); }}
再找一下 backgroundAnimation 賦值的地方
/// <summary>/// When overridden in a derived class, is invoked whenever application code or internal processes call System.Windows.FrameworkElement.ApplyTemplate()./// </summary>public override void OnApplyTemplate(){ base.OnApplyTemplate(); // retrieve BackgroundAnimation storyboard var border = GetTemplateChild("WindowBorder") as Border; if (border != null) { this.backgroundAnimation = border.Resources["BackgroundAnimation"] as Storyboard; if (this.backgroundAnimation != null) { this.backgroundAnimation.Begin(); } }}
backgroundAnimation 其實是從資源檔中載入的,找到 ModernWindow.xaml 檔案,相關代碼:
<Border.Resources> <Storyboard x:Key="BackgroundAnimation"> <ColorAnimation Storyboard.TargetName="WindowBorderBackground" Storyboard.TargetProperty="Color" To="{DynamicResource WindowBackgroundColor}" Duration="0:0:.6" /> </Storyboard></Border.Resources>
到這裡整個主題更換的流程就很明朗了。
三 、總結
總結一下主題更換的簡要流程:
- ComboBox 綁定 SettingsAppearanceViewModel 類中的 Themes 和 SelectedTheme 兩個欄位。
- SettingsAppearanceViewModel.SelectedTheme在 set 的時候更改 AppearanceManager.Current.ThemeSource的值。
- AppearanceManager.Current.ThemeSource 被更改時進行佈景主題資源的置換以及其他一系列操作,最後觸發 AppearanceManager.Current.PropertyChanged 事件
- ModernWindow 中綁定到 AppearanceManager.Current.PropertyChanged 事件的函數 OnAppearanceManagerPropertyChanged 被觸發,開啟 backgroundAnimation 動畫。
四、下一篇參考本流程來自己實現一個簡單的主題更換功能
WPF Modern UI 主題更換原理