Compatibilidad con reconocimiento por monitor para extensores de Visual Studio

Las versiones anteriores a Visual Studio 2019 tenían su contexto de reconocimiento de PPP establecido en reconocimiento del sistema, en lugar de reconocimiento de PPP por monitor (PMA). La ejecución en el reconocimiento del sistema dio lugar a una experiencia visual degradada (por ejemplo, fuentes o iconos borrosos) siempre que Visual Studio tuviera que representar en monitores con diferentes factores de escala o remotos en máquinas con diferentes configuraciones de visualización (por ejemplo, diferentes escalado de Windows).

El contexto de reconocimiento de PPP de Visual Studio 2019 se establece como PMA, cuando el apoyo ambiental, lo que permite que Visual Studio se represente según la configuración de la pantalla donde se hospeda en lugar de una única configuración definida por el sistema. En última instancia, se traduce en una interfaz de usuario siempre nítida para las áreas expuestas que admiten el modo PMA.

Consulte la documentación desarrollo de aplicaciones de escritorio de valores altos de PPP en Windows para obtener más información sobre los términos y el escenario general descritos en este documento.

Inicio rápido

  • Asegúrese de que Visual Studio se está ejecutando en modo PMA (consulte Habilitación de PMA)

  • Validar que la extensión funciona correctamente en un conjunto de escenarios comunes (consulte Prueba de las extensiones para problemas de PMA).

  • Si encuentra problemas, puede usar las estrategias y recomendaciones que se describen en este documento para diagnosticar y corregir esos problemas. También deberá agregar el nuevo paquete NuGet Microsoft.VisualStudio.DpiAwareness al proyecto para acceder a las API necesarias.

Habilitar PMA

Para habilitar PMA en Visual Studio, deben cumplirse los siguientes requisitos:

Una vez cumplidos estos requisitos, Visual Studio habilita automáticamente el modo PMA en todo el proceso.

Nota:

El contenido de Windows Forms en Visual Studio (por ejemplo, el Explorador de propiedades) solo admite PMA cuando tiene Visual Studio 2019 versión 16.1 o posterior.

Prueba de las extensiones para problemas de PMA

Visual Studio admite oficialmente los marcos de interfaz de usuario WPF, Windows Forms, Win32 y HTML/JS. Cuando Visual Studio se coloca en modo PMA, cada pila de interfaz de usuario se comporta de forma diferente. Por lo tanto, independientemente del marco de interfaz de usuario, se recomienda que se realice una superación de prueba para asegurarse de que toda la interfaz de usuario es compatible con el modo PMA.

Se recomienda validar los siguientes escenarios comunes:

  • Cambiar el factor de escala de un único entorno de supervisión mientras se ejecuta la aplicación.

    Este escenario ayuda a probar que la interfaz de usuario responde al cambio dinámico de PPP de Windows.

  • Acoplamiento o desacoplación de un portátil en el que se establece un monitor conectado en la principal y el monitor conectado tiene un factor de escala diferente al portátil mientras se ejecuta la aplicación.

    Este escenario ayuda a probar que la interfaz de usuario responde al cambio de PPP de pantalla, así como al control de las pantallas que se agregan o quitan dinámicamente.

  • Tener varios monitores con diferentes factores de escala y mover la aplicación entre ellos.

    Este escenario ayuda a probar que la interfaz de usuario responde al cambio de PPP de pantalla.

  • Comunicación remota en una máquina cuando las máquinas locales y remotas tienen diferentes factores de escala para el monitor principal.

    Este escenario ayuda a probar que la interfaz de usuario responde al cambio dinámico de PPP de Windows.

Una buena prueba preliminar para si la interfaz de usuario podría tener problemas es si el código utiliza las clases Microsoft.VisualStudio.Utilities.Ppp.PppHelper, Microsoft.VisualStudio.PlatformUI.DpiHelper o VsUI::CDpiHelper. Estas clases de PppHelper antiguas solo admiten el reconocimiento de PPP del sistema y no siempre funcionarán correctamente cuando el proceso sea PMA.

El uso típico de estos PppHelpers tendrá el siguiente aspecto:

