Visual Studio genişleticileri için monitör başına farkındalık desteği

Visual Studio 2019'dan önceki sürümlerde, DPI tanıma bağlamı monitör başına DPI algılama (PMA) yerine sistem algılamalı olarak ayarlanmıştı. Sistem farkındalığında çalışmak, Visual Studio'yu farklı ölçek faktörlerine sahip monitörler arasında işlemek veya farklı görüntü yapılandırmalarına sahip makinelere (farklı Windows ölçeklendirmesi gibi) uzak bir şekilde işlemek zorunda kaldığı her durumda görsel deneyimin (bulanık yazı tipleri veya simgeler gibi) düşmesine neden oldu.

Visual Studio 2019'un DPI farkındalık bağlamı PMA olarak ayarlanır ve Çevre desteği, Visual Studio'nun tek bir sistem tanımlı yapılandırma yerine barındırıldığı ekranın yapılandırmasına göre işlenmesine olanak tanır. Sonuçta PMA modunu destekleyen yüzey alanları için her zaman net bir kullanıcı arabirimine çevriliyor.

Bu belgede ele alınan koşullar ve genel senaryo hakkında daha fazla bilgi için Windows üzerinde Yüksek DPI Masaüstü Uygulaması Geliştirme belgelerine bakın.

Hızlı başlangıç

  • Visual Studio'nın PMA modunda çalıştığından emin olun (Bkz. PMA'yı Etkinleştirme)

  • Uzantınızın yaygın senaryolar kümesinde düzgün çalıştığını doğrulayın (Bkz . PMA sorunları için uzantılarınızı test etme)

  • Sorun bulursanız, bu sorunları tanılamak ve düzeltmek için bu belgede açıklanan stratejileri/önerileri kullanabilirsiniz. Gerekli API'lere erişmek için projenize yeni Microsoft.VisualStudio.DpiAwareness NuGet paketini de eklemeniz gerekir.

PMA'yı etkinleştirme

Visual Studio'da PMA'yı etkinleştirmek için aşağıdaki gereksinimlerin karşılanması gerekir:

Bu gereksinimler karşılandığında Visual Studio işlem boyunca PMA modunu otomatik olarak etkinleştirir.

Not

Visual Studio'daki Windows Forms içeriği (örneğin Özellik Tarayıcısı), PMA'yı yalnızca Visual Studio 2019 sürüm 16.1 veya sonraki bir sürüme sahip olduğunuzda destekler.

Uzantılarınızı PMA sorunları için test edin

Visual Studio WPF, Windows Forms, Win32 ve HTML/JS UI çerçevelerini resmi olarak destekler. Visual Studio PMA moduna geçirildiğinde, her ui yığını farklı davranır. Bu nedenle, UI çerçevesi ne olursa olsun, tüm kullanıcı arabiriminin PMA moduyla uyumlu olduğundan emin olmak için bir test geçişinin gerçekleştirilmesi önerilir.

Aşağıdaki yaygın senaryoları doğrulamanız önerilir:

  • Uygulama çalışırken tek bir monitör ortamının ölçek faktörünü değiştirme.

    Bu senaryo, kullanıcı arabiriminin dinamik Windows DPI değişikliğine yanıt verdiğini test etmeye yardımcı olur.

  • Bağlı monitörün birincil monitöre ayarlandığı ve bağlı monitörün uygulama çalışırken dizüstü bilgisayardan farklı bir ölçek faktörüne sahip olduğu bir dizüstü bilgisayarı yerleştirme/çıkarma.

    Bu senaryo, kullanıcı arabiriminin görüntü DPI değişikliğine yanıt verdiğinin yanı sıra dinamik olarak eklenen veya kaldırılan ekranları işlemenin testine yardımcı olur.

  • Farklı ölçek faktörlerine sahip birden çok monitöre sahip olmak ve uygulamayı bunlar arasında taşımak.

    Bu senaryo, kullanıcı arabiriminin görünen DPI değişikliğine yanıt verdiğini test etmeye yardımcı olur

  • Yerel ve uzak makineler birincil monitör için farklı ölçek faktörlerine sahip olduğunda makineye uzaktan erişim.

    Bu senaryo, kullanıcı arabiriminin dinamik Windows DPI değişikliğine yanıt verdiğini test etmeye yardımcı olur.

