This article is available in English too, check it out here.
الكود: Geming.DisplayMgr.msi
المحتويات
محتويات هذا الموضوع:
- المحتويات
- نظرة خاطفة
- مقدمة
- معرفة الوضع الحالي
- معرفة الأوضاع المتاحة
- تغيير الإعدادات الحالية
- تدوير الشاشة
- مثال
- خاتمة
نظرة خاطفة
درسنا اليوم يتكلم عن كيفية تغيير إعدادات الشاشة برمجيا. سوف نبدأ أولا بإذن الله بشرح المفاهيم المطلوبة لهذا الدرس ثم سوف نتبعها بالأكواد وبالتفصيل اللازم لهذه الأكواد.
سوف نمر أو سوف نتعرض لأربعة أفكار، معرفة الإعدادات الحالية للشاشة، معرفة الأوضاع المتاحة للشاشة، تغيير هذه الأوضاع أو الإعدادات، وأخيرا تدوير (دوران) الشاشة.
أسلوبنا في هذا الدرس أسلوب تسلسلي، فكل فكرة أو كل فقرة من الكود هي مرتبطة بسابقتها.
وأخيرا، يمكنك تحميل برنامج مثال برنامج يقوم بتغيير أوضاع الشاشة.
مقدمة
بداية، ما هي أوضاع الشاشة؟ الوضع أو Mode للشاشة هو عبارة عن أربعة إعدادات أو اختيارات، وهي:
-
الأبعاد Bounds أو Resolution:وهي الطول والعرض بالبيكسل Pixel. مثلا 800 x 600 او 1024 x 768.
-
نظام الألوان Color System او Bit Count أو Bits Per Pixel/Pel أو BPP:وهو كمية الألوان التي يمكن للشاشة التعامل معها. وهو مثلا 24-bit أو 32-bit ويسميان High Color، أو 16-bit ويسمى True Color. فكلما زاد عدد البتات Bits كلما أصبحت الألوان والصورة أفضل. وأيضا كلما زاد عدد البتات، كلما زادت نسبة الاستهلاك من موارد الجهاز (الذكارة مثلا.)
-
معدل التصحيح Refresh Rate أو كما يسمى Frequency أو Monitor Flicker:وهو معدل تصحيح الشاشة أي أن الشاشة تقوم بعمل Refresh لنفسها بنسبة معينة. وهذا المعدل كلما زاد كلما سبب للمستخدم صُداع وإجهاد للعين. في الأفضل يكون هذا المعدل 75-Hertz فأقل.
-
التدوير Rotation أو Orientation:وهو كيفية تدوير الشاشة. فربما في بعض الشاشات تجد إمكانية لتدوير الشاشة فتجعلها بزاوية °90 مثلا باتجاه عقارب الساعة وهكذا.
إذا فالأربع إعدادات، الأبعاد Resolution، نظام الألوان Bit Count، معدل التصحيح Refresh Rate، الدوران Orientation، هذه الأربعة تسمى وضع الشاشة Display Mode.
من الأمور المهمة التي يجب معرفتها أنه ليست كل الشاشة مشابهة لبعضها. فإن كارت الفيديو VGA والشاشة نفسها هما الذان يتحكمان في الأوضاع. فعلى سبيل المثال، تختلف الشاشات العادية CRT (شاشات آشعة الكاثود) وشاشات الكريستال LCD تختلفان في الأبعاد، فالأول يكون النسبة بين الطول والعرض تساوي 4:3 بينما الأخرى تختلف فيها هذه النسبة فيأخذ العرض نسبة أكبر منه في شاشات CRT. وأيضا هناك كروت شاشة لا تدعم نظام الألوان 32-bit وتدعم بدلا منه 24-bit.
أيضا يجب عليك معرفة أن لكل شاشة الأوضاع التي تدعمها Display Supported Modes. فهناك شاشات رغم دعمها لنظام الألوان 32-bit مثلا، فهي لا تدعمه مع أبعاد مثل 1024 x 768.
وأيضا فالشاشات لا تدعم اختيارات عشوائية مثل 1000 x 750 حتى ولو كانت بينها تناسب مع نظام الشاشة (4:3).
لهذا فإنه يجب عليك قبل محاولة تغيير الأوضاع برمجيا أن تتأكد من دعم الشاشة لهذا الوضع. فربما الشاشة لا تدعم دوران بدرجة معينة مع أبعاد معينة أو مع نظام ألوان معين، وهكذا.
لمعرفة الأوضاع المتاحة للشاشة، قم بالوصول إلى إعدادات الشاشة Display Settings من لوحة التحكم Control Panel، والتي تكون مشابهة إلى حد كبير لشكل 1.