Point screenTopRight = logicalBounds.TopRight.LogicalToDeviceUnits();

POINT screenIntTopRight = new POINT
{
    x = (int)screenTopRIght.X,
    y = (int)screenTopRIght.Y
}

// Declared via P/Invoke
IntPtr monitor = MonitorFromPoint(screenIntTopRight, MONITOR_DEFAULTTONEARST);

En el ejemplo anterior, un rectángulo que representa los límites lógicos de una ventana se convierte en unidades de dispositivo para que se pueda pasar al método nativo MonitorFromPoint que espera coordenadas del dispositivo para devolver un puntero de monitor preciso.

Clases de problemas

Cuando el modo PMA está habilitado para Visual Studio, la interfaz de usuario podría replicar problemas de varias maneras comunes. La mayoría de estos problemas, si no todos, pueden producirse en cualquiera de los marcos de interfaz de usuario admitidos de Visual Studio. Además, estos problemas también pueden producirse cuando se hospeda una parte de la interfaz de usuario en escenarios de escalado de PPP en modo mixto (consulte la documentación de Windows para obtener más información).

Creación de ventanas Win32

Al crear ventanas con CreateWindow() o CreateWindowEx(), un patrón común es crear la ventana en coordenadas (0,0) (la esquina superior o izquierda de la pantalla principal) y, a continuación, moverla a su posición final. Sin embargo, si lo hace, puede provocar que la ventana desencadene un mensaje o evento modificado por PPP, lo que puede volver a intentar otros mensajes o eventos de la interfaz de usuario y, finalmente, provocar un comportamiento o representación no deseados.

Selección de ubicación de elementos WPF

Al mover elementos de WPF mediante el antiguo Microsoft.VisualStudio.Utilities.Ppp.PppHelper, es posible que las coordenadas de la parte superior izquierda no se calculen correctamente siempre que los elementos estén en un PPP no principal.

Serialización de tamaños o posiciones del elemento de la interfaz de usuario

Cuando el tamaño o la posición de la interfaz de usuario (si se guardan como unidades de dispositivo) se restauran en un contexto de PPP diferente al que se almacenó, se colocará y ajustará el tamaño incorrectamente. Esto sucede porque las unidades de dispositivo tienen una relación de PPP inherente.

Escalado incorrecto

Los elementos de la interfaz de usuario creados en el PPP principal se escalarán correctamente, pero cuando se mueven a una pantalla con un PPP diferente, no se escalan y su contenido es demasiado grande o demasiado pequeño.

Límite incorrecto

De forma similar al problema de escalado, los elementos de la interfaz de usuario calculan correctamente sus límites en su contexto de PPP principal, pero cuando se mueven a un PPP no principal, no calcularán los nuevos límites correctamente. Por lo tanto, la ventana de contenido es demasiado pequeña o demasiado grande en comparación con la interfaz de usuario de hospedaje, lo que da como resultado un espacio vacío o recorte.

Arrastrar y colocar

Siempre que dentro de escenarios de PPP en modo mixto (por ejemplo, diferentes elementos de la interfaz de usuario que representan en diferentes modos de reconocimiento de PPP), las coordenadas de arrastrar y colocar podrían estar mal calculadas, lo que da lugar a que la posición final termine incorrecta.

Interfaz de usuario fuera del proceso

Algunas interfaces de usuario se crean fuera de proceso y si el proceso externo de creación está en un modo de reconocimiento de PPP diferente al de Visual Studio, esto puede introducir cualquiera de los problemas de representación anteriores.

Controles, imágenes o diseños de Windows Forms representados incorrectamente

No todos los contenidos de Windows Forms admiten el modo PMA. Como resultado, es posible que vea un problema de representación con diseños o escalado incorrectos. Una posible solución en este caso es representar explícitamente el contenido de Windows Forms en "System Aware" DpiAwarenessContext (consulte Forzar un control en un elemento DpiAwarenessContext específico).

Controles o ventanas de Windows Forms que no se muestran

Una de las principales causas de este problema es que los desarrolladores intentan volver aparentar un control o una ventana con pppAwarenessContext en una ventana con un valor de PppAwarenessContext diferente.

