Windows 11 向けデスクトップ アプリケーションで角の丸めを適用する

角の丸めは、Windows 11 Geometry の最も注目すべき機能です。 Windows 11 では、すべての UWP アプリを含む、すべてのインボックス アプリとその他ほとんどのアプリの最上位レベルのウィンドウの角が自動的に丸められます。 ただし、一部の Win32 アプリは、丸められない場合があります。 このトピックでは、システムによって自動的に丸められない場合に、Win32 アプリのメイン ウィンドウの角を丸くする方法について説明します。

注意

設計上、アプリは、最大化されたり、スナップされたとき、仮想マシン (VM) や Windows 仮想デスクトップ (WVD) で実行されるとき、または Windows Defender Application Guard (WDAG) として実行されるときは丸められません。

角が丸い Windows 11 のメモ帳アプリ。

アプリが丸められない理由

アプリのメイン ウィンドウが自動丸め処理を受け取らない場合、それは、処理されないようにフレームをカスタマイズしたためです。 アプリは、デスクトップ ウィンドウ マネージャー (DWM) の観点から見た場合、次の 3 つの主要なカテゴリに分類されます。

  1. 既定で丸められるアプリ。

    これには、メモ帳などの完全なシステム指定のフレームやキャプション コントロール (最小/最大/閉じるボタン) を必要とするアプリが含まれます。 また、WS_THICKFRAME と WS_CAPTION のウィンドウ スタイルの設定や、システムが角を角を丸めるのに使用できる 1 ピクセルの非クライアント領域の境界線の指定など、適切な丸めのための十分な情報をシステムに提供するアプリも含まれます。

  2. ポリシーでは丸められないが、丸め "可能な" アプリ。

    通常、このカテゴリのアプリでは、ウィンドウ フレームの大部分をカスタマイズする必要がありますが、Microsoft Office などシステムによって描画される境界線やシャドウも必要です。 アプリがポリシーで丸められない場合、次のいずれかが原因であると考えられます。

    • フレーム スタイルがない
    • 非クライアント領域が空
    • その他のカスタマイズ (カスタム シャドウに使用される追加の子以外のウィンドウなど)

    これらのいずれかを変更すると、自動丸め処理が正常に動作しなくなります。 システム ヒューリスティックを使用してできるだけ多くのアプリを丸めようとしましたが、予測できないカスタマイズの組み合わせがいくつかあるため、これらの場合のために手動のオプトイン API を用意しています。 お使いのアプリでこれらの問題に対処するか、次のセクションで説明されているオプトイン API を呼び出した場合、システムによって自動的に丸めることができます。 ただし、API はシステムに対するヒントであり、カスタマイズによっては丸め処理が保証されないことに注意してください。

  3. オプトイン API が呼び出された場合でも、丸められることがないアプリ。

    これらのアプリにはフレームや境界線がなく、通常、UI は大幅にカスタマイズされています。 アプリで以下のいずれかが実行されている場合、丸めることはできません。

    • ピクセル単位のアルファ レイヤー化
    • ウィンドウ領域

    たとえば、アプリで、カスタムのシャドウ効果を達成するために、ピクセル単位のアルファ レイヤー化を使用してメイン ウィンドウの周囲に透明のピクセルが描画されている場合、ウィンドウは四角形ではなくなるため、システムは丸め処理を実行できません。

角の丸めを選択する方法

[API の定義]

アプリがポリシーによって丸められない場合、必要に応じて新しい API を呼び出して、アプリで角の丸めが選択されるようにすることができます。 この API は、新しい DWM_WINDOW_CORNER_PREFERENCE 列挙値に示されているように、DwmSetWindowAttribute API に渡される列挙値として表現されます。 DWM_WINDOW_CORNER_PREFERENCEdwmapi.h header で定義されており、最新の Insider Preview SDK でのみ利用できます。

