دعم DPI العالي في WPF ── أسباب الضبابية والتلطّخ رغم أنه «يفترض أن يكون محصّناً ضد DPI» وطرق العلاج
· آخر تحديث: · غو كومورا · WPF, DPI العالي, Windows, .NET, C#, .NET Framework, XAML, UI, الاستشارات التقنية
سجل التعديلات (2 تحديثات، آخر تحديث 2 Sep، 2026)
سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.
- أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
- أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
- النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621610)
هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.
غو كومورا (2026). دعم DPI العالي في WPF ── أسباب الضبابية والتلطّخ رغم أنه «يفترض أن يكون محصّناً ضد DPI» وطرق العلاج. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621610 https://comcomponent.com/ar/blog/wpf-high-dpi-guide/
- DOI (أحدث نسخة)
- 10.5281/zenodo.21621610
- DOI (هذه النسخة)
- 10.5281/zenodo.22240994
«أليس صحيحاً أنه لا حاجة لفعل أي شيء بخصوص دعم DPI إن كان التطبيق مبنياً بـ WPF؟» سؤال نتلقّاه كثيراً في استشارات الدقة العالية (high DPI). نصف الإجابة صحيح، ونصفها غير صحيح. والأعراض التي تُطرح فعلياً هي: «تبدو الشاشة واضحة على الحاسوب المحمول وحده، لكن عند توصيله بشاشة خارجية في قاعة الاجتماعات تتلطّخ الشاشة كلّها»، أو «النصوص واضحة لكن أيقونات شريط الأدوات وحدها ضبابية»، أو «تختلف سماكة خطوط الحدود في القائمة من مكان لآخر أو تختفي»، أو «الجزء المدمج من عنصر تحكّم تقارير قديم داخل الشاشة صغير وحده». وكلّها أعراض تحدث فعلياً في تطبيقات WPF التي يُفترض أنها «محصّنة ضد DPI».
في مقال الأمس «دعم DPI العالي في WinForms»، نظّمنا آلية تحجيم DPI في Windows، وأوضاع إدراك DPI (Unaware / System Aware / Per-Monitor V2)، وكيفية إنقاذ تطبيقات WinForms من عصر كتابة 96 DPI مباشرة في الشيفرة. وهذا المقال هو نسخة WPF من ذلك. نترك النظرية العامة لـ DPI virtualization وأوضاع إدراك DPI لمقال WinForms، ونركّز هنا على موضوع خاص بـ WPF: «لماذا وأين تبقى المشكلة في WPF الذي يُفترض أنه يدعم DPI منذ البداية». نرتّب حسب ترتيب الاستخدام العملي: تشخيص الأعراض، وطريقة الإعلان عن دعم Per-Monitor DPI (لكل من .NET Framework 4.6.2 و.NET)، وحلول تلطّخ الخطوط والصور النقطية، وفخاخ مزج WindowsFormsHost، وحتى جدول القرار بشأن مدى ما ينبغي فعله.
المصطلحات المستخدمة في هذا المقال
النظرية العامة لأوضاع إدراك DPI نتركها لـ مقال WinForms، لكن نثبت أولاً المصطلحات التي نستخدمها في النص دون مقدّمة.
| المصطلح | المعنى |
|---|---|
| DIP | Device Independent Pixel (وحدة مستقلّة عن الجهاز). وحدة تخطيط WPF تجعل 1/96 إنش يساوي 1. الرقم 120 في Width="120" في XAML هو هذا |
| HWND | معرّف يخصّصه Windows لكل نافذة (مقبض النافذة). يملك WPF HWND للنوافذ العلوية فقط، أما الأزرار والنصوص في الداخل فيرسمها WPF بنفسه. العناصر التي تجلب HWND وحدها تقع «خارج» رسم WPF (الفصل 7) |
| GDI | واجهة برمجة الرسم ثنائي الأبعاد التقليدية في Windows. آلية ترسم بالبكسل، ودعم تحجيم Per-Monitor DPI لها «لا يوجد» في الجدول الرسمي1 |
| الوضع الفرعي للبكسل | حالة لا تقع فيها حدود العنصر على موضع صحيح من البكسل الفعلي. الحافة في موضع كسري تُرسم بتنعيم الحواف (طمس الحدود بلون وسيط) فتبدو متلطّخة (القسم 5.1) |
| DPI virtualization | إجراء إنقاذ: لنظام التشغيل يكبّر أو يصغّر نتيجة رسم النافذة كصورة نقطية لتطبيقات Unaware / System Aware. لا ينهار التخطيط، لكن الشاشة كلّها تتلطّخ1 |
| البيان (manifest) | XML يُضمَّن في الملف التنفيذي. المدخل الوحيد لإعلان وضع إدراك DPI في WPF (الفصل 4) |
1. الخلاصة أولاً
أولاً، ثلاث نقاط تشكّل هيكل المقال كلّه.
- WPF يتابع «DPI عند بدء التشغيل» بشكل صحيح منذ البداية. وحدة التخطيط هي DIP، ونظام الرسم يتحجّم تلقائياً وفق System DPI، فهو System DPI Aware افتراضياً. لا حاجة لضبط أو فحص AutoScaleMode كما في WinForms.2
- المشكلات المتبقّية رغم ذلك تنقسم إلى 4 فئات. (أ) تلطّخ الشاشة كلّها عند النقل إلى شاشة مختلفة DPI، (ب) ضبابية الصور النقطية والأيقونات، (ج) تلطّخ الخطوط وحدود الجداول، (د) المحتوى الممزوج مثل WindowsFormsHost / WebBrowser. الأسباب والعلاج مختلفة، فابدأ بجدول الفصل 3.
- عمل جانب WPF هو «إعلان سطر واحد + فحص الباقي». متابعة التخطيط يتولاها الإطار، فهدف التعديل الحقيقي هو الشيفرة التي تتعامل مع البكسل مباشرة، وأصول الصور النقطية، والمحتوى الممزوج فقط (الفصل 8).
خلاصة كل فئة من الأربع كالتالي.
| الفئة | الخلاصة | التفصيل |
|---|---|---|
| (أ) تلطّخ الشاشة كلّها | العلاج الجذري هو دعم Per-Monitor DPI. يدعم WPF على .NET Framework 4.6.2 فما بعده هذا كإطار، وبمجرّد الإعلان في البيان تُنفَّذ إعادة تحجيم النافذة نفسها تلقائياً34. يبقى WPF على .NET (من Core 3.1 حتى .NET 8) أيضاً System Aware افتراضياً، ولا توجد في WPF آلية مماثلة لـ ApplicationHighDpiMode / SetHighDpiMode في WinForms |
الفصل 4 |
| (ب) ضبابية الصور النقطية | اجعل الأصول المتّجهية مثل Path / Geometry الخيار الأول. وللأصول التي لا تتوفّر إلا كصور نقطية، جهّز دقّات متعدّدة وبدّلها بحسب DPI. الاستيفاء الافتراضي في WPF (Linear) هو الأكثر تسبّباً بالضبابية للصور الصغيرة كالأيقونات عند مقاييس غير صحيحة القسمة5 | القسم 5.3 |
| (ج) تلطّخ الخطوط المفردة | مشكلة وضع فرعي للبكسل سابقة على DPI نفسه. الخطوة الأولى هي UseLayoutRounding="True" على العنصر الجذر، أما SnapsToDevicePixels فأداة أخرى للإلصاق وقت الرسم. القيمة الافتراضية لكليهما معطّلة67 |
القسم 5.1 |
| (د) المحتوى الممزوج | العنصر الذي يحدّد سقف دعم Per-Monitor. ويُذكر صراحة أن سلوك Per-Monitor لـ WPF عندما «يُوضع» داخل ElementHost / HwndSource غير مدعوم رسمياً4 | الفصل 7 |
النظرية العامة لأوضاع إدراك DPI (DPI virtualization، والفرق بين System Aware وPer-Monitor V2، والتجاوز من جانب المستخدم عبر خصائص exe) وكيفية إعداد بيئة اختبار DPI مختلطة مشتركة مع الفصول 2-3 و7 من مقال WinForms، فلا نكرّرها في هذا المقال.
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 24، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
2. لماذا WPF «محصَّن ضد DPI» ── DIP وSystem DPI Aware
جذر مشكلة الدقة العالية في WinForms يكمن في أن الإحداثيات والأحجام مكتوبة مباشرة بالبكسل الفعلي، وأنه يحمل آلية (AutoScale) أُضيفت لاحقاً لإعادة تحويل تخطيط مفترَض على 96 DPI وقت التشغيل. أما نقطة انطلاق WPF فمختلفة. الرقم 120 في Width="120" المكتوبة في XAML ليس بكسلاً فعلياً، بل وحدة مستقلّة عن الجهاز (DIP) تجعل 1/96 من الإنش يساوي 1. يُحسب التخطيط كلّه بهذه الوحدة، ويُحوَّل وقت الرسم إلى معامل يتناسب مع System DPI (1.5 ضعف في حالة 150%). وتُرسم النصوص أيضاً كخطوط متّجهية، فلا يحدث تدهور شبيه بالصور النقطية عند التكبير. وبفضل هذه الآلية، يعمل تطبيق WPF واعياً بـ System DPI دون أي إعلان.2
نضع فيما يلي جدول فروق مع WinForms.
| WinForms | WPF | |
|---|---|---|
| وحدة الإحداثيات والحجم | بكسل فعلي | DIP (1/96 من الإنش) |
| عند عدم الإعلان عن شيء | Unaware (ضبابية بتكبير نظام التشغيل للصورة) | System Aware (واضح على الشاشة الرئيسية) |
| متابعة DPI عند بدء التشغيل | إعادة حساب على مستوى النموذج عبر AutoScaleMode. يلزم ضبط وفحص | تحجيم تلقائي من نظام الرسم. لا حاجة لضبط |
| النموذج النمطي للانهيار عند DPI العالي | تراكب العناصر وقصّها في التخطيط ذي الإحداثيات الثابتة | لا ينهار التخطيط، بل يظهر كتلطّخ وضبابية |
| حوادث ناتجة عن المصمّم (Designer) | إعادة كتابة AutoScaleDimensions (شائعة) | لا يحدث من حيث المبدأ (تُحفظ XAML بوحدة DIP كما هي) |
كما يوضّح الجدول، فإن العمل الذي كان محور الجهد في WinForms ── «إصلاح انهيار التخطيط» و«منع حوادث المصمّم» ── لا يكاد يحدث في WPF. فمقولة «WPF محصَّن ضد DPI» تصوّر صحيح.
غير أن التلقائية تقتصر على نطاق System Aware فقط. وSystem Aware وضع «التكيّف مع دقة DPI الخاصة بالشاشة الرئيسية عند تسجيل الدخول»، ولا يشمل المتابعة (Per-Monitor) لبيئة تختلف فيها DPI من شاشة إلى أخرى. كما أن ما يُكبَّر بوحدة DIP هو فقط ما يرسمه WPF بنفسه (النصوص، الأشكال، عناصر التحكّم)، بينما تصبح الصور النقطية ضبابية عند تكبيرها، والمحتوى الممزوج الذي يحمل HWND يقع خارج نطاق تحجيم WPF. والاستشارات من نوع «كان يُفترض أن يكون محصّناً ضد DPI» تحدث في الغالب في هذا الجزء المتبقّي بالذات.
3. مشكلات تحدث رغم ذلك ── تشخيص السبب من العرض
هذا هو جدول التشخيص الأول الذي نستخدمه عند تلقّي استشارة. وبما أن التطابق بين الأعراض والأسباب في WPF أوضح حتى من WinForms، يمكن تحديد السبب تقريباً من هذا الجدول وحده.
| العرض | السبب | العلاج |
|---|---|---|
| تلطّخ منتظم للشاشة كلّها عند نقلها إلى شاشة بدقة DPI مختلفة، ويُصلح بالعودة إلى الشاشة الأصلية | ما زال System Aware. نظام التشغيل يكبّر النافذة كلّها كصورة نقطية | الفصل 4 (التحوّل إلى Per-Monitor) |
| تلطّخ مباشرة بعد تغيير إعداد التحجيم، أو عند اتّصال RDP من عميل بدقة DPI عالية | السبب نفسه أعلاه (لأن System DPI يُثبَّت عند تسجيل الدخول) | الفصل 4 |
| النصوص واضحة لكن الأيقونات والصور وحدها ضبابية أو صغيرة | تكبير الأصول النقطية بالاستيفاء | القسم 5.3 |
| خط حدود أو إطار بعرض 1px يختلف سُمكه من مكان لآخر، أو يتلطّخ قليلاً | وضع فرعي للبكسل وتنعيم الحواف | القسم 5.1 |
| تظهر النصوص الصغيرة متلطّخة على شاشة بدقة 100% (96 DPI) | تنعيم حواف تنسيق النص الافتراضي (Ideal) | القسم 5.2 |
| تصبح الصور المصنوعة ذاتياً (WriteableBitmap، RenderTargetBitmap، إلخ) ضبابية | حساب حجم البكسل على افتراض 96 DPI | الفصل 6 |
| ينزاح موضع النافذة أو إحداثيات الفأرة أو إحداثيات لقطة الشاشة | خلط بين DIP والبكسل الفعلي | الفصل 6 |
| داخل WindowsFormsHost / WebBrowser / بعض عناصر تحكّم الجهات الخارجية فقط صغير أو خشن أو منهار | محتوى ممزوج (خارج نطاق تحجيم WPF) | الفصل 7 |
نضيف توضيحاً بشأن السطر الأول «تلطّخ منتظم للشاشة كلّها». هذه حالة يعمل فيها DPI virtualization (تكبير نظام التشغيل للصورة النقطية) المذكور في الفصل 2 من مقال WinForms على تطبيق واعٍ بـ System DPI، وليس عطلاً في التطبيق. فالتطبيق الواعي بـ System DPI يرسم بـ«دقة DPI الخاصة بالشاشة الرئيسية عند تسجيل الدخول»، وفي الشاشات ذات الدقة المختلفة يكبّر نظام التشغيل أو يصغّر لموازنة الفرق. هذا إجراء إنقاذي من نظام التشغيل: لا ينهار التخطيط، لكنه يتلطّخ بدلاً من ذلك.2 والإصلاح يعني قطع هذا الإنقاذ والإعلان عن «سأتابع بنفسي دقة DPI الخاصة بكل شاشة»، أي التحوّل إلى Per-Monitor.
وعلى العكس، يمكن إصلاح الأعراض من السطر الثالث فما بعده حتى مع البقاء على System Aware. فإن كانت الاستشارة من نوع «لا يوجد تشغيل متعدّد الشاشات، لكن الأيقونات وخطوط الحدود قذرة على شاشة 150%»، يمكن تخطّي الفصل 4 والبدء من الفصل 5 مباشرة.
3.1 خطوات إعادة إنتاج الأعراض والتحقّق منها على جهازك
هذا المقال لا يتضمّن لقطات شاشة. التلطّخ والضبابية أوضح حين تعيد إنتاجهما على جهازك من أن تراهما في صورة ثابتة. بالخطوات التالية أكّد أعراض الجدول أعلاه بعينك. كيفية بناء بيئة اختبار DPI مختلطة نفسها في الفصل 7 من مقال WinForms، لكن يمكن التحقّق إلى هذا الحد حتى بشاشة واحدة.
- غيّر معامل التكبير. ابدأ > الإعدادات > النظام > العرض > «تغيير الحجم والتخطيط» > «تغيير حجم النص والتطبيقات والعناصر الأخرى»، وبدّل بين 100% / 125% / 150%.8 أكبر أثر يظهر عند 125% و150% (لأنهما غير صحيحتي القسمة، وهذا موضوع القسم 4.1).
- أعد تشغيل التطبيق. WPF System Aware افتراضياً، لذا التطبيق قيد التشغيل لا يتابع معامل التكبير الجديد. بعد التغيير أعد التشغيل حتماً. إن لم يُصلح ذلك، سجّل الخروج ثم الدخول (لأن System DPI يُثبَّت عند تسجيل الدخول).
- انظر إلى التفاصيل بعدسة المكبّر. مفتاح شعار Windows +
+(زائد) يشغّل المكبّر.9 ارفع إلى نحو 400% وانظر إلى المواضع الثلاثة بالترتيب.- خطوط الحدود والإطارات ── إن برز رمادي خفيف بعرض 1px على أحد جانبي الخط أو كليهما، فهذا الوضع الفرعي للبكسل في القسم 5.1. الفيصل أن خطوطاً محدَّدة كلّها بـ
1تبدو مختلفة الكثافة أو السماكة من سطر لآخر. - الأيقونات والصور ── إن تلّطخ المحيط بطبقة مزدوجة، وتحوّل خط كان يفترض أن يكون 1px إلى لون وسيط بعرض 2px، فهذا تكبير الاستيفاء النقطي في القسم 5.3. وضوح نص الشاشة نفسها هو فيصل التشخيص (النص متّجه فلا يضباب).
- الشاشة كلّها ── إن ضباب النص وخطوط الحدود والأيقونات بالقدر نفسه، فالمشكلة ليست رسماً فردياً بل DPI virtualization من نظام التشغيل. في هذه الحالة انتقل إلى الفصل 4.
- خطوط الحدود والإطارات ── إن برز رمادي خفيف بعرض 1px على أحد جانبي الخط أو كليهما، فهذا الوضع الفرعي للبكسل في القسم 5.1. الفيصل أن خطوطاً محدَّدة كلّها بـ
- انقل النافذة بين الشاشات (إن وُجدت شاشتان أو أكثر). اسحب النافذة إلى شاشة بمعامل تكبير مختلف. إن تلّطخت في الوجهة وعادت بالرجوع، فهذا الصف الأول في الجدول. وإن لم يتغيّر المظهر بالنقل، فإن (أ) لا يحدث.
- غيّر معامل التكبير أثناء التشغيل. غيّر إعداد الخطوة 1 والتطبيق ما زال يعمل. تطبيق System Aware يتلطّخ في الحال.
الخطوات 1 إلى 5 تصلح كما هي كإجراء فحص بعد التحوّل إلى Per-Monitor. بنود الاختبار الموصى بها رسمياً أربعة أيضاً: النقل بين الشاشات، والتشغيل عند DPI مختلفة، وتغيير معامل التكبير أثناء التشغيل، وتسجيل الخروج/الدخول بعد تبديل الشاشة الرئيسية.1
4. تلطّخ الشاشات المتعدّدة ── دعم Per-Monitor DPI
4.1 حدود System Aware
يُثبَّت System DPI عند تسجيل الدخول بدقة DPI الخاصة بالشاشة الرئيسية. فإن كانت شاشة الحاسوب المحمول المدمجة بدقة 150% هي الشاشة الرئيسية، يرسم WPF كل النوافذ بمعامل 1.5، وعند النقل إلى شاشة خارجية بدقة 100% يعرضها نظام التشغيل مصغَّرة إلى 2/3. ويبدو هذا التحجيم من نظام التشغيل ضبابياً بوجه خاص عند النسب غير الصحيحة القسمة.2 ويمكن إدراك بالتجربة أن التدهور البصري في تركيبة غير مستوية مثل 125% و150% أكبر من فرق الضعفين بين 200% و100%.
بمعنى آخر، تقتصر المشكلة في WPF الواعي بـ System DPI على الاستخدام المتنقّل بين شاشات بدقّات DPI مختلفة، والحالات التي يتغيّر فيها إعداد التحجيم بعد تسجيل الدخول (كتغيّر الشاشة الرئيسية بفصل محطّة الإرساء وتوصيلها، أو دخول دقة DPI الخاصة بالعميل عبر RDP، وما شابه). وإن كان تطبيقاً داخلياً يستخدمه الجميع على سطح مكتب ثابت بشاشة واحدة، يكتمل الأمر عملياً بالبقاء على System Aware. سنعود إلى هذا القرار في جدول الفصل 8.
4.2 طريقة الإعلان في .NET Framework 4.6.2 فما بعده
دُمج دعم Per-Monitor DPI في WPF ضمن .NET Framework 4.6.2.3 وقبل ذلك، حتى لو أُعلن عن Per-Monitor لنظام التشغيل، لم يكن جانب WPF يتابع تغيّر DPI، وكان يلزم كتابة إعادة تحجيم النافذة كلّها يدوياً (ولهذا السبب كانت أمثلة عصر Windows 8.1 مبنية بشكل كبير باستخدام DLL مساعدة أصلية). وابتداءً من 4.6.2، يعالج WPF رسالة WM_DPICHANGED تلقائياً، ويتولّى ضبط حجم النافذة وإعادة التخطيط وإعادة الرسم.
المتطلّبان اثنان: نظام تشغيل Windows 10 Anniversary Update (1607) فما بعده، وبناء يستهدف .NET Framework 4.6.2 فما بعده.4 يُكتب الإعلان في بيان التطبيق.
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings>
<!-- .NET Framework 4.6.2〜4.7.2 は PerMonitor 単独で宣言する(開発者ガイドと同じ)。
WPF の PerMonitorV2 サポートは .NET Framework 4.8 以降のため、
4.8+ / .NET では「PerMonitorV2, PerMonitor」と V2 を先頭に置く -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"
>PerMonitor</dpiAwareness>
<!-- dpiAwareness を認識しない古い OS 向け(System Aware にフォールバック) -->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
لهذا البناء المزدوج معنى. يُتعرَّف على عنصر dpiAwareness ابتداءً من Windows 10 1607، ويُستخدم أول قيمة يُتعرَّف عليها من بين القيم المفصولة بفاصلة.10 وفي أنظمة التشغيل القديمة التي لا تعرف عنصر dpiAwareness نفسه، يحدث رجوع احتياطي إلى إعلان dpiAware (System Aware)، وهذا هو المنطق.
انتبه هنا إلى نقطة التمييز بين استخدام PerMonitor وPerMonitorV2 بحسب هدف بناء الإطار. فـ دعم WPF الرسمي لـ PerMonitorV2 (وMixed-Mode DPI) يبدأ من .NET Framework 4.8 فما بعده11، وحتى مثال دليل مطوّري 4.6.2 هو إعلان PerMonitor منفردة.4 فإن كتبت V2 في المقدّمة مع هدف بناء 4.6.2 إلى 4.7.2، سيختار نظام التشغيل Windows 10 1703 فما بعده وضع V2 غير المدعوم من الإطار، فينتج سلوك غير متوقَّع خصوصاً حول المحتوى الممزوج كـ WindowsFormsHost. وننصح بـالترقية إلى 4.8 فما بعده (أو WPF على .NET) أولاً، ثم وضع V2 في المقدّمة بكتابة PerMonitorV2, PerMonitor، وترك تحجيم المناطق غير الخاصة بالعميل كشريط العنوان وشريط التمرير لنظام التشغيل (راجع الفصل 3 من مقال WinForms).
توجد مطبّة واحدة تتعلّق بهدف البناء. فحتى لو كان .NET Framework الخاص ببيئة التشغيل 4.6.2 فما بعده، إن بقي هدف المشروع 4.6.1 فما قبله، تكون متابعة Per-Monitor معطّلة افتراضياً. في هذه الحالة، فعّلها صراحة عبر مفتاح AppContext في app.config.4
<configuration>
<runtime>
<!-- 二重否定に注意: 「DPI変更でスケールしない」を false にする=有効化 -->
<AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/>
</runtime>
</configuration>
سبب الاستفسارات من نوع «كتبت البيان لكنه لا يعمل» يعود بحسب التجربة تقريباً إلى إحدى حالتين: إما هدف البناء هذا، أو أن البيان غير مدرَج في البناء (بقاء إعدادات المشروع على البيان الافتراضي).
4.3 طريقة الإعلان في .NET (من Core 3.1 إلى .NET 8)
يبقى WPF على .NET أيضاً واعياً بـ System DPI افتراضياً بلا بيان. وفي مقابل امتلاك WinForms مدخلاً مخصّصاً في ملف المشروع مثل ApplicationHighDpiMode أو Application.SetHighDpiMode، لا توجد في WPF آلية رسمية لتبديل وضع الوعي بـ DPI من الشيفرة أو إعدادات المشروع. طريقة الإعلان هي نفسها في .NET Framework.
خطوات إضافة البيان يمكن كتابتها بأسماء القوائم دون لقطات شاشة.
- في مستكشف الحلول انقر بالزر الأيمن على المشروع واختر «إضافة» > «عنصر جديد» (الاختصار Ctrl+Shift+A).
- من القائمة اختر قالب «ملف بيان التطبيق»، وأضفه بالاسم الافتراضي
app.manifest. - افتح
app.manifestالمضاف، واكتب إعلانdpiAwarenessنفسه في القسم 4.2 كعنصر<asmv3:application>. القالب يحتوي سلفاً علىrequestedExecutionLevel(مستوى طلب UAC) وغيره، فـأضف دون حذفها. - في
<PropertyGroup>بملف المشروع اكتب<ApplicationManifest>app.manifest</ApplicationManifest>للإشارة إليه (في مشاريع نمط SDK قد يُتعرَّف تلقائياً إن أُضيف باسمapp.manifest، لكن التصريح أوضح).
في مشاريع .NET Framework، بدل الخطوة 4 اختر البيان المضاف من القائمة المنسدلة خصائص المشروع > علامة تبويب «التطبيق» > «البيان».
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
</Project>
بما أن بيئة التشغيل حديثة، فلا حاجة إلى مفتاح AppContext الوارد في القسم 4.2. انتبه فقط إلى أن «الانتقال إلى .NET» لا يعني «حل مشكلة الدقة العالية تلقائياً». فسهولة الانتقال تخص جانب WinForms (القسم 4.1 من مقال WinForms)، أما WPF فقد بلغ منذ .NET Framework 4.6.2 المستوى نفسه الذي هو عليه الآن.
يبقى بعد الإعلان عمل ينبغي إنجازه كما في WinForms، لكن المحتوى مختلف. في WinForms كانت ساحة المعركة الرئيسية هي إصلاح انهيار التخطيط. أما في WPF فيتابع الإطار التخطيط بنفسه، فيبقى فحص عرض الشاشات كلّها، وتبديل الأصول النقطية بحسب DPI (القسم 5.3)، ومتابعة الشيفرة التي تتعامل مع البكسل مباشرة (الفصل 6)، والتحقّق من المحتوى الممزوج (الفصل 7). وبعدد شاشات متساوٍ، عادة ما يكون إجمالي جهد التحوّل إلى Per-Monitor أصغر بدرجة من WinForms.
5. تلطّخ الرسم وضبابيته ── الخطوط المفردة والنصوص والصور النقطية
محتوى هذا الفصل مستقل عن التحوّل إلى Per-Monitor. فهو يعمل حتى مع البقاء على System Aware، لذا يستحق التطبيق حتى في التطبيقات التي لا تعمل بشاشات متعدّدة.
5.1 تلطّخ الخطوط المفردة وحدود الجداول ── UseLayoutRounding وSnapsToDevicePixels
التخطيط بوحدة DIP يعني أن حدود العناصر لا تقع بالضرورة على مواضع صحيحة من البكسل الفعلي. ففي 125%، يصبح 1 DIP = 1.25px، فيكون خط حدود بعرض 1 DIP معادلاً لـ 1.25 بكسل فعلي، وتُرسم الحافة التي تقع في منتصف بكسل شبه شفافة بتنعيم الحواف. والنتيجة هي «تلطّخ الخط» و«اختلاف السماكة الظاهرة من سطر لآخر رغم أن التحديد 1px نفسه في الكل». وحتى عند 96 DPI، يحدث الأمر نفسه إن وقع الحساب على 0.5px بسبب قسمة أحجام النجمة (*) في Grid أو حساب هوامش التوسيط. هذه خاصية رسم فرعي للبكسل في WPF، وليست مشكلة DPI بحد ذاتها، بل مشكلة «يسهل ظهورها كلّما ارتفعت DPI».6
الخطوة الأولى هي تقريب التخطيط. أضف سطراً واحداً إلى العنصر الجذر.
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
UseLayoutRounding="True">
UseLayoutRounding آلية تقرّب قيم البكسل غير الصحيحة في مسار التخطيط، وهي معطّلة افتراضياً، وإن ضُبطت على الجذر تنتشر إلى الشجرة البصرية كلّها.6 أما SnapsToDevicePixels المشابه في الاسم فدوره مختلف: لا يعمل على التخطيط، بل يلصق الحواف بحدود البكسل وقت الرسم. وهو معطّل افتراضياً (false) أيضاً، ويُورَّث الضبط إلى الشجرة الفرعية. وتذكر الوثائق الرسمية استخدامه في تخفيف التلطّخ الناتج عن تنعيم الحواف حول الخطوط المفردة في بيئات تتجاوز 96 DPI.7
| UseLayoutRounding | SnapsToDevicePixels | |
|---|---|---|
| توقيت العمل | مسار التخطيط (تقريب نتائج Measure / Arrange إلى بكسل صحيح) | وقت الرسم (إلصاق الحواف بحدود البكسل) |
| القيمة الافتراضية | false | false |
| نطاق التطبيق | ينتشر إلى العناصر السليلة إن ضُبط على الجذر | يُورَّث إلى العناصر السليلة أيضاً |
| الأثر الرئيسي | التخطيط عموماً. انزياح 0.5px في حجم العنصر وموضعه، وما ينتج عنه من تلطّخ الخطوط والحدود | تلطّخ متبقٍّ لعناصر فردية مثل Border والخطوط المفردة |
| معيار الاستخدام | ضعه أولاً على النافذة الجذر. للتطبيقات الجديدة، اجعله في نمط مشترك لكل النوافذ | طبّقه بدقة على المواضع المتبقّية بعد UseLayoutRounding |
إن تردّدت، فالترتيب هو: «UseLayoutRounding="True" على الجذر، ثم SnapsToDevicePixels="True" على ما يبقى». وقد يكون من الآثار الجانبية للتقريب أن تصبح عروض الأعمدة المقسَّمة بالتساوي عبر أحجام النجمة غير متساوية بمقدار 1px، لكن نادراً ما شكّل هذا مشكلة في شاشات تطبيقات الأعمال حسب تجربتنا. أما تلطّخ الشيفرة التي ترسم ذاتياً عبر DrawingContext، فتوجد له أيضاً أداة منخفضة المستوى تسمّى GuidelineSet، لكن تقريب إحداثيات الرسم فقط يحل معظم الحالات.
5.2 تلطّخ النصوص ── TextFormattingMode
الشكوى القديمة «نصوص WPF باهتة أو متلطّخة» تتعلّق بوضع تنسيق النص أكثر من DPI. لتنسيق النص في WPF وضعان: Ideal (الافتراضي) الذي يضع الأحرف وفق المقاييس المثالية الأصلية للخط، وDisplay الذي يضعها وفق مقاييس متوافقة مع GDI.12 وIdeal يمتاز بتباعد أحرف جميل ومرونة مع التحجيم، لكن رسم نص صغير نسبياً عند 96 DPI (100%) قد يبدو متلطّخاً بتنعيم الحواف. وضبط TextOptions.TextFormattingMode="Display" على الجذر يمنح إحساساً بالوضوح شبيهاً بـ WinForms.
لكن الأمر يعكس نفسه في بيئات الدقة العالية. فـ Display تنسيق يتوافق مع شبكة بكسل 96 DPI، لذا قد تنخفض الجودة عكسياً تحت التحجيم. والفصل العملي هو: إن كان معظم المستخدمين على بيئة 100%، استخدم Display، أما في البيئة القياسية الحالية التي أصبح 125% فما فوقها السائد فيها، فابقَ على Ideal الافتراضي. ويمكن أيضاً تنفيذ تبديل إلى Display فقط عند 100%، لكن الشاشات التي تستحق هذا الجهد (كشاشات محرّرات النصوص) محدودة.
5.3 ضبابية الصور والأيقونات ── تفضيل الأصول المتّجهية ودقّات متعدّدة
يضع WPF أيضاً الصور النقطية المحدَّدة في Image بوحدة DIP، ويكبّرها بحسب DPI. فأيقونة بحجم 16×16px تُكبَّر بالاستيفاء إلى 24×24px في بيئة 150%، وتصبح ضبابية بوضوح مع خوارزمية الاستيفاء الافتراضية (Linear).5 ومظهر «النصوص واضحة لكن الأيقونات وحدها باهتة» في تطبيقات WPF يعود تقريباً كلّه إلى هذا.
توجد 3 حلول مرتَّبة حسب الأولوية.
- اجعلها أصولاً متّجهية (الخيار الأول). الأيقونات المحفوظة بصيغة
Path/Geometry/DrawingImageتُرسم واضحة عند أي DPI، ولا تحتاج شيفرة تبديل. وتتوفّر أدوات تصميم وأدوات تحويل من SVG إلى XAML بكثرة. يستحق الأمر أن يكون قاعدة أن أيقونات التطبيقات الجديدة تُحفظ متّجهية منذ البداية. وخطوط الأيقونات مثل Segoe MDL2 Assets تمنح الأثر نفسه. - بدّل الصور النقطية متعدّدة الدقّات بحسب DPI. للأصول التي لا يمكن تحويلها إلى متّجهية كالصور الفوتوغرافية ولقطات الشاشة، جهّز نسخاً لـ 96 / 120 / 144 / 192 DPI (16 / 20 / 24 / 32px مثلاً)، واختر بحسب DPI الحالية. ويوصي الدليل الرسمي للمطوّرين أيضاً بتبديل الأصول بحسب DPI كعلاج للضبابية.2
- اختر الاستيفاء عبر
RenderOptions.BitmapScalingMode. حل مخفّف عندما لا يمكن زيادة عدد الأصول. عند مضاعفات صحيحة مثل 200%، يكون التكبير كنقاط بـNearestNeighborأكثر وضوحاً، بينما يناسبHighQuality(Fant) تصغير الصور الكبيرة.5
يُنفَّذ تبديل الخيار 2 عبر OnDpiChanged (متاح ابتداءً من .NET Framework 4.6.2) إن كان دعم Per-Monitor مفعَّلاً بالفعل.
public partial class MainWindow : Window
{
protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi)
{
base.OnDpiChanged(oldDpi, newDpi);
// モニター間移動やスケーリング変更のたびに呼ばれる
AppIcon.Source = IconAssets.SelectFor(newDpi.DpiScaleX);
}
}
public static class IconAssets
{
// 16 / 24 / 32 px で用意した資産から拡大率に合うものを選ぶ
public static BitmapImage SelectFor(double scale) => scale switch
{
<= 1.0 => Load("icon16.png"),
<= 1.5 => Load("icon24.png"),
_ => Load("icon32.png"),
};
private static BitmapImage Load(string name) =>
new(new Uri($"pack://application:,,,/Assets/{name}"));
}
إن بقيت على System Aware، لا تتغيّر DPI بعد بدء التشغيل، فيكفي اختيارها مرّة واحدة عند بدء التشغيل (Loaded) عبر VisualTreeHelper.GetDpi(this). وقبل كتابة شيفرة التبديل، فإن إعادة طرح السؤال في كل مرّة: «ألا يمكن أصلاً جعل هذا الأصل متّجهياً؟» هو في النهاية أسهل الطرق صيانة.
6. الشيفرة التي تتعامل مع البكسل ومتابعة تغيّر DPI
ما يخرج عن مظلّة التحجيم التلقائي لـ WPF هو الشيفرة التي تحسب البكسل الفعلي بنفسها. توجد ثلاثة أنماط نمطية، وكلّها تظهر بشكل مزعج: «مثالي على جهاز التطوير (100%)، وضبابي أو منزاح فقط عند العميل (150%)».
الأول هو شيفرة تصنع مخزناً مؤقّتاً للبكسل بنفسها، مثل WriteableBitmap / RenderTargetBitmap. فإن صُنعت بعدد بكسل مساوٍ لحجم DIP، تُكبَّر بالاستيفاء 1.5 ضعف في بيئة 150% وتتلطّخ. يمكن الحصول على DPI الحالية من بنية DpiScale التي يعيدها VisualTreeHelper.GetDpi، فاجعل عدد البكسل بالبكسل الفعلي، وضمّن DPI بشكل صحيح في المخزن المؤقّت.
// imageHost: 描画結果を表示する要素(ActualWidth/Height は DIP)
DpiScale dpi = VisualTreeHelper.GetDpi(imageHost);
int pixelWidth = (int)Math.Ceiling(imageHost.ActualWidth * dpi.DpiScaleX);
int pixelHeight = (int)Math.Ceiling(imageHost.ActualHeight * dpi.DpiScaleY);
// 96,96 固定で作らず、実際の DPI を渡す(1 物理ピクセル = 1 バッファピクセルになる)
var bitmap = new WriteableBitmap(
pixelWidth, pixelHeight,
dpi.PixelsPerInchX, dpi.PixelsPerInchY,
PixelFormats.Bgra32, null);
الثاني هو إحداثيات الشاشة والتفاعل مع Win32. الإحداثيات التي يعيدها PointToScreen، والإحداثيات المستقبَلة عبر WM_MOUSEMOVE أو الخطّافات، والإحداثيات الممرَّرة إلى MoveWindow، كلّها بالبكسل الفعلي، وإن خُلطت مع DIP في جانب XAML تنزاح بمقدار 1.25 ضعف في بيئة 125%. استخدم مصفوفة CompositionTarget للتحويل.
var source = PresentationSource.FromVisual(this);
if (source?.CompositionTarget is { } target)
{
// DIP → 物理ピクセル
Point device = target.TransformToDevice.Transform(new Point(x, y));
// 物理ピクセル → DIP
Point dip = target.TransformFromDevice.Transform(devicePoint);
}
الثالث هو التخزين المؤقّت (caching). إن كنت تخزّن مؤقّتاً صوراً نقطية أو قيم تخطيط أو نصوصاً منسَّقة صُنعت بتضمين DPI، فسيصبح ذلك بالياً في كل مرّة تُنقل فيها النافذة بين الشاشات بعد التحوّل إلى Per-Monitor. أعد إنشاءها عبر OnDpiChanged (أو حدث DpiChanged الخاص بالنافذة) كما في القسم 5.3. وهنا أيضاً، إن بقيت على System Aware، فإن DPI ثابتة عند بدء التشغيل، فلا حاجة لشيفرة متابعة. وتقدير «تكلفة التحوّل إلى Per-Monitor = عدد الشيفرات التي تتعامل مع البكسل» يتوافق جيداً مع الواقع.
علماً بأن التعامل مع خيط الواجهة عند تنفيذ معالجة إعادة الإنشاء هذه بشكل غير متزامن هو كما نظّمناه في «async وخيط الواجهة في WPF/WinForms».
7. فخاخ المحتوى الممزوج ── WindowsFormsHost وWebBrowser والجهات الخارجية
يقتصر امتداد التحجيم التلقائي لـ WPF على ما يرسمه WPF بنفسه. العناصر التي تحمل HWND تقع «خارج» رسم WPF، فيصبح التعامل مع DPI أكثر تعقيداً بدرجة. وتذكر الوثائق الرسمية لـ Windows أيضاً صراحة: «الإطار الآخر الموضوع داخل WPF، وWPF الموضوع داخل إطار آخر، لا يُحجَّمان تلقائياً».1
| شكل المزج | ما الذي يحدث | التعامل الواقعي |
|---|---|---|
| WindowsFormsHost (وضع WinForms داخل WPF) | يحوّل المضيف بين نظامي إحداثيات DIP والبكسل الفعلي، لكن تحجيم المحتوى الداخلي يبقى في حدود ما يستطيع عنصر تحكّم WinForms التعامل معه13 | افحص دعم DPI لجانب WinForms الداخلي (AutoScaleMode وغيره) وفق معيار مقال WinForms. لا تتوقّع متابعة Per-Monitor |
| عنصر تحكّم WebBrowser (محرّك IE) | محتوى أصلي بـ HWND منفصل. قد لا تتطابق نسبة التكبير مع تحجيم التطبيق | يُعامل كإرث (legacy). أضفه إلى مواد النظر عند دراسة الانتقال إلى WebView2 |
| ElementHost / HwndSource (وضع WPF داخل WinForms أو Win32) | سيناريو Per-Monitor غير مدعوم رسمياً4 | صمّم في حدود System Aware بما يتوافق مع وضع الوعي بـ DPI الخاص بالتطبيق المضيف |
| عناصر تحكّم من جهات خارجية | مستوى الدعم يختلف من منتج لآخر. المنتجات غير الداعمة لـ Per-Monitor تحدّد الحد الأعلى لتلك الشاشة | اجرِ جرداً لحالة دعم المورّد وإصداراته قبل اتّخاذ قرار التحوّل إلى Per-Monitor |
الأكثر شيوعاً في العمل الفعلي هو السطر الأول. فإن حوّلت إلى Per-Monitor تطبيقاً بتشكيل «انتقل إلى WPF، لكن معاينة التقارير والرسوم البيانية وحدها لا تزال تحمل عناصر تحكّم WinForms / ActiveX قديمة عبر WindowsFormsHost»، سيتابع جزء WPF بشكل مثالي، بينما الجزء الداخلي للمضيف وحده لا يتابع DPI بعد النقل، ويبقى صغيراً وخشناً. وتوجد آلية على مستوى Win32 لاستضافة DPI مختلطة (SetThreadDpiHostingBehavior)، لكنها ليست شيئاً يمكن استخدامه بسهولة من WPF، ونحن في شركتنا نبدأ بحسم الأمر على أن «الجزء الممزوج يحدّد الحد الأعلى لدعم Per-Monitor»، ونبدأ بإعداد قائمة المحتوى الممزوج أولاً. ومواد القرار في اتّجاه إزالة المزج مجمَّعة في «كيف تختار بين WinForms وWPF وWinUI» و«الإبقاء على ActiveX/OCX أم تغليفه أم استبداله».
8. إلى أين تصل ── جدول القرار والتقدّم المرحلي
في حالة WPF، الخياران الفعليان اثنان فقط (لا وجود لخيار «لا تفعل شيئاً = Unaware» كما في WinForms، لأنه واعٍ بـ System DPI حتى بلا إعلان).
| البقاء على System Aware | دعم Per-Monitor | |
|---|---|---|
| المظهر | واضح على الشاشة الرئيسية. يتلطّخ على شاشة بدقة DPI مختلفة، أو بعد تغيير التحجيم، أو عبر RDP | واضح على كل الشاشات |
| عمل الإعلان | لا حاجة (افتراضي) | إضافة البيان فقط (الفصل 4) |
| العمل المتبقّي بعد الإعلان | ── | فحص عرض الشاشات كلّها + تبديل الأصول النقطية بحسب DPI (القسم 5.3) + دعم DpiChanged للشيفرة المتعلّقة بالبكسل (الفصل 6) + التحقّق من المحتوى الممزوج (الفصل 7) |
| العنصر الذي يحدّد السقف | ── | WindowsFormsHost وWebBrowser وعناصر تحكّم الجهات الخارجية |
| الحالة المناسبة | تطبيق داخلي يركّز على شاشة واحدة وسطح مكتب ثابت. تطبيق يحوي محتوًى ممزوجاً كثيراً | استخدام مختلط بين حاسوب محمول وشاشة خارجية، تطبيق رئيسي طويل العمر، منتج يُوزَّع على العملاء |
محاور القرار هي نفسها المذكورة في الفصل 7 من مقال WinForms (عمر التطبيق، بيئة الاستخدام، ميزانية التعديل)، لكن الفرق هو أن التكلفة الحدّية للتحوّل إلى Per-Monitor في WPF صغيرة. الإعلان ملف واحد، والتخطيط يتابعه الإطار، والعمل المتبقّي يتناسب مع عدد الشيفرات المتعلّقة بالبكسل والأصول. وإن كان التطبيق قليل المحتوى الممزوج، فإن عائد الاستثمار في الوصول إلى Per-Monitor أعلى بوضوح منه في WinForms. والطريقة التي نوصي بها فعلياً في مشاريعنا تتألّف من ثلاث مراحل.
- تحسين الجودة مع البقاء على System Aware:
UseLayoutRoundingعلى الجذر، وتحويل الأيقونات إلى متّجهية أو دقّات متعدّدة، وإصلاح DPI في سلسلةWriteableBitmap. تُحل هنا كل المشكلات باستثناء تلطّخ الشاشات المتعدّدة، وحتى إن قرّر عدم التحوّل إلى Per-Monitor، يبقى هذا العمل أصلاً مفيداً. - إعلان Per-Monitor والفحص: أضف البيان، وشغّل الشاشات كلّها في بيئة DPI مختلطة لاستخراج المواضع المشكلة. والمشكلات التي تُكتشف هنا ينبغي أن تُصنَّف ضمن أحد الفصول 5 إلى 7.
- تنفيذ متابعة تغيّر DPI: تبديل الأصول وإعادة إنشاء التخزين المؤقّت عبر
OnDpiChanged. وفي هذه المرحلة يُحدَّد «إلى أين يُقبل المحتوى الممزوج».
يمكن استخدام بيئة الاختبار المذكورة في الفصل 7 من مقال WinForms كما هي (شاشتان بتحجيم مختلف، وتبديل الشاشة الرئيسية مع إعادة تسجيل الدخول، وتغيير التحجيم أثناء التشغيل، وRDP من عميل بدقة DPI عالية).1 وما ينبغي التركيز عليه في WPF هو خطوط الحدود والأيقونات والرسم الذاتي عند النسب غير الصحيحة القسمة (125% / 150%)، وسلوك سحب النافذة عبر الشاشات. ومعرفة توزيع بيئات الاستخدام (نسبة المستخدمين الذين يعملون بشاشات متعدّدة) مسبقاً يجعل قرار الاستثمار ثابتاً دون تذبذب. تناولنا هذا المنظور أيضاً في «تصميم UX لتطبيقات Windows».
9. الخلاصة
بفضل DIP والتحجيم التلقائي، يكون WPF واعياً بـ System DPI منذ البداية، ولا يكاد وجود لـ«صراع انهيار التخطيط» الذي كان الساحة الرئيسية في WinForms. ومع ذلك، تبقى أربع فئات من المشكلات ── تلطّخ الشاشات المتعدّدة (عدم دعم Per-Monitor)، وضبابية الصور النقطية، وتلطّخ الخطوط المفردة، والمحتوى الممزوج ── ولكل منها علاج مستقر.
- تلطّخ الشاشات المتعدّدة: في WPF على .NET Framework 4.6.2 فما بعده أو .NET، يُحصل على متابعة Per-Monitor تلقائياً بمجرّد إعلان البيان
- الخطوط المفردة وحدود الجداول: الخطوة الأولى هي
UseLayoutRounding="True"على الجذر، ثمSnapsToDevicePixelsلما يبقى - الأيقونات: الأصول المتّجهية هي الخيار الأول، والصور النقطية تحتاج دقّات متعدّدة + تبديلاً بحسب DPI
- الشيفرة التي تتعامل مع البكسل وحدها، كـ
WriteableBitmapوإحداثيات الشاشة، هي الهدف الحقيقي للتعديل. وعددها هو ما يحدّد جهد التحوّل إلى Per-Monitor - WindowsFormsHost / WebBrowser / الجهات الخارجية هي ما يحدّد سقف الدعم. اجرِ جرداً لها أولاً
حتى التطبيقات التي توقّفت عند فكرة «لا بد أن يكون الأمر جيداً لأنه WPF»، غالباً ما يتبيّن من خلال هذا التنظيم أن المواضع التي تحتاج إصلاحاً محدودة العدد. إن تردّدت في تحديد إلى أين يمكن إصلاح تطبيقك، أو في مسح الوضع الحالي بما فيه المحتوى الممزوج، أو في تحديد مستوى الدعم المناسب، يمكننا مساعدتك.
مقالات ذات صلة
- دعم DPI العالي في WinForms ── أسباب الضبابية والانهيار على شاشات 4K والمعالجة الواقعية
- كيف تختار بين WinForms وWPF وWinUI - جدول قرار عملي
- async وخيط الواجهة في WPF/WinForms في صفحة واحدة
- تصميم UX لتطبيقات Windows - الأولويات بحسب بيئة الاستخدام
مجالات الاستشارة ذات الصلة
تتعامل شركة كومورا سوفت ذ.م.م. مع دعم الدقة العالية في تطبيقات WPF / WinForms (مسح الوضع الحالي، وقرار إمكانية التحوّل إلى Per-Monitor، وجرد المحتوى الممزوج وتعديله)، والتحقيق في أسباب أعطال العرض المصاحبة لاستبدال الحواسيب أو إدخال شاشات 4K، واستشارات تجديد واجهة المستخدم.
- الاستشارة التقنية ومراجعة التصميم
- تطوير تطبيقات Windows
- استغلال الأصول القائمة ودعم الانتقال
- التواصل معنا
روابط مرجعية
-
Microsoft Learn, High DPI Desktop Application Development on Windows. حول جدول دعم Per-Monitor بحسب إطار واجهة المستخدم، وأن الإطار الآخر الموضوع داخل WPF وWPF الموضوع داخل إطار آخر لا يُحجَّمان تلقائياً، ونقاط الاختبار في بيئة DPI مختلطة. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Developing a Per-Monitor DPI-Aware WPF Application. حول أن WPF واعٍ بـ System DPI افتراضياً، وآلية التحجيم التلقائي عبر DIP، وأن النقل إلى شاشة بدقة DPI مختلفة يجعل نظام التشغيل يحجّم الصورة فتصبح ضبابية بوجه خاص عند النسب غير الصحيحة القسمة، وفكرة تبديل الأصول النقطية بحسب DPI. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, What’s new in .NET Framework. حول تفعيل الوعي بـ Per-Monitor DPI في WPF على .NET Framework 4.6.2، ومفتاح Switch.System.Windows.DoNotScaleForDpiChanges، ودليل المطوّرين على GitHub. ↩ ↩2
-
GitHub (microsoft/WPF-Samples), Per Monitor DPI Developer Guide. حول متطلّبات Windows 10 Anniversary Update و.NET Framework 4.6.2 فما بعده، وصياغة dpiAwareness / dpiAware في البيان، وAppContextSwitchOverrides للأهداف الأقدم من 4.6.2، وعدم دعم تشكيل وضع WPF داخل HwndSource / ElementHost لـ Per-Monitor. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, BitmapScalingMode Enum. حول أن الافتراضي (Unspecified) هو Linear، وخصائص خوارزميات الاستيفاء HighQuality (Fant) وNearestNeighbor. ↩ ↩2 ↩3
-
Microsoft Learn, Layout - WPF. حول التخطيط بوحدة DIP ورسم فرعي للبكسل يسبّب ضبابية الحواف، وأن تقريب التخطيط (UseLayoutRounding) معطّل افتراضياً، وانتشاره إلى الشجرة البصرية عند ضبطه على العنصر الجذر. ↩ ↩2 ↩3
-
Microsoft Learn, UIElement.SnapsToDevicePixels Property. حول أن القيمة الافتراضية false، وتوريث الضبط إلى الشجرة الفرعية عند ضبطه على الجذر، وإمكانية تخفيف التشوّه البصري الناتج عن تنعيم الحواف حول الخطوط المفردة في بيئات تتجاوز 96 DPI. ↩ ↩2
-
Microsoft Support, Change your screen resolution and layout in Windows. حول اختيار معامل التكبير من «تغيير الحجم والتخطيط» في «ابدأ > الإعدادات > النظام > العرض» عبر «تغيير حجم النص والتطبيقات والعناصر الأخرى». ↩
-
Microsoft Support, Use Magnifier to make things on the screen easier to see. حول تشغيل المكبّر بمفتاح شعار Windows + علامة الزائد، وإمكان تكبير جزء من الشاشة. ↩
-
Microsoft Learn, Setting the default DPI awareness for a process. حول أسبقية عنصر dpiAwareness (ابتداءً من Windows 10 1607) على dpiAware، وسلوك الرجوع الاحتياطي الذي يستخدم أول قيمة يُتعرَّف عليها من بين القيم المفصولة بفاصلة. ↩
-
Microsoft Learn, What’s new in .NET Framework. حول إضافة دعم Per-Monitor V2 DPI Awareness وMixed-Mode DPI Scaling إلى WPF في .NET Framework 4.8، وتحسين التفاعل مع HWND المستضاف / WinForms، ومفتاح AppContext اللازم للتفعيل. ↩
-
Microsoft Learn, TextFormattingMode Enum. حول وضعي تنسيق النص Ideal (المقاييس المثالية) وDisplay (المقاييس المتوافقة مع GDI). ↩
-
Microsoft Learn, Layout Considerations for the WindowsFormsHost Element. حول تحويل WindowsFormsHost بين نظامي إحداثيات DIP والبكسل الفعلي، وأن التحجيم يبقى في حدود ما يستطيع عنصر تحكّم Windows Forms الداخلي التعامل معه. ↩
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
دعم DPI العالي في WinForms ── أسباب الضبابية والانهيار على شاشات 4K والمعالجة الواقعية
نرتّب أسباب ضبابية تطبيقات WinForms وانهيار تخطيطها على شاشات 4K انطلاقاً من DPI virtualization وأوضاع إدراك DPI (System Aware / Per-Moni...
دمج مصادقة Entra ID في تطبيقات WinForms/WPF ── التشكيل العملي لـ MSAL.NET ووسيط WAM
نرتّب خطوات دمج مصادقة Entra ID في تطبيقات WinForms/WPF لسطح المكتب. نشرح مفهوم العميل العامّ، وتسجيل التطبيق، وAcquireTokenSilent في MSA...
التاريخ والوقت والمناطق الزمنية في تطبيقات الأعمال ── من فخاخ DateTime إلى مبدأ التخزين بـ UTC وتصميم الاختبارات
ينزاح الوقت 9 ساعات بعد نقل الخادم. نرتّب أسباب أعطال التاريخ والوقت انطلاقاً من Kind في DateTime والتحويل الضمني. نشرح التمييز مع DateTi...
إقامة تطبيقات Windows في علبة النظام والإشعارات المنبثقة ── مطبات NotifyIcon وكيفية اختيار AppNotification
نرتّب إقامة تطبيقات Windows للأعمال في علبة النظام والإشعارات المنبثقة. نشرح الاستخدام الصحيح لـ NotifyIcon، وإعادة التسجيل عند إعادة تشغ...
كيف نفهم عزل الجلسات في Windows ── Session 0 وRDP وتشغيل عدة مستخدمين معاً
نوضح مفهوم «الجلسة» الذي يربك كثيراً من مطوّري تطبيقات Windows. نشرح عملياً سبب عزل Session 0 الذي يمنع الخدمة من عرض واجهة، وسلوك الجلسا...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- سمعت أن WPF لا يحتاج إلى دعم DPI، فلماذا يحدث التلطّخ؟
- وحدة تخطيط WPF هي وحدة مستقلّة عن الجهاز (DIP) مقدارها 1/96 من الإنش، ويعمل افتراضياً بوصفه واعياً بـ System DPI، لذا يتابع بشكل صحيح دقة DPI الخاصة بالشاشة الرئيسية عند بدء التشغيل. لكن بما أن System DPI يُثبَّت عند تسجيل الدخول، فإن نقل النافذة إلى شاشة بدقة DPI مختلفة يجعل نظام التشغيل يكبّر أو يصغّر النافذة كلّها كصورة نقطية لموازنة الفرق، فتتلطّخ الشاشة كلّها بانتظام. ويبرز التدهور بوجه خاص عند تركيبات غير صحيحة القسمة مثل 125% و150%. والعلاج الجذري هو الإعلان عن دعم Per-Monitor DPI في البيان (manifest)، وفي WPF على .NET Framework 4.6.2 فما بعده تُنفَّذ إعادة تحجيم النافذة تلقائياً.
- لماذا يتلطّخ خط حدود بعرض 1px في WPF، أو تختلف سماكته من مكان لآخر؟
- لأن التخطيط يتم بوحدة DIP، فلا يُضمن أن تقع حدود العناصر على مواضع صحيحة من البكسل الفعلي. ففي بيئة 125%، يصبح 1 DIP = 1.25px، وأي حافة تقع في منتصف بكسل تُرسم شبه شفافة بتنعيم الحواف (anti-aliasing) فتتلطّخ. الخطوة الأولى هي ضبط UseLayoutRounding="True" على النافذة الجذر، إذ يُقرَّب مسار التخطيط القيم غير الصحيحة للبكسل وينتشر ذلك على الشجرة البصرية كلّها. وللمواضع التي يبقى فيها الخلل، يُطبَّق SnapsToDevicePixels="True" بدقة، وهو يلصق الحواف بحدود البكسل عند الرسم. القيمة الافتراضية لكليهما معطّلة.
- لماذا تكون النصوص واضحة في WPF بينما تبدو الأيقونات وحدها ضبابية؟
- لأن النصوص تُرسم كخطوط متّجهية (vector fonts) فتبدو واضحة عند أي دقة DPI، بينما تُوضع الصور النقطية بوحدة DIP وتُكبَّر بالاستيفاء (interpolation) بحسب DPI. فأيقونة بحجم 16×16px تُكبَّر إلى 24×24px في بيئة 150%، وتصبح ضبابية بالاستيفاء الافتراضي (Linear). وترتيب أولويات العلاج هو: (1) جعلها أصولاً متّجهية عبر Path أو Geometry أو DrawingImage أو خطوط الأيقونات، (2) تجهيز صور نقطية بدقّات متعدّدة وتبديلها بحسب DPI عبر VisualTreeHelper.GetDpi أو OnDpiChanged، (3) ضبط خوارزمية الاستيفاء عبر RenderOptions.BitmapScalingMode.
- كيف يُضبط دعم Per-Monitor في WPF؟
- يُعلن عنه بكتابة عنصر dpiAwareness في بيان التطبيق (application manifest). ولا توجد في WPF وسيلة إعداد مشروع أو تبديل من الشيفرة مثل ApplicationHighDpiMode في WinForms. والمتطلّبات هي نظام تشغيل Windows 10 بإصدار 1607 فما بعده، وهدف بناء (target) .NET Framework 4.6.2 فما بعده، وإن أُعلن عنه يتولّى الإطار تلقائياً كل شيء من معالجة WM_DPICHANGED حتى إعادة تحجيم النافذة. ودعم WPF لـ PerMonitorV2 يبدأ من .NET Framework 4.8 فما بعده، لذا يُعلن عنه في 4.6.2 إلى 4.7.2 بـ PerMonitor منفردة. وإن بقي هدف البناء 4.6.1 فما قبله، تبقى المتابعة معطّلة، فيلزم تفعيلها عبر مفتاح AppContext. علماً بأن سلوك Per-Monitor في WPF عند وضعه داخل ElementHost أو HwndSource غير مدعوم رسمياً.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.