Bagikan melalui


Mengembangkan Aplikasi WPF Per-Monitor DPI-Aware

API Penting

Nota

Halaman ini mencakup pengembangan WPF warisan untuk Windows 8.1. Jika Anda mengembangkan aplikasi WPF untuk Windows 10, silakan lihat dokumentasi terbaru di GitHub.

 

Windows 8.1 memberi pengembang fungsionalitas baru untuk membuat aplikasi desktop yang sadar DPI per monitor. Untuk memanfaatkan fungsionalitas ini, aplikasi yang sadar akan DPI per monitor harus melakukan hal berikut:

  • Mengubah dimensi jendela untuk mempertahankan ukuran fisik yang tampak konsisten pada tampilan apa pun
  • Tata letak ulang dan render ulang grafik untuk ukuran jendela baru
  • Pilih font yang diskalakan dengan tepat untuk tingkat DPI
  • Memilih dan memuat aset bitmap yang disesuaikan untuk tingkat DPI

Untuk memfasilitasi pembuatan aplikasi sadar DPI per monitor, Windows 8.1 menyediakan Microsoft Win32APIs berikut:

  • SetProcessDpiAwareness (atau entri manifes DPI) mengatur proses ke tingkat kesadaran DPI tertentu, yang kemudian menentukan bagaimana Windows menskalakan UI. Ini menggantikan SetProcessDPIAware.
  • GetProcessDpiAwareness mengembalikan tingkat kesadaran DPI. Ini menggantikan IsProcessDPIAware.
  • GetDpiForMonitor mengembalikan DPI untuk monitor.
  • Pemberitahuan jendela WM_DPICHANGED dikirim ke aplikasi yang sadar DPI per monitor ketika posisi jendela berubah sehingga sebagian besar areanya berpotongan dengan monitor yang memiliki DPI berbeda dari DPI sebelum posisi berubah atau ketika pengguna menggeser slider tampilan. Untuk membuat aplikasi yang mengubah ukuran dan merender ulang dirinya sendiri ketika pengguna memindahkannya ke tampilan yang berbeda, gunakan pemberitahuan ini.

Untuk informasi lebih lanjut tentang berbagai tingkat kesadaran DPI yang didukung untuk aplikasi desktop di Windows 8.1, lihat topik Menulis Aplikasi DPI-Aware Desktop dan Win32.

Penskalakan DPI dan WPF

Aplikasi Windows Presentation Foundation (WPF) adalah sistem default yang sadar akan DPI. Untuk definisi tingkat kesadaran DPI yang berbeda, silakan lihat topik Menulis Aplikasi DPI-Aware Desktop dan Win32. Sistem grafis WPF menggunakan unit independen perangkat untuk memungkinkan resolusi dan kemandirian perangkat. WPF menskalakan setiap piksel independen perangkat secara otomatis berdasarkan DPI sistem saat ini. Ini memungkinkan aplikasi WPF untuk menskalakan secara otomatis ketika DPI monitor tempat jendela berada sama dengan DPI sistem. Namun, karena aplikasi WPF menyadari DPI sistem, aplikasi akan diskalakan oleh sistem operasi ketika aplikasi dipindahkan ke monitor dengan DPI yang berbeda atau ketika penggeser di panel kontrol digunakan untuk mengubah DPI. Penskalaan dalam OS dapat mengakibatkan aplikasi WPF tampak buram terutama ketika penskalaan tidak terintegrasi. Untuk menghindari penskalaan aplikasi WPF, aplikasi perlu diperbarui agar sadar akan DPI per monitor.

Panduan Langkah Sampel WPF Per Monitor Aware

sampel Per Monitor Aware WPF adalah contoh aplikasi WPF yang diperbarui agar sadar akan DPI per monitor. Sampel terdiri dari dua proyek:

  • NativeHelpers.vcxproj: Ini adalah proyek pembantu bawaan yang mengimplementasikan fungsionalitas inti untuk membuat aplikasi WPF yang tanggap DPI per monitor dengan menggunakan API Win32 yang telah disebutkan. Proyek ini berisi dua kelas:
    • PerMonDPIHelpers: Kelas yang menyediakan fungsi pembantu untuk operasi terkait DPI, seperti mendapatkan DPI monitor aktif saat ini dan mengatur proses agar menyadari DPI setiap monitor, dll.
    • PerMonitorDPIWindow: Kelas dasar yang berasal dari System.Windows.Window yang mengimplementasikan fungsionalitas untuk membuat jendela aplikasi WPF menjadi sadar dpi per monitor. Menyesuaikan ukuran jendela, ukuran penyajian grafis, dan ukuran font berdasarkan DPI monitor daripada DPI sistem.
  • WPFApplication.csproj: Sampel aplikasi WPF yang menggunakan PerMonitorDPIWindow (PerMonitorDPIWindow), dan menampilkan bagaimana jendela aplikasi dan penyajian mengubah ukuran ketika jendela dipindahkan ke monitor dengan DPI yang berbeda atau ketika penggeser di panel Kontrol tampilan digunakan untuk mengubah DPI.

Untuk menjalankan sampel, ikuti langkah-langkah di bawah ini:

  1. Unduh dan unzip sampel Per Monitor Aware WPF
  2. Mulai Microsoft Visual Studio dan pilih file > Open > Project/Solution
  3. Telusuri ke direktori yang berisi sampel yang tidak di-zip. Buka direktori bernama untuk sampel, dan klik dua kali file Visual Studio Solution (.sln)
  4. Tekan F7 atau gunakan Build > Build Solution untuk membangun sampel
  5. Tekan Ctrl+F5 atau gunakan Penelusuran Kesalahan > Mulai Tanpa Penelusuran Kesalahan untuk menjalankan contoh

Untuk melihat dampak perubahan DPI pada aplikasi WPF yang diperbarui agar sadar akan DPI per monitor menggunakan kelas dasar dalam sampel, pindahkan jendela aplikasi ke dan dari tampilan yang memiliki DPI yang berbeda. Saat jendela dipindahkan antara monitor, ukuran jendela dan skala UI diperbarui berdasarkan DPI tampilan dengan menggunakan sistem grafis WPF yang dapat diskalakan, daripada diskalakan oleh OS. UI aplikasi dirender secara asli dan tidak tampak buram. Jika Anda tidak memiliki dua tampilan dengan DPI yang berbeda, ubah DPI dengan mengubah slider di panel Kontrol tampilan. Mengubah slider dan mengklik Terapkan akan mengubah ukuran jendela aplikasi dan memperbarui skala UI secara otomatis.

Memperbarui Aplikasi WPF yang ada agar mendukung dpi-berdasarkan layar menggunakan proyek pembantu dalam contoh WPF

Jika Anda memiliki aplikasi WPF yang ada dan ingin memanfaatkan proyek pembantu DPI dari sampel untuk membuatnya sadar DPI, ikuti langkah-langkah ini.

  1. Mengunduh dan membuka zip sampel WPF Sadar Per Monitor

  2. Mulai Visual Studio dan pilih File > Buka > Project/Solution

  3. Telusuri ke direktori yang berisi aplikasi WPF yang ada dan klik dua kali file Visual Studio Solution (.sln)

  4. Klik kanan pada Solution > Tambahkan > Proyek yang Adacuplikan layar yang mengilustrasikan pilihan menu tambahkan proyek yang ada

  5. Dalam dialog pemilihan file telusuri ke direktori yang berisi sampel yang tidak di-zip. Buka direktori bernama untuk sampel, telusuri ke folder "NativeHelpers", pilih file proyek Visual C++ "NativeHelpers.vcxproj" dan klik OK

  6. Klik kanan pada proyek NativeHelpers dan pilih Build. Ini akan menghasilkan NativeHelpers.dll yang akan ditambahkan sebagai referensi ke Aplikasi WPF pada langkah berikutnyacuplikan layar yang mengilustrasikan pemilihan menu build

  7. Tambahkan referensi ke NativeHelpers.dll dari Aplikasi WPF Anda. Perluas proyek aplikasi WPF Anda, klik kanan Referensi dan klik Tambahkan Referensi...

  8. Dalam dialog yang dihasilkan, perluas bagian Solusi. Di bawah Projects, pilih NativeHelpers, dan klik OKcuplikan layar yang menggambarkan dialog resource manager

  9. Perluas proyek aplikasi WPF Anda, perluas properti , dan buka AssemblyInfo.cs. Lakukan penambahan berikut ke AssemblyInfo.cs

    • Tambahkan referensi ke System.Windows.Media di bagian referensi (menggunakan System.Windows.Media;)
    • Tambahkan atribut DisableDpiAwareness ([assembly: DisableDpiAwareness])

    cuplikan layar yang mengilustrasikan properti tambahan

  10. Mewarisi kelas jendela WPF utama dari kelas dasar PerMonitorDPIWindow

    • Perbarui file .cs dari jendela WPF utama agar mewarisi kelas dasar PerMonitorDPIWindow

      • Tambahkan referensi ke NativeHelpers di bagian referensi dengan menambahkan baris using NativeHelpers;
      • Mewarisi kelas jendela utama dari kelas PerMonitorDPIWindow

      cuplikan layar yang mengilustrasikan referensi c++

    • Perbarui file .xaml pada jendela WPF utama untuk mewarisi dari kelas dasar PerMonitorDPIWindow.

      • Tambahkan referensi ke NativeHelpers di bagian referensi dengan menambahkan baris xmlns:src="clr-namespace:NativeHelpers;assembly=NativeHelpers"
      • Mewarisi kelas jendela utama dari kelas PerMonitorDPIWindow

      cuplikan layar yang mengilustrasikan penambahan referensi .xaml

  11. Tekan F7 atau gunakan Build > Build Solution untuk membangun sampel

  12. Tekan Ctrl+F5 atau gunakan Debug > Mulai Tanpa Debugging untuk menjalankan contoh

Contoh aplikasi Per Monitor Aware WPF ini menggambarkan bagaimana aplikasi WPF dapat diperbarui agar memiliki kesadaran terhadap DPI per monitor dengan merespons pemberitahuan jendela WM_DPICHANGED. Sebagai respons terhadap notifikasi jendela, sampel memperbarui transformasi skala yang digunakan oleh WPF berdasarkan DPI dari monitor tempat jendela berada. Parameter wParam dari pemberitahuan jendela berisi DPI baru dalam parameter wParam. lParam berisi persegi panjang dengan ukuran dan posisi jendela baru yang disarankan, diskalakan untuk skala DPI baru.

Nota:

Nota

Karena sampel ini menimpa ukuran jendela dan transformasi skala dari simpul akar jendela WPF, pekerjaan lebih lanjut mungkin diperlukan oleh pengembang aplikasi jika:

  • Ukuran jendela berdampak pada bagian lain dari aplikasi seperti Jendela WPF ini yang dihosting di dalam aplikasi lain.
  • Aplikasi WPF yang memperluas kelas ini mengonfigurasi beberapa transformasi lain pada visual akar; sampel ini mungkin akan menimpa transform lain yang sedang diterapkan oleh aplikasi WPF itu sendiri.

 

Gambaran umum proyek pembantu dalam sampel WPF

Untuk membuat aplikasi WPF yang ada menjadi memahami DPI per-monitor, pustaka dari NativeHelpers menyediakan fungsi berikut:

  • Menandai aplikasi WPF sebagai mendukung DPI per monitor: Aplikasi WPF ditandai sebagai mendukung DPI per monitor dengan memanggil SetProcessDpiAwareness untuk proses saat ini. Menandai aplikasi sebagai mengenali DPI per-monitor akan memastikan bahwa

    • OS tidak menskalakan aplikasi ketika DPI sistem tidak cocok dengan DPI monitor saat ini dari jendela aplikasi aktif
    • Pesan WM_DPICHANGED dikirim setiap kali DPI jendela berubah
  • Menyesuaikan dimensi jendela, tata letak ulang, dan merender ulang konten grafis dan memilih font berdasarkan DPI awal monitor jendela aktif: Setelah aplikasi ditandai sebagai sadar DPI per monitor, WPF masih akan menskalakan ukuran jendela, grafik, dan ukuran font berdasarkan DPI sistem. Karena, saat peluncuran aplikasi, DPI sistem tidak dijamin sama dengan DPI monitor tempat jendela diluncurkan, pustaka menyesuaikan nilai-nilai ini setelah jendela dimuat. Kelas dasar PerMonitorDPIWindow memperbaruinya di handler OnLoaded().

    Ukuran Window diperbarui dengan mengubah properti Lebar dan Tinggi pada Window. Tata letak dan ukuran diperbarui dengan menerapkan transformasi skala yang sesuai ke simpul akar jendela WPF.

    void PerMonitorDPIWindow::OnLoaded(Object^ , RoutedEventArgs^ ) 
    {   
    if (m_perMonitorEnabled)
        {
        m_source = (HwndSource^) PresentationSource::FromVisual((Visual^) this);
        HwndSourceHook^ hook = gcnew HwndSourceHook(this, &PerMonitorDPIWindow::HandleMessages);
        m_source->AddHook(hook); 
    
        //Calculate the DPI used by WPF.                    
        m_wpfDPI = 96.0 *  m_source->CompositionTarget->TransformToDevice.M11; 
    
        //Get the Current DPI of the monitor of the window. 
        m_currentDPI = NativeHelpers::PerMonitorDPIHelper::GetDpiForWindow(m_source->Handle);
    
        //Calculate the scale factor used to modify window size, graphics and text
        m_scaleFactor = m_currentDPI / m_wpfDPI; 
    
        //Update Width and Height based on the on the current DPI of the monitor
        Width = Width * m_scaleFactor;
        Height = Height * m_scaleFactor;
    
        //Update graphics and text based on the current DPI of the monitor
    UpdateLayoutTransform(m_scaleFactor);
        }
    }
    
    void PerMonitorDPIWindow::UpdateLayoutTransform(double scaleFactor)
    {
    // Adjust the rendering graphics and text size by applying the scale transform to the top         
    level visual node of the Window     
    
    if (m_perMonitorEnabled) 
        {       
            auto child = GetVisualChild(0);
            if (m_scaleFactor != 1.0) 
           {
            ScaleTransform^ dpiScale = gcnew ScaleTransform(scaleFactor, scaleFactor);
            child->SetValue(Window::LayoutTransformProperty, dpiScale);
            }
            else 
            {
            child->SetValue(Window::LayoutTransformProperty, nullptr);
            }           
        }
    }
    
  • Merespons pemberitahuan jendela WM_DPICHANGED: Memperbarui ukuran jendela, grafik, dan ukuran font berdasarkan DPI yang diteruskan di pemberitahuan jendela. Kelas dasar PerMonitorDPIWindow menangani pemberitahuan jendela dalam metode HandleMessages().

    Ukuran Jendela diperbarui dengan memanggil SetWindowPos menggunakan informasi yang diteruskan di lparam pesan jendela. Ukuran tata letak dan grafik diperbarui dengan menerapkan transformasi skala yang sesuai ke simpul akar jendela WPF. Faktor skala dihitung dengan menggunakan DPI yang diteruskan dalam wparam dari pesan jendela.

    IntPtr PerMonitorDPIWindow::HandleMessages(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, bool% )
    {
    double oldDpi;
    switch (msg)
        {
        case WM_DPICHANGED:
        LPRECT lprNewRect = (LPRECT)lParam.ToPointer();
        SetWindowPos(static_cast<HWND>(hwnd.ToPointer()), 0, lprNewRect->left, lprNewRect-
            >top, lprNewRect->right - lprNewRect->left, lprNewRect->bottom - lprNewRect->top, 
           SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE);
        oldDpi = m_currentDPI;
        m_currentDPI = static_cast<int>(LOWORD(wParam.ToPointer()));
        if (oldDpi != m_currentDPI) 
            {
            OnDPIChanged();
            }
        break;
        }
    return IntPtr::Zero;
    }
    
    void PerMonitorDPIWindow::OnDPIChanged() 
    {
    m_scaleFactor = m_currentDPI / m_wpfDPI;
    UpdateLayoutTransform(m_scaleFactor);
    DPIChanged(this, EventArgs::Empty);
    }
    
    void PerMonitorDPIWindow::UpdateLayoutTransform(double scaleFactor)
    {
    // Adjust the rendering graphics and text size by applying the scale transform to the top         
    level visual node of the Window     
    
    if (m_perMonitorEnabled) 
        {       
            auto child = GetVisualChild(0);
            if (m_scaleFactor != 1.0) 
           {
            ScaleTransform^ dpiScale = gcnew ScaleTransform(scaleFactor, scaleFactor);
            child->SetValue(Window::LayoutTransformProperty, dpiScale);
            }
            else 
            {
            child->SetValue(Window::LayoutTransformProperty, nullptr);
            }           
        }
    }
    

Menangani perubahan DPI untuk aset seperti gambar

Untuk memperbarui konten grafis, sampel aplikasi WPF menerapkan transformasi skala ke simpul akar aplikasi WPF. Meskipun ini berfungsi dengan baik untuk konten yang dirender secara asli oleh WPF (persegi panjang, teks dll.), ini menyiratkan bahwa aset bitmap seperti gambar diskalakan oleh WPF.

Untuk menghindari bitmap kabur yang disebabkan oleh penskalaan, pengembang aplikasi WPF dapat menulis kontrol gambar DPI kustom yang memilih aset yang berbeda berdasarkan DPI saat ini dari monitor jendela aktif. Kontrol gambar dapat mengandalkan peristiwa DPIChanged() yang diaktifkan untuk jendela WPF yang menggunakan dari PerMonitorDPIWindow, ketika DPI berubah.

Nota

Kontrol gambar juga harus memilih kontrol yang tepat selama peluncuran aplikasi di Loaded() penanganan aktivitas jendela WPF.