每個有理想的UWP應用都會打標題欄的主意,尤爲當微軟提供 將 Acrylic 擴展到標題欄 這個功能後,大部分Windows 10的原生應用都不乖了,紛紛佔領了標題欄的一畝三分地。這篇博客將介紹在UWP中如何自定義標題欄。git
UWP的限制不少,標題欄的自定義幾乎所有內容集中在 這篇文檔 裏面。但只參考這篇文章作起來還不夠順手,我參考了微軟開源的計算器應用中的 TitleBar 寫了一個示例應用,能夠在 這裏 查看它的源碼。我也把TitleBar實際應用到了個人 OnePomodoro 應用裏面了。github
若是隻想簡單地自定義標題欄的顏色能夠經過ApplicationViewTitleBar,ApplicationViewTitleBar表示應用程序的標題欄,它提供了一些顏色屬性用於控制標題欄的顏色,示例代碼以下:shell
// using Windows.UI.ViewManagement; var titleBar = ApplicationView.GetForCurrentView().TitleBar; // Set active window colors titleBar.ForegroundColor = Windows.UI.Colors.White; titleBar.BackgroundColor = Windows.UI.Colors.Green; titleBar.ButtonForegroundColor = Windows.UI.Colors.White; titleBar.ButtonBackgroundColor = Windows.UI.Colors.SeaGreen; titleBar.ButtonHoverForegroundColor = Windows.UI.Colors.White; titleBar.ButtonHoverBackgroundColor = Windows.UI.Colors.DarkSeaGreen; titleBar.ButtonPressedForegroundColor = Windows.UI.Colors.Gray; titleBar.ButtonPressedBackgroundColor = Windows.UI.Colors.LightGreen; // Set inactive window colors titleBar.InactiveForegroundColor = Windows.UI.Colors.Gray; titleBar.InactiveBackgroundColor = Windows.UI.Colors.SeaGreen; titleBar.ButtonInactiveForegroundColor = Windows.UI.Colors.Gray; titleBar.ButtonInactiveBackgroundColor = Windows.UI.Colors.SeaGreen;
有幾點須要注意:c#
若要隱藏默認標題欄並將你的內容擴展到標題欄區域中,請將 CoreApplicationViewTitleBar.ExtendViewIntoTitleBar 屬性設置爲 true。CoreApplicationViewTitleBar容許應用定義在應用窗口中顯示的自定義標題欄。示例代碼以下:windows
// using Windows.ApplicationModel.Core; // Hide default title bar. var coreTitleBar = CoreApplication.GetCurrentView().TitleBar; coreTitleBar.ExtendViewIntoTitleBar = true;
將內容擴展到標題欄,標題按鈕的顏色就變複雜了。由於應用內容的顏色可能和按鈕的顏色衝突。這種狀況下有幾種方案,其中最簡單的一種方案是寫死爲一個不會衝突的顏色,但切換主題時可能會讓這些顏色出問題。計算器應用中訂閱UISettings的ColorValuesChanged事件,動態地根據ThemeResources的值改變標題欄顏色,而且更進一步地考慮到使用高對比度主題的狀況,因此訂閱了AccessibilitySettings的HighContrastChanged事件:api
if (_accessibilitySettings.HighContrast) { // Reset to use default colors. applicationTitleBar.ButtonBackgroundColor = null; applicationTitleBar.ButtonForegroundColor = null; applicationTitleBar.ButtonInactiveBackgroundColor = null; applicationTitleBar.ButtonInactiveForegroundColor = null; applicationTitleBar.ButtonHoverBackgroundColor = null; applicationTitleBar.ButtonHoverForegroundColor = null; applicationTitleBar.ButtonPressedBackgroundColor = null; applicationTitleBar.ButtonPressedForegroundColor = null; } else { Color bgColor = Colors.Transparent; Color fgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlPageTextBaseHighBrush"]).Color; Color inactivefgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlForegroundChromeDisabledLowBrush"]).Color; Color hoverbgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlBackgroundListLowBrush"]).Color; Color hoverfgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlForegroundBaseHighBrush"]).Color; Color pressedbgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlBackgroundListMediumBrush"]).Color; Color pressedfgColor = ((SolidColorBrush)Application.Current.Resources["SystemControlForegroundBaseHighBrush"]).Color; applicationTitleBar.ButtonBackgroundColor = bgColor; applicationTitleBar.ButtonForegroundColor = fgColor; applicationTitleBar.ButtonInactiveBackgroundColor = bgColor; applicationTitleBar.ButtonInactiveForegroundColor = inactivefgColor; applicationTitleBar.ButtonHoverBackgroundColor = hoverbgColor; applicationTitleBar.ButtonHoverForegroundColor = hoverfgColor; applicationTitleBar.ButtonPressedBackgroundColor = pressedbgColor; applicationTitleBar.ButtonPressedForegroundColor = pressedfgColor; }
這段代碼中,當使用高對比度主題時將標題欄的按鈕顏色還原成默認值,不然設置成ThemeResource中對應的顏色,運行效果以下:app
但如今的UWP應用經常在Dark和Light主題之間反覆橫跳,而Application.Current.Resources只能拿到程序加載時的ThemeResource的值,因此這段代碼在應用內的主題切換後無效。我暫時不清楚怎麼在代碼裏拿到最新的ThemeResource,爲解決這個問題只好讓TitleBar本身在XAML中獲取當前的ThemeResource,代碼以下:ide
<UserControl.Resources> <SolidColorBrush x:Key="ButtonForegroundColor" Color="{ThemeResource SystemBaseHighColor}" /> <SolidColorBrush x:Key="ButtonInactiveForegroundBrush" Color="{ThemeResource SystemChromeDisabledLowColor}" /> <SolidColorBrush x:Key="ButtonHoverBackgroundBrush" Color="{ThemeResource SystemListLowColor}" /> <SolidColorBrush x:Key="ButtonHoverForegroundBrush" Color="{ThemeResource SystemBaseHighColor}" /> <SolidColorBrush x:Key="ButtonPressedBackgroundBrush" Color="{ThemeResource SystemListMediumColor}" /> <SolidColorBrush x:Key="ButtonPressedForegroundBrush" Color="{ThemeResource SystemBaseHighColor}" /> </UserControl.Resources>
Color fgColor = ((SolidColorBrush)Resources["ButtonForegroundColor"]).Color; Color inactivefgColor = ((SolidColorBrush)Resources["ButtonInactiveForegroundBrush"]).Color; Color hoverbgColor = ((SolidColorBrush)Resources["ButtonHoverBackgroundBrush"]).Color; Color hoverfgColor = ((SolidColorBrush)Resources["ButtonHoverForegroundBrush"]).Color; Color pressedbgColor = ((SolidColorBrush)Resources["ButtonPressedBackgroundBrush"]).Color; Color pressedfgColor = ((SolidColorBrush)Resources["ButtonPressedForegroundBrush"]).Color;
都將內容擴展到標題欄了,確定是想在標題欄上放置本身須要的UI元素,默認狀況下標題欄的範圍爲拖動、點擊等Windows的窗體行爲保留,在這個範圍的自定義UI內容沒辦法獲取鼠標點擊。 爲了讓自定義的UI內容獲取鼠標,能夠用Window.SetTitleBar方法指定某一元素能用於窗體的拖動和點擊。ui
<Grid x:Name="LayoutRoot" Height="32" HorizontalAlignment="Stretch"> <Grid x:Name="BackgroundElement" Height="32" Background="Transparent" /> <StackPanel Orientation="Horizontal"> <StackPanel x:Name="ItemsPanel" Orientation="Horizontal"> </StackPanel> <TextBlock x:Name="AppName" x:Uid="AppName" Text="ExtendViewIntoTitleBarDemo" </StackPanel> </Grid>
Window.Current.SetTitleBar(BackgroundElement);
上面的代碼指定TitlaBar中的BackgroundElement
元素爲可拖動區域,而下面的StackPanel則用於放置交互內容,例如標題或後退按鈕。這個StackPanel必須比BackgroundElement
具備較高的Z
順序才能接收到用戶的鼠標輸入。this
標題欄的右邊有188像素的系統保留區域,用於系統標題按鈕(「後退」、「最小化」、「最大化」、「關閉」)。其實這幾個按鈕也就佔用了141像素的控件,還有一小塊空間是默認的可拖動區域,這小塊空間確保了不管怎麼設置都總有一個用戶可拖動的區域。
上面說的188像素是100%縮放的狀況,經過上面的截圖能夠看到實際上可能不同,一般來講會在窗體加載時,或者訂閱CoreApplicationViewTitleBar.LayoutMetricsChanged事件,而後經過CoreApplicationViewTitleBar
獲取具體的值。
_coreTitleBar.LayoutMetricsChanged += OnLayoutMetricsChanged; private void OnLayoutMetricsChanged(CoreApplicationViewTitleBar sender, object args) { LayoutRoot.Height = _coreTitleBar.Height; SetTitleBarPadding(); } private void SetTitleBarPadding() { double leftAddition = 0; double rightAddition = 0; if (FlowDirection == FlowDirection.LeftToRight) { leftAddition = _coreTitleBar.SystemOverlayLeftInset; rightAddition = _coreTitleBar.SystemOverlayRightInset; } else { leftAddition = _coreTitleBar.SystemOverlayRightInset; rightAddition = _coreTitleBar.SystemOverlayLeftInset; } LayoutRoot.Padding = new Thickness(leftAddition, 0, rightAddition, 0); }
上面的StackPanel
是可交互區域,詳細的內容以下:
<StackPanel Orientation="Horizontal"> <StackPanel x:Name="ItemsPanel" Orientation="Horizontal"> <StackPanel.Resources> <Style TargetType="Button" BasedOn="{StaticResource NavigationBackButtonNormalStyle}"> <Setter Property="Foreground" Value="{StaticResource TitleBarForeground}" /> <Setter Property="FontSize" Value="10" /> <Setter Property="Width" Value="46" /> <Setter Property="Height" Value="32" /> <Setter Property="IsTabStop" Value="False" /> </Style> </StackPanel.Resources> </StackPanel> <TextBlock x:Name="AppName" x:Uid="AppName" Text="ExtendViewIntoTitleBarDemo" Margin="12,0,12,0" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="{ThemeResource SystemControlPageTextBaseHighBrush}" FontSize="12" IsHitTestVisible="False" TextAlignment="Left" TextTrimming="CharacterEllipsis" /> </StackPanel>
其中AppName
用於顯示標題欄,ItemsPanel
用於放其它按鈕。TitleBar裏定義了Buttons
屬性,調用TitleBar能夠經過Buttons
屬性指定按鈕(這部分代碼我凌晨兩點寫的,寫得十分敷衍,但寫完又懶得改了)。
public ObservableCollection<Button> Buttons { get; } = new ObservableCollection<Button>(); private void OnButtonsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { ItemsPanel.Children.Clear(); foreach (var button in Buttons) { ItemsPanel.Children.Add(button); } }
<local:TitleBar> <local:TitleBar.Buttons> <Button x:Name="OptionsButton" Content="" ToolTipService.ToolTip="Options" /> <Button Content="" ToolTipService.ToolTip="Options" /> <Button Content="" ToolTipService.ToolTip="Options" /> <Button Content="" ToolTipService.ToolTip="Options" /> </local:TitleBar.Buttons> </local:TitleBar>
按鈕的樣式來自NavigationBackButtonNormalStyle
並稍做修改,大體上作到和標準的標題欄按鈕同樣。
當窗體處於非激活狀態應該讓按鈕和標題都變灰,能夠訂閱Window
的Activated事件,在非激活狀態時改變顏色:
Window.Current.Activated += OnWindowActivated; private void OnWindowActivated(Object sender, WindowActivatedEventArgs e) { VisualStateManager.GoToState( this, e.WindowActivationState == CoreWindowActivationState.Deactivated ? WindowNotFocused.Name : WindowFocused.Name, false); }
<UserControl.Resources> <SolidColorBrush x:Key="TitleBarForeground" x:Name="TitleBarForeground" Color="{ThemeResource SystemBaseHighColor}" /> </UserControl.Resources> <Grid x:Name="LayoutRoot" Height="32" HorizontalAlignment="Stretch"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="WindowFocusStates"> <VisualState x:Name="WindowFocused" /> <VisualState x:Name="WindowNotFocused"> <VisualState.Setters> <Setter Target="AppName.Foreground" Value="{ThemeResource SystemControlForegroundChromeDisabledLowBrush}" /> <Setter Target="TitleBarForeground.Color" Value="{ThemeResource SystemChromeDisabledLowColor}" /> </VisualState.Setters> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups>
當應用在全屏或平板模式下運行時,系統將隱藏標題欄和標題控制按鈕。 可是,用戶能夠調用標題欄,以使其以覆蓋形式顯示在應用的 UI 頂部。 你能夠處理隱藏或調用標題欄時將通知的 CoreApplicationViewTitleBar.IsVisibleChanged 事件,並根據須要顯示或隱藏你的自定義標題欄內容。
LayoutRoot.Visibility = _coreTitleBar.IsVisible ? Visibility.Visible : Visibility.Collapsed;
這部分比較難截圖就不搞了,想看效果能夠試玩個人番茄鍾應用。
就這樣,使人頭痛的自定義標題欄處理完了。還好微軟開源了它的計算器里正好有我須要的代碼,抄了個爽。有一些處理得很差,若是錯誤請指正。
calculator_TitleBar.xaml.cpp at master
ApplicationViewTitleBar Class (Windows.UI.ViewManagement) - Windows UWP applications Microsoft Docs
DinoChan_ExtendViewIntoTitleBarDemo How to handle titlebar when ExtendViewIntoTitleBar