Kullanıcı arabiriminizde sorun olup olmadığına yönelik iyi bir ön test, kodun Microsoft.VisualStudio.Utilities.Dpi.DpiHelper, Microsoft.VisualStudio.PlatformUI.DpiHelper veya VsUI::CDpiHelper sınıflarını kullanıp kullanmadığıdır. Bu eski DpiHelper sınıfları yalnızca Sistem DPI farkındalığını destekler ve işlem PMA olduğunda her zaman düzgün çalışmaz.

Bu DpiHelper'ların tipik kullanımı şöyle görünür:

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

Önceki örnekte, bir pencerenin mantıksal sınırlarını temsil eden bir dikdörtgen cihaz birimlerine dönüştürülür, böylece doğru bir izleyici işaretçisini geri döndürmek için cihaz koordinatlarını bekleyen yerel MonitorFromPoint yöntemine geçirilebilir.

Sorun sınıfları

Visual Studio için PMA modu etkinleştirildiğinde, kullanıcı arabirimi sorunları çeşitli yaygın yollarla çoğaltabilir. Bu sorunların tümü değilse çoğu Visual Studio'nun desteklenen kullanıcı arabirimi çerçevelerinden herhangi birinde oluşabilir. Ayrıca, bir kullanıcı arabirimi parçası karma mod DPI ölçeklendirme senaryolarında barındırıldığında da bu sorunlar oluşabilir (daha fazla bilgi edinmek için Windows belgelerine bakın).

Win32 penceresi oluşturma

CreateWindow() veya CreateWindowEx() ile pencere oluştururken yaygın bir desen, pencereyi koordinatlarda (0,0) (birincil ekranın üst/sol köşesinde) oluşturmak ve ardından son konumuna taşımaktır. Ancak bunu yapmak pencerenin DPI değiştirilmiş bir iletiyi veya olayını tetiklemesine neden olabilir. Bu da diğer kullanıcı arabirimi iletilerini veya olaylarını yeniden denenebilir ve sonunda istenmeyen davranış veya işlemeye yol açabilir.

WPF öğesi yerleştirme

Eski Microsoft.VisualStudio.Utilities.Dpi.DpiHelper kullanarak WPF öğelerini taşırken, öğeler birincil olmayan bir DPI üzerinde olduğunda sol üst koordinatlar doğru hesaplanmayabilir.

Ui öğesi boyutlarının veya konumlarının seri hale getirilmesi

Kullanıcı arabirimi boyutu veya konumu (cihaz birimleri olarak kaydedildiyse) depolandığı konumdan farklı bir DPI bağlamında geri yüklendiğinde, yanlış konumlandırılır ve boyutlandırılır. Bunun nedeni cihaz birimlerinin doğal bir DPI ilişkisi olmasıdır.

Yanlış ölçeklendirme

Birincil DPI üzerinde oluşturulan kullanıcı arabirimi öğeleri doğru ölçeklendirilir, ancak farklı bir DPI'ye sahip bir ekrana taşındığında yeniden ölçeklendirilemez ve içerikleri çok büyük veya çok küçük olur.

Yanlış sınırlayıcı

Ölçeklendirme sorununa benzer şekilde, kullanıcı arabirimi öğeleri kendi sınırlarını birincil DPI bağlamlarında doğru hesaplar, ancak birincil olmayan bir DPI'ye taşındığında yeni sınırları doğru hesaplamaz. Bu nedenle, içerik penceresi barındırma kullanıcı arabirimine kıyasla çok küçük veya çok büyük olduğundan boş alan veya kırpmayla sonuçlanır.

Sürükle ve bırak

Karma mod DPI senaryolarında (örneğin, farklı DPI tanıma modlarında işlenen farklı kullanıcı arabirimi öğeleri) her zaman sürükle ve bırak koordinatları yanlış hesaplanabilir ve son bırakma konumu yanlış sonuçlanır.

İşlem dışı kullanıcı arabirimi

Bazı kullanıcı arabirimi işlem dışı oluşturulur ve dış işlem oluşturma işlemi Visual Studio'dan farklı bir DPI tanıma modundaysa, bu işlem önceki işleme sorunlarından herhangi birini ortaya çıkarabilir.

Windows Forms denetimleri, görüntüleri veya düzenleri yanlış işlendi

