Применение закругленных углов в настольных приложениях для Windows 11

Скругленные углы — это самая заметная особенность в геометрическом оформлении интерфейса Windows 11. В Windows 11 система автоматически округляет угол окна верхнего уровня для всех приложений в папке "Входящие", включая все приложения WinUI и большинство других приложений. Но в некоторых приложениях Win32 окна могут не скругляться. В этой статье объясняется, как применить скругление углов главного окна приложения Win32, если система не делает это автоматически.

Примечание.

По умолчанию скругление в приложениях не применяется при максимальном развертывании и прикреплении окон, а также при работе на виртуальной машине, в Виртуальном рабочем столе Windows (WVD) и в окне Application Guard в Windows Defender (WDAG).

Снимок экрана: приложение Блокнота в Windows 11 с округленными углами.

Почему мое приложение не выглядит округлым?

Если углы основного окна вашего приложения не скругляются автоматически, это связано с тем, что настроенные вами параметры рамки препятствуют скруглению. В контексте диспетчера окон рабочего стола (DWM), приложения делятся на три основные категории:

  1. Приложения, в которых скругление применяется по умолчанию.

    К ним относятся приложения, которым требуется полноценная рамка и элементы управления заголовком, предоставляемые системой (кнопки свернуть, развернуть и закрыть), такие как Блокнот. Сюда также входят приложения, которые предоставляют системе достаточно информации, чтобы она могла правильно скруглить углы, например задать стили окна WS_THICKFRAME и WS_CAPTION или предоставить границу неклиентской области шириной в 1 пиксель, которую система может использовать для скругления углов.

  2. Приложения, которые не скруглены политикой, но могут быть скруглены.

    Приложения этой категории обычно самостоятельно настраивают большую часть рамки окна, но им по-прежнему требуется отображаемая системой граница и тень (например, Microsoft Office). Если в вашем приложении скругление не применяется в соответствии с политикой, это может быть вызвано одной из следующих причин:

    • нехватка стилей рамок;
    • пустая область, не относящаяся к клиенту;
    • другие настройки, такие как дополнительные окна, не являющиеся дочерними элементами и используемые для настраиваемых теней.

    Изменение одного из этих элементов приведет к нарушению автоматического округления. Хотя мы старались охватить как можно больше приложений с помощью нашей системной эвристики, существуют некоторые комбинации настроек, которые мы не можем предсказать. Для таких случаев мы предусмотрели ручное включение через API. Если вы решите эти проблемы в своем приложении или вызовете опциональный API, описанный в следующем разделе, система может скруглить окно вашего приложения. Обратите внимание, что API является рекомендацией для системы и не гарантирует округление в зависимости от настроек.

  3. Приложения, для которых нельзя применить скругление, даже если они вызывают API предоставления согласия.

    У таких приложений нет рамки или границ, а их пользовательский интерфейс обычно имеет широкие возможности настройки. Если в приложении выполняется одно из следующих действий, в нем нельзя применить скругление:

    • альфа-наложение для каждого пикселя;
    • Области окон.

    Например, приложение может использовать поканальное альфа-наложение, чтобы размещать прозрачные пиксели вокруг главного окна и достигать эффекта настраиваемой тени. В результате окно теряет прямоугольную форму, и система не может скруглить его.

Как предоставить согласие на скругление углов

Если ваше приложение не использует округление по политике, вы можете по желанию воспользоваться этими API, чтобы вашему приложению включить поддержку скругленных углов. Вы задаете параметр округления углов, необходимый для вашего приложения, передав значение перечисления DWM_WINDOW_CORNER_PREFERENCE (показанное в следующей таблице) в функцию DwmSetWindowAttribute.

Значение перечисления Описание
DWMWCP_DEFAULT Система определяет, следует ли скруглять углы окон.
DWMWCP_DONOTROUND Никогда не скругляйте углы окон.
DWMWCP_ROUND Скруглите углы, если это уместно.
DWMWCP_ROUNDSMALL При необходимости скругляйте углы с небольшим радиусом.

Указатель на соответствующее значение из этого перечисления передаётся в третий параметр функции DwmSetWindowAttribute. Для второго параметра, специфицирующего, какой атрибут вы задаете, передайте значение DWMWA_WINDOW_CORNER_PREFERENCE, определенное в перечислении DWMWINDOWATTRIBUTE.

Приложения C#

DwmSetWindowAttribute — это собственный API Win32, который не предоставляется непосредственно коду .NET. Для объявления функции необходимо использовать реализацию языка P/Invoke (код C#, приведенный в приведенном ниже примере). Во всех стандартных приложениях WinForms и WPF углы скругляются автоматически. Но если вы настраиваете рамку окна или используете стороннюю платформу, вам может потребоваться предоставить согласие на применение скругления углов. Дополнительные сведения см. в разделе с примерами.

Примеры

В приведенных ниже примерах показано, как вызвать DwmSetWindowAttribute или DwmGetWindowAttribute, чтобы управлять возможностями скругления в приложении, если политика не предусматривает скругление в приложении.

Примечание.

Для краткости и ясности в этих примерах обработка ошибок исключена.

Пример 1. Скругление углов главного окна приложения в C# — WPF

В этом примере показано, как вызвать DwmSetWindowAttribute из C# с помощью атрибута [DllImport]. Обратите внимание, что это определение относится к скругленным углам. Функция DwmSetWindowAttribute предназначена для приема различных параметров в зависимости от предоставленных флагов, поэтому это не сигнатура общего назначения. Пример также включает копии соответствующих перечислений из заголовочного файла dwmapi.h. Так как API Win32 принимает указатель на третий параметр, обязательно используйте ключевое слово ref, чтобы можно было передать адрес переменной при вызове функции. Это можно сделать в классе MainWindow в MainWindow.xaml.cs.

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.
    // Copied from dwmapi.h
    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.
    // Copied from dwmapi.h
    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, PreserveSig = false)]
    internal static extern void DwmSetWindowAttribute(IntPtr hwnd,
                                                     DWMWINDOWATTRIBUTE attribute,
                                                     ref DWM_WINDOW_CORNER_PREFERENCE pvAttribute,
                                                     uint cbAttribute);
    // ...
    // Various other definitions
    // ...
}

Затем в конструкторе MainWindow после вызова InitializeComponent создайте новый экземпляр класса 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 сначала необходимо импортировать dwmapi.dll и сигнатуру функции DwmSetWindowAttribute с использованием P/Invoke. Это можно сделать в вашем основном классе 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.
    // Copied from dwmapi.h
    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.
    // Copied from dwmapi.h
    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, PreserveSig = false)]
    internal static extern void DwmSetWindowAttribute(IntPtr hwnd,
                                                     DWMWINDOWATTRIBUTE attribute, 
                                                     ref DWM_WINDOW_CORNER_PREFERENCE pvAttribute, 
                                                     uint cbAttribute);
    
    // ...
    // Various other definitions
    // ...
}

Вызов DwmSetWindowAttribute также соответствует вызову в приложении WPF, но при этом вам не нужно использовать вспомогательный класс для получения HWND, так как это просто свойство Form. Вызовите эту функцию из конструктора Form после вызова InitializeComponent.

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. Скругление углов меню с небольшим радиусом — С++

По умолчанию меню — это всплывающие окна, углы которых не скругляются. Если приложение создает пользовательское меню и вы хотите, чтобы оно соответствовало политике скругления для других стандартных меню, можно вызвать 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;
}