single.php

C# WinUI3 でウィンドウの影をカスタマイズする方法

C# WinUI 3アプリを作っていく途中で、調べたことを忘録的に投稿します。今回はWinUI3プロジェクトで、ウィンドウの下に表示される影をカスタマイズする方法です。

ウィンドウの下に影

確か、Windows 7/Vistaの時代に実装された記憶のある「影」。

マウスポインターなどにも[ドロップシャドウ]と呼ばれるエフェクトが追加され、この頃から、立体感のあるスタイルになってきたウィンドウズ。

その後も、Windows11でも実装されています。

このスタイルは、C#などで作成したウィンドウでも適用されて、こんな感じでデスクトップや他のウィンドウと重なる部分に[ドロップシャドウ]効果が追加されます。

[パフォーマンス オプション]で変更

Windowsの[パフォーマンス オプション]画面に[ウィンドウの下に影を表示する]項目があり、この設定をオフにすると影が非表示になります。

ただし、この設定は[Windows]全体に適用されてしまいデスクトップに表示されるすべてのウィンドウの影が非表示になります。

WM_NCCALCSIZE を変更

Windows11で導入された四隅のラウンド(丸み)が無くなってしまいますが、Win32のメッセージ処理で[WM_NCCALCSIZE](ウィンドウのクライアント領域のサイズと位置を計算する必要がある場合に送信される)を編集して影を非表示にできます。

例えば、次のコードのようにXamlを作成します。

<Window
  x:Class="WinUi3Samples.DropShadow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:local="using:WinUi3Samples"
  xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="d"
  Title="DropShadow">

  <Grid Name="gridMain" VerticalAlignment="Center" HorizontalAlignment="Center">
    <ToggleButton Name="ToggleDropShadow" Content="ウィンドウの影を非表示"  Checked="ToggleDropShadow_Checked" Unchecked="ToggleDropShadow_Unchecked" />
  </Grid>
</Window>

実行すると、こんな感じで四隅がラウンドした形状でウィンドウが表示されます。

DWMでウィンドウの形状にアクセスするには[DwmSetWindowAttribute 関数]を利用します。

表示するウィンドウに次のコードを追加します。

[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int uFlags);

[DllImport("user32.dll")]
private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, WindowLongIndexFlags nIndex, WinProc newProc);

private delegate IntPtr WinProc(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

private WinProc? newWidProc = null;
private IntPtr oldWidProc = IntPtr.Zero;
private const int WM_NCCALCSIZE = 0x0083;

private const int SWP_NOMOVE = 0x0002;
private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;
private const int SWP_DRAWFRAME = 0x0020;

public DropShadow()
{
  this.InitializeComponent();
  this.ExtendsContentIntoTitleBar = true;
  var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
  SubClassing();
}

private void SubClassing()
{
  var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
  newWidProc = new WinProc(NewWindowProc);
  oldWidProc = SetWindowLongPtr64(hWnd, WindowLongIndexFlags.GWL_WNDPROC, newWidProc);
}

private IntPtr NewWindowProc(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam)
{
  switch (Msg)
  {
    case WM_NCCALCSIZE:
      if(ToggleDropShadow.IsChecked == true)
      {
        return new IntPtr(0);
      }
      break;
  }
  return CallWindowProc(oldWidProc, hWnd, Msg, wParam, lParam);
}

private void ToggleDropShadow_Checked(object sender, RoutedEventArgs e)
{
  ToggleDropShadow.Content = "ウィンドウの影を表示";
  var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
  SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_DRAWFRAME);    
}

private void ToggleDropShadow_Unchecked(object sender, RoutedEventArgs e)
{
  ToggleDropShadow.Content = "ウィンドウの影を非表示";
  var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
  SetWindowPos(hWnd, 0, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_DRAWFRAME);
}

このコードを実行すると、こんな感じで画面中央のトグルボタンでウィンドウの下に表示される影を切り替えが可能です。

まとめ

今回はWinUI3プロジェクトで、ウィンドウの下に表示される影をカスタマイズする方法について紹介しました。

ウィンドウの下に表示される影は、Win32を利用してウィンドウの[WM_NCCALCSIZE]メッセージを編集して非表示が可能でした。

WinUI 3アプリで表示したウィンドウの下に表示される影をカスタマイズしたい人の参考になれば幸いです。

スポンサーリンク

最後までご覧いただき、ありがとうございます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です