Windows Forms içeriğinin tümü PMA modunu desteklemez. Sonuç olarak, yanlış düzenler veya ölçeklendirmeyle ilgili işleme sorunu görebilirsiniz. Bu durumda olası bir çözüm, Windows Forms içeriğini "Sistem Farkında" DpiAwarenessContext içinde açıkça işlemektir (bkz . Denetimi belirli bir DpiAwarenessContext'e zorlama).

Windows Forms denetimleri veya pencereleri görüntülenmiyor

Bu sorunun temel nedenlerinden biri, bir DpiAwarenessContext içeren bir denetimi veya pencereyi farklı bir DpiAwarenessContext içeren bir pencereye yeniden ayrıştırmaya çalışan geliştiricilerdir.

Aşağıdaki görüntüler, üst öğe pencerelerindeki geçerli varsayılan Windows işletim sistemi kısıtlamalarını gösterir:

A screenshot of the correct parenting behavior

Not

İş Parçacığı Barındırma Davranışını ayarlayarak bu davranışı değiştirebilirsiniz (Dpi_Hosting_Behavior numaralandırmasına bakın).

Sonuç olarak, desteklenmeyen modlar arasında üst-alt ilişki ayarlarsanız, bu başarısız olur ve denetim veya pencere beklendiği gibi işlenmeyebilir.

Sorunları tanılama

PMA ile ilgili sorunları tanımlarken dikkate alınması gereken birçok faktör vardır:

  • Kullanıcı arabirimi veya API mantıksal veya cihaz değerleri bekliyor mu?

    • WPF kullanıcı arabirimi ve API'leri genellikle mantıksal değerler kullanır (ancak her zaman kullanmaz)
    • Win32 kullanıcı arabirimi ve API'leri genellikle cihaz değerlerini kullanır
  • Değerler nereden geliyor?

    • Başka bir kullanıcı arabiriminden veya API'den değer alıyorsanız, cihaz veya mantıksal değerleri geçirmesi gerekir.
    • Birden çok kaynaktan değer alıyorsanız, bunların tümü aynı tür değerleri mi kullanıyor/bekliyor yoksa dönüştürmelerin karıştırılıp eşleştirilmesi gerekiyor mu?
  • Kullanıcı arabirimi sabitleri kullanılıyor mu ve hangi formdalar?

  • İş parçacığı, aldığı değerler için doğru DPI bağlamında mı?

    Karma DPI barındırmayı etkinleştirme değişiklikleri genellikle kod yollarını doğru bağlama yerleştirmelidir, ancak ana ileti döngüsü veya olay akışı dışında yapılan işler yanlış DPI bağlamında yürütülebilir.

  • Değerler DPI bağlam sınırlarını aşıyor mu?

    Sürükle ve bırak, koordinatların DPI bağlamlarını geçebildiği yaygın bir durumdur. Pencere doğru şeyi yapmaya çalışır, ancak bazı durumlarda, ana bilgisayar kullanıcı arabiriminin eşleşen bağlam sınırlarını sağlamak için dönüştürme çalışması yapması gerekebilir.

PMA NuGet paketi

Yeni DpiAwarness kitaplıkları Microsoft.VisualStudio.DpiAwareness NuGet paketinde bulunabilir.

Aşağıdaki araçlar, Visual Studio tarafından desteklenen bazı farklı ui yığınlarında PMA ile ilgili sorunların hatalarını ayıklamaya yardımcı olabilir.

Snoop

Snoop, yerleşik Visual Studio XAML araçlarının sahip olmadığı bazı ek işlevlere sahip bir XAML hata ayıklama aracıdır. Ek olarak, Wpf kullanıcı arabirimini görüntüleyebilmek ve değiştirebilmek için Snoop'un Visual Studio'da etkin bir şekilde hata ayıklaması gerekmez. Snoop'un PMA sorunlarını tanılamak için yararlı olabileceği iki ana yol, mantıksal yerleştirme koordinatlarını veya boyut sınırlarını doğrulamak ve kullanıcı arabiriminin doğru DPI'ye sahip olduğunu doğrulamaktır.

Visual Studio XAML araçları

Snoop gibi Visual Studio'daki XAML araçları da PMA sorunlarını tanılamaya yardımcı olabilir. Olası bir suçlu bulunduktan sonra kesme noktaları ayarlayabilir ve ui sınırlarını ve geçerli DPI'yi incelemek için Canlı Görsel Ağaç penceresini ve hata ayıklama pencerelerini kullanabilirsiniz.

PMA sorunlarını düzeltme stratejileri

DpiHelper çağrılarını değiştirme

Çoğu durumda, PMA modunda kullanıcı arabirimi sorunlarının düzeltilmesi, yönetilen koddaki çağrıları eski Microsoft.VisualStudio.Utilities.Dpi.DpiHelper ve Microsoft.VisualStudio.PlatformUI.DpiHelper sınıflarına yeni Microsoft.VisualStudio.Utilities.DpiAwareness yardımcı sınıfına yapılan çağrılarla değiştirmek için kullanılır.

// 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));

Yerel kod için, eski VsUI::CDpiHelper sınıfına yapılan çağrıların yeni VsUI::CDpiAwareness sınıfına yapılan çağrılarla değiştirilmesi gerekir.

// 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);

Yeni DpiAwareness ve CDpiAwareness sınıfları, DpiHelper sınıfları ile aynı birim dönüştürme yardımcılarını sunar, ancak ek bir giriş parametresi gerektirir: dönüştürme işlemi için başvuru olarak kullanılacak UI öğesi. Görüntü ölçeklendirme yardımcılarının yeni DpiAwareness/CDpiAwareness yardımcılarında bulunmadığını ve gerekirse ImageService'in kullanılması gerektiğini unutmayın.

Yönetilen DpiAwareness sınıfı WPF Görselleri, Windows Forms Denetimleri ve Win32 HWND'leri ve HMONITOR'lar (her ikisi de IntPtrs biçiminde) için yardımcılar sunarken, yerel CDpiAwareness sınıfı HWND ve HMONITOR yardımcıları sunar.

Yanlış DpiAwarenessContext içinde görüntülenen Windows Forms iletişim kutuları, pencereler veya denetimler

Farklı DpiAwarenessContexts'e sahip pencerelerin başarılı bir üst öğesinden sonra bile (Windows varsayılan davranışı nedeniyle), kullanıcılar farklı DpiAwarenessContexts'e sahip pencereler farklı ölçeklendirildikçe ölçeklendirme sorunlarını görmeye devam edebilir. Sonuç olarak, kullanıcılar kullanıcı arabiriminde hizalama/bulanık metin veya görüntü sorunları görebilir.

Çözüm, uygulamadaki tüm pencereler ve denetimler için doğru DpiAwarenessContext kapsamını ayarlamaktır.

Üst düzey karma mod (TLMM) iletişim kutuları

Kalıcı iletişim kutuları gibi üst düzey pencereler oluştururken, iş parçacığının pencere (ve tutamacı) oluşturulmadan önce doğru durumda olduğundan emin olmak önemlidir. İş parçacığı, yerel olarak CDpiScope yardımcısını veya yönetilen DpiAwareness.EnterDpiScope yardımcısını kullanarak Sistem tanımaya yerleştirilebilir. (TLMM genellikle WPF olmayan iletişim kutuları/windows üzerinde kullanılmalıdır.)

Alt düzey karma mod (CLMM)

Varsayılan olarak, alt pencereler üst öğe olmadan oluşturulursa geçerli iş parçacığı DPI tanıma bağlamını veya bir üst öğeyle oluşturulduğunda ebeveynin DPI tanıma bağlamını alır. Üst öğesinden farklı bir DPI tanıma bağlamı olan bir çocuk oluşturmak için, iş parçacığı istenen DPI tanıma bağlamı içine konulabilir. Daha sonra alt öğe üst öğe olmadan oluşturulabilir ve üst pencereye el ile yeniden ayrılabilir.

CLMM sorunları

Ana mesajlaşma döngüsünün veya olay zincirinin bir parçası olarak gerçekleşen kullanıcı arabirimi hesaplama çalışmalarının çoğu doğru DPI tanıma bağlamında zaten çalışıyor olmalıdır. Ancak, bu ana iş akışlarının dışında (boşta kalma süresi görevi veya kullanıcı arabirimi iş parçacığı dışında) koordinat veya boyutlandırma hesaplamaları yapılıyorsa, geçerli DPI tanıma bağlamı yanlış olabilir ve bu da kullanıcı arabiriminin yanlış konumlandırılması veya yanlış boyutlandırma sorunlarına neden olabilir. İş parçacığını kullanıcı arabirimi çalışması için doğru duruma getirmek sorunu genel olarak düzeltir.

CLMM'nin dışında kalma

WPF olmayan bir araç penceresi PMA'yı tam olarak desteklemek üzere geçiriliyorsa, CLMM'yi geri çevirmesi gerekir. Bunu yapmak için yeni bir arabirim uygulanmalıdır: 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);
};

Yönetilen diller için, bu arabirimi uygulamak için en iyi yer Microsoft.VisualStudio.Shell.ToolWindowPane'dan türetilen aynı sınıftadır. C++ için bu arabirimi uygulamak için en iyi yer, vsshell.h dosyasından IVsWindowPane uygulayan aynı sınıftadır.

Arabirimdeki Mode özelliği tarafından döndürülen değer bir __VSDPIMODE (ve yönetilen bir uint'e yayınlanır):

enum __VSDPIMODE
{
    VSDM_Unaware    = 0x01,
    VSDM_System     = 0x02,
    VSDM_PerMonitor = 0x03,
}
  • Farkında değil, araç penceresinin 96 DPI işlemesi gerektiği anlamına gelir; Windows diğer tüm DPI'ler için ölçeklendirmeyi işler. İçeriğin biraz bulanık olmasına neden olan.
  • Sistem, araç penceresinin birincil görüntü DPI'sı için DPI'yi işlemesi gerektiği anlamına gelir. Eşleşen DPI'lı tüm ekranlar net görünür, ancak DPI farklıysa veya oturum sırasında değişirse, Windows ölçeklendirmeyi işler ve biraz bulanık olur.
  • PerMonitor, araç penceresinin tüm ekranlarda ve DPI değiştiğinde tüm DPI'leri işlemesi gerektiği anlamına gelir.

Not

Visual Studio yalnızca PerMonitorV2 tanımayı desteklediğinden PerMonitor sabit listesi değeri DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 Windows değerine çevrilir.

Denetimi belirli bir DpiAwarenessContext içine zorlama

PMA modunu destekleyecek şekilde güncelleştirilmeyen eski kullanıcı arabirimi, Visual Studio PMA modunda çalışırken küçük ayarlamaların çalışması gerekebilir. Bu tür bir düzeltme, kullanıcı arabiriminin doğru DpiAwarenessContext içinde oluşturulduğundan emin olmayı içerir. Kullanıcı arabiriminizi belirli bir DpiAwarenessContext içine zorlamak için aşağıdaki kodla bir DPI kapsamı girebilirsiniz:

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(...);
}

Not

DpiAwarenessContext'i zorlamak yalnızca WPF olmayan kullanıcı arabiriminde ve üst düzey WPF iletişim kutularında çalışır. Araç pencerelerinde veya tasarımcılarda barındırılacak WPF kullanıcı arabirimi oluşturulurken, içerik WPF UI ağacına eklenir eklenmez geçerli DpiAwarenessContext işlemine dönüştürülür.

Bilinen sorunlar

Windows Forms

Yeni karma mod senaryolarını iyileştirmek için Windows Forms, üst öğesi açıkça ayarlanmadığı her durumda denetimleri ve pencereleri oluşturma biçimini değiştirdi. Daha önce, açık üst öğe içermeyen denetimler, oluşturulan denetimin veya pencerenin geçici üst öğesi olarak bir iç "Park Penceresi" kullanıyordu.

.NET 4.8'den önce DpiAwarenessContext'ini pencerenin oluşturma zamanında geçerli iş parçacığı DPI farkındalık bağlamından alan tek bir "Park Penceresi" vardı. Ayrıştırılmamış tüm denetimler, denetimin tanıtıcısı oluşturulduğunda Park Penceresi ile aynı DpiAwarenessContext'i devralır ve uygulama geliştiricisi tarafından son/beklenen üst öğeye yeniden ayrıştırılır. "Park Penceresi" son üst pencereden daha yüksek bir DpiAwarenessContext'e sahipse bu durum zamanlama tabanlı hatalara neden olabilir.

.NET 4.8 itibarıyla, karşılaşılan her DpiAwarenessContext için bir "Park Penceresi" vardır. Diğer önemli fark, denetim için kullanılan DpiAwarenessContext'in, tanıtıcı oluşturulduğunda değil, denetim oluşturulduğunda önbelleğe alınmasıdır. Bu, genel bitiş davranışının aynı olduğu, ancak zamanlama tabanlı bir sorun olarak kullanılanları tutarlı bir soruna dönüştürebileceği anlamına gelir. Ayrıca uygulama geliştiricisine kullanıcı arabirimi kodunu yazmak ve doğru şekilde kapsamını belirlemek için daha belirleyici bir davranış sunar.