先看一下最終效果,左圖為使用亞克力材質並新增組合顏色的效果;右圖為MicaAlt材質的效果。兩者都自定義了標題欄並且最大限度地保留了DWM提供的原生視窗效果(最大化最小化、關閉出現的動畫、視窗陰影、拖拽佈局器等)。接下來把各部分的實現一個個拆開來講講。
一、使用視窗材質特效
先粗略介紹一下目前win11和win10上的材質特效型別及一些特性:
Windows10 1903 ~ Windows11 lastest:Acrylic 亞克力材質 支援使用組合顏色 視窗失去焦點時不失效、有拖動視窗延遲 API: SetWindowCompositionAttribute
(1803版本開放此API 但是對Win32應用支援不好)
Windows11: API: SetWindowAttribute 其實現的材質特性:原生不支援組合顏色 對於非WindowStyle.None的視窗失焦失效 無拖動視窗延遲 提供了暗亮模式
Acrylic 亞克力材質 動態模糊,背景取決於視窗下方的內容
Mica 雲母材質 背景僅取桌面桌布(第三方動態桌布軟體無效) 顏色變化較為平緩 win11系統應用的視窗背景大部分使用此材質
MicaAlt 同Mica材質,區別是此材質的顏色變化更突出 檔案管理器的標題欄使用此材質
(在win11上同樣支援使用win10的SetWindowCompositionAttribute啟用舊版API,只不過需要不同的使用條件)
前面的文章中介紹了在win11上啟用材質的方法,但有不少弊端。之後諾爾大佬探究出從底層滿足呼叫條件的方法,總結如下:
1. 無論呼叫哪個API,都需要設定AllowTransparency="True",弊端是帶來效能問題和滑鼠穿透(即使DWM已經繪製了底層顏色),改為呼叫:
1 var hwndSource = (HwndSource)PresentationSource.FromVisual(_window); 2 hwndSource.CompositionTarget.BackgroundColor = Colors.Transparent;
2. WindowChrome.GlassFrameThickness 對於SetWindowCompositionAttribute需要值為-1,另一者則需要為0,弊端是可能我們並不需要WindowsChrome來擴充整個客戶區,改為呼叫DwmExtendFrameIntoClientArea,詳細見後文示例程式。
如果你想動手實現一下,可以參照:[.NET,WPF] 窗體雲母, 亞克力, 透明, 混合顏色, 模糊背景, 亮暗色主題全講 (slimenull.com)
下面給出我封裝好的附加屬性:
WindowEffectTest/WindowEffectTest/WindowMaterial.cs at master · TwilightLemon/WindowEffectTest (github.com)
使用方法很簡單,在你的xaml中新增以下作為Window的子標籤,並且將Window.Background設為Transparent.
<local:WindowMaterial.Material> <local:WindowMaterial x:Name="windowMaterial" IsDarkMode="True" UseWindowComposition="False" MaterialMode="MicaAlt" CompositonColor="#CC6699FF" > </local:WindowMaterial> </local:WindowMaterial.Material>
屬性解釋: IsDarkMode: 暗色模式,主要對Mica(Alt)材質生效
UseWindowComposition: 在win10上無效,指示使用SetWindowCompositionAttribute,在win11上啟用舊版的亞克力特效,一般用於建立無邊框視窗的背景材質,此屬性為True時會忽略MaterialMode
MaterialMode: 指示使用的材質型別 None / Acrylic / Mica / MicaAlt
CompositionColor: 指示使用混合顏色的值,僅對MaterialMode=Acrylic(直接設定Window.Background) 和 使用SetWindowCompositionAttribute時有效
幸運的話可以得到以下效果:
如果嘗試使用亞克力材質並設定混合色的話,無論使用哪個API都會得到類似的效果:
區別在於:如果使用SetWindowAttribute提供的亞克力材質,可以調整IsDarkMode來配置背景色,但是效果不是很好。
使用附加的WindowChromeEx來將客戶區擴充至標題欄
如果WindowChrome直接附加在視窗上會覆蓋掉我們設定的GlassFrameThickness,故這裡的設計是將WindowChrome附加在WindowMaterial上進行管理。你可以接著使用熟悉的WindowChrome提供的屬性,然後把它作為資源提供給WindowMaterial.WindowChromeEx屬性。
<Window.Resources> <WindowChrome x:Key="windowChrome" ResizeBorderThickness="8"/> </Window.Resources> <local:WindowMaterial.Material> <local:WindowMaterial x:Name="windowMaterial" IsDarkMode="True" UseWindowComposition="False" WindowChromeEx="{StaticResource windowChrome}" MaterialMode="Acrylic" CompositonColor="#CC6699FF" > </local:WindowMaterial> </local:WindowMaterial.Material>
然後就能得到:
MicaAlt (DarkMode) 以及 使用 WindowComposition 的亞克力材質效果:
二、自定義標題欄並保留DWM動畫
簡單地介紹以下我的實現: 在Windows所以視窗建立底層都是走的WinAPI,WPF也不例外。可以透過僅提供WS_CAPTION標籤來建立一個沒有三大按鈕的標題欄:
1 [DllImport("user32.dll", EntryPoint = "SetWindowLong")] 2 private static extern int SetWindowLong32(HandleRef hWnd, int nIndex, int dwNewLong); 3 4 [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] 5 private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, IntPtr dwNewLong); 6 7 public const int GWL_STYLE = -16; 8 public const long WS_CAPTION = 0x00C00000L; 9 10 public static IntPtr SetWindowLongPtr(HandleRef hWnd, int nIndex, IntPtr dwNewLong) 11 => IntPtr.Size == 8 12 ? SetWindowLongPtr64(hWnd, nIndex, dwNewLong) 13 : new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32())); 14 15 public static void EnableDwmAnimation(Window w) 16 { 17 var myHWND = new WindowInteropHelper(w).Handle; 18 IntPtr myStyle = new(WS_CAPTION); 19 SetWindowLongPtr(new HandleRef(null, myHWND), GWL_STYLE, myStyle); 20 }
然後按照上述的方法新增WindowChrome讓客戶區覆蓋標題欄即可。 (這裡提前繪製好了自定義的標題欄樣式,你需要自行處理暗色模式的變化等等)
這樣WindowStyle就會失效,並且在實現WindowStyle.None的效果同時帶上WS_CAPTION標籤讓DWM認為這是一個“原生”標題欄視窗。經過測試,另外還需加上WS_THICKFRAME|WS_MAXIMIZEBOX|WS_MINIMIZEBOX 等標籤讓視窗行為更貼近原生。(新增WS_THICKFRAME 在移動視窗時出現系統的視窗布局器)
同樣失效的還有ResizeMode.NoResize,如果你需要固定視窗大小,目前暫行的方法是設定WindowsChrome的ResizeBorderThickness="0",如果按照WinAPI的方法加上WS_SYSMENU則會同時帶回原生標題欄的三大按鈕。(為什麼微軟對它的解釋是標題欄選單..?)
使用我封裝好的附加屬性:
WindowEffectTest/WindowEffectTest/DwmAnimation.cs at master · TwilightLemon/WindowEffectTest (github.com)
在Window標籤中新增:(同樣地這會使WindowStyle和ResizeMode失效)
local:DwmAnimation.EnableDwmAnimation="True"
三、新增更多視窗行為
以下Demo使用了諾爾大佬的WPF Suite來幫助簡化流程。
Demo 1. 建立一個失焦不失效的亞克力材質圓角視窗 (Win11),所有設定如下:
關鍵部分在ws:WindowOption.Corner="Round"這一句,始終使用圓角視窗,並且擁有原生邊框陰影 (右圖為對照,無陰影)
手動實現參照官方文件:Apply rounded corners in desktop apps - Windows apps | Microsoft Learn
Demo 2. 新增win11的佈局器
在Button中新增 ws:WindowOption.IsMaximumButton="True" 以在滑鼠懸停時顯示佈局器
本文的所有效果均可透過諾爾大佬的WPF Suite快速實現,大家快去用呀!
Documentation | EleCho.WpfSuite
最後附上測試專案地址:
TwilightLemon/WindowEffectTest: 測試win10/11的模糊效果 (github.com)
參考文章:
[1]. 在 Windows 11 應用中使用的材料 - Windows apps | Microsoft Learn
[2]. [.NET,WPF] 窗體雲母, 亞克力, 透明, 混合顏色, 模糊背景, 亮暗色主題全講 (slimenull.com)
[3]. WPF在win10/11上啟用模糊特效 適配Dark/Light Mode - TwilightLemon - 部落格園 (cnblogs.com)
......
本作品採用 知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議 進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名TwilightLemon和原文網址,不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。