Bagaimana cara menulis kode WinForms yang skala otomatis ke pengaturan font dan dpi sistem?

143

Intro: Ada banyak komentar di luar sana yang mengatakan "WinForms tidak mengatur skala ke pengaturan DPI / font dengan baik; beralihlah ke WPF." Namun, saya pikir itu didasarkan pada. NET 1.1; tampaknya mereka benar-benar melakukan pekerjaan yang cukup bagus untuk mengimplementasikan penskalaan otomatis dalam .NET 2.0. Setidaknya berdasarkan penelitian dan pengujian kami sejauh ini. Namun, jika beberapa dari Anda di luar sana lebih tahu, kami akan senang mendengar dari Anda. (Tolong jangan repot-repot berdebat bahwa kita harus beralih ke WPF ... itu bukan pilihan saat ini.)

Pertanyaan:

  • Apa yang ada di WinForms TIDAK berskala otomatis dengan benar dan karenanya harus dihindari?

  • Panduan desain apa yang harus diikuti oleh programmer ketika menulis kode WinForms sehingga akan skala otomatis dengan baik?

Pedoman Desain yang telah kami identifikasi sejauh ini:

Lihat jawaban wiki komunitas di bawah ini.

Apakah ada yang salah atau tidak memadai? Adakah pedoman lain yang harus kita adopsi? Apakah ada pola lain yang perlu dihindari? Bimbingan lain apa pun tentang ini akan sangat dihargai.

Brian Kennedy
sumber

Jawaban:

127

Kontrol yang tidak mendukung penskalaan dengan benar:

  • Labeldengan AutoSize = Falsedan Fontdiwarisi. Secara eksplisit mengatur Fontkontrol sehingga muncul dengan huruf tebal di jendela Properties.
  • ListViewlebar kolom tidak menskala. Ganti formulir ScaleControluntuk melakukannya. Lihat jawaban ini
  • SplitContainer's Panel1MinSize, Panel2MinSizedan SplitterDistancesifat
  • TextBoxdengan MultiLine = Truedan Fontdiwarisi. Secara eksplisit mengatur Fontkontrol sehingga muncul dengan huruf tebal di jendela Properties.
  • ToolStripButtongambar. Dalam konstruktor formulir:

    • Set ToolStrip.AutoSize = False
    • Atur ToolStrip.ImageScalingSizesesuai dengan CreateGraphics.DpiXdan.DpiY
    • Tetapkan ToolStrip.AutoSize = Truejika perlu.

    Kadang AutoSize- kadang bisa dibiarkan Truetetapi kadang-kadang gagal untuk mengubah ukuran tanpa langkah-langkah itu. Bekerja tanpa perubahan itu dengan .NET Framework 4.5.2 dan EnableWindowsFormsHighDpiAutoResizing.

  • TreeViewgambar. Atur ImageList.ImageSizesesuai dengan CreateGraphics.DpiXdan .DpiY. Untuk StateImageList, bekerja tanpa perubahan dengan .NET Framework 4.5.1 dan EnableWindowsFormsHighDpiAutoResizing.
  • FormUkurannya. Skala ukuran tetap Formsecara manual setelah pembuatan.