列挙値 説明
DWMWCP_DEFAULT ウィンドウの角を丸めるかどうかをシステムに決めさせます。
DWMWCP_DONOTROUND ウィンドウの角は丸められません。
DWMWCP_ROUND 必要に応じて、角を丸めます。
DWMWCP_ROUNDSMALL 必要に応じて角を丸め、半径を小さくします。

この列挙値からの適切な値へのポインターが DwmSetWindowAttribute の 3 番目のパラメーターに渡されます。 設定する属性を指定する2番目のパラメーターには、DWMWINDOWATTRIBUTE 列挙で定義されている新しい DWMWA_WINDOW_CORNER_PREFERENCE 値を渡します。

C# アプリの場合

DwmSetWindowAttribute はネイティブ C++ API です。 アプリが .NET に基づいており、C# が使用される場合は、P/Invoke を使用して dwmapi.dll と DwmSetWindowAttribute 関数シグネチャをインポートする必要があります。 すべての標準の WinForms および WPF アプリは、他のアプリ同様、自動的に丸められますが、ウィンドウ枠をカスタマイズしたり、サードパーティのフレームワークを使用したりする場合は、そうすることで既定の丸めが失われる場合、角の丸めを選択しなければならないことがあります。 詳細については、「例」を参照してください。

次の例は、DwmSetWindowAttribute または DwmGetWindowAttribute を呼び出して、アプリがポリシーによって丸められない場合に、アプリの丸めエクスペリエンスを制御する方法を示しています。

注意

簡潔でわかりやすくするために、これらの例からエラー処理は省かれています。

例 1 - C# - WPF でのアプリのメイン ウィンドウの丸め

C# WPF デスクトップ アプリで DwmSetWindowAttribute を呼び出すには、P/Invoke を使用して dwmapi.dll と DwmSetWindowAttribute 関数シグネチャをインポートする必要があります。 最初に、ネイティブの dwmapi.h ヘッダーから必要な列挙値を再定義してから、元のネイティブ関数と同等の C# 型を使用して関数を宣言する必要があります。 元の関数は 3 番目のパラメーターのポインターを受け取るため、関数を呼び出すときに変数のアドレスを渡すことができるように、必ず ref キーワードを使用してください。 これは、MainWindow.xaml.cs の MainWindow クラスで実行できます。

using System.Runtime.InteropServices;
using System.Windows.Interop;

public partial class MainWindow : Window
{
    // The enum flag for DwmSetWindowAttribute's second parameter, which tells the function what attribute to set.
    public enum DWMWINDOWATTRIBUTE
    {
        DWMWA_WINDOW_CORNER_PREFERENCE = 33
    }

    // The DWM_WINDOW_CORNER_PREFERENCE enum for DwmSetWindowAttribute's third parameter, which tells the function
    // what value of the enum to set.
    public enum DWM_WINDOW_CORNER_PREFERENCE
    {
        DWMWCP_DEFAULT      = 0,
        DWMWCP_DONOTROUND   = 1,
        DWMWCP_ROUND        = 2,
        DWMWCP_ROUNDSMALL   = 3
    }

    // Import dwmapi.dll and define DwmSetWindowAttribute in C# corresponding to the native function.
    [DllImport("dwmapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern long DwmSetWindowAttribute(IntPtr hwnd,
                                                     DWMWINDOWATTRIBUTE attribute,
                                                     ref DWM_WINDOW_CORNER_PREFERENCE pvAttribute,
                                                     uint cbAttribute);

    // ...
    // Various other definitions
    // ...
}

次に、MainWindow コンストラクターで、InitalizeComponent 呼び出しの後に、WindowInteropHelper クラスの新しいインスタンスを作成して、基になる HWND へのポインターを取得します (ウィンドウ ハンドル)。 EnsureHandle メソッドを使用して、表示される前に、ウィンドウの HWND の作成をシステムに必ず強制します。通常、システムでは、コンストラクターを終了した後にのみ、これが行われるためです。

public MainWindow()
{
    InitializeComponent();

    IntPtr hWnd = new WindowInteropHelper(GetWindow(this)).EnsureHandle();
    var attribute = DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE;
    var preference = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND;
    DwmSetWindowAttribute(hWnd, attribute, ref preference, sizeof(uint));
    
    // ...
    // Perform any other work necessary
    // ...
}

例 2 - C# - WinForms でのアプリのメイン ウィンドウの丸め

WPF と同様、WinForms アプリでは、最初に P/Invoke を使用して dwmapi.dll と DwmSetWindowAttribute 関数シグネチャをインポートする必要があります。 これは、プライマリ Form クラスで行うことができます。

using System;
using System.Runtime.InteropServices;

public partial class Form1 : Form
{
    // The enum flag for DwmSetWindowAttribute's second parameter, which tells the function what attribute to set.
    public enum DWMWINDOWATTRIBUTE
    {
        DWMWA_WINDOW_CORNER_PREFERENCE = 33
    }            

