WPF 模擬UWP原生視窗樣式——亞克力|雲母材質、自定義標題欄樣式、原生DWM動畫 (附我封裝好的類)

TwilightLemon發表於2024-08-22

  先看一下最終效果,左圖為使用亞克力材質並新增組合顏色的效果;右圖為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標籤來建立一個沒有三大按鈕的標題欄:

WPF 模擬UWP原生視窗樣式——亞克力|雲母材質、自定義標題欄樣式、原生DWM動畫 (附我封裝好的類)
 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 }
View Code

然後按照上述的方法新增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和原文網址,不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。

相關文章