自定義Window有多是設計或功能上的要求,能夠是非必要的,而自定義RibbonWindow則不同:html
如上圖所示,在Windows 10 上運行打開RibbonWindow,能夠看到標題欄的內容(包括分隔符)沒有居中對齊,缺乏下邊框。git
在最大化的時候標題欄內容甚至超出屏幕範圍。github
WPF提供的Ribbon是個很古老很古老的控件,附帶的RibbonWindow也十分古老。RibbonWindow在之前應該能夠運行良好,但多年沒有更新,在.NET 4.5(或者說是WIN7平臺,我沒仔細考究)後就出現了這個問題。做爲專業軟件這可能無法接受,而這個問題微軟好像也沒打算修復。之前的作法一般是使用Fluent.Ribbon之類的第三方組件,由於我已經在Kino.Toolkit.Wpf中提供了使用WindowChrome自定義的Window,爲了統一外觀因而順手自定義一個ExtendedRibbonWindow。chrome
RibbonWindow是派生自Window,並使用了WindowChrome,它的ControlTemplate大概是這樣:shell
<Grid> <Border Name="PART_ClientAreaBorder" Background="{TemplateBinding Control.Background}" BorderBrush="{TemplateBinding Control.BorderBrush}" BorderThickness="{TemplateBinding Control.BorderThickness}" Margin="{Binding Path=(SystemParameters.WindowNonClientFrameThickness)}" /> <Border BorderThickness="{Binding Path=(WindowChrome.WindowChrome).ResizeBorderThickness, RelativeSource={RelativeSource TemplatedParent}}"> <Grid> <Image Name="PART_Icon" WindowChrome.IsHitTestVisibleInChrome="True" HorizontalAlignment="Left" VerticalAlignment="Top" Width="{Binding Path=(SystemParameters.SmallIconWidth)}" Height="{Binding Path=(SystemParameters.SmallIconHeight)}" /> <AdornerDecorator> <ContentPresenter Name="PART_RootContentPresenter" /> </AdornerDecorator> <ResizeGrip Name="WindowResizeGrip" WindowChrome.ResizeGripDirection="BottomRight" HorizontalAlignment="Right" VerticalAlignment="Bottom" Visibility="Collapsed" IsTabStop="False" /> </Grid> </Border> </Grid>
Ribbon的Chrome部分徹底依賴於WindowChrome,PART_ClientAreaBorder負責爲ClientArea提供背景顏色。在PART_ClientAreaBorder後面的另外一個Border纔是真正的ClientArea部分,它用於放置Ribbon。由於Ribbon的一些按鈕位於標題欄,因此Ribbon必須佔用標題欄的位置,而且由Ribbon顯示本來應該由Window顯示的標題。WindowChrome的標題欄高度是SystemParameters.WindowNonClientFrameThickness.Top,在Windows 10,100% DPI的狀況下爲27像素。而Ribbon標題欄部分使用了SystemParameters.WindowCaptionHeight做爲高度,這個屬性的值爲23,因此纔會出現對不齊的問題。c#
<DockPanel Grid.Column="0" Grid.ColumnSpan="3" LastChildFill="True" Height="{Binding Path=(SystemParameters.WindowCaptionHeight)}">
而最大化的時候徹底沒有調整Ribbon的Margin,而且WindowChrome自己在最大化就會有問題。因此不能直接使用WindowChrome,而應該使用自定義的UI覆蓋WindowChrome的內容。windows
我在Kino.Toolkit.Wpf提供了一個自定義RibbonWindow,基本上代碼和ControlTempalte與自定義Window同樣,運行效果如上圖所示。在自定義RibbonWindow裏我添加了RibbonStyle屬性,默認值是一個解決Ribbon標題欄問題的Ribbon樣式,裏面使用SystemParameters.WindowNonClientFrameThickness做爲標題的高度。api
<DockPanel Grid.Column="0" Grid.ColumnSpan="3" Margin="0,-1,0,0" Height="{Binding Path=(SystemParameters.WindowNonClientFrameThickness).Top}" LastChildFill="True">
RibbonWindow還添加了一個StyleTypedProperty:ide
[StyleTypedProperty(Property = nameof(RibbonStyle), StyleTargetType = typeof(Ribbon))]
StyleTypedProperty 應用於類定義並肯定類型爲 TargetType 的屬性的 Style。使用了這個屬性的控件能夠在Blend中使用 "右鍵"->"編輯其餘模板"->"編輯RibbonSytle" 建立Ribbon的Style。設計
不過雖然我這麼貼心地加上這個Attribute,但個人Blend複製Ribbon模板老是報錯。
我也見過一些很專業的軟件沒處理RibbonWindow,反正外觀上的問題忍一忍就過去了,實在受不了能夠買一個有現代化風格的控件庫,只是爲了標題欄對不齊這種小事比較難說服上面贊成引入一個新的組件。除了使用我提供的解決方案,stackoverflow也由很多關於這個問題的討論及解決方案可供參考,例如這個:
c# - WPF RibbonWindow + Ribbon = Title outside screen - Stack Overflow
順便一提,ExtendedRibbonWindow須要繼承RibbonWindow,因此無法直接集成ExtendedWindow。由於ExtendedWindow不少功能都試用附加屬性和控件代碼分離,因此ExtendedRibbonWindow須要重複的代碼不會太多。
RibbonWindow Class (System.Windows.Controls.Ribbon) Microsoft Docs
Ribbon Class (System.Windows.Controls.Ribbon) Microsoft Docs
WindowChrome Class (System.Windows.Shell) Microsoft Docs
SystemParameters Class (System.Windows) Microsoft Docs
StyleTypedPropertyAttribute Class (System.Windows) Microsoft Docs