Pedoman Desain:

  • Semua ContainerControls harus disetel sama AutoScaleMode = Font. (Font akan menangani perubahan DPI dan perubahan pada pengaturan ukuran font sistem; DPI hanya akan menangani perubahan DPI, bukan perubahan pada pengaturan ukuran font sistem.)

  • Semua ContainerControls juga harus diset dengan sama AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);, dengan asumsi 96dpi (lihat butir berikutnya) dan Font default MS Sans Serif (lihat dua peluru di bawah). Itu ditambahkan secara otomatis oleh desainer berdasarkan DPI Anda membuka desainer di ... tetapi hilang dari banyak file desainer tertua kami. Mungkin Visual Studio .NET (versi sebelum VS 2005) tidak menambahkan itu dengan benar.

  • Apakah semua perancang Anda bekerja di 96dpi (kami mungkin dapat beralih ke 120dpi; tetapi kebijaksanaan di internet mengatakan untuk tetap berpegang pada 96dpi; eksperimen dilakukan di sana; secara desain, tidak masalah karena hanya mengubah AutoScaleDimensionsbaris yang sisipan desainer). Untuk mengatur Visual Studio agar dijalankan pada 96dpi virtual pada tampilan resolusi tinggi, temukan file .exe, klik kanan untuk mengedit properti, dan di bawah Kompatibilitas pilih "Ganti perilaku penskalaan DPI tinggi. Penskalaan dilakukan oleh: Sistem".

  • Pastikan Anda tidak pernah mengatur Font di level kontainer ... hanya di kontrol daun ATAU di konstruktor Form paling dasar Anda jika Anda ingin Font default aplikasi-lebar selain MS Sans Serif. (Mengatur Font pada Wadah tampaknya mematikan penskalaan otomatis wadah itu karena secara alfabet muncul setelah pengaturan pengaturan AutoScaleMode dan AutoScaleDimensions.) Perhatikan bahwa jika Anda mengubah Font di konstruktor Form paling dasar Anda, itu akan menyebabkan Dimensi AutoScale Anda untuk menghitung secara berbeda dari 6x13; khususnya, jika Anda mengubah ke Segoe UI (font default Win 10), maka itu akan menjadi 7x15 ... Anda harus menyentuh setiap Formulir di Designer sehingga dapat menghitung ulang semua dimensi dalam file .designer itu, termasuk itu AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);.

  • JANGAN gunakan Jangkar Rightatau Bottomberlabuh ke UserControl ... posisinya tidak akan skala otomatis; sebagai gantinya, letakkan Panel atau wadah lain ke dalam UserControl Anda dan Jangkar Kontrol Anda yang lain ke Panel itu; memiliki penggunaan Panel Dock Right, Bottomatau Filldi UserControl Anda.

  • Hanya kontrol di daftar Kontrol ketika ResumeLayoutpada akhir InitializeComponentdipanggil akan diskalakan otomatis ... jika Anda menambahkan kontrol secara dinamis, maka Anda harus SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout();menggunakan kontrol itu sebelum menambahkannya. Dan posisi Anda juga perlu disesuaikan jika Anda tidak menggunakan mode Dock atau Layout Manager suka FlowLayoutPanelatau TableLayoutPanel.

  • Kelas-kelas dasar yang diturunkan ContainerControlharus dibiarkan AutoScaleModedisetel ke Inherit(nilai default yang ditetapkan di kelas ContainerControl; tetapi BUKAN set standar oleh perancang). Jika Anda mengaturnya ke hal lain, dan kemudian kelas turunan Anda mencoba mengaturnya ke Font (sebagaimana mestinya), maka tindakan pengaturan yang Fontakan menghapus pengaturan desainer AutoScaleDimensions, menghasilkan benar-benar mematikan penskalaan otomatis! (Pedoman ini dikombinasikan dengan yang sebelumnya berarti Anda tidak akan pernah dapat membuat kelas dasar dalam desainer ... semua kelas harus dirancang sebagai kelas dasar atau sebagai kelas daun!)

  • Hindari menggunakan Form.MaxSizestatis / di Designer. MinSizedan MaxSizepada Formulir tidak skala sebanyak yang lainnya. Jadi, jika Anda melakukan semua pekerjaan Anda dalam 96dpi, maka ketika pada DPI yang lebih tinggi Anda MinSizetidak akan menimbulkan masalah, tetapi mungkin tidak seketat yang Anda harapkan, tetapi Anda MaxSizedapat membatasi penskalaan Ukuran Anda, yang dapat menyebabkan masalah. Jika Anda mau MinSize == Size == MaxSize, jangan lakukan itu di Designer ... lakukan itu di konstruktor Anda atau OnLoadtimpa ... atur keduanya MinSizedan MaxSizeke Ukuran Anda dengan skala yang benar.

  • Semua Kontrol pada tertentu Panelatau Containerharus menggunakan Penahan atau Docking. Jika Anda mencampurnya, penskalaan otomatis yang dilakukan olehnya Panelakan sering bertingkah aneh dengan cara yang aneh.

  • Ketika melakukan penskalaan otomatis, ia akan mencoba untuk menskala Formulir keseluruhan ... namun, jika dalam proses itu ia berjalan ke batas atas ukuran layar, itu adalah batas keras yang kemudian dapat dikacaukan (klip) scaling. Oleh karena itu, Anda harus memastikan semua Formulir di Perancang dengan ukuran 100% / 96dpi berukuran tidak lebih besar dari 1024x720 (yang sesuai dengan 150% pada layar 1080p atau 300% yang merupakan nilai yang disarankan Windows pada layar 4K). Tetapi Anda perlu mengurangi untuk bar judul / caption Win10 raksasa ... jadi lebih seperti 1000x680 Ukuran maks ... yang pada perancang akan seperti 994x642 ClientSize. (Jadi, Anda dapat melakukan Referensi FindAll di ClientSize untuk menemukan pelanggar.)