    // The DWM_WINDOW_CORNER_PREFERENCE enum for DwmSetWindowAttribute's third parameter, which tells the function
    // what value of the enum to set.
    public enum DWM_WINDOW_CORNER_PREFERENCE
    {
        DWMWCP_DEFAULT      = 0,
        DWMWCP_DONOTROUND   = 1,
        DWMWCP_ROUND        = 2,
        DWMWCP_ROUNDSMALL   = 3
    }               

    // Import dwmapi.dll and define DwmSetWindowAttribute in C# corresponding to the native function.
    [DllImport("dwmapi.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern long DwmSetWindowAttribute(IntPtr hwnd, 
                                                     DWMWINDOWATTRIBUTE attribute, 
                                                     ref DWM_WINDOW_CORNER_PREFERENCE pvAttribute, 
                                                     uint cbAttribute);
    
    // ...
    // Various other definitions
    // ...
}

DwmSetWindowAttribute の呼び出しは、WPF アプリの場合と同じですが、HWND を取得するためにヘルパー クラスを使用する必要はありません。これは、単に Form のプロパティであるためです。 InitializeComponent の呼び出しの後、Form コンストラクター内からこれを呼び出します。

public Form1()
{
    InitializeComponent();

    var attribute = DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE;
    var preference = DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND;
    DwmSetWindowAttribute(this.Handle, attribute, ref preference, sizeof(uint));
    
    // ...
    // Perform any other work necessary
    // ...
}

例 3 - C++ でのアプリのメイン ウィンドウの丸め

ネイティブ C++ アプリの場合、ウィンドウ作成後にメッセージ処理関数で DwmSetWindowAttribute を呼び出して、システムに丸めるよう指示できます。

LRESULT ExampleWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 
{
    switch (message)
    {
    // ...
    // Handle various window messages...
    // ...

    case WM_CREATE:
        // ...
        // Perform app resource initialization after window creation
        // ...
        
        if(hWnd)
        {
            DWM_WINDOW_CORNER_PREFERENCE preference = DWMWCP_ROUND;
            DwmSetWindowAttribute(hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &preference, sizeof(preference));
        }
        break;

    // ...
    // Handle various other window messages...
    // ...
    }

    return 0;
}

例 4 - 半径が小さいメニューの角の丸め - C++

既定では、メニューはポップアップ ウィンドウであり、丸められません。 アプリでカスタム メニューを作成し、他の標準メニューの丸めポリシーに従わせたい場合は、API を呼び出して、既定の丸めポリシーと一致していないように見える場合でも、このウィンドウを丸める必要があることをシステムに知らせることができます。

HWND CreateCustomMenu()
{
    // Call an app-specific helper to make the window, using traditional APIs.
    HWND hWnd = CreateMenuWindowHelper();

    if (hWnd)
    {
        // Make sure we round the window, using the small radius 
        // because menus are auxiliary UI.
        DWM_WINDOW_CORNER_PREFERENCE preference = DWMWCP_ROUNDSMALL;
        DwmSetWindowAttribute(hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, &preference, sizeof(preference));
    }

    return hWnd;
}