Obsługa rozpoznawania monitora dla rozszerzników programu Visual Studio
Wersje wcześniejsze niż program Visual Studio 2019 miały kontekst rozpoznawania DPI ustawiony na rozpoznawanie systemu, a nie na monitor z obsługą DPI (PMA). Uruchomienie rozpoznawania systemu spowodowało obniżoną wydajność wizualizacji (np. rozmyte czcionki lub ikony) za każdym razem, gdy program Visual Studio musiał renderować różne monitory o różnych czynnikach skalowania lub zdalnie na maszynach z różnymi konfiguracjami wyświetlania (np. różnymi skalowaniem systemu Windows).
Kontekst rozpoznawania dpi programu Visual Studio 2019 jest ustawiany jako PMA, gdy wsparcie środowiska go, dzięki czemu program Visual Studio może być renderowany zgodnie z konfiguracją wyświetlacza, w którym jest hostowana, a nie z jedną konfiguracją zdefiniowaną przez system. Ostatecznie przekłada się na zawsze ostry interfejs użytkownika dla obszarów powierzchni, które obsługują tryb PMA.
Aby uzyskać więcej informacji na temat terminów i ogólnego scenariusza omówionego w tym dokumencie, zapoznaj się z dokumentacją dotyczącą tworzenia aplikacji klasycznych o wysokiej rozdzielczości w systemie Windows .
Szybki start
Upewnij się, że program Visual Studio jest uruchomiony w trybie PMA (zobacz Włączanie umowy PMA)
Sprawdzanie poprawności rozszerzenia działa poprawnie w zestawie typowych scenariuszy (zobacz Testowanie rozszerzeń pod kątem problemów z usługą PMA)
Jeśli znajdziesz problemy, możesz użyć strategii/zaleceń omówionych w tym dokumencie, aby zdiagnozować i rozwiązać te problemy. Musisz również dodać nowy pakiet NuGet Microsoft.VisualStudio.DpiAwareness do projektu, aby uzyskać dostęp do wymaganych interfejsów API.
Włączanie umowy PMA
Aby włączyć usługę PMA w programie Visual Studio, należy spełnić następujące wymagania:
- Windows 10 april 2018 Update (wersja 1803, RS4) lub nowszy
- .NET Framework 4.8 RTM lub nowszy
- Program Visual Studio 2019 z włączoną opcją "Optymalizowanie renderowania dla ekranów z różnymi gęstościami pikseli"
Po spełnieniu tych wymagań program Visual Studio automatycznie włącza tryb PMA w całym procesie.
Uwaga
Zawartość formularzy systemu Windows w programie Visual Studio (na przykład Przeglądarka właściwości) obsługuje usługę PMA tylko wtedy, gdy masz program Visual Studio 2019 w wersji 16.1 lub nowszej.
Testowanie rozszerzeń pod kątem problemów z usługą PMA
Program Visual Studio oficjalnie obsługuje struktury WPF, Windows Forms, Win32 i HTML/JS UI. Gdy program Visual Studio jest umieszczany w trybie PMA, każdy stos interfejsu użytkownika działa inaczej. W związku z tym, niezależnie od struktury interfejsu użytkownika, zaleca się wykonanie testu testu w celu zapewnienia zgodności wszystkich interfejsów użytkownika z trybem PMA.
Zaleca się zweryfikowanie następujących typowych scenariuszy:
Zmiana współczynnika skalowania pojedynczego środowiska monitora podczas działania aplikacji.
Ten scenariusz pomaga przetestować, czy interfejs użytkownika odpowiada na dynamiczną zmianę DPI systemu Windows.
Dokowanie/oddokowanie laptopa, w którym dołączony monitor jest ustawiony na podstawowy, a dołączony monitor ma inny współczynnik skalowania niż laptop, gdy aplikacja jest uruchomiona.
Ten scenariusz pomaga przetestować, czy interfejs użytkownika odpowiada na zmianę dpi wyświetlania, a także obsługa wyświetla dynamicznie dodawane lub usuwane.
Posiadanie wielu monitorów z różnymi czynnikami skalowania i przenoszenie aplikacji między nimi.
Ten scenariusz pomaga przetestować, czy interfejs użytkownika odpowiada na zmianę dpi wyświetlania
Komunikacja zdalna na maszynie, gdy maszyny lokalne i zdalne mają różne czynniki skalowania dla monitora podstawowego.
Ten scenariusz pomaga przetestować, czy interfejs użytkownika odpowiada na dynamiczną zmianę DPI systemu Windows.
Dobrym wstępnym testem, czy interfejs użytkownika może mieć problemy, jest to, czy kod korzysta z klas Microsoft.VisualStudio.Utilities.Dpi.DpiHelper, Microsoft.VisualStudio.PlatformUI.DpiHelper lub VsUI::CDpiHelper. Te stare klasy DpiHelper obsługują tylko rozpoznawanie dpi systemu i nie zawsze będą działać poprawnie, gdy proces jest PMA.
Typowe użycie tych elementów DpiHelpers będzie wyglądać następująco:
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);
W poprzednim przykładzie prostokąt reprezentujący granice logiczne okna jest konwertowany na jednostki urządzenia, aby można było przekazać go do natywnej metody MonitorFromPoint, która oczekuje współrzędnych urządzenia w celu zwrócenia dokładnego wskaźnika monitora.
Klasy problemów
Gdy tryb PMA jest włączony dla programu Visual Studio, interfejs użytkownika może replikować problemy na kilka typowych sposobów. Większość, jeśli nie wszystkie, z tych problemów może wystąpić w dowolnej z obsługiwanych struktur interfejsu użytkownika programu Visual Studio. Ponadto te problemy mogą wystąpić również, gdy część interfejsu użytkownika jest hostowana w scenariuszach skalowania DPI w trybie mieszanym (zapoznaj się z dokumentacją systemu Windows, aby dowiedzieć się więcej).
Tworzenie okna Win32
Podczas tworzenia okien za pomocą polecenia CreateWindow() lub CreateWindowEx() typowy wzorzec polega na utworzeniu okna na współrzędnych (0,0) (w lewym górnym rogu ekranu podstawowego), a następnie przeniesieniu go do końcowej pozycji. Może to jednak spowodować wyzwolenie przez okno komunikatu lub zdarzenia zmienionego dpi, co może spowodować ponowne pobranie innych komunikatów lub zdarzeń interfejsu użytkownika i ostatecznie prowadzić do niepożądanego zachowania lub renderowania.
Umieszczanie elementów WPF
Podczas przenoszenia elementów WPF przy użyciu starego elementu Microsoft.VisualStudio.Utilities.Dpi.DpiHelper współrzędne w lewym górnym rogu mogą nie być obliczane poprawnie, gdy elementy znajdują się w nie podstawowej dpi.
Serializacja rozmiarów lub pozycji elementów interfejsu użytkownika
Gdy rozmiar lub pozycja interfejsu użytkownika (jeśli zapisano jako jednostki urządzenia) zostanie przywrócona w innym kontekście DPI niż to, co było przechowywane w lokalizacji, zostanie ona umieszczona i niepoprawnie o rozmiarze. Dzieje się tak, ponieważ jednostki urządzeń mają nieodłączną relację DPI.
Niepoprawne skalowanie
Elementy interfejsu użytkownika utworzone w podstawowej rozdzielczości DPI będą jednak skalowane poprawnie, jednak po przeniesieniu do wyświetlacza z inną wartością DPI nie są skalowane ponownie, a ich zawartość jest zbyt duża lub zbyt mała.
Nieprawidłowa granica
Podobnie jak w przypadku problemu ze skalowaniem, elementy interfejsu użytkownika prawidłowo obliczają swoje granice w podstawowym kontekście DPI, jednak po przeniesieniu do innej niż podstawowa dpi nie będą prawidłowo obliczać nowych granic. W związku z tym okno zawartości jest zbyt małe lub zbyt duże w porównaniu z interfejsem użytkownika hostingu, co powoduje puste miejsce lub wycinki.
Przeciągnij i upuść
Za każdym razem, gdy wewnątrz scenariuszy DPI w trybie mieszanym (na przykład różne elementy interfejsu użytkownika renderowania w różnych trybach rozpoznawania DPI), współrzędne przeciągania i upuszczania mogą być błędnie obliczane, co powoduje, że końcowa pozycja upuszczania kończy się niepoprawnie.
Interfejs użytkownika poza procesem
Niektóre interfejsy użytkownika są tworzone poza procesem i jeśli proces tworzenia zewnętrznego jest w innym trybie rozpoznawania DPI niż Program Visual Studio, może to powodować dowolne z poprzednich problemów z renderowaniem.
Kontrolki, obrazy lub układy formularzy systemu Windows są niepoprawnie renderowane
Nie wszystkie treści formularzy systemu Windows obsługują tryb PMA. W związku z tym może wystąpić problem z renderowaniem nieprawidłowych układów lub skalowania. Możliwe rozwiązanie w tym przypadku polega na jawnym renderowaniu zawartości formularzy systemu Windows w funkcji DpiAwarenessContext (odwoływanie się do wymuszenia kontrolki w określonej wartości DpiAwarenessContext).
Kontrolki formularzy systemu Windows lub okna nie są wyświetlane
Jedną z głównych przyczyn tego problemu jest to, że deweloperzy próbują ponownie przejąć kontrolkę lub okno z jednym dpiAwarenessContext do okna z innym dpiAwarenessContext.
Na poniższych obrazach przedstawiono bieżące domyślne ograniczenia systemu operacyjnego Windows w oknach nadrzędnych:
Uwaga
To zachowanie można zmienić, ustawiając zachowanie hostingu wątków (zapoznaj się z Dpi_Hosting_Behavior wyliczeniem).
W związku z tym, jeśli ustawisz relację nadrzędny-podrzędny między nieobsługiwanym trybem, zakończy się niepowodzeniem, a kontrolka lub okno może nie być renderowane zgodnie z oczekiwaniami.
Diagnozowanie problemów
Podczas identyfikowania problemów związanych z usługą PMA należy wziąć pod uwagę wiele czynników:
Czy interfejs użytkownika lub interfejs API oczekuje wartości logicznych lub urządzeń?
- Interfejsy użytkownika i interfejsy API WPF zwykle używają wartości logicznych (ale nie zawsze)
- Interfejs użytkownika i interfejsy API win32 zwykle używają wartości urządzeń
Skąd pochodzą wartości?
- W przypadku odbierania wartości z innego interfejsu użytkownika lub interfejsu API jest przekazywaniem wartości urządzenia lub wartości logicznych.
- Jeśli otrzymujesz wartości z wielu źródeł, czy wszystkie używają/oczekują tego samego typu wartości, czy konwersje muszą być mieszane i dopasowane?
Czy stałe interfejsu użytkownika są używane i w jakim formularzu się znajdują?
Czy wątek jest prawidłowym kontekstem DPI dla odbieranych wartości?
Zmiany w celu włączenia hostingu mieszanego DPI powinny zazwyczaj umieszczać ścieżki kodu w odpowiednim kontekście, jednak praca wykonywana poza główną pętlą komunikatów lub przepływem zdarzeń może być wykonywana w niewłaściwym kontekście DPI.
Czy wartości przekraczają granice kontekstu DPI?
Przeciąganie i upuszczanie to typowa sytuacja, w której współrzędne mogą przekraczać konteksty DPI. Okno próbuje wykonać właściwą czynność, ale w niektórych przypadkach interfejs użytkownika hosta może wymagać wykonania konwersji w celu zapewnienia pasujących granic kontekstu.
Pakiet NuGet PMA
Nowe biblioteki DpiAwarness można znaleźć w pakiecie NuGet Microsoft.VisualStudio.DpiAwareness .
Zalecane narzędzia
Poniższe narzędzia mogą ułatwić debugowanie problemów związanych z usługą PMA w niektórych różnych stosach interfejsu użytkownika obsługiwanych przez program Visual Studio.
Snoop
Snoop to narzędzie debugowania XAML, które ma pewne dodatkowe funkcje, których wbudowane narzędzia XAML programu Visual Studio nie mają. Ponadto funkcja Snoop nie musi aktywnie debugować programu Visual Studio, aby móc wyświetlać i dostosowywać interfejs użytkownika WPF. Dwa główne sposoby, w jaki snoop może być przydatne do diagnozowania problemów z pmA polega na weryfikowaniu współrzędnych rozmieszczenia logicznego lub granic rozmiaru, a walidacja interfejsu użytkownika ma właściwą dpi.
Narzędzia XAML programu Visual Studio
Podobnie jak w przypadku narzędzia Snoop, narzędzia XAML w programie Visual Studio mogą pomóc w diagnozowaniu problemów z usługą PMA. Po znalezieniu prawdopodobnego winowajcy można ustawić punkty przerwania i użyć okna Dynamiczne drzewo wizualne, a także okna debugowania, aby sprawdzić ograniczenia interfejsu użytkownika i bieżące dpi.
Strategie rozwiązywania problemów z usługą PMA
Zamień wywołania dpiHelper
W większości przypadków rozwiązywanie problemów z interfejsem użytkownika w trybie PMA sprowadza się do zastąpienia wywołań w kodzie zarządzanym do starej klasy pomocnika Microsoft.VisualStudio.Utilities.Dpi.DpiHelper i Microsoft.VisualStudio.PlatformUI.DpiHelper z wywołaniami nowej klasy pomocnika 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));
W przypadku kodu natywnego spowoduje to zastąpienie wywołań starej klasy VsUI::CDpiHelper wywołaniami nowej klasy 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);
Nowe klasy DpiAwareness i CDpiAwareness oferują te same pomocniki konwersji jednostek co klasy DpiHelper, ale wymagają dodatkowego parametru wejściowego: element interfejsu użytkownika do użycia jako odwołanie do operacji konwersji. Należy pamiętać, że pomocnicy skalowania obrazów nie istnieją w nowych pomocnikach dpiAwareness/CDpiAwareness i w razie potrzeby należy użyć usługi ImageService .
Zarządzana klasa DpiAwareness oferuje pomocników dla wizualizacji WPF, kontrolek formularzy systemu Windows i Win32 HWNDs i HMONITORs (zarówno w postaci IntPtrs), podczas gdy natywna klasa CDpiAwareness oferuje pomocników HWND i HMONITOR.
Okna dialogowe, okna lub kontrolki formularzy systemu Windows wyświetlane w niewłaściwym pliku DpiAwarenessContext
Nawet po pomyślnym rodzicielstwie okien z różnymi dpiAwarenessContexts (z powodu domyślnego zachowania systemu Windows) użytkownicy mogą nadal widzieć problemy ze skalowaniem w miarę skalowania okien z różnymi dpiAwarenessContexts skalować inaczej. W związku z tym użytkownicy mogą zobaczyć problemy z wyrównaniem/rozmytym tekstem lub obrazem w interfejsie użytkownika.
Rozwiązaniem jest ustawienie prawidłowego zakresu DpiAwarenessContext dla wszystkich okien i kontrolek w aplikacji.
Okna dialogowe trybu mieszanego najwyższego poziomu (TLMM)
Podczas tworzenia okien najwyższego poziomu, takich jak modalne okna dialogowe, należy upewnić się, że wątek jest w poprawnym stanie przed utworzeniem okna (i jego uchwytu). Wątek można umieścić w rozpoznawaniu systemu przy użyciu pomocnika CDpiScope w macierzystym lub dpiAwareness.EnterDpiScope w zarządzanym. (PROGRAM TLMM powinien być zwykle używany w oknach dialogowych/windows innych niż WPF).
Tryb mieszany na poziomie podrzędnym (CLMM)
Domyślnie okna podrzędne odbierają kontekst rozpoznawania dpi bieżącego wątku w przypadku utworzenia bez elementu nadrzędnego lub kontekstu rozpoznawania dpi elementu nadrzędnego podczas tworzenia z elementem nadrzędnym. Aby utworzyć element podrzędny z innym kontekstem rozpoznawania dpi niż jego element nadrzędny, wątek można umieścić w odpowiednim kontekście rozpoznawania dpi. Następnie element podrzędny można utworzyć bez elementu nadrzędnego i ręcznie przenadzorować do okna nadrzędnego.
Problemy z programem CLMM
Większość zadań obliczeniowych interfejsu użytkownika wykonywanych w ramach głównej pętli obsługi komunikatów lub łańcucha zdarzeń powinna być już uruchomiona w odpowiednim kontekście rozpoznawania dpi. Jeśli jednak obliczenia współrzędnych lub ustalania rozmiaru są wykonywane poza głównymi przepływami pracy (takimi jak podczas zadania w czasie bezczynności lub poza wątkiem interfejsu użytkownika, bieżący kontekst rozpoznawania dpi może być niepoprawny, co może prowadzić do błędnego zastąpowania interfejsu użytkownika lub problemów z niewłaściwym ustalaniem rozmiaru. Umieszczenie wątku w prawidłowym stanie dla pracy interfejsu użytkownika zwykle rozwiązuje problem.
Rezygnacja z programu CLMM
Jeśli migrowane jest okno narzędzia innego niż WPF w celu pełnej obsługi umowy PMA, konieczne będzie rezygnacja z programu CLMM. W tym celu należy zaimplementować nowy interfejs: 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);
};
W przypadku języków zarządzanych najlepszym miejscem do zaimplementowania tego interfejsu jest ta sama klasa, która pochodzi z microsoft.VisualStudio.Shell.ToolWindowPane. W przypadku języka C++najlepszym miejscem do zaimplementowania tego interfejsu jest ta sama klasa, która implementuje interfejs IVsWindowPane z vsshell.h.
Wartość zwrócona przez właściwość Mode w interfejsie jest __VSDPIMODE (i rzutowana na uint w zarządzanym):
enum __VSDPIMODE
{
VSDM_Unaware = 0x01,
VSDM_System = 0x02,
VSDM_PerMonitor = 0x03,
}
- Nieświadome oznacza, że okno narzędzi musi obsługiwać 96 DPI, system Windows będzie obsługiwać skalowanie dla wszystkich innych interfejsów DPI. Wynikowa zawartość jest nieco rozmyta.
- System oznacza, że okno narzędzi musi obsługiwać dpi dla podstawowego wyświetlania DPI. Każdy ekran z pasującym dpi będzie wyglądał ostry, ale jeśli dpi jest inny lub zmienia się podczas sesji, system Windows obsłuży skalowanie i będzie nieco rozmyty.
- Narzędzie PerMonitor oznacza, że okno narzędzi musi obsługiwać wszystkie interfejsy DPI na wszystkich ekranach i za każdym razem, gdy zmienia się dpi.
Uwaga
Program Visual Studio obsługuje tylko świadomość PerMonitorV2, więc wartość wyliczenia PerMonitor przekłada się na wartość systemu Windows DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2.
Wymuś kontrolkę w określonym dpiAwarenessContext
Starszy interfejs użytkownika, który nie jest aktualizowany w celu obsługi trybu PMA, może nadal wymagać drobnych poprawek do działania, gdy program Visual Studio działa w trybie PMA. Jedna z takich poprawek polega na upewnieniu się, że interfejs użytkownika jest tworzony w odpowiednim dpiAwarenessContext. Aby wymusić przejście interfejsu użytkownika do określonej wartości DpiAwarenessContext, możesz wprowadzić zakres DPI z następującym kodem:
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(...);
}
Uwaga
Wymuszanie dpiAwarenessContext działa tylko w oknach dialogowych WPF innych niż WPF i WPF najwyższego poziomu. Podczas tworzenia interfejsu użytkownika WPF, który ma być hostowany wewnątrz okien narzędzi lub projektantów, gdy tylko zawartość zostanie wstawiona do drzewa interfejsu użytkownika WPF, zostanie przekonwertowana na bieżący proces DpiAwarenessContext.
Znane problemy
Windows Forms
Aby zoptymalizować nowe scenariusze trybu mieszanego, formularze systemu Windows zmieniły sposób tworzenia kontrolek i okien zawsze, gdy ich element nadrzędny nie został jawnie ustawiony. Wcześniej kontrolki bez jawnego elementu nadrzędnego używały wewnętrznego "okna parkingowego" jako tymczasowego elementu nadrzędnego do tworzonego formantu lub okna.
Przed platformą .NET 4.8 istniała jedna "okno parkingowe", które pobiera wartość DpiAwarenessContext z bieżącego kontekstu rozpoznawania dpi wątku w czasie tworzenia okna. Każda nieparzysta kontrolka dziedziczy tę samą kontrolkę DpiAwarenessContext co okno parkingowe po utworzeniu uchwytu kontrolki i zostanie ponownie zastąpiona ostatnim/oczekiwanym elementem nadrzędnym przez dewelopera aplikacji. Spowodowałoby to błędy oparte na chronometrażu, jeśli "Okno parkingowe" miało wyższy tekst DpiAwarenessContext niż końcowe okno nadrzędne.
Od platformy .NET 4.8 istnieje teraz "Okno parkingowe" dla każdego napotkanego elementu DpiAwarenessContext. Druga główna różnica polega na tym, że dpiAwarenessContext używany dla kontrolki jest buforowany podczas tworzenia kontrolki, a nie podczas tworzenia uchwytu. Oznacza to, że ogólne zachowanie końcowe jest takie samo, ale może przekształcić to, co kiedyś było problemem opartym na chronometrażu, w spójny problem. Zapewnia również deweloperowi aplikacji bardziej deterministyczne zachowanie podczas pisania kodu interfejsu użytkownika i prawidłowego określania zakresu.