Compartir vía


Aplicación de esquinas redondeadas en aplicaciones de escritorio para Windows 11

Las esquinas redondeadas son la característica más apreciable de la geometría de Windows 11. En Windows 11, el sistema redondea automáticamente las esquinas de las ventanas de nivel superior para todas las aplicaciones de la bandeja de entrada, incluidas todas las aplicaciones para UWP y la mayoría de las demás aplicaciones. Sin embargo, es posible que algunas aplicaciones de Win32 no se redondeen. En este tema se describe cómo redondear las esquinas de la ventana principal de la aplicación de Win32 si el sistema no las redondea automáticamente.

Nota

Por su naturaleza, las aplicaciones no se redondean cuando se maximizan, acoplan o ejecutan en una máquina virtual (VM), en Windows Virtual Desktop (WVD) o como ventana de Protección de aplicaciones de Windows Defender (WDAG).

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

¿Por qué no se redondea mi aplicación?

Si la ventana principal de la aplicación no recibe el redondeo automático, es porque ha personalizado el marco de una manera que lo impide. Las aplicaciones se dividen en tres categorías principales desde la perspectiva del Administrador de ventanas de escritorio (DWM):

  1. Aplicaciones redondeadas de manera predeterminada.

    Esto incluye las aplicaciones que solicitan un marco completo proporcionado por el sistema y controles de título (botones para minimizar, maximizar y cerrar), como el Bloc de notas. También se incluyen las aplicaciones que proporcionan suficiente información al sistema como para redondearlas correctamente. Por ejemplo, establecer los estilos de ventana WS_THICKFRAME y WS_CAPTION o proporcionar un borde de área que no es de cliente de 1 píxel, de modo que el sistema puede usarlo para redondear las esquinas.

  2. Aplicaciones que no se redondean por directiva, pero que se pueden redondear.

    Por lo general, las aplicaciones de esta categoría desean personalizar la mayor parte del marco de ventana, pero quieren el borde y la sombra dibujados por el sistema, como Microsoft Office. Si la aplicación no se redondea por directiva, podría deberse a una de las siguientes causas:

    • Falta de estilos de marco
    • Área vacía que no es cliente
    • Otras personalizaciones, como ventanas adicionales no secundarias usadas para sombras personalizadas

    Al cambiar una de estas cosas, se interrumpirá el redondeo automático. Aunque intentamos redondear tantas aplicaciones como sea posible con la heurística del sistema, hay algunas combinaciones de personalizaciones que no podemos predecir. Por este motivo, proporcionamos una API opcional de acceso manual para esos casos. Si soluciona estos problemas en la aplicación o llama a la API de participación que se describe en la sección siguiente, el sistema tiene la posibilidad de redondear la ventana de la aplicación. Sin embargo, tenga en cuenta que la API es una sugerencia para el sistema y no se garantiza el redondeo, en función de las personalizaciones.

  3. Aplicaciones que nunca se pueden redondear, incluso si llaman a la API de participación.

    Estas aplicaciones no tienen marco ni bordes y normalmente tienen una interfaz de usuario muy personalizada. Si la aplicación realiza una de las siguientes acciones, no se puede redondear:

    • Capas alfa por píxel
    • Regiones de ventana

    Por ejemplo, una aplicación podría usar la capa alfa por píxel para dibujar píxeles transparentes alrededor de su ventana principal a fin de que tenga un efecto de sombra personalizado. Esto hace que la ventana ya no sea un rectángulo y, por tanto, el sistema no puede redondearla.

Cómo optar por las esquinas redondeadas

Si una aplicación no se redondea por alguna directiva, se pueden usar estas API para que la aplicación opte por esquinas redondeadas. Para especificar la opción de redondeo de esquinas que desee para la aplicación, use un valor de la enumeración DWM_WINDOW_CORNER_PREFERENCE (que se muestra en la tabla siguiente) en la función DwmSetWindowAttribute.