En las imágenes siguientes se muestran las restricciones del sistema operativo Windows predeterminadas actuales en las ventanas primarias:

A screenshot of the correct parenting behavior

Nota:

Puede cambiar este comportamiento estableciendo el comportamiento de hospedaje de subprocesos (consulte Dpi_Hosting_Behavior enumeración).

Como resultado, si establece la relación de elementos primarios y secundarios entre modos no admitidos, se producirá un error y es posible que el control o la ventana no se representen según lo previsto.

Diagnosticar problemas

Hay muchos factores que se deben tener en cuenta al identificar problemas relacionados con pma:

  • ¿La interfaz de usuario o la API esperan valores lógicos o de dispositivo?

    • La interfaz de usuario y las API de WPF suelen usar valores lógicos (pero no siempre)
    • Normalmente, la interfaz de usuario y las API de Win32 usan valores de dispositivo
  • ¿De dónde proceden los valores?

    • Si recibe valores de otra interfaz de usuario o API, pasa el dispositivo o los valores lógicos.
    • Si recibe valores de varios orígenes, ¿todos usan o esperan los mismos tipos de valores o deben combinarse las conversiones?
  • ¿Hay constantes de interfaz de usuario en uso y en qué forma están?

  • ¿El subproceso está en el contexto de PPP correcto para los valores que está recibiendo?

    Los cambios para habilitar el hospedaje de PPP mixto normalmente deben colocar rutas de acceso de código en el contexto correcto; sin embargo, el trabajo realizado fuera del bucle de mensajes principal o el flujo de eventos podría ejecutarse en el contexto de PPP incorrecto.

  • ¿Los valores cruzan los límites de contexto de PPP?

    Arrastrar y colocar es una situación común en la que las coordenadas pueden cruzar contextos de PPP. Window intenta hacer lo correcto, pero en algunos casos, es posible que la interfaz de usuario del host tenga que realizar el trabajo de conversión para garantizar la coincidencia de límites de contexto.

Paquete NuGet de PMA

Las nuevas bibliotecas de PppAwarness se pueden encontrar en el paquete NuGet Microsoft.VisualStudio.DpiAwareness .

Las siguientes herramientas pueden ayudar a depurar problemas relacionados con PMA en algunas de las diferentes pilas de interfaz de usuario compatibles con Visual Studio.

Snoop

Snoop es una herramienta de depuración XAML que tiene alguna funcionalidad adicional que las herramientas XAML integradas de Visual Studio no tienen. Además, Snoop no necesita depurar Visual Studio activamente para poder ver y ajustar su interfaz de usuario de WPF. Las dos formas principales de Snoop pueden ser útiles para diagnosticar problemas de PMA es validar coordenadas de ubicación lógica o límites de tamaño, y para validar la interfaz de usuario tiene el PPP correcto.

Herramientas XAML de Visual Studio

Al igual que Snoop, las herramientas XAML de Visual Studio pueden ayudar a diagnosticar problemas de PMA. Una vez encontrado un culpable probable, puede establecer puntos de interrupción y usar la ventana Árbol visual dinámico, así como las ventanas de depuración, para inspeccionar los límites de la interfaz de usuario y ppp actuales.

Estrategias para corregir problemas de PMA

Reemplazar llamadas de PppHelper

En la mayoría de los casos, la corrección de problemas de interfaz de usuario en el modo PMA se reduce a reemplazar las llamadas en código administrado a la antigua clase auxiliar Microsoft.VisualStudio.Utilities.DpiHelper y Microsoft.VisualStudio.PlatformUI.DpiHelper , con llamadas a la nueva clase auxiliar Microsoft.VisualStudio.Utilities.DpiAwareness .

// Remove this kind of use:
Point deviceTopLeft = new Point(window.Left, window.Top).LogicalToDeviceUnits();

// Replace with this use:
Point deviceTopLeft = window.LogicalToDevicePoint(new Point(window.Left, window.Top));

En el caso del código nativo, implicará reemplazar las llamadas a la clase VsUI::CDpiHelper anterior por llamadas a la nueva clase VsUI::CDpiAwareness.

