Verarbeiten hoher DPI-Werte und der DPI-Skalierung in Ihrer Office-Lösung

Viele Computer und Anzeigekonfigurationen unterstützen jetzt hohe DPI-Auflösungen (Punkte pro Zoll) und können mehrere Monitore mit unterschiedlichen Größen und Pixeldichten verbinden. Hierfür müssen Anwendungen angepasst werden, wenn der Benutzer die App auf einen Monitor mit einer anderen DPI-Auflösung verschiebt oder die Zoomebene ändert. Anwendungen, die keine DPI-Skalierung unterstützen, werden auf Monitoren mit niedriger DPI-Auflösung möglicherweise korrekt angezeigt, sehen auf einem Monitor mit hoher DPI-Auflösung jedoch vielleicht verzerrt oder unscharf aus.

Office 2016-Anwendungen, z. B. Word und Excel, wurden so aktualisiert, dass sie auf Änderungen des Skalierungsfaktors reagieren können. Ihre Office-Lösung muss jedoch auch auf Änderungen reagieren, damit sie korrekt dargestellt wird, wenn sich der DPI-Wert ändert. Dieser Artikel beschreibt, wie Office dynamische DPI-Werte unterstützt, und welche Schritte Sie unternehmen können, um die beste Anzeige für Ihre Office-Erweiterbarkeitslösung zur Verarbeitung der DPI-Skalierung sicherzustellen.

DPI-Skalierungssymptome in Ihrer Lösung

Windows wendet die DPI-Skalierung an, wenn eine Anwendung von einem Display zu einem anderen Display mit einem anderen DPI-Wert verschoben wird. Dies geschieht in Szenarien, wenn zum Beispiel eine Anwendung auf einen anderen Monitor gezogen wird oder Ihr Laptop angedockt wird. Wenn Ihre Office-Lösung von der DPI-Skalierung beeinträchtigt wird, werden Sie eines oder mehrere der folgenden Symptome feststellen:

  • Die Fenster werden an der falschen Position dargestellt oder haben eine falsche Größe.
  • Elemente wie Schaltflächen und Beschriftungen werden an der falschen Stelle im Fenster der Lösung angezeigt.
  • Schriftarten und Bilder werden zu klein, zu groß oder an der falschen Stelle angezeigt.

Die folgenden Typen von Office-Lösungen können betroffen von der DPI-Skalierung beeinträchtigt werden:

  • VSTO-Add-Ins
  • Benutzerdefinierte Aufgabenbereiche
  • COM-Add-Ins
  • ActiveX-Steuerelemente
  • Menübanderweiterungen
  • OLE-Server
  • Office-Web-Add-Ins

Modi für den DPI-Grad in Windows

In diesem Artikel werden wir uns mit den verschiedenen Modi für den DPI-Grad befassen, die Windows unterstützt. Jeder Modus für den DPI-Grad unterstützt unterschiedliche Funktionen, wie in der folgenden Tabelle beschrieben. Dies ist eine vereinfachte Beschreibung der Modi, um zu veranschaulichen, wie Office-Lösungen diese unterstützen. Weitere Informationen zu den Modi für den DPI-Grad finden Sie unter Entwicklung von Desktopanwendungen mit hohen DPI-Werten unter Windows.

Modus Beschreibung Wenn der DPI-Wert geändert wird
Nicht DPI-fähig Die Anwendung wird immer so gerendert, als befände sie sich auf einem Bildschirm mit einem DPI-Wert von 96. Die Anwendung wird auf primären und sekundären Monitoren Bitmap-gestreckt.
Kompatibel mit systemspezifischen DPI-Werten Die Anwendung ermittelt den DPI-Wert des primären angeschlossenen Monitors bei der Windows-Anmeldung, kann aber nicht auf DPI-Änderungen reagieren. Weitere Informationen finden Sie im Abschnitt Konfigurieren von Windows zum Beheben unscharfer Apps in diesem Artikel. Die Anwendung wird Bitmap-gestreckt, wenn sie auf eine neue Anzeige mit einem anderen DPI-Wert verschoben wird.
Mit monitorspezifischen DPI-Werten kompatibel Die Anwendung kann sich selbst ordnungsgemäß erneut darstellen, wenn sich der DPI-Wert ändert. Windows sendet DPI-Benachrichtigungen an die Fenster auf der obersten Ebene in der Anwendung, damit diese erneut dargestellt werden kann, wenn sich der DPI-Wert ändert.
Pro Monitor v2 Die Anwendung kann sich selbst ordnungsgemäß erneut darstellen, wenn sich der DPI-Wert ändert. Windows sendet DPI-Benachrichtigungen an die Fenster auf der obersten Ebene und auch an die untergeordneten Fenster, damit die Anwendung erneut dargestellt werden kann, wenn sich der DPI-Wert ändert.

So unterstützt Office die DPI-Skalierung