Valor de enumeración Descripción
DWMWCP_DEFAULT Deje que el sistema decida si redondear o no las esquinas de la ventana.
DWMWCP_DONOTROUND Nunca redondee las esquinas de la ventana.
DWMWCP_ROUND Redondee las esquinas si procede.
DWMWCP_ROUNDSMALL Redondee las esquinas si procede, con un radio pequeño.

Se usa un puntero que señala al valor adecuado de esta enumeración con el tercer parámetro de DwmSetWindowAttribute. Para el segundo parámetro, que especifica el atributo que se establece, use el valor de DWMWA_WINDOW_CORNER_PREFERENCE definido en la enumeración DWMWINDOWATTRIBUTE.

Para aplicaciones de C#

DwmSetWindowAttribute es una API de Win32 nativa y no se expone directamente al código de .NET. Deberá usar la implementación del lenguaje de P/Invoke para declarar la función (el código de C# se muestra en el ejemplo siguiente). Todas las aplicaciones estándar de WinForms y WPF se redondean automáticamente, pero, si personaliza el marco de la ventana o usa un marco de terceros, es posible que tenga que configurar el uso de las esquinas redondeadas. Consulte la sección Ejemplos para obtener más detalles.

Ejemplos

En los ejemplos siguientes se muestra cómo puede llamar a DwmSetWindowAttribute o DwmGetWindowAttribute para controlar la experiencia de redondeo de la aplicación si esta no se redondea a través de una directiva.

Nota

El control de errores se omitió en estos ejemplos a efectos de brevedad y claridad.

Ejemplo 1: Redondeo de la ventana principal de una aplicación en C# - WPF

En este ejemplo se muestra cómo llamar a DwmSetWindowAttribute desde C# mediante el atributo [DllImport]. Tenga en cuenta que esta definición es específica de las esquinas redondeadas. La función DwmSetWindowAttribute está diseñada para tomar parámetros diferentes en función de las marcas proporcionadas, por lo que no se trata de una firma de uso general. En el ejemplo también se incluyen copias de las enumeraciones pertinentes del archivo de encabezado dwmapi.h. Dado que la API de Win32 toma un puntero para el tercer parámetro, asegúrese de usar la palabra clave ref para que pueda pasar la dirección de una variable al llamar a la función. Puede hacerlo en la clase MainWindow en 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
    // ...
}

A continuación, en el constructor MainWindow, después de llamar a InitalizeComponent, cree una nueva instancia de la clase WindowInteropHelper para adquirir un puntero al HWND (identificador de ventana) subyacente. Asegúrese de usar el método EnsureHandle para forzar al sistema a crear un HWND para la ventana antes de que se muestre, ya que normalmente el sistema solo lo hace después de salir del constructor.

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
    // ...
}

Ejemplo 2: Redondeo de la ventana principal de una aplicación en C# - WinForms

Al igual que con WPF, para una aplicación WinForms primero deberá importar dwmapi.dll y la firma de la función DwmSetWindowAttribute con P/Invoke. Puede hacerlo en la clase Form principal.

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
    // ...
}

Llamar a DwmSetWindowAttribute también es igual que con una aplicación WPF, pero no tiene que usar una clase auxiliar para obtener el HWND porque simplemente es una propiedad de la clase Form. Llame a la función desde el constructor Form, después de realizar la llamada a 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
    // ...
}

Ejemplo 3: Redondeo de la ventana principal de una aplicación en C++

Para una aplicación nativa de C++, puede llamar a DwmSetWindowAttribute en la función de procesamiento de mensajes después de la creación de la ventana para pedir al sistema que la redondee.

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;
}

Ejemplo 4: Redondeo de las esquinas de un menú con un radio pequeño - C++

De manera predeterminada, los menús son ventanas emergentes, que no se redondean. Si la aplicación crea un menú personalizado y desea que siga la directiva de redondeo de otros menús estándar, puede llamar a la API para que el sistema sepa que esta ventana se debe redondear, aunque parezca que no coincide con la directiva de redondeo predeterminada.

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;
}