// Remove this kind of use:
int cx = VsUI::DpiHelper::LogicalToDeviceUnitsX(m_cxS);
int cy = VsUI::DpiHelper::LogicalToDeviceUnitsY(m_cyS);

// Replace with this use:
int cx = m_cxS;
int cy = m_cyS;
VsUI::CDpiAwareness::LogicalToDeviceUnitsX(m_hwnd, &cx);
VsUI::CDpiAwareness::LogicalToDeviceUnitsY(m_hwnd, &cy);

Las nuevas clases DpiAwareness y CDpiAwareness ofrecen los mismos asistentes de conversión de unidades que las clases DpiHelper, pero requieren un parámetro de entrada adicional: el elemento de interfaz de usuario que se usará como referencia para la operación de conversión. Es importante tener en cuenta que los asistentes de escalado de imágenes no existen en los nuevos asistentes de PppAwareness/CDpiAwareness y, si es necesario, se debe usar ImageService en su lugar.

La clase PppAwareness administrada ofrece asistentes para objetos visuales de WPF, controles de Windows Forms y HWND de Win32 y HMONITOR (ambos en forma de IntPtrs), mientras que la clase CDpiAwareness nativa ofrece asistentes HWND y HMONITOR.

Cuadros de diálogo, ventanas o controles de Windows Forms que se muestran en pppAwarenessContext incorrectos

Incluso después de un correcto elemento primario de las ventanas con diferentes dpiAwarenessContexts (debido al comportamiento predeterminado de Windows), es posible que los usuarios sigan viendo problemas de escalado a medida que las ventanas con diferentes pppAwarenessContexts se escalan de forma diferente. Como resultado, los usuarios pueden ver problemas de alineación o texto borroso o imagen en la interfaz de usuario.

La solución consiste en establecer el ámbito dpiAwarenessContext correcto para todas las ventanas y controles de la aplicación.

Cuadros de diálogo de modo mixto de nivel superior (TLMM)

Al crear ventanas de nivel superior, como diálogos modales, es importante asegurarse de que el subproceso está en el estado correcto antes de crear la ventana (y su identificador). El subproceso se puede colocar en Reconocimiento del sistema mediante el asistente CDpiScope en el asistente de PppAwareness.EnterDpiScope en administrado. (Por lo general, TLMM debe usarse en cuadros de diálogo o windows que no sean WPF).

Modo mixto de nivel secundario (CLMM)

De forma predeterminada, las ventanas secundarias reciben el contexto de reconocimiento de PPP del subproceso actual si se crean sin un elemento primario o el contexto de reconocimiento de PPP del elemento primario cuando se crean con un elemento primario. Para crear un elemento secundario con un contexto de reconocimiento de PPP diferente al primario, el subproceso se puede colocar en el contexto de reconocimiento de PPP deseado. A continuación, el elemento secundario se puede crear sin un elemento primario y volver a conectarse manualmente a la ventana primaria.

Problemas de CLMM