Der wichtigste Faktor bei der Ermittlung, wie die Office-Lösung die DPI-Skalierung verarbeiten kann, besteht darin, ob es sich bei der Lösung um ein Fenster auf oberster Ebene oder um ein untergeordnetes Fenster handelt. Die folgende Abbildung zeigt einige Beispiele für Office-Lösungen, die als Fenster auf oberster Ebene oder untergeordnete Fenster ausgeführt werden, und welche Modi für den DPI-Grad diese im Windows April 2018 Update (1803) oder höher verwenden.

Excel, das ein ActiveX-Steuerelement und einen benutzerdefinierten Aufgabenbereich als untergeordnete Fenster hosten. Ein separates VSTO/COM-Add-In wird als Fenster der obersten Ebene ausgeführt. Office ist ein Fenster der obersten Ebene.

In dieser Abbildung gilt Folgendes:

  • Das COM-/VSTO-Fenster der obersten Ebene ist „Mit monitorspezifischen DPI-Werten kompatibel“.
  • Das untergeordnete Fenster des ActiveX-Steuerelements ist „Kompatibel mit systemspezifischen DPI-Werten“.
  • Das Office-Fenster der obersten Ebene ist „Mit monitorspezifischen DPI-Werten kompatibel“.
  • Das untergeordnete Fenster des benutzerdefinierten Aufgabenbereichs ist „Kompatibel mit systemspezifischen DPI-Werten“.

Verwalten des Thread-DPI-Kontexts

Wenn die Host-Office-App gestartet wird, wird ihr Hauptthread im Kontext „Mit monitorspezifischen DPI-Werten kompatibel“ ausgeführt. Wenn Ihr Lösungscode Threads erstellt oder Aufrufe von Office empfängt, müssen Sie den Thread-DPI-Kontext verwalten.

Erstellung von neuen Threads mit dem korrekten DPI-Kontext

Wenn Ihre Lösung zusätzliche Threads erstellt, erzwingt Office für die Threads den Kontext „Mit monitorspezifischen DPI-Werten kompatibel“. Wenn Ihr Code einen anderen Kontext erwartet, müssen Sie die SetThreadDpiAwarenessContext-Funktion verwenden, um den erwarteten Thread-DPI-Grad festzulegen.

Erstellen eines Kontextblocks für eingehende Threadanrufe

Diagramm, in dem der Kontextblock in der Office-App angezeigt wird, wobei der Thread in den Kontext „Kompatibel mit systemspezifischen DPI-Werten“ für Aufrufe des Fensters auf oberster Ebene geschaltet wird.

Ihre Lösung interagiert mit ihrer Office-Host-App, es gibt also eingehende Anrufe für Ihre Lösung von Office, z. B. Ereignisrückrufe. Wenn Office Ihre Lösung aufruft, weist es einen Kontextblock auf, der für den Threadkontext den Kontext „Kompatibel mit systemspezifischen DPI-Werten“ erzwingt. Sie müssen den Threadkontext so ändern, dass er dem DPI-Grad Ihres Fensters entspricht. Sie können einen ähnlichen Kontextblock implementieren, um den Threadkontext für eingehende Anrufe umzuschalten. Verwenden Sie die SetThreadDpiAwarenessContext-Funktion, um den Kontext an den Fensterkontext anzupassen.

Hinweis

Der Kontextblock sollte den ursprünglichen DPI-Threadkontext wiederherstellen, bevor andere Komponenten außerhalb Ihres Lösungscodes aufgerufen werden.

Kontextblock von verwaltetem Code

Im folgenden Beispielcode wird gezeigt, wie Sie Ihren eigenen Kontext Block erstellen.

public struct DPI_AWARENESS_CONTEXT
        {
            private IntPtr value;

            private DPI_AWARENESS_CONTEXT(IntPtr value)
            {
                this.value = value;
            }

            public static implicit operator DPI_AWARENESS_CONTEXT(IntPtr value)
            {
                return new DPI_AWARENESS_CONTEXT(value);
            }

            public static implicit operator IntPtr(DPI_AWARENESS_CONTEXT context)
            {
                return context.value;
            }

            public static bool operator ==(IntPtr context1, DPI_AWARENESS_CONTEXT context2)
            {
                return AreDpiAwarenessContextsEqual(context1, context2);
            }

            public static bool operator !=(IntPtr context1, DPI_AWARENESS_CONTEXT context2)
            {
                return !AreDpiAwarenessContextsEqual(context1, context2);
            }

            public override bool Equals(object obj)
            {
                return base.Equals(obj);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }

        private static DPI_AWARENESS_CONTEXT DPI_AWARENESS_CONTEXT_HANDLE = IntPtr.Zero;

        public static readonly DPI_AWARENESS_CONTEXT DPI_AWARENESS_CONTEXT_INVALID = IntPtr.Zero;
        public static readonly DPI_AWARENESS_CONTEXT DPI_AWARENESS_CONTEXT_UNAWARE = new IntPtr(-1);
        public static readonly DPI_AWARENESS_CONTEXT DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = new IntPtr(-2);
        public static readonly DPI_AWARENESS_CONTEXT DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = new IntPtr(-3);
        public static readonly DPI_AWARENESS_CONTEXT DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = new IntPtr(-4);

        public static DPI_AWARENESS_CONTEXT[] DpiAwarenessContexts =
        {
            DPI_AWARENESS_CONTEXT_UNAWARE,
            DPI_AWARENESS_CONTEXT_SYSTEM_AWARE,
            DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE,
            DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
        };

class DPIContextBlock : IDisposable
    {
        private DPI_AWARENESS_CONTEXT resetContext;
        private bool disposed = false;

        public DPIContextBlock(DPI_AWARENESS_CONTEXT contextSwitchTo)
        {
            resetContext = SetThreadDpiAwarenessContext(contextSwitchTo);
         }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    SetThreadDpiAwarenessContext(resetContext);
                }
            }
            disposed = true;
        }
    }