kjbartel
sumber
NumericUpDowntidak skala dengan Marginbaik juga. Tampaknya margin diskalakan dua kali. Jika saya skala kembali, itu terlihat bagus.
siapaoe
AutoScaleMode = Fonttidak bekerja dengan baik untuk pengguna yang menggunakan font yang sangat besar dan dengan di Ubuntu. Kami lebih sukaAutoScaleMode = DPI
KindDragon
> TextBox dengan MultiLine = True dan Font diwarisi. Menjadi gila sepanjang hari - itu adalah perbaikan! Terima kasih banyak! Omong-omong, perbaikan yang sama juga merupakan perbaikan untuk kontrol ListBox. : D
neminem
Bagi saya, kotak daftar dengan font yang diwarisi tidak skala dengan baik. Mereka lakukan setelah secara eksplisit diatur. (.NET 4.7)
PulseJet
Dalam utas reddit ini yang berurusan dengan masalah penskalaan winform, saya menemukan tautan ini ke Telerik Demo Monitor DPI Sampel disclaimer saya belum menggunakannya sendiri. Artikel Telerik
surfmuggle
27

Pengalaman saya cukup berbeda dengan jawaban terpilih saat ini. Dengan melangkah melalui .NET framework code dan membaca kode sumber referensi, saya menyimpulkan bahwa semuanya sudah ada untuk penskalaan otomatis berfungsi, dan hanya ada masalah halus di suatu tempat yang mengacaukannya. Ternyata ini benar.

Jika Anda membuat tata letak yang dapat dipantulkan dengan benar / otomatis, maka hampir semuanya berfungsi persis seperti seharusnya, secara otomatis, dengan pengaturan default yang digunakan oleh Visual Studio (yaitu, AutoSizeMode = Font pada formulir induk, dan Mewarisi pada yang lain).

Satu-satunya gotcha adalah jika Anda telah mengatur properti Font pada formulir di perancang. Kode yang dihasilkan akan mengurutkan penugasan berdasarkan abjad, yang berarti bahwa AutoScaleDimensionsakan ditetapkan sebelumnya Font . Sayangnya, ini benar-benar memecah logika penskalaan otomatis WinForms.

Cara mengatasinya sederhana. Entah tidak menyetel Fontproperti di desainer sama sekali (atur di konstruktor formulir Anda), atau atur ulang secara manual tugas-tugas ini (tetapi kemudian Anda harus terus melakukan ini setiap kali Anda mengedit formulir di desainer). Voila, penskalaan yang hampir sempurna dan otomatis sepenuhnya dengan kerumitan minimal. Bahkan ukuran formulir diskalakan dengan benar.


Saya akan membuat daftar masalah yang diketahui di sini saat saya menjumpainya:

  • Nested TableLayoutPanel menghitung margin kontrol secara salah . Tidak ada cara kerja yang diketahui untuk menghindari margin dan bantalan sama sekali - atau menghindari panel tata letak meja bersarang.
Roman Starkov
sumber
1
Kembali tidak menetapkan Fontdalam desainer: Suatu pemikiran muncul di pikiran: pergi ke depan dan atur font di desainer, sehingga Anda dapat merancang dengan font yang diinginkan. LALU di konstruktor, setelah tata letak, baca properti font itu dan atur kembali nilai yang sama? Atau mungkin hanya meminta tata letak untuk dilakukan lagi? [Peringatan: Saya belum punya alasan untuk menguji pendekatan ini.] Atau sesuai jawaban Knowleech , dalam perancang tentukan dalam piksel (jadi perancang Visual Studio tidak akan mengubah skala pada monitor DPI tinggi), dan dalam kode baca nilai itu, konversi dari piksel ke poin (untuk mendapatkan penskalaan yang benar).
ToolmakerSteve
1
Setiap bit kode kami memiliki dimensi skala otomatis yang diatur tepat sebelum mode skala otomatis dan semuanya berskala sempurna. Sepertinya pesanan tidak masalah dalam banyak kasus.
Josh
Saya mencari kode saya untuk contoh di mana AutoScaleDimensionstidak diatur ke new SizeF(6F, 13F)seperti yang direkomendasikan dalam jawaban teratas. Ternyata dalam setiap contoh, properti Font formulir telah disetel (tidak bawaan). Tampaknya ketika AutoScaleMode = Font, kemudian AutoScaleDimensionsdihitung berdasarkan properti font formulir. Selain itu, pengaturan Penskalaan pada Panel Kontrol Windows tampaknya memiliki pengaruh AutoScaleDimensions.
Walter Stabosz
24

Targetkan Aplikasi Anda untuk .Net Framework 4.7 dan jalankan di bawah Windows 10 v1703 (Creators Update Build 15063). Dengan .Net 4.7 di bawah Windows 10 (v1703), MS membuat banyak peningkatan DPI .

Dimulai dengan .NET Framework 4.7, Windows Forms mencakup penyempurnaan untuk DPI umum tinggi dan skenario DPI dinamis. Ini termasuk:

  • Perbaikan dalam penskalaan dan tata letak sejumlah kontrol Windows Forms, seperti kontrol MonthCalendar dan kontrol CheckedListBox.

  • Penskalaan sekali jalan. Dalam .NET Framework 4.6 dan versi sebelumnya, penskalaan dilakukan melalui beberapa lintasan, yang menyebabkan beberapa kontrol diskalakan lebih dari yang diperlukan.

  • Dukungan untuk skenario DPI dinamis di mana pengguna mengubah DPI atau faktor skala setelah aplikasi Windows Forms diluncurkan.

Untuk mendukungnya, tambahkan manifes aplikasi ke aplikasi Anda dan beri tanda bahwa aplikasi Anda mendukung Windows 10:

<compatibility xmlns="urn:schemas-microsoft.comn:compatibility.v1">
    <application>
        <!-- Windows 10 compatibility -->
        <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
    </application>
</compatibility>

Selanjutnya, tambahkan app.configdan nyatakan aplikasi Per Monitor Sadar. Ini SEKARANG dilakukan di app.config dan BUKAN di manifes seperti sebelumnya!

<System.Windows.Forms.ApplicationConfigurationSection>
   <add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection> 

Ini PerMonitorV2 baru sejak Windows 10 Kreator Update:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

Juga dikenal sebagai Per Monitor v2. Kemajuan atas mode kesadaran DPI per-monitor asli, yang memungkinkan aplikasi mengakses perilaku penskalaan terkait DPI baru pada basis jendela tingkat-atas.

  • Pemberitahuan perubahan DPI jendela anak - Dalam konteks Per Monitor v2, seluruh pohon jendela diberitahu tentang perubahan DPI yang terjadi.

  • Penskalaan area non-klien - Semua jendela akan secara otomatis area non-kliennya digambar dengan sensitif DPI. Panggilan ke EnableNonClientDpiScaling tidak perlu.

  • S caling dari menu Win32 - Semua menu NTUSER yang dibuat dalam konteks Per Monitor v2 akan diskalakan dengan mode per-monitor.

  • Penskalaan Dialog - Dialog Win32 yang dibuat dalam konteks Per Monitor v2 akan secara otomatis merespons perubahan DPI.

  • Peningkatan penskalaan kontrol comctl32 - Berbagai kontrol comctl32 telah meningkatkan perilaku penskalaan DPI dalam konteks Per Monitor v2.

  • Perilaku bertema yang ditingkatkan - UxTheme menangani dibuka dalam konteks jendela Per Monitor v2 akan beroperasi dalam hal DPI yang terkait dengan jendela itu.

Sekarang Anda dapat berlangganan 3 acara baru untuk mendapat pemberitahuan tentang perubahan DPI:

  • Control.DpiChangedAfterParent , yang dipecat Terjadi ketika pengaturan DPI untuk kontrol diubah secara programatis setelah acara perubahan DPI untuk kontrol atau formulir induknya telah terjadi.

  • Control.DpiChangedBeforeParent , yang dipecat ketika pengaturan DPI untuk kontrol diubah secara pemrograman sebelum acara perubahan DPI untuk kontrol atau formulir induknya telah terjadi.

  • Form.DpiChanged , yang diaktifkan ketika pengaturan DPI berubah pada perangkat tampilan tempat formulir saat ini ditampilkan.

Anda juga memiliki 3 metode pembantu tentang penanganan / penskalaan DPI:

  • Control.LogicalToDeviceUnits , yang mengubah nilai dari logis ke piksel perangkat.

  • Control.ScaleBitmapLogicalToDevice , yang mengubah skala gambar bitmap ke DPI logis untuk suatu perangkat.

  • Control.DeviceDpi , yang mengembalikan DPI untuk perangkat saat ini.

Jika Anda masih melihat masalah, Anda dapat menyisih dari peningkatan DPI melalui entri app.config .

Jika Anda tidak memiliki akses ke kode sumber, Anda dapat pergi ke properti aplikasi di Windows Explorer, pergi ke kompatibilitas dan pilih System (Enhanced)

masukkan deskripsi gambar di sini

yang mengaktifkan penskalaan GDI untuk juga meningkatkan penanganan DPI:

Untuk aplikasi yang berbasis Windows GDI sekarang dapat skala DPI ini berdasarkan per-monitor. Ini berarti bahwa aplikasi ini akan, secara ajaib, menjadi DPI per monitor.

Lakukan semua langkah itu dan Anda harus mendapatkan pengalaman DPI yang lebih baik untuk aplikasi WinForms. Tapi ingat, Anda harus menargetkan aplikasi Anda untuk .net 4.7 dan membutuhkan setidaknya Windows 10 Build 15063 (Pembaruan Pembuat). Di Pembaruan Windows 10 1709 berikutnya, kita mungkin mendapatkan lebih banyak perbaikan.

magicandre1981
sumber
12

Panduan yang saya tulis di tempat kerja:

WPF bekerja di 'unit perangkat independen' yang berarti semua kontrol skala sempurna ke layar dpi tinggi. Di WinForms, dibutuhkan lebih banyak perawatan.

WinForms bekerja dalam piksel. Teks akan diskalakan menurut sistem dpi tetapi akan sering dipangkas oleh kontrol yang tidak dihitung. Untuk menghindari masalah seperti itu, Anda harus menghindari ukuran dan penentuan posisi eksplisit. Ikuti aturan ini:

  1. Di mana pun Anda menemukannya (label, tombol, panel) atur properti AutoSize ke True.
  2. Untuk tata letak, gunakan FlowLayoutPanel (a la WPF StackPanel) dan TableLayoutPanel (a la WPF Grid) untuk tata letak, daripada vanilla Panel.
  3. Jika Anda mengembangkan pada mesin dpi tinggi, perancang Visual Studio bisa membuat frustrasi. Ketika Anda mengatur AutoSize = True, itu akan mengubah ukuran kontrol ke layar Anda. Jika kontrol memiliki AutoSizeMode = GrowOnly, itu akan tetap ukuran ini untuk orang-orang di dpi normal, yaitu. lebih besar dari yang diharapkan. Untuk memperbaikinya, buka desainer di komputer dengan dpi normal dan lakukan klik kanan, atur ulang.
Kolonel Panic
sumber
3
untuk dialog yang dapat diubah ukurannya AutoSize pada semuanya akan menjadi mimpi buruk, saya tidak ingin tombol saya menjadi lebih besar dan lebih kecil karena saya meningkatkan ukuran dialog saya secara manual saat menjalankan program.
Josh
10

Saya merasa sangat sulit untuk membuat WinForms bermain bagus dengan DPI tinggi. Jadi, saya menulis metode VB.NET untuk mengganti perilaku formulir:

Public Shared Sub ScaleForm(WindowsForm As System.Windows.Forms.Form)
    Using g As System.Drawing.Graphics = WindowsForm.CreateGraphics
        Dim sngScaleFactor As Single = 1
        Dim sngFontFactor As Single = 1
        If g.DpiX > 96 Then
            sngScaleFactor = g.DpiX / 96
            'sngFontFactor = 96 / g.DpiY
        End If
        If WindowsForm.AutoScaleDimensions = WindowsForm.CurrentAutoScaleDimensions Then
            'ucWindowsFormHost.ScaleControl(WindowsForm, sngFontFactor)
            WindowsForm.Scale(sngScaleFactor)
        End If
    End Using
End Sub
jrh
sumber
6

Saya baru-baru ini menemukan masalah ini, terutama dalam kombinasi dengan Visual Studio rescaling ketika editor dibuka pada sistem dpi tinggi. Saya menemukan itu yang terbaik untuk menjaga AutoScaleMode = Font , tetapi untuk mengatur Bentuk Font untuk font default, tetapi menentukan ukuran dalam pixel , tidak titik, yaitu: Font = MS Sans; 11px. Dalam kode, saya kemudian mengatur ulang font ke default: Font = SystemFonts.DefaultFontdan semuanya baik-baik saja.

Hanya dua sen saya. Saya pikir saya berbagi, karena “menjaga AutoScaleMode = Font” , dan “Mengatur ukuran font dalam pixel untuk Perancang” adalah sesuatu yang tidak saya temukan di internet.

Saya memiliki beberapa rincian lebih lanjut di Blog saya: http://www.sgrottel.de/?p=1581&lang=en

Knowleech
sumber
4

Selain jangkar tidak berfungsi dengan baik: Saya akan melangkah lebih jauh dan mengatakan bahwa penentuan posisi yang tepat (alias, menggunakan properti Lokasi) tidak berfungsi dengan baik dengan penskalaan font. Saya harus mengatasi masalah ini dalam dua proyek yang berbeda. Di keduanya, kami harus mengubah posisi semua kontrol WinForms untuk menggunakan TableLayoutPanel dan FlowLayoutPanel. Menggunakan properti Dock (biasanya diatur ke Isi) di dalam TableLayoutPanel bekerja dengan sangat baik dan skala baik dengan font sistem DPI.

Brannon
sumber