La mayoría del trabajo de cálculo de la interfaz de usuario que se produce como parte del bucle de mensajería principal o la cadena de eventos ya debe estar ejecutándose en el contexto correcto de reconocimiento de PPP. Sin embargo, si los cálculos de coordenadas o ajuste de tamaño se realizan fuera de estos flujos de trabajo principales (por ejemplo, durante una tarea de tiempo de inactividad o fuera del subproceso de la interfaz de usuario, el contexto de reconocimiento de PPP actual podría ser incorrecto, lo que podría dar lugar a problemas de cambio de tamaño o errores de ajuste de tamaño de la interfaz de usuario. Colocar el subproceso en el estado correcto para el trabajo de la interfaz de usuario suele corregir el problema.

No participar en CLMM

Si se está migrando una ventana de herramientas que no sea WPF a un PMA totalmente compatible, tendrá que optar por no participar en CLMM. Para ello, es necesario implementar una nueva interfaz: IVsDpiAware.

[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IVsDpiAware
{
    [ComAliasName("Microsoft.VisualStudio.Shell.Interop.VSDPIMode")]
    uint Mode {get;}
}
IVsDpiAware : public IUnknown
{
    public:
        HRRESULT STDMETHODCALLTYPE get_Mode(__RCP__out VSDPIMODE *dwMode);
};

Para los lenguajes administrados, el mejor lugar para implementar esta interfaz es en la misma clase que deriva de Microsoft.VisualStudio.Shell.ToolWindowPane. Para C++, el mejor lugar para implementar esta interfaz es en la misma clase que implementa IVsWindowPane desde vsshell.h.

El valor devuelto por la propiedad Mode en la interfaz es un __VSDPIMODE (y se convierte en un uint en administrado):

enum __VSDPIMODE
{
    VSDM_Unaware    = 0x01,
    VSDM_System     = 0x02,
    VSDM_PerMonitor = 0x03,
}
  • Unware significa que la ventana de herramientas necesita controlar 96 PPP, Windows lo controlará escalando para todos los demás DPIs. Resulta en que el contenido es ligeramente borroso.
  • El sistema significa que la ventana de herramientas debe controlar el PPP de la pantalla principal. Cualquier pantalla con un PPP coincidente tendrá un aspecto nítido, pero si el PPP es diferente o cambia durante la sesión, Windows controlará el escalado y será ligeramente borroso.
  • PerMonitor significa que la ventana de herramientas debe controlar todos los DPIs en todas las pantallas y cada vez que cambia el PPP.

Nota:

Visual Studio solo admite el reconocimiento de PerMonitorV2, por lo que el valor de enumeración PerMonitor se traduce al valor de Windows de DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2.

Forzar un control a un valor de PppAwarenessContext específico

La interfaz de usuario heredada que no se está actualizando para admitir el modo PMA, puede que todavía necesite pequeños ajustes para funcionar mientras Visual Studio se ejecuta en modo PMA. Una de estas correcciones implica asegurarse de que la interfaz de usuario se está creando en el valor de PppAwarenessContext correcto. Para forzar la interfaz de usuario a un valor de PppAwarenessContext determinado, puede escribir un ámbito de PPP con el código siguiente:

using (DpiAwareness.EnterDpiScope(DpiAwarenessContext.SystemAware))
{
    Form form = new MyForm();
    form.ShowDialog();
}
void MyClass::ShowDialog()
{
    VsUI::CDpiScope dpiScope(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);
    HWND hwnd = ::CreateWindow(...);
}

Nota:

Forzar pppAwarenessContext solo funciona en cuadros de diálogo de WPF y WPF que no son de WPF. Al crear la interfaz de usuario de WPF que se va a hospedar dentro de ventanas de herramientas o diseñadores, tan pronto como el contenido se inserta en el árbol de interfaz de usuario de WPF, se convierte en el proceso actual DpiAwarenessContext.

Problemas conocidos

Windows Forms

Para optimizar los nuevos escenarios de modo mixto, Windows Forms cambió cómo crea controles y ventanas cada vez que su elemento primario no se estableció explícitamente. Anteriormente, los controles sin un elemento primario explícito usaban una "Ventana de estacionamiento" interna como elemento primario temporal para el control o ventana que se está creando.

Antes de .NET 4.8, había una única "Ventana de estacionamiento" que obtiene su DpiAwarenessContext del contexto de reconocimiento de PPP del subproceso actual en el momento de creación de la ventana. Cualquier control no primario hereda el mismo DpiAwarenessContext que la ventana de estacionamiento cuando se crea el identificador del control y sería reparente con el elemento primario final o esperado por el desarrollador de la aplicación. Esto provocaría errores basados en el tiempo si la "ventana de estacionamiento" tuviera un valor de PppAwarenessContext mayor que la ventana principal final.

A partir de .NET 4.8, ahora hay una "ventana de estacionamiento" para cada PppAwarenessContext que se ha encontrado. La otra diferencia importante es que pppAwarenessContext usado para el control se almacena en caché cuando se crea el control, no cuando se crea el identificador. Esto significa que el comportamiento final general es el mismo, pero puede convertir lo que solía ser un problema basado en tiempo en un problema coherente. También proporciona al desarrollador de aplicaciones un comportamiento más determinista para escribir su código de interfaz de usuario y determinar el ámbito correctamente.