Podpora sledování monitorování pro extendery sady Visual Studio
Verze před sadou Visual Studio 2019 měly kontext rozpoznávání DPI nastavený na systém, a ne na monitor DPI (PMA). Spuštění v systému vedlo ke snížení vizuálního prostředí (např. rozmazaných písem nebo ikon) vždy, když visual Studio muselo vykreslit monitory s různými faktory škálování nebo vzdáleně do počítačů s různými konfiguracemi zobrazení (např. různé škálování Windows).
Kontext rozpoznávání DPI sady Visual Studio 2019 je nastavený jako PMA, když ho podpora prostředí, což sadě Visual Studio umožňuje vykreslit se podle konfigurace zobrazení, kde je hostovaná, a ne podle jedné konfigurace definované systémem. Nakonec se přeloží do vždy ostrého uživatelského rozhraní pro plochy, které podporují režim PMA.
Rychlý start
Ujistěte se, že visual Studio běží v režimu PMA (viz Povolení PMA).
Ověřte, že vaše rozšíření funguje správně napříč sadou běžných scénářů (viz Testování rozšíření pro problémy PMA)
Pokud narazíte na problémy, můžete tyto problémy diagnostikovat a opravit pomocí strategií a doporučení probíraných v tomto dokumentu. Budete také muset do projektu přidat nový balíček NuGet Microsoft.VisualStudio.DpiAwareness , abyste měli přístup k požadovaným rozhraním API.
Povolení PMA
Pokud chcete v sadě Visual Studio povolit PMA, je potřeba splnit následující požadavky:
- Aktualizace Windows 10 z dubna 2018 (v1803, RS4) nebo novější
- .NET Framework 4.8 RTM nebo novější
- Visual Studio 2019 s povolenou možností Optimalizovat vykreslování obrazovek s různými hustotami pixelů
Jakmile jsou tyto požadavky splněné, Visual Studio automaticky povolí režim PMA v celém procesu.
Poznámka:
model Windows Forms obsah v sadě Visual Studio (například Prohlížeč vlastností) podporuje PMA pouze v případě, že máte Visual Studio 2019 verze 16.1 nebo novější.
Testování problémů s PMA rozšířeními
Visual Studio oficiálně podporuje architektury WPF, model Windows Forms, Win32 a HTML/JS UI. Při vložení sady Visual Studio do režimu PMA se každý zásobník uživatelského rozhraní chová odlišně. Proto bez ohledu na architekturu uživatelského rozhraní se doporučuje provést testovací průchod, aby se zajistilo, že je veškeré uživatelské rozhraní kompatibilní s režimem PMA.
Doporučujeme ověřit následující běžné scénáře:
Změna měřítka jednoho monitorového prostředí v době, kdy je aplikace spuštěná.
Tento scénář pomáhá otestovat, že uživatelské rozhraní reaguje na dynamickou změnu DPI systému Windows.
Ukotvení nebo uvolnění přenosného počítače, na kterém je připojený monitor nastavený na primární a připojený monitor má při spuštění aplikace jiný faktor škálování než přenosný počítač.
Tento scénář pomáhá otestovat, že uživatelské rozhraní reaguje na změnu DPI zobrazení a také dynamické přidání nebo odebrání displejů.
Několik monitorů s různými faktory škálování a přesouvání aplikace mezi nimi
Tento scénář pomáhá otestovat, že uživatelské rozhraní reaguje na změnu DPI zobrazení.
Vzdálená komunikace do počítače, pokud místní a vzdálené počítače mají různé faktory škálování primárního monitoru.
Tento scénář pomáhá otestovat, že uživatelské rozhraní reaguje na dynamickou změnu DPI systému Windows.
Dobrým předběžným testem, jestli uživatelské rozhraní může mít problémy, je to, jestli kód využívá třídy Microsoft.VisualStudio.Utilities.DpiHelper, Microsoft.VisualStudio.PlatformUI.DpiHelper nebo VsUI::CDpiHelper. Tyto staré třídy DpiHelper podporují pouze rozpoznávání DPI systému a nebudou vždy fungovat správně, když je proces PMA.
Typické použití těchto dpiHelpers bude vypadat takto:
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);
V předchozím příkladu se obdélník představující logické hranice okna převede na jednotky zařízení, aby bylo možné jej předat nativní metodě MonitorFromPoint, která očekává souřadnice zařízení, aby se vrátil přesný ukazatel monitorování.
Třídy problémů
Pokud je pro Visual Studio povolený režim PMA, může uživatelské rozhraní replikovat problémy několika běžnými způsoby. Většina, pokud ne všechny, k těmto problémům může dojít v některé z podporovaných architektur uživatelského rozhraní sady Visual Studio. K těmto problémům může dojít také v případě, že je část uživatelského rozhraní hostovaná ve scénářích škálování DPI v smíšeném režimu (další informace najdete v dokumentaci k Windows).
Vytvoření okna Win32
Při vytváření oken pomocí CreateWindow() nebo CreateWindowEx() je běžným vzorem vytvoření okna na souřadnicích (0,0) (v levém horním nebo levém rohu primárního displeje) a pak ho přesunout do konečné pozice. To však může způsobit, že okno aktivuje změněnou zprávu nebo událost DPI, která může opakovat jiné zprávy nebo události uživatelského rozhraní a nakonec vést k nežádoucímu chování nebo vykreslování.
Umístění elementu WPF
Při přesouvání elementů WPF pomocí starého souboru Microsoft.VisualStudio.Utilities.Dpi.DpiHelper nemusí být souřadnice vlevo nahoře správně vypočítány, kdykoli jsou prvky v neprimárním DPI.
Serializace velikostí nebo pozic prvků uživatelského rozhraní
Když se velikost nebo umístění uživatelského rozhraní (pokud se uloží jako jednotky zařízení) obnoví v jiném kontextu DPI, než na jakém byla uložena, umístí se a nastaví nesprávně. K tomu dochází, protože jednotky zařízení mají vlastní relaci DPI.
Nesprávné škálování
Prvky uživatelského rozhraní vytvořené na primárním DPI se ale správně škálují, ale při přesunutí na displej s jiným DPI se neškálí a jejich obsah je příliš velký nebo příliš malý.
Nesprávné ohraničování
Podobně jako u problému se škálováním se prvky uživatelského rozhraní správně vypočítají v primárním kontextu DPI, ale když se přesunou na neprimární DPI, nebudou správně počítat nové hranice. V takovém případě je okno obsahu v porovnání s hostitelským uživatelským rozhraním příliš malé nebo příliš velké, což vede k prázdnému prostoru nebo výřezu.
Přetažení
Kdykoli ve scénářích DPI ve smíšeném režimu (například různé prvky uživatelského rozhraní vykreslované v různých režimech rozpoznávání DPI) lze přepočítat souřadnice přetažení, což vede k nesprávné pozici přetažení.
Uživatelské rozhraní mimo proces
Některé uživatelské rozhraní se vytvoří mimo proces a pokud je vytváření externího procesu v jiném režimu rozpoznávání DPI než v sadě Visual Studio, může to představovat některý z předchozích problémů s vykreslováním.
model Windows Forms ovládací prvky, obrázky nebo rozložení se nesprávně vykreslují
Ne všechny model Windows Forms obsah podporují režim PMA. V důsledku toho se může zobrazit problém s vykreslováním s nesprávnými rozloženími nebo škálováním. V tomto případě je možné řešení explicitně vykreslit model Windows Forms obsah v dpiAwarenessContext s podporou systému (odkaz na vynucení ovládacího prvku do konkrétní dpiAwarenessContext).
model Windows Forms ovládací prvky nebo okna se nezobrazují
Jednou z hlavních příčin tohoto problému je, že se vývojáři snaží převést ovládací prvek nebo okno s jedním DpiAwarenessContext na okno s jiným DpiAwarenessContext.
Následující obrázky znázorňují aktuální výchozí omezení operačního systému Windows v nadřazených oknech:
Poznámka:
Toto chování můžete změnit nastavením chování hostování vláken (viz Dpi_Hosting_Behavior výčtu).
Pokud nastavíte vztah nadřazený-podřízený mezi nepodporovanými režimy, selže a ovládací prvek nebo okno se nemusí vykreslit podle očekávání.
Diagnostika problémů
Při identifikaci problémů souvisejících s PMA je potřeba vzít v úvahu mnoho faktorů:
Očekává uživatelské rozhraní nebo rozhraní API logické hodnoty nebo hodnoty zařízení?
- Uživatelské rozhraní WPF a rozhraní API obvykle používají logické hodnoty (ale ne vždy).
- Uživatelské rozhraní Win32 a rozhraní API obvykle používají hodnoty zařízení.
Odkud pocházejí hodnoty?
- Pokud přijímáte hodnoty z jiného uživatelského rozhraní nebo rozhraní API, předávají zařízení nebo logické hodnoty.
- Pokud přijímáte hodnoty z více zdrojů, používají/očekávají, že všechny používají nebo očekávají stejné typy hodnot nebo musí být převody smíšené a shodné?
Používají se konstanty uživatelského rozhraní a v jakém tvaru se nacházejí?
Je vlákno ve správném kontextu DPI pro hodnoty, které přijímá?
Změny povolení smíšeného hostování DPI by měly obecně umístit cesty kódu do správného kontextu, ale práce provedená mimo hlavní smyčku zpráv nebo tok událostí může proběhnout v nesprávném kontextu DPI.
Jsou hodnoty napříč hranicemi kontextu DPI?
Přetažení je běžná situace, kdy souřadnice můžou překračovat kontexty DPI. Okno se pokusí udělat správnou věc, ale v některých případech může uživatelské rozhraní hostitele potřebovat provést převod, aby se zajistily odpovídající hranice kontextu.
Balíček NuGet PMA
Nové knihovny DpiAwarness najdete v balíčku NuGet Microsoft.VisualStudio.DpiAwareness .
Doporučené nástroje
Následující nástroje můžou pomoct ladit problémy související s PMA v některých různých sadách uživatelského rozhraní podporovaných sadou Visual Studio.
Snoop
Snoop je nástroj pro ladění XAML, který má některé další funkce, které integrované nástroje VISUAL Studio XAML nemají. Kromě toho Snoop nemusí aktivně ladit Visual Studio, aby bylo možné zobrazit a upravit jeho uživatelské rozhraní WPF. Dva hlavní způsoby, jak může být Snoop užitečný pro diagnostiku problémů PMA, je ověřování souřadnic logického umístění nebo hranic velikosti a pro ověřování uživatelského rozhraní má správné DPI.
Visual Studio XAML Tools
Podobně jako Snoop můžou nástroje XAML v sadě Visual Studio pomoct diagnostikovat problémy PMA. Jakmile zjistíte pravděpodobného viníka, můžete nastavit zarážky a použít okno živého vizuálního stromu a ladicí okna ke kontrole hranic uživatelského rozhraní a aktuálního DPI.
Strategie pro řešení problémů PMA
Nahrazení volání DpiHelper
Ve většině případů se oprava problémů s uživatelským rozhraním v režimu PMA omezuje na nahrazení volání ve spravovaném kódu na starou třídu pomocníka Microsoft.VisualStudio.Utilities.Dpi.DpiHelper a Microsoft.VisualStudio.PlatformUI.DpiHelper s voláními nové pomocné třídy 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));
Pro nativní kód bude zahrnovat nahrazení volání staré třídy VsUI::CDpiHelper voláním nové třídy 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);
Nové třídy DpiAwareness a CDpiAwareness nabízejí stejné pomocné rutiny pro převod jednotek jako třídy DpiHelper, ale vyžadují další vstupní parametr: element UI, který se má použít jako referenci pro operaci převodu. Je důležité si uvědomit, že pomocné rutiny pro škálování obrázků neexistují v nových pomocných rutinách DpiAwareness/CDpiAwareness a v případě potřeby by se měla použít imageService .
Spravovaná třída DpiAwareness nabízí pomocné rutiny pro vizuály WPF, model Windows Forms Controls a Win32 HWND a HMONITORs (jak ve formě IntPtrs), zatímco nativní třída CDpiAwareness nabízí pomocné rutiny HWND a HMONITOR.
model Windows Forms dialogy, okna nebo ovládací prvky zobrazené v nesprávném dpiAwarenessContext
I po úspěšném nadřazení oken s různými dpiAwarenessContexts (kvůli výchozímu chování Systému Windows) se můžou uživatelům stále zobrazovat problémy se škálováním jako okna s různou škálou DpiAwarenessContexts odlišně. V důsledku toho se uživatelům může v uživatelském rozhraní zobrazit problémy se zarovnáním nebo rozmazaným textem nebo obrázkem.
Řešením je nastavit správný obor DpiAwarenessContext pro všechna okna a ovládací prvky v aplikaci.
Dialogy se smíšeným režimem nejvyšší úrovně (TLMM)
Při vytváření oken nejvyšší úrovně, jako jsou modální dialogy, je důležité před vytvořením okna (a jeho popisovačem) zajistit, aby vlákno bylo ve správném stavu. Vlákno lze umístit do povědomí o systému pomocí pomocné rutiny CDpiScope v nativním prostředí nebo Pomocník DpiAwareness.EnterDpiScope ve spravované. (NÁSTROJ TLMM by se měl obecně používat v dialogových oknech nebo oknech mimo WPF.)
Smíšený režim na úrovni podřízenosti (CLMM)
Podřízená okna ve výchozím nastavení obdrží kontext rozpoznávání DPI aktuálního vlákna, pokud je vytvořen bez nadřazeného objektu, nebo kontext rozpoznávání DPI nadřazeného objektu při vytváření s nadřazeným objektem. Chcete-li vytvořit dítě s jiným kontextem rozpoznávání DPI než jeho nadřazený kontext, lze vlákno umístit do požadovaného kontextu rozpoznávání DPI. Potom lze dítě vytvořit bez nadřazeného objektu a ručně znovu zobrazit nadřazené okno.
Problémy s CLMM
Většina výpočtů uživatelského rozhraní, ke kterým dochází v rámci hlavní smyčky zasílání zpráv nebo řetězu událostí, by už měla běžet ve správném kontextu rozpoznávání DPI. Pokud jsou však výpočty souřadnic nebo velikosti provedeny mimo tyto hlavní pracovní postupy (například během úlohy nečinnosti nebo mimo vlákno uživatelského rozhraní), může být aktuální kontext rozpoznávání DPI nesprávný, což může vést k problémům s chybou umístění uživatelského rozhraní nebo chybnou velikostí. Když umístíte vlákno do správného stavu pro práci s uživatelským rozhraním, obvykle problém vyřešíte.
Odhlášení z CLMM
Pokud se migruje okno nástroje mimo WPF, aby plně podporovalo PMA, bude potřeba se odhlásit z CLMM. K tomu je potřeba implementovat nové rozhraní: 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);
};
Pro spravované jazyky je nejlepším místem pro implementaci tohoto rozhraní stejná třída, která je odvozena od Microsoft.VisualStudio.Shell.ToolWindowPane. Nejlepší místo pro implementaci tohoto rozhraní v jazyce C++ je ve stejné třídě, která implementuje IVsWindowPane z vsshell.h.
Hodnota vrácená vlastností Mode v rozhraní je __VSDPIMODE (a přetypování na uint ve spravovaném objektu):
enum __VSDPIMODE
{
VSDM_Unaware = 0x01,
VSDM_System = 0x02,
VSDM_PerMonitor = 0x03,
}
- Nevěděli jsme, že okno nástroje potřebuje zpracovat 96 DPI, Systém Windows ho zpracuje pro všechny ostatní rozhraní DPI. Výsledkem je mírně rozmazaný obsah.
- Systém znamená, že okno nástroje musí zpracovat DPI primárního displeje DPI. Jakékoli zobrazení s odpovídajícím DPI bude vypadat ostře, ale pokud se dpi během relace liší nebo změní, Windows bude měřítko zpracovávat a bude mírně rozmazané.
- PerMonitor znamená, že okno nástroje musí zpracovávat všechny DPI na všech displejích a při každé změně DPI.
Poznámka:
Visual Studio podporuje pouze sledování PerMonitorV2, takže hodnota výčtu PerMonitor se překládá na hodnotu Windows DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2.
Vynucení ovládacího prvku do konkrétní hodnoty DpiAwarenessContext
Starší uživatelské rozhraní, které se neaktualizuje tak, aby podporovalo režim PMA, může stále vyžadovat menší úpravy, aby fungovalo, když visual Studio běží v režimu PMA. Jednou z těchto oprav je kontrola, že se uživatelské rozhraní vytváří ve správném DpiAwarenessContext. Pokud chcete uživatelské rozhraní vynutit do konkrétního DpiAwarenessContext, můžete zadat obor DPI s následujícím kódem:
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(...);
}
Poznámka:
Vynucení dpiAwarenessContext funguje jenom v dialogových oknech WPF a WPF nejvyšší úrovně. Při vytváření uživatelského rozhraní WPF, které se má hostovat uvnitř oken nástrojů nebo návrhářů, jakmile se obsah vloží do stromu uživatelského rozhraní WPF, převede se na aktuální proces DpiAwarenessContext.
Známé problémy
Windows Forms
Pokud chcete optimalizovat nové scénáře ve smíšeném režimu, model Windows Forms změnil způsob vytváření ovládacích prvků a oken vždy, když jejich nadřazený objekt explicitně nenastavil. Dříve ovládací prvky bez explicitního nadřazeného objektu používaly jako dočasné nadřazené okno ovládacího prvku nebo okna, které se vytváří.
Před rozhraním .NET 4.8 došlo k jedinému "parkovacímu okně", které získá dpiAwarenessContext z aktuálního kontextu rozpoznávání DPI vlákna v době vytváření okna. Každý neparentovaný ovládací prvek dědí stejný DpiAwarenessContext jako parkovací okno při vytvoření popisovače ovládacího prvku a bude znovu vrácen k konečnému nebo očekávanému nadřazeného objektu vývojářem aplikace. To by způsobilo selhání na základě časování, pokud okno parkování mělo vyšší DpiAwarenessContext než konečné nadřazené okno.
Od verze .NET 4.8 je nyní k dispozici okno s parkováním pro každou zjištěnou hodnotu DpiAwarenessContext. Dalším hlavním rozdílem je, že DpiAwarenessContext použitý pro ovládací prvek je uložen v mezipaměti při vytvoření ovládacího prvku, ne při vytvoření popisovače. To znamená, že celkové koncové chování je stejné, ale může změnit, co se použilo k problému založenému na načasování, na konzistentní problém. Poskytuje také vývojáři aplikací deterministické chování při psaní kódu uživatelského rozhraní a správnému určení rozsahu.