Windows 11용 데스크톱 앱에서 둥근 모서리 적용

둥근 모서리는 Windows 11 Geometry의 가장 눈에 띄는 특징입니다. Windows 11에서 시스템은 모든 UWP 앱 및 대부분의 기타 앱을 포함한 모든 받은 편지함 앱의 최상위 창 모서리를 자동으로 둥글게 처리합니다. 그러나 일부 Win32 앱은 둥글게 처리되지 않을 수 있습니다. 이 항목에서는 시스템이 자동으로 둥글게 처리하지 않는 경우, Win32 앱의 주 창 모서리를 둥글게 처리하는 방법에 대해 설명합니다.

참고

디자인적으로 앱이 최대화되거나, 스냅 또는 VM(Virtual Machine)에서 실행되거나, WVD(Windows Virtual Desktop)에서 실행되거나, WDAG(Windows Defender Application Guard) 창으로 실행될 때는 둥글게 처리되지 않습니다.

A screenshot of the Notepad app on Windows 11 with rounded corners.

내 앱이 둥글게 처리되지 않는 이유는 무엇인가요?

앱의 주 창이 자동으로 둥글게 처리되지 않는 경우 이를 차단하도록 프레임을 사용자 지정했기 때문입니다. 앱은 DWM(바탕 화면 창 관리자) 관점에서 다음과 같은 세 가지 주요 범주로 분류됩니다.

  1. 기본적으로 둥글게 처리되는 앱.

    여기에는 메모장과 같은 완전한 시스템 제공 프레임 및 캡션 컨트롤(최소/최대/닫기 버튼)을 원하는 앱이 포함됩니다. 또한 WS_THICKFRAME 및 WS_CAPTION 창 스타일을 설정하거나 시스템이 모서리를 둥글게 처리하는 데 사용할 수 있는 1픽셀 비 클라이언트 영역 테두리를 제공하는 등 모서리를 둥글게 처리할 수 있도록 시스템에 충분한 정보를 제공하는 앱도 포함되어 있습니다.

  2. 정책에 의해 둥글게 처리되지 않지만 둥글게 처리할 수 있는 앱.

    이 카테고리의 앱은 일반적으로 대부분의 창 프레임을 사용자 지정하지만, Microsoft Office와 같이 시스템에서 그린 테두리와 그림자를 유지하려 합니다. 앱이 정책에 따라 둥글게 처리되지 않은 경우 다음 중 하나가 원인일 수 있습니다.

    • 프레임 스타일 부족
    • 비어 있는 비 클라이언트 영역
    • 기타 사용자 지정(예: 사용자 지정 그림자에 사용되는 추가 하위가 아닌 창)

    이 중 하나를 변경하면 자동으로 진행하는 둥글게 처리가 중단됩니다. 시스템 추론을 사용하여 최대한 많은 앱을 둥글게 처리하려고 시도했지만 예측할 수 없는 몇 가지 사용자 지정 조합이 있어서, 이러한 경우에 대해 수동 설정 API를 제공했습니다. 앱에서 이러한 문제를 해결하거나 다음 섹션에 설명된 설정 API를 호출하면 시스템이 앱의 창을 둥글게 처리할 수 있습니다. 그러나 API는 시스템에 대한 힌트이며 사용자 지정에 따라 둥글게 처리되지 않을 수 있습니다.

  3. 설정 API를 호출하더라도 둥글게 처리할 수 없는 앱.

    이러한 앱에는 프레임 또는 테두리가 없으며 일반적으로 과도하게 사용자 지정된 UI가 있습니다. 앱이 다음 중 하나를 수행하는 경우 둥글게 처리할 수 없습니다.

    • 픽셀당 알파 계층화
    • 창 영역

    예를 들어, 앱은 픽셀당 알파 계층화를 사용하여 주 창 주위에 투명한 픽셀을 그려 사용자 지정 그림자 효과를 줄 수 있습니다. 이로 인해 창은 더 이상 직사각형이 아니므로 시스템이 둥글게 처리할 수 없습니다.

둥근 모서리에 설정하는 방법

앱이 정책에 따라 둥글게 처리되지 않은 경우 선택적으로 이러한 API를 사용하여 앱이 둥근 모서리에 설정하도록 할 수 있습니다. (다음 표에 표시된) DWM_WINDOW_CORNER_PREFERENCE 열거형 값을 DwmSetWindowAttribute 함수에 전달하여 앱에 대해 원하는 모서리 둥근 처리 옵션을 지정합니다.

열거형 값 설명
DWMWCP_DEFAULT 시스템에서 창 모서리를 둥글게 처리할지 여부를 결정하도록 합니다.
DWMWCP_DONOTROUND 창 모서리를 둥글게 처리하지 않습니다.
DWMWCP_ROUND 해당하는 경우 모서리를 둥글게 처리합니다.
DWMWCP_ROUNDSMALL 해당하는 경우 작은 반경으로 모서리를 둥글게 처리합니다.

이 열거형의 적절한 값에 대한 포인터는 DwmSetWindowAttribute세 번째 매개 변수로 전달됩니다. 설정하는 특성을 지정하는 두 번째 매개 변수의 경우 DWMWINDOWATTRIBUTE열거형에 정의된DWMWA_WINDOW_CORNER_PREFERENCE 값을 전달합니다.

C# 앱의 경우

DwmSetWindowAttribute 네이티브 Win32 API이며 .NET 코드에 직접 노출되지 않습니다. 언어의 P/Invoke 구현을 사용하여 함수라 말해야 합니다(아래 예제에서는 C# 코드가 제공됨). 모든 표준 WinForms 및 WPF 앱은 자동으로 둥글게 처리되지만 창 프레임을 사용자 지정하거나 타사 프레임워크를 사용하는 경우 둥근 모서리를 설정해야 할 수 있습니다. 자세한 내용은 예제 섹션을 참조하세요.

예제

다음 예는 앱이 정책에 따라 둥글게 처리되지 않은 경우 DwmSetWindowAttribute 또는 DwmGetWindowAttribute를 호출하여 앱이 둥글게 처리하는 환경을 제어하는 방법을 보여줍니다.

참고

간결함과 명확성을 위해 해당 예제에서는 오류 처리를 생략했습니다.

예 1 - C#에서 앱의 주 창 둥글게 처리 - WPF

이 예제에서는 [DllImport] 특성을 사용하여 C#에서 DwmSetWindowAttribute를 호출하는 방법을 보여 줍니다. 이 정의는 둥근 모서리에만 적용됩니다. DwmSetWindowAttribute 함수는 제공된 플래그에 따라 다른 매개 변수를 사용하도록 설계되었으므로 범용 서명이 아닙니다. 이 예제에는 dwmapi.h 헤더 파일의 관련 열거형 복사본도 포함되어 있습니다. Win32 API는 세 번째 매개변수에 대한 포인터를 사용하므로 함수를 호출할 때 변수의 주소를 전달할 수 있도록 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.
    // 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(창 핸들)에 대한 포인터를 획득합니다. 일반적으로 시스템은 생성자를 종료한 후에만 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.
    // 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 앱과 동일하지만 단순히 Form의 속성이기 때문에 HWND를 가져오기 위해 도우미 클래스를 사용할 필요가 없습니다. 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;
}