Kontextblock von systemeigenem Code

#include <winuser.h>
/* DpiAwarenessContextBlock can be used to simplify setting and resetting the DPI_AWARENESS_CONTEXT of
the current thread.  When the object is constructed, the DPI_AWARENESS_CONTEXT is set, and when the object is
destructed, the DPI awareness context is reverted to the previous awareness context at construct time.

This object allows us to write code such as:

// Thread state is currently DPI_AWARENESS_SYSTEM_AWARE
if (condition)
{
DpiAwarenessContextBlock perMonitorAware(DPI_AWARENESS_PER_MONITOR_AWARE);
... // Create a top-level hwnd with the current thread state, DPI_AWARENESS_PER_MONITOR_AWARE
}
// Thread state automatically returns to DPI_AWARENESS_SYSTEM_AWARE

*/
class DpiAwarenessContextBlock
{
public:
      DpiAwarenessContextBlock(DPI_AWARENESS_CONTEXT dpiContext) noexcept;
      ~DpiAwarenessContextBlock();

      // Copy and move are not to be used with these context objects
      DpiAwarenessContextBlock(const DpiAwarenessContextBlock&) = delete;
      DpiAwarenessContextBlock(DpiAwarenessContextBlock&&) = delete;

private:
      DPI_AWARENESS_CONTEXT m_contextReversalType;
      bool m_doContextSwitch;
};

inline DpiAwarenessContextBlock::DpiAwarenessContextBlock(DPI_AWARENESS_CONTEXT dpiContext) noexcept
{
      m_contextReversalType = SetThreadDpiAwarenessContext(dpiContext);
}

inline DpiAwarenessContextBlock::~DpiAwarenessContextBlock()
{
      SetThreadDpiAwarenessContext(m_contextReversalType);
}

Verwaltung des Fensters auf oberster Ebene

Beim Starten von Office-Anwendungen wird ein Aufruf von SetThreadDpiAwarenessContext als DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE vorgenommen. In diesem Kontext werden DPI-Änderungen an den HWND eines beliebigen Fensters auf oberster Ebene gesendet, das als „Mit monitorspezifischen DPI-Werten kompatibel“ ausgeführt wird. Fenster der obersten Ebene sind das Office-Anwendungsfenster sowie alle zusätzlichen Fenster auf oberster Ebene, die von Ihrer Lösung erstellt werden. Wenn eine Office-Anwendung zu einer neuen Anzeige verschoben wird, wird diese benachrichtigt, damit sie sich dynamisch skalieren und korrekt in der DPI der neuen Anzeige darstellen kann. Die Office-Lösung kann Fenster der obersten Ebene erstellen, die sich in einem beliebigen Modus für den DPI-Grad befinden. Die Fenster auf oberster Ebene können auch auf DPI-Änderungen reagieren, indem sie Windows-Nachrichten auf die Änderungen abhören.

Wenn Sie untergeordnete Fenster erstellen, die Ihrem Fenster auf oberster Ebene übergeordnet sind, können Sie diese auch auf einen beliebigen Modus für den DPI-Grad festlegen. Wenn Sie jedoch den Modus „Mit monitorspezifischen DPI-Werten kompatibel“ verwenden, erhalten die untergeordneten Fenster keine DPI-Änderungsbenachrichtigungen. Weitere Informationen zu Modi für den DPI-Grad in Windows finden Sie unter Entwicklung von Desktopanwendungen mit hohen DPI-Werten unter Windows.

Verwaltung von untergeordneten Fenstern

Wenn Sie mit ActiveX-Steuerelementen und benutzerdefinierten Aufgabenbereichen arbeiten, erstellt Office das untergeordnete Fenster für Ihre Lösung. Sie können weitere untergeordnete Fenster erstellen, Sie müssen aber den DPI-Grad des übergeordneten Fensters beachten. Office wird im Modus „Mit monitorspezifischen DPI-Werten kompatibel“ ausgeführt, was bedeutet, dass alle untergeordneten Elemente in Ihrer Lösung keine DPI-Änderungsbenachrichtigungen erhalten. Nur der Modus „Pro Monitor v2“ unterstützt das Senden von DPI-Änderungen an untergeordnete Fenster (Office unterstützt „Pro Monitor v2“ nicht). Für ActiveX-Steuerelemente gibt es jedoch eine Lösung. Weitere Informationen hierzu finden Sie im Abschnitt ActiveX-Steuerelemente weiter unten in diesem Artikel.

Hinweis

Wenn das untergeordnete Fenster ein Fenster auf oberster Ebene erstellt, können Sie einen beliebigen Modus für den DPI-Grad für das neue Fenster auf oberster Ebene verwenden. Weitere Informationen zum Verwalten von Fenstern der obersten Ebene finden Sie im Abschnitt Verwaltung des Fensters auf oberster Ebene in diesem Artikel.

Sie werden sehen, dass zwei unterschiedliche DPI-Modi auf das untergeordnete Fenster angewendet werden, je nachdem, welche Version von Windows 10 Office ausgeführt wird.

Office-DPI-Verhalten im Windows Fall Creators Update (1709)

Da Office-Apps den Modus „Mit monitorspezifischen DPI-Werten kompatibel“ verwenden, werden die untergeordneten Fenster Ihrer Lösung ebenfalls im Modus „Mit monitorspezifischen DPI-Werten kompatibel“ erstellt. Dies bedeutet, dass Windows davon ausgeht, dass Ihre Lösung beim Zeichnen in einer neuen DPI-Auflösung aktualisiert wird. Da Ihr Fenster keine DPI-Änderungsbenachrichtigungen erhalten kann, ist die Benutzeroberfläche Ihrer Lösung möglicherweise nicht korrekt.

Diagramm, in dem untergeordnete Fenster, die im Kontext „Mit monitorspezifischen DPI-Werten kompatibel“ ausgeführt werden, im Windows Fall Creators Update (1709) gezeigt werden.

Office-DPI-Verhalten im Windows April 2018 Update (1803)

Beim Windows April 2018 (1803) Update und höher verwendet das Office-DPI-Hostingverhalten für einige Szenarien eine DPI-Skalierung im gemischten Modus. Auf diese Weise können Fenster im Modus „Kompatibel mit systemspezifischen DPI-Werten“ Office-Fenstern übergeordnet werden, die auf den Modus „Mit monitorspezifischen DPI-Werten kompatibel“ festgelegt sind. So kann eine verbesserte Kompatibilität sichergestellt werden, wenn sich der DPI-Wert ändert, wenn die Fenster Bitmap-gestreckt werden. Die Fenster können nach der Bitmap-Streckung noch unscharf sein.

Diagramm, in dem untergeordnete Fenster, die im Kontext „Kompatibel mit systemspezifischen DPI-Werten“ ausgeführt werden, im Windows April 2018 Update (1803) gezeigt werden.

Wenn Sie neue untergeordnete Fenster erstellen, stellen Sie sicher, dass diese dem DPI-Grad ihres übergeordneten Fensters entsprechen. Sie können die GetWindowDpiAwarenessContext-Funktion verwenden, um die DPI-Wahrnehmung des übergeordneten Fensters abzurufen. Weitere Informationen zur Konsistenz des DPI-Grads finden Sie im Abschnitt „Erzwungenes Zurücksetzen des prozessweiten DPI-Grads unter Entwicklung von Desktopanwendungen mit hohen DPI-Werten unter Windows.

Hinweis

Sie können sich nicht auf den Prozess-DPI-Grad verlassen, da dieser möglicherweise PROCESS_SYSTEM_DPI_AWARE zurückgibt, auch dann, wenn der Kontext des DPI-Grads des Anwendungshauptthreads DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE lautet. Verwenden Sie die GetThreadDpiAwarenessContext-Funktion, um den Kontext des Thread-DPI-Grads zu erhalten.

Einstellungen für Office- und Windows-DPI-Kompatibilität

Wenn Benutzer auf Add-Ins oder Lösungen stoßen, die nicht korrekt gerendert werden, können einige Kompatibilitätseinstellungen das Problem lösen.

Konfigurieren von Office zur Optimierung für Kompatibilität

Office bietet eine Einstellung zur Optimierung für Kompatibilität beim Verschieben in unterschiedliche DPI-Skalierungen auf unterschiedlichen Bildschirmen. Der Kompatibilitätsmodus deaktiviert die DPI-Skalierung, sodass alles in Office beim Verschieben in eine Anzeige mit unterschiedlicher DPI-Skalierung Bitmap-gestreckt wird.

Der Kompatibilitätsmodus erzwingt, dass Office im Modus „Kompatibel mit systemspezifischen DPI-Werten“ ausgeführt wird. Dies bewirkt, dass Anwendungsfenster Bitmap-gestreckt werden, sodass diese möglicherweise unscharf aussehen. Die Office-Lösung kann diese Einstellung nicht steuern, da sie vom Benutzer ausgewählt wird. Durch Verwenden des Anzeigekompatibilitätsmodus können die meisten Darstellungsprobleme gelöst werden. Weitere Informationen finden Sie unter Office-Support für HD-Anzeigen.

Konfigurieren von Windows zur Behebung unscharfer Apps

Windows 10 (Version 1803) und höher umfasst eine Einstellung zur Korrektur von Apps derart, dass sie nicht unscharf sind. Dies ist eine weitere Einstellung, die Sie versuchen können, wenn die Lösung nicht korrekt gerendert wird. Die Office-Lösung kann diese Einstellung nicht steuern, da sie vom Benutzer ausgewählt wird. Weitere Informationen finden Sie unter Korrigieren von Apps, die in Windows 10 unscharf angezeigt werden.

Unterstützung der DPI-Skalierung in Ihrer Lösung

Einige Lösungen können DPI-Änderungen empfangen und darauf reagieren. Einige bietet eine Problemumgehung, wenn sie keine Benachrichtigungen empfangen können. In der folgenden Tabelle sind die Details für jeden Lösungstyp aufgeführt.

Lösungstyp Fenstertyp Kann auf die DPI-Skalierung reagieren Weitere Details
VSTO-Add-Ins Oberstes Element und Nachfolgerelemente Ja Siehe Hilfestellung zu VSTO-Add-Ins.
Untergeordnetes Element für Office-Fenster Nein Siehe Konfigurieren von Office zur Optimierung für Kompatibilität.
Benutzerdefinierter Aufgabenbereich Oberstes Element und Nachfolgerelemente Ja Siehe Hilfestellung für Fenster auf oberster Ebene.
Untergeordnetes Element für Office-Fenster Nein Siehe Konfigurieren von Office zur Optimierung für Kompatibilität.
COM-Add-In Oberstes Element und Nachfolgerelemente Ja Siehe Hilfestellung zu COM-Add-Ins.
Untergeordnetes Element für Office-Fenster Nein Siehe Konfigurieren von Office zur Optimierung für Kompatibilität.
ActiveX-Steuerelement Oberstes Element und Nachfolgerelemente Ja Siehe Hilfestellung zu ActiveX-Steuerelement.
Untergeordnetes Element für Office-Fenster Ja
Web-Add-In Ja Siehe Hilfestellung zu Office-Web-Add-In.
Menübanderweiterung Siehe Hilfestellung zur Menübanderweiterung.
OLE-Server oder -Client Siehe Hilfestellung zur OLE-Server/-Client.

VSTO-Add-In

Wenn Ihr VSTO-Add-In untergeordnete Fenster erstellt, die beliebigen Office-Fenstern übergeordnet sind, stellen Sie sicher, dass diese dem DPI-Grad des übergeordneten Fensters entsprechen. Sie können die GetWindowdpiAwarenessContext-Funktion verwenden, um den DPI-Grad des übergeordneten Fensters abzurufen. Ihre untergeordneten Fenstern empfangen keine DPI-Änderungsbenachrichtigungen. Wenn Ihre Lösung nicht richtig gerendert wird, müssen Benutzer für Office den Kompatibilitätsmodus festlegen.

Für alle Fenster der obersten Ebene, die Ihr VSTO-Add-In erstellt, können Sie einen beliebigen Modus für den DPI-Grad festlegen. Im folgenden Beispielcode wird gezeigt, wie der gewünschte DPI-Grad eingerichtet und wie auf DPI-Änderungen reagiert wird. Außerdem müssen Sie „app.config“, wie im Artikel Unterstützung hoher DPI-Werte in Windows Forms beschrieben, anpassen.

using System;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace SharedModule
{
    // DpiAwareWindowsForm
    // For any top level winform you create, derive from the DpiWindowsForm class
    // if you are creating Windows Forms with the Dpi Awareness Context set to 
    // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE or DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
    //
    // For example, if you Window form class is defined as:
    //    public partial class TopLevelWinForm : Form
    //
    // update to:
    //    public partial class TopLevelWinForm : DpiAwareWindowsForm
    //
    // When showing the form, call SetThreadDpiAwarenessContext() or use a context block to
    // to set the desired Dpi Awareness Context.
    //
    // For example, here is code to show a Windows Form using a context block as Per Monitor Aware v2.
    //
    //    DPIContextBlock context = new DPIContextBlock(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
    //    TopLevelWinForm frm = new TopLevelWinForm();
    //    frm.Show();
    //
    public partial class DpiAwareWindowsForm : Form
    {
        private SizeF m_newDpi = SizeF.Empty;
        private SizeF m_oldDpi = SizeF.Empty;

        public DpiAwareWindowsForm()
        {
            this.HandleCreated += new EventHandler((sender, args) =>
            {
                m_oldDpi = m_newDpi = DPIHelper.GetDpiForWindowSizeF(this.Handle);
            });
        }

        public void OnDpiChangedEvent(RECT newRect)
        {
            this.SuspendLayout();

            // Resize form
            this.Width = newRect.Width;
            this.Height = newRect.Height;

            // Resize controls and set font sizes
            ScaleAllChildControls(this.Controls, m_oldDpi.Width, m_newDpi.Width);
            this.ResumeLayout(true);
        }

        // Additional changes may be needed for controls that set Anchor or Dock properties 
        private void ScaleAllChildControls(Control.ControlCollection controls, float oldDpi, float newDpi)
        {
            float scaleFactorChange = newDpi / oldDpi;

            foreach (Control control in controls)
            {
                control.Top = (int)(control.Top * scaleFactorChange);
                control.Left = (int)(control.Left * scaleFactorChange);
                control.Width = (int)(control.Width * scaleFactorChange);
                control.Height = (int)(control.Height * scaleFactorChange);
                control.Font = ScaleFont(control.Font, oldDpi, newDpi);
            }
        }

        private Font ScaleFont(Font font, float oldDpi, float newDpi)
        {
            float fontSizePx = 0.0f;
            float fontSizePt = 0.0f;

            fontSizePx = font.SizeInPoints / 72 * oldDpi;
            fontSizePt = fontSizePx * (newDpi / oldDpi) * 72 / oldDpi;

            return new Font(font.Name, fontSizePt, font.Style, GraphicsUnit.Point);
        }

        protected override void WndProc(ref Message m)
        {
            switch ((DPIHelper.WinMessages)m.Msg)
            {
                case DPIHelper.WinMessages.WM_DPICHANGED:
                    // Marshal the value in the lParam into a Rect.
                    RECT newDisplayRect = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));

                    // Remember current DPI and calculate current from WParam.
                    // Both X and Y are the same on Windows for Dpi.
                    m_oldDpi = m_newDpi;

                    m_newDpi.Width = (float)(m.WParam.ToInt32() >> 16);
                    m_newDpi.Height = (float)(m.WParam.ToInt32() & 0x0000FFFF);

                    // DPI should be the same for both width and height on Windows devices.
                    Debug.Assert(m_newDpi.Height == m_newDpi.Width);

                    if (m_oldDpi.Width != m_newDpi.Width)
                    {
                        OnDpiChangedEvent(newDisplayRect);
                    }
                    base.DefWndProc(ref m);
                    break;
                default:
                    base.WndProc(ref m);
                    break;
            }
        }
    }
}

Benutzerdefinierte Aufgabenbereiche

Ein benutzerdefinierter Aufgabenbereich wird als untergeordnetes Fenster von Office erstellt. Wenn dieser im Windows Fall Creators Update (1709) ausgeführt wird, wird der benutzerdefinierte Aufgabenbereich mit demselben Modus für den DPI-Grad wie Office ausgeführt. Wenn dieser im Windows April 2018 Update (1803) und höher ausgeführt wird, wird der benutzerdefinierte Aufgabenbereich mit demselben Modus für den DPI-Grad wie das System ausgeführt.

Da benutzerdefinierte Aufgabenbereiche untergeordnete Fenster sind, können sie keine DPI-Benachrichtigungen erhalten. Wenn sie nicht ordnungsgemäß dargestellt werden, muss der Benutzer den Office-DPI-Kompatibilitätsmodus verwenden. Wenn der benutzerdefinierte Aufgabenbereich Fenster der obersten Ebene erstellt, können diese Fenster in einem beliebigen Modus für den DPI-Grad ausgeführt werden und DPI-Änderungsbenachrichtigungen empfangen. Weitere Informationen finden Sie im Abschnitt Verwaltung des Fensters auf oberster Ebene in diesem Artikel.

COM-Add-Ins

COM-Add-Ins, die Fenster auf oberster Ebene erstellen, können DPI-Benachrichtigungen erhalten. Sie sollten einen Kontextblock erstellen, um den Thread auf den für das Fenster gewünschten DPI-Grad festzulegen; erstellen Sie dann das Fenster. Das korrekte Verarbeiten der DPI-Benachrichtigungen ist nicht ganz einfach, lesen Sie deshalb unbedingt die Informationen unter Entwicklung von Desktopanwendungen mit hohen DPI-Werten unter Windows, um weitere Informationen zu erhalten.

Die WM_DPICHANGED-Meldung wird gesendet, wenn sich der DPI-Wert für ein Fenster geändert hat. Diese Meldung wird in nicht verwaltetem Code von der Windows-Prozedur für den HWND verarbeitet. Ein Beispiel für Handlercode für eine DPI-Änderung finden Sie im Artikel WM_DPICHANGED.

COM-Add-Ins, die untergeordnete Fenster anzeigen, die einem Fenster in Office übergeordnet sind, können keine DPI-Benachrichtigungen empfangen. Wenn sie nicht ordnungsgemäß dargestellt werden, muss der Benutzer den Office-DPI-Kompatibilitätsmodus verwenden.

ActiveX-Steuerelemente

Wie die DPI-Skalierung in ActiveX-Steuerelementen unterstützt wird, hängt davon ab, ob das Steuerelement Fenster aufweist oder nicht.

ActiveX-Steuerelemente mit Fenstern

ActiveX-Steuerelemente mit Fenstern erhalten jedes Mal, wenn die Größe des Steuerelements geändert wird, eine WM_SIZE-Meldung. Wenn dieses Ereignis ausgelöst wird, kann der Ereignishandlercode die GetDpiForWindow-Funktion mit dem HWND des Steuerelements aufrufen, um den DPI-Wert abzurufen und die Unterschiede des Skalierungsfaktors zu berechnen und diese bei Bedarf anzupassen.

Im folgenden Beispiel wird ein MFC-basiertes ActiveX-Steuerelement so aktiviert, dass es auf das OnSize-Ereignis reagieren kann.

void ChangeWindowFontDPI(HWND hWnd, UINT dpi) 
{ 
LOGFONT fontInfo1 = { 0 }; 
// Calculate the font height based on the DPI. 
fontInfo1.lfHeight = -MulDiv(DESIRED_HEIGHT, dpi, 72); 
fontInfo1.lfQuality = CLEARTYPE_QUALITY; 
wcscpy_s(fontInfo1.lfFaceName, DESIRED_FONT_NAME); 
 
::SendMessage(hWnd, WM_SETFONT, (WPARAM)::CreateFontIndirectW(&fontInfo1), TRUE); 
} 
 
BOOL CALLBACK CMainDialog::EnumChildProc(HWND hWnd, LPARAM lParam) 
{ 
CMainDialog* _this = (CMainDialog*) lParam; 
if (_this != nullptr) 
{ 
// Calculate the scale factor difference between the old and new DPI changes. 
double scale = (((double) _this->m_newDPI) /  
   (((double) _this->m_currentDPI) / 100.0)) / 100; 
 
RECT rect = {}; 
::GetWindowRect(hWnd, &rect); 
 
POINT pt = { rect.left, rect.top }; 
::ScreenToClient(::GetParent(hWnd), &pt); 
 
// Adjust the window based on the scale changes. 
::MoveWindow(hWnd, 
pt.x * scale, 
pt.y * scale, 
(rect.right - rect.left) * scale, 
(rect.bottom - rect.top) * scale, 
TRUE); 
 
ChangeWindowFontDPI(hWnd, _this->m_newDPI); 
return TRUE; 
} 
return FALSE; 
} 
 
void CMainDialog::OnSize(UINT nType, int cx, int cy) 
{ 
CDialog::OnSize(nType, cx, cy); 
 
// Get the new DPI and enumerate the child windows that will use that value. 
m_currentDPI = ::GetDpiForWindow(this->GetSafeHwnd()); 
::EnumChildWindows(this->GetSafeHwnd(), EnumChildProc, (LPARAM)this); 
} 

ActiveX-Steuerelemente in Windows

ActiveX-Steuerelemente in Windows müssen nicht unbedingt einen HWND aufweisen. Wenn ein ActiveX-Steuerelement in einen Zeichenbereich eingefügt wird, liegt es im Entwurfsmodus vor. In Office-Anwendungen gibt der Hostingcontainer 0 für den Aufruf von hDC-GetWindow>() im Ereignis ::OnDraw zurück, wenn sich das Steuerelement im Entwurfsmodus befindet. In diesem Fall kann kein zuverlässiger DPI-Wert abgerufen werden.

Wenn sich das Steuerelement aber im Laufzeitmodus befindet, gibt Office den HWND an der Stelle zurück, an der das Steuerelement gezeichnet werden soll. In diesem Fall kann der Entwickler des Steuerelements GetDpiForWindow aufrufen und den aktuellen DPI-Wert sowie die Skalierungsschriftarten, Steuerelemente usw. abrufen.

Menübanderweiterbarkeit

Alle Rückrufe von Office für benutzerdefinierte Menübandsteuerelemente weisen den DPI-Thread-Grad „Kompatibel mit systemspezifischen DPI-Werten“ auf. Wenn Ihre Lösung einen anderen DPI-Thread-Grad erwartet, sollten Sie einen Kontextblock implementieren, um den Thread-Grad wie erwartet festzulegen. Weitere Informationen finden Sie unter Erstellen eines Kontextblocks.

OLE-Clients und -Server

Wenn ein OLE-Server in einem OLE-Clientcontainer gehostet wird, können Sie derzeit keine aktuellen oder unterstützten DPI-Informationen bereitstellen. Dies kann Probleme verursachen, da einige Kombinationen von gemischten Modi mit übergeordneten und untergeordneten Fenstern von der aktuellen Windows-Architektur nicht unterstützt werden.

Vor Office 365 Version 2109 für Excel und PowerPoint und Version 2203 für Word

Wenn Excel, PowerPoint oder Word erkennen, dass mehrere Monitore mit unterschiedlichen DPI-Skalierungen vorhanden sind, wird die direkte Aktivierung nicht unterstützt. Der OLE-Server wird indirekt aktiviert. Wenn Probleme mit OLE-Serverinteraktionen auftreten, muss der Benutzer den Office-DPI-Kompatibilitätsmodus verwenden.

Nach Office 365 Version 2109 für Excel und PowerPoint und Version 2203 für Word

Ab Version 2109 für Excel und PowerPoint und Version 2203 für Word ermöglichen alle drei Anwendungen die direkte Aktivierung im Pro Monitor DPI-fähigen Modus, wenn bestimmte Bedingungen erfüllt sind. Wenn die Direkte Aktivierung als OLE-Server erfolgt, überprüfen Office-Anwendungen die DPI_AWARENESS_CONTEXT des übergeordneten Fensters, das vom Container bereitgestellt wird, und ermöglichen die direkte Aktivierung, je nachdem, welcher der folgenden Office DPI-Anzeigemodi vom Benutzer ausgewählt wird:

  • "Optimieren für optimale Darstellung" ermöglicht die direkte Aktivierung, wenn das vom Container bereitgestellte Fenster DPI-fähig pro Monitor (DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE oder DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) ist.
  • "Kompatibilität optimieren" ermöglicht die direkte Aktivierung, wenn das vom Container bereitgestellte Fenster System DPI-fähig (DPI_AWARENESS_CONTEXT_SYSTEM_AWARE) ist und sowohl die Containeranwendung als auch die Office-Anwendung denselben System-DPI-Wert aufweisen.

Diese Überprüfung der Kompatibilität des DPI-Kontexts bedeutet, dass OLE-Container mit dpi unaware nicht in der Lage sind, eine der Apps vor Ort zu aktivieren. Die Problemumgehung besteht darin, die DPI-Wahrnehmung der Container-App in System DPI-fähig zu ändern (und die Einstellung "Kompatibilität optimieren" in der Office-App zu verwenden). Dies kann über SetProcessDpiAwareness, eingebettetes oder externes Manifest erfolgen. Das externe Manifest ist besonders nützlich für vorhandene VB Forms-Lösungen, da die Lösung nicht neu kompiliert werden muss.

Wenn Sie als OLE-Container fungieren, werden sowohl Excel als auch PowerPoint auf den Server zurückversetzt, um die Kompatibilität der DPI-Sensibilisierung zu überprüfen, und sie verhindern die direkte Aktivierung nicht.

Office-Web-Add-Ins

Office-Add-Ins, die mit der JavaScript-API für Office erstellt wurden, werden innerhalb eines Browsersteuerelements ausgeführt. Sie können die DPI-Skalierung mit denselben Techniken wie beim Entwurf anderer Web-Apps verarbeiten. Es gibt viele Onlineressourcen, die beim Entwerfen einer Webseite für Bildschirme mit hohen Auflösungen behilflich sind.

Überprüfen, dass Ihre Lösung die DPI-Skalierung unterstützt

Nachdem Sie Ihre Anwendung so aktualisiert haben, dass sie die DPI-Skalierung unterstützt, sollten Sie Ihre Änderungen in einer gemischten DPI-Umgebung überprüfen. Überprüfen Sie, ob der Benutzeroberflächencode ordnungsgemäß auf DPI Änderungen reagiert, wenn die Fenster Ihrer Lösung von einem Bildschirm auf einen anderen verschoben werden, der andere DPI-Werte aufweist. Weitere Informationen zu Techniken für das Testen der DPI-Skalierung finden Sie unter Entwicklung von Desktopanwendungen mit hohen DPI-Werten unter Windows.

Vielleicht finden Sie auch diese zusätzlichen Methoden hilfreich:

  • Bei einem Laptop können Sie den primären Monitor auf einen externen Monitor festlegen und das Docking des Laptops dann aufheben. Dadurch wird erzwungen, dass der primäre Monitor in die Laptopanzeige wechselt.
  • Verwenden Sie das Open-Source-Tool WinSpy++ zum Debuggen. Sie können das Tool verwenden, um die Einstellung des DPI-Grads eines beliebigen Fensters anzuzeigen.
  • Sie können Remotedesktop verwenden, um mehrere Monitore auf einem Remotecomputer zu testen, indem Sie auf der Registerkarte „Anzeige“ die Option „Alle Monitore für Remotesitzung verwenden“ verwenden, wie im folgenden Screenshot dargestellt.

App „Remotedesktopverbindung“, in der die Registerkarte „Anzeige“ und die Auswahl der Option „Alle Monitore für Remotesitzung verwenden“ gezeigt wird.

Siehe auch

Artikel

Codebeispiele