قم بعد ذلك بالضغط على إعدادات متقدمة Advanced Settings لتظهر لك شاشة Advanced Settings كما في الشكل 2.
ومن ثم اضغط على List All Modes كي تظهر أمامك قائمة بالأوضاع المتاحة (للأسف لا يظهر فيها الدوران Orientation.) انظر شكل 2.
قم بالرجوع مرة أخرى إلى شاشة Advanced Options وهناك ابحث عن خصائص كارت اللشاشة ومنه ابحث عن الدوران وقم بتجربة تدوير الشاشة 90° باتجاه عقارب الساعة Clockwise.
لاحظ أنه ليست كل الشاشات تدعم الدوران. لاحظ أيضا أن اتجاه عقارب الساعة هذا بالنسبة للشاشة وليس بالنسبة إليك.
للأسف فبيئة الدوت نت لا تدعم الاختيارات الخاصة بالأجزاء الصلبة Hardware من الجهاز، فلهذا سوف نقوم باستخدام دوال الويندوز Windows API كي نقوم بتغيير إعدادات الشاشة. وسوف تكون الدوال التي نحتاجها موجودة بإذن الله في مكتبة user32.dll.
معرفة الوضع الحالي
الدالة الأولى معنا وهي التي سوف نستخدمها لمعرفة الإعدادات أو الوضع الحالي للشاشة والأوضاع الأخرى المتاحة هي دالة EnumDisplaySettings(). وتعريف هذه الدالة كالتالي:
BOOL EnumDisplaySettings( LPCTSTR lpszDeviceName, // display device DWORD iModeNum, // graphics mode [In, Out] LPDEVMODE lpDevMode // graphics mode settings );
تأخذ هذه الدالة 3 مدخلات:
-
lpszDeviceName:تأخذ اسم الشاشة التي تريد أن تعرف معلومات عنها. في الغالب فإن شاشة واحدة تكون متصلة بالجهاز فلهذا يمكنك استخدام القيمة NULL لإحضار معلومات حول الشاشة الرئيسية.
-
iModeNum:نوع البيانات التي تريدها. يمكنك استخدام القيمة -1 لتحديد أن البيانات التي نريدها هي البيانات الحالية.
-
lpDevMode:عنصر من نوع DEVMODE يحوي جميع البيانات المطلوبة.
كيف تعمل هذه الدالة؟ بداية، فإن المدخل الأول lpszDeviceName سوف يأخذ -في الغالب- قيمة NULL لتحديد الشاشة الرئيسية كمحور للاهتمام.
أما العنصر الثالث وهو عنصر من نوع DEVMODE فعند نجاح هذه الدالة فسوف يكون يحوي البيانات المطلوبة.
أما العنصر الأهم وهو iModeNum فهو يعمل كالتالي: عند تحديد قيمة -1 تقوم باسترجاع الإعدادات الحالية للشاشة وتكون هذه الإعدادات عبارة عن عنصر من نوع DEVMODE.
أما لمعرفة الأوضاع المتاحة للشاشة جميعها فإنك تقوم بوضع الأمر EnumDisplaySettings() في دوارة Loop وتقوم في هذه الدوارة بالنداء على الدالة وفي كل مرة تقوم بزيادة قيمة iModeNum لترجع وضع مختلف. فمثلا، أول دورة تكون القيمة 0 ليرجع الوضع الأول ثم الثانية تكون 1 ليرجع الوضع الثاني، ثم الثالثة تكون 2 ليرجع الوضع الثالث، وهكذا حتى ترجع لك الدالة قيمة FALSE فحينها تكون انتهت جميع الأوضاع المتاحة للشاشة.
إذا، ما هو تركيب العنصر DEVMODE؟ تعريف العنصر DEVMODE هو كالتالي:
لا تقلق حيال هذه التعريفات. هذه التعريفات هي بلغة C وهي خاصة بدوال الويندوز. سوف نقوم بشرح كيفية استخدام هذه الدوال والتعريفات في كود الـ C#.
typedef struct DEVMODE { BCHAR dmDeviceName[CCHDEVICENAME]; WORD dmSpecVersion; WORD dmDriverVersion; WORD dmSize; WORD dmDriverExtra; DWORD dmFields; union { struct { short dmOrientation; short dmPaperSize; short dmPaperLength; short dmPaperWidth; short dmScale; short dmCopies; short dmDefaultSource; short dmPrintQuality; }; POINTL dmPosition; DWORD dmDisplayOrientation; DWORD dmDisplayFixedOutput; }; short dmColor; short dmDuplex; short dmYResolution; short dmTTOption; short dmCollate; BYTE dmFormName[CCHFORMNAME]; WORD dmLogPixels; DWORD dmBitsPerPel; DWORD dmPelsWidth; DWORD dmPelsHeight; union { DWORD dmDisplayFlags; DWORD dmNup; } DWORD dmDisplayFrequency; #if(WINVER >= 0x0400) DWORD dmICMMethod; DWORD dmICMIntent; DWORD dmMediaType; DWORD dmDitherType; DWORD dmReserved1; DWORD dmReserved2; #if (WINVER >= 0x0500) || (_WIN32_WINNT >= 0x0400) DWORD dmPanningWidth; DWORD dmPanningHeight; #endif #endif /* WINVER >= 0x0400 */ }
هذا العنصر عبارة عن Structure يمهنا منه فقط 5 أعضاء/متغيرات Members:
-
dmPelsWidth و dmPelsHeight:الأبعاد (الطول والعرض) للشاشة.
-
dmBitsPerPels:نظام الألوان (عدد البتات Bits.)
-
dmDisplayOrientation:تدوير الشاشة. يمكنها أن تكون قيمة من 4 قيم:
-
DMDO_DEFAULT = 0ليس هناك أي تدوير.
-
DMDO_90 = 1تدوير 90° باتجاه عقارب الساعة.
-
DMDO_180 = 2تدوير 180° باتجاه عقارب الساعة.
-
DMDO_270 = 3تدوير 270° باتجاه عقارب الساعة.
-
-
dmDisplayFrequency:معدل التصحيح Frequency بالهرتز Hertz.
الآن يأتي دور الدوت نت أو C# بالأخص.كي يمكنك استخدام دوال أو عناصر ويندوز Windows API في برنامجك يجب أن تقوم بتعريفها.
فالتالي هو تعريف الدالة EnumDisplaySettings() في C#:
لا تنسى إضافة سطر using للـ Namespace المسماة System.Runtime.Interoperability وهي لازمة لجميع الأكواد في هذا الدرس.
[DllImport("User32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern Boolean EnumDisplaySettings( [param: MarshalAs(UnmanagedType.LPTStr)] string lpszDeviceName, [param: MarshalAs(UnmanagedType.U4)] int iModeNum, [In, Out] ref DEVMODE lpDevMode);
وهذا هو تعريف العناصر Structures التي تستخدمها الدالة:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct DEVMODE { // You can define the following constant // but OUTSIDE the structure because you know // that size and layout of the structure is very important // CCHDEVICENAME = 32 = 0x50 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string dmDeviceName; // In addition you can define the last character array // as following: //[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] //public Char[] dmDeviceName; // After the 32-bytes array [MarshalAs(UnmanagedType.U2)] public UInt16 dmSpecVersion; [MarshalAs(UnmanagedType.U2)] public UInt16 dmDriverVersion; [MarshalAs(UnmanagedType.U2)] public UInt16 dmSize; [MarshalAs(UnmanagedType.U2)] public UInt16 dmDriverExtra; [MarshalAs(UnmanagedType.U4)] public UInt32 dmFields; public POINTL dmPosition; [MarshalAs(UnmanagedType.U4)] public UInt32 dmDisplayOrientation; [MarshalAs(UnmanagedType.U4)] public UInt32 dmDisplayFixedOutput; [MarshalAs(UnmanagedType.I2)] public Int16 dmColor; [MarshalAs(UnmanagedType.I2)] public Int16 dmDuplex; [MarshalAs(UnmanagedType.I2)] public Int16 dmYResolution; [MarshalAs(UnmanagedType.I2)] public Int16 dmTTOption; [MarshalAs(UnmanagedType.I2)] public Int16 dmCollate; // CCHDEVICENAME = 32 = 0x50 [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] public string dmFormName; // Also can be defined as //[MarshalAs(UnmanagedType.ByValArray, // SizeConst = 32, ArraySubType = UnmanagedType.U1)] //public Byte[] dmFormName; [MarshalAs(UnmanagedType.U2)] public UInt16 dmLogPixels; [MarshalAs(UnmanagedType.U4)] public UInt32 dmBitsPerPel; [MarshalAs(UnmanagedType.U4)] public UInt32 dmPelsWidth; [MarshalAs(UnmanagedType.U4)] public UInt32 dmPelsHeight; [MarshalAs(UnmanagedType.U4)] public UInt32 dmDisplayFlags; [MarshalAs(UnmanagedType.U4)] public UInt32 dmDisplayFrequency; [MarshalAs(UnmanagedType.U4)] public UInt32 dmICMMethod; [MarshalAs(UnmanagedType.U4)] public UInt32 dmICMIntent; [MarshalAs(UnmanagedType.U4)] public UInt32 dmMediaType; [MarshalAs(UnmanagedType.U4)] public UInt32 dmDitherType; [MarshalAs(UnmanagedType.U4)] public UInt32 dmReserved1; [MarshalAs(UnmanagedType.U4)] public UInt32 dmReserved2; [MarshalAs(UnmanagedType.U4)] public UInt32 dmPanningWidth; [MarshalAs(UnmanagedType.U4)] public UInt32 dmPanningHeight; } [StructLayout(LayoutKind.Sequential)] public struct POINTL { [MarshalAs(UnmanagedType.I4)] public int x; [MarshalAs(UnmanagedType.I4)] public int y; }
العنصر DEVMODE هو عنصر معقد جدا إلى أبعد درجة ويختلف باختلاف نظام التشغيل، ولهذا فإننا افترضنا أن المستخدم يعمل على Windows XP او إصدار أعلى وأن نظام تشغيله هو 32-bit.
هنا يظهر سؤال: من أين لنا بالعنصر POINTL؟ إذا رجعنا إلى التعريف الأصلي للعنصر DEVMODE فسوف نجد أنه يحوي على أعضاء Members من نوع POINTL فلذلك يجب تعريف هذا العنصر أيضا.
والآن إلى النقطة الأكثر إثارة. الكود التالي يقوم باسترجاع إعدادات الشاشة الحالية ويقوم بطباعتها على الشاشة.
public static void GetCurrentSettings() { DEVMODE mode = new DEVMODE(); mode.dmSize = (ushort)Marshal.SizeOf(mode); if (EnumDisplaySettings(null, -1, ref mode) == true) // Succeeded { Console.WriteLine("Current Mode:nt" + "{0} by {1}, " + "{2} bit, " + "{3} degrees, " + "{4} hertz", mode.dmPelsWidth, mode.dmPelsHeight, mode.dmBitsPerPel, mode.dmDisplayOrientation * 90, mode.dmDisplayFrequency); } }
معرفة الأوضاع المتاحة
ماذا عن الأوضاع المتاحة للشاشة؟ بنفس الطريقة سوف نقوم بالحصول عليها ولكن سوف نستخدم رقم الوضع بدلا من الرقم -1 الخاص بالوضع الحالي.
public static void EnumerateSupportedModes() { DEVMODE mode = new DEVMODE(); mode.dmSize = (ushort)Marshal.SizeOf(mode); int modeIndex = 0; // 0 = The first mode Console.WriteLine("Supported Modes:"); while (EnumDisplaySettings(null, modeIndex, ref mode) == true) // Mode found { Console.WriteLine("t" + "{0} by {1}, " + "{2} bit, " + "{3} degrees, " + "{4} hertz", mode.dmPelsWidth, mode.dmPelsHeight, mode.dmBitsPerPel, mode.dmDisplayOrientation * 90, mode.dmDisplayFrequency); modeIndex++; // The next mode } }
لاحظ استخدامنا لعداد في كل دورة، ولاحظ أيضا اعتمادنا على القيمة التي ترجعها الدالة.
تغيير الإعدادات الحالية
فهمت الفكرة؟ الآن ننتقل إلى جزء آخر من الموضوع، وهو كيفية تغيير إعدادات الشاشة. ويتم هذا عن طريق دالة أخرى وهي ChangeDisplaySettings() وهذه الدالة تعريفها كالتالي:
LONG ChangeDisplaySettings( LPDEVMODE lpDevMode, // graphics mode DWORD dwflags // graphics mode options );
تأخذ هذه الدالة مدخلين:
-
lpDevMode:عنصر من نوع DEVMODE يحتوي على الإعدادات الجديدة.
-
dwflags:اختيارات متقدمة ليس لها حاجة في موضوعنا اليوم، لهذا سوف تأخذ القيمة صفر 0.
ولكن كيف نملأ العنصر lpDevMode حيث أنه يحتوي على عدد ضخم من المتغيرات؟ كما ذكرنا سابقا، أنت لا تحتاج إلى التعامل مع جميع هذه العناصر، فقط الـ 5 عناصر الأهم بالنسبة إلينا. فسياسة هذه الدالة ChangeDisplaySettings() كالتالي: تقوم باسترجاع الإعدادات الحالية للشاشة عن طريق الدالة EnumDisplaySettings() كي تملأ المتغيرات الخاصة بـ lpDevMode، ثم تقوم بعد ذلك بتغيير المتغيرات خاصتك التي تحتاجها مثل dmPelsHeight و dmPelsWidth لتغيير الأبعاد. وأخيرا تقوم بالنداء على هذه الدالة ChangeDisplaySettings() ليتم تطبيق إعداداتك.
يجب الملاحظة أن هذه الدالة تقوم بإرجاع قيمة رقمية تمثل حالة العملية التي تم تطبيقها وهي تكون واحدة من أربعة:
-
DISP_CHANGE_SUCCESSFUL = 0معناها أن العملية نجحت.
-
DISP_CHANGE_BADMODE = -2معناها أن الوضع Mode المختار لا تدعمه الشاشة، مثلا دوران مع أبعاد معينة، أو أبعاد غير موجودة مثل 1000 x 750 مثلا.
-
DISP_CHANGE_FAILED = -1العملية فشلت لسبب غير معروف.
-
DISP_CHANGE_RESTART = 1يجب إعادة تشغيل الجهاز حتى يتم تطبيق الاختيارات. بعد انتهاء عصر Windows 9x فإن هذه الدالة لا تقوم بإرجاع هذه القيمة.
والآن إلى الكود. الكود التالي هو تعريف هذه الدالة في كي نستطيع استخدامها في الـ C#:
[DllImport("User32.dll")] [return: MarshalAs(UnmanagedType.I4)] public static extern int ChangeDisplaySettings( [In, Out] ref DEVMODE lpDevMode, [param: MarshalAs(UnmanagedType.U4)] uint dwflags);
والآن إلى إثارة أكثر، الكود التالي يقوم بتغيير إعدادات الشاشة إلى 800 x 600 بنظام ألوان 16-bit:
const int DISP_CHANGE_SUCCESSFUL = 0; const int DISP_CHANGE_RESTART = 1; const int DISP_CHANGE_FAILED = -1; const int DISP_CHANGE_BADMODE = -2; static void Main() { // Changing the display resolution // to 800 by 600 // and the color system (bit count) // to 16-bit ChangeDisplaySettings(800, 600, 16); } public static void ChangeDisplaySettings(int width, int height, int bitCount) { DEVMODE originalMode = new DEVMODE(); originalMode.dmSize = (ushort)Marshal.SizeOf(originalMode); // Retrieving current settings // to edit them EnumDisplaySettings(null, ENUM_CURRENT_SETTINGS, ref originalMode); // Making a copy of the current settings // to allow reseting to the original mode DEVMODE newMode = originalMode; // Changing the settings newMode.dmPelsWidth = (uint)width; newMode.dmPelsHeight = (uint)height; newMode.dmBitsPerPel = (uint)bitCount; // Capturing the operation result int result = ChangeDisplaySettings(ref newMode, 0); if (result == DISP_CHANGE_SUCCESSFUL) { Console.WriteLine("Succeeded.n"); // Inspecting the new mode GetCurrentSettings(); Console.WriteLine(); // Waiting for seeing the results Console.ReadKey(true); ChangeDisplaySettings(ref originalMode, 0); } else if (result == DISP_CHANGE_BADMODE) Console.WriteLine("Mode not supported."); else if (result == DISP_CHANGE_RESTART) Console.WriteLine("Restart required."); else Console.WriteLine("Failed. Error code = {0}", result); }
تدوير الشاشة
وإلى النقطة الأكثر إثارة ومتعة في موضوعنا، وهي تدوير الشاشة. الكود الذي استخدمناه سابقا قام بتغيير الإعدادات مثل الأبعاد ونظام الألوان. لماذا لا نقوم الآن بتغيير التدوير عن طريق المتغير dmDisplayOrientation مثلما فعلنا في تغيير باقي الإعدادات؟ هذا ما يقوم به الكود التالي:
الكود مختصر للإفادة.
static void Main() { // 0 degrees ( DMDO_DEFAULT = 0 ) Console.WriteLine("Press any key to rotate the screen . . ."); Console.ReadKey(true); Console.WriteLine(); RotateScreen(true); // 90 degrees ( DMDO_90 = 1 ) Console.WriteLine ("Press any key to rotate the screen . . ."); Console.ReadKey(true); Console.WriteLine(); RotateScreen(true); // 180 degrees ( DMDO_180 = 2 ) Console.WriteLine("Press any key to rotate the screen . . ."); Console.ReadKey(true); Console.WriteLine(); RotateScreen(true); // 270 degrees ( DMDO_270 = 3 ) Console.WriteLine("Press any key to rotate the screen . . ."); Console.ReadKey(true); Console.WriteLine(); RotateScreen(true); // 0 degrees ( DMDO_DEFAULT = 0 ) } public static void RotateScreen(bool clockwise) { // Retrieving current settings // ... // Rotating the screen if (clockwise) if (newMode.dmDisplayOrientation DMDO_DEFAULT) newMode.dmDisplayOrientation--; else newMode.dmDisplayOrientation = DMDO_270; // Swapping width and height; uint temp = newMode.dmPelsWidth; newMode.dmPelsWidth = newMode.dmPelsHeight; newMode.dmPelsHeight = temp; // Capturing the operation result // ... }
مثال
يمكنك تحميل برنامجنا Display Settings Sample والذي يقوم بتغيير إعدادات الشاشة وتدويرها وما إلى غير ذلك.
خاتمة
كان هذا درسنا اليوم، طويلا ومعقدا قليلا (أو كثيرا!) ولكن استفدنا الكثير.
أطيب الأمنيات.