Feeds:
تدوينات
تعليقات

المحتويات

  • المحتويات
  • مقدمة
  • مدخل لعالم تحزيم البرمجيات في الفيجوال استوديو
    • التعريف
    • اهداف التحزيم
    • متي يتم استخدام تقنية التحزيم في برامجنا
    • الاشخاص المختصون بعملية التحزيم
  • انواع التحزيم
    • الطريقة الاولي : ClickOnce Deployment
      • التعريف
      • مميزات تلك الطريقة
      • عيوب تلك الطريقة
      • دواعي الاستخدام لتلك الطريقة
    • الطريقة الثانية: Windows Installer Deployment
      • التعريف
      • مميزات تلك الطريقة
      • عيبوب تلك الطريقة
      • دواعي الاستخدام لتلك الطريقة
  • خاتمة

مقدمة

اخواني الاعزاء بعد السلام والتحية ان شاء الله سوف نبدأ دورة جديدة لتحزيم البرامج من خلال Visaul Studio وسوف تكون تلك الدورة شاملة باذن الله جميع جوانب التحزيم ولكافة المستويات المبتدأين قبل المحترفين.

سوف تتكون الدورة باذن الله من اجزاء وقصدت عدم تحديد اجزائها نظرا لاخلاص نيتي في تقديم كم هائل من المعلومات في ذلك النوع من البرمجيات وعدم اقتصارها علي معلومات سطحية او غيره وسوف نقوم بالتلخيص والايجاز في بعض النقاط التي قد لا يحتاجها الكثيرون في شغلهم ولكن سوف نذكرها من باب العلم بالشي ليس الا .

وطبعا يا ريت اي من الاخوة المتخصصين في صنع الكتب الالكترونية ان يقوم بالمتابعة معي باذن الله والعمل علي نقل تلك المادة واخراجها في النهاية في صورة كتاب الكتروني حتي يتسفيد الكثير من الناس منه فضلا عن المنتدي حيث في نهاية الدورة باذن الله ان يتم اخراج هذا الكتاب بعد ترتيبه وتنقيحه وفهرسته بمشيئة الله وعونه .

مدخل لعالم تحزيم البرمجيات في الفيجوال استوديو

التعريف

كلمة تحزيم تعني في عالم البرمجة جمع اكثر من ملف باي نوع في مكان وحيد.

بمعني : مثلا مشروعك فيه اكثر من ملف مع اكثر من قاعدة بيانات مع اكثر من ملف صورة او صوتيات او ملتي ميديا او غيره من انواع الملفات وانتا تريد ان تنقل تلك الملفات دفعة واحده لمكان معين سواء كان المكان جهاز اخر ( كجهاز العميل ) او شبكة نت محلية LAN او شبكة عالمية Internet ولكن من كثرة الملفات انتا تخشي ضياع احد الملفات مما يؤدي الي فشل النظام كاملا كاعتماد النظام علي مكتبة DLL مثلا وبدونها لن يعمل برنامجك فما الحل في ذلك

الحل :
يلجا الكثير من المطورين باستخدام تقنية التحزيم من اجل الحفاظ علي ملفات مشاريعهم التي اخذت الكثير من الوقت والجهد في تنفيذها من الضياع او اللعب فيها سواء بقصد او بدون قصد . حيث يقومون باستخدام هذا التحزيم في جمع ملفات مشروعهم في ملف واحد ياخذ الامتداد MSI.

اهداف التحزيم

  1. الحفاظ علي ملفات المشروع من العبث او الضياع
  2. سرعة نقل الملفات من مصادر انتاجها الي اماكن استخدامها وتداولها
  3. نشر البرامج التي تم برمجتها الي المستخدم لها
  4. التقليل من حجم الملفات الاصلي عن طريق ضغطها في عملية التحزيم ( اختيارية )
  5. امكانية الاستفادة من تقنية التحزيم في عمليات التحديثات الدورية للبرمجيات اي Upgrade Versions

متي يتم استخدام تقنية التحزيم في برامجنا

ليس لها وقت محدد ولكنها تتوقف حسب الرغبة او الهدف من استخدامها والتي سبق سردها كاهداف للتحزيم

الاشخاص المختصون بعملية التحزيم

  • مطوري البرمجيات
  • المبرمجين
  • محللي النظم والمعلومات
  • عند العمل في فريق عمل يعمل علي نطاق واسع وبعيد المدي كالفروع مثلا
  • كل شخص يريد حفظ ملفاته من الضياع او التلف

انواع التحزيم

للتحزيم انواع وكل نوع يستخدم في هدف معين ومن الممكن الجمع بينهم في مشروع واحد اذا تطلب الامر ذلك .

  1. تحزيم برمجيات تطبيقات الويندوز Windows Setup Project
  2. تحزيم برمجيات تطبيقات الويب Web Setup Project
  3. تحزيم برمجيات الوحدات والمكتبات DLL – MergeModule Project
  4. تحزيم اي نوع اخر من الملفات والمصادر CAB project
  5. تحزيم برمجيات البوكيت بي سي والاجهزة الكفية التي تعمل تحت انظمة WIndows CE – SmartDeviceCab

شكل 1 توضح نوع كل تحزيم في الدوت نت:

شكل 1 - شاشة إنشاء مشروع جديد - مشاريع التحزيم

شكل 1 - شاشة إنشاء مشروع جديد - مشاريع التحزيم

طرق التحزيم

التحزيم له طريقتين من خلال الدوت نت كما يلي :

  1. الطريق الاولي ( ClickOnce Deployment )
  2. الطريقة الثانية ( Windows Installer Deployment )

وفيما يلي سوف نتناول باختصار صورة كل من الطريقتين :

الطريقة الاولي : ClickOnce Deployment

التعريف

هي طريقة يتم فيها استخدام المعالج من اجل تحزيم ملفات المشروع وانتاج في النهاية ملف يحمل الامتداد MSI ويحتوي بين طياته علي جميع ملفات المشروع التي قمنا بدمجها بداخله .

مميزات تلك الطريقة

  1. سرعة في عملية تحزيم المشروع باقل مجهود يذكر واقل خبره مطلوبه
  2. امكانية تنصيب البرامج التي تمت تحزيمها من خلالها من علي CD Room
  3. امكانية الاستفادة من خواص التحديث الالي للبرامج المنشورة بها
  4. لا تحتاج الي خبرة كبيرة من اجل استخدامها حيث تتم عملية التحزيم في صورة معالج تحزيم وبعده خطوات سهلة

عيوب تلك الطريقة

  1. لا يعطي لك المرونة والقوة المطلوبة لتحزيم البرامج بشكل احترافي مثلما تزودك بها الطريقة الثانية للتحزيم
  2. تحتاج البرامج المحزمة من خلالها الي توافر كل من .NET Framework و Windows Installer علي الجهاز الا هيتم تسطيبها عليها ومن دونهما لن تعمل البرامج

دواعي الاستخدام لتلك الطريقة

  • في حالة الحاجة لنقل ملفات المشروع الي Web Server علي الانترنت يتم استخدام تلك الطريقة نظرا لسهولة وبساطة العملية وعدم حاجتها علي وجود عمليات تحقق من برامج ومتطلبات لبدأ عملية التسطيب
  • في حالة العمل مع فريق برمجي وتم تقسيم النظام الي مجموعات وكل عضو في الفريق ياخذ جزا لبرمجته وللك يستخدم هذا النوع من اجل تجميع جميع ملفات المشروع في مكان واحد دون جمعها يدويا
  • في حالة تجربة مشروع او برنامج علي جهاز اخر للتاكد من خلوه من العيوب او Bugs لذلك يتم استخدام تلك الطريقة في التحزيم ولكن انتبه يجب ان تحتوي الانظمة المراد تجربه البرنامج عليها علي NET Framework و Windows Installer حتي يعمل البرنامج .
  • في حالة برمجة برامج تعمل علي الشبكات ووجب الحاجة الي نقل ملفات المشروع الي مجلد علي سيرفر شبكة داخلية مثلا : عند عمل برنامج يعمل علي شبكة داخلية واردنا نقل البرنامج الي عنوان مجلد مشيرا علي تلك الشبكة وليكن كما يلي :

\\ServerName\sharedFolder

الطريقة الثانية: Windows Installer Deployment

التعريف

هي طريقة يتم فيها استخدام المعالج من اجل تحزيم ملفات المشروع وانتاج في النهاية ملف يحمل الامتداد MSI ويحتوي بين طياته علي جميع ملفات المشروع التي قمنا بدمجها بداخله كما تفعل الطريقة الاولي ولكن بصورة اقوي واكبر منها تكاد تشبه البرامج العالمية في التحزيم .

مميزات تلك الطريقة

  1. تشبه في استخامها البرامج العالمية في التحزيم كا InstallShield و Wise و SetupFactory و Installaware وغيرها
  2. تملك مرونة كبيرة عن الطريقة الاولي في التحزيم
  3. تعطيك تحكم اكبر في عمليات الاعداد لبرنامجك علي جهاز العميل او الطرف الاخر
  4. امكانية البحث في مسجلات النظام قبل تثبيت البرنامج
  5. امكانية تحديد نوافذ محدده تظهر للمستخدم اثناء عملية التثبيت للنظام
  6. امكانية عمل اختصارات للبرنامج علي سطح المكتب او في قائمة Start Programe
  7. امكانية تحديد امكانيات ومواصفات خاصة سواء للانظمة او قطع الهاردوير الازمة لاتمام عملية التثبيت للبرنامج كتحديد نظام تشغيل معين يقبل التثبيت من عدمه او كتحديد حجم معين متاح من الذاكرة في الجهاز المراد تثبيت البرنامج عليه كجعل برنامجنا لا يتم تثبيته الا علي الاجهزة التي تمتلك رامات اكبر من 512 كمثال مثلا وغيره من الشروط .
  8. يوجد العديد من المميزات التي تجعله الطريقة الاقوي في عملية التحزيم والذي لا يتسع الوقت للحديث عنها .

عيبوب تلك الطريق

  1. تحتاج الي وقت حسب نظرتك لعملية التحزيم الذي يحتاجها نظامك من اجل النشر والعمل علي اجهزة العملاء
  2. تحتاج الي خبرة ودراية كافية من اجل التحزيم بهذا النوع وفهم اليه التحزيم من اجل استخدامها

دواعي الاستخدام لتلك الطريقة

  • في حالة حاجتك لعمل نشر كامل لبرنامجك بصورة احترافية
  • في حالة رغبتك في استخدام شروط التحزيم وتحديد انظمة او معدات محدده لتثبيت برنامجك عليها
  • في حالة بيع النظام لمستخدمين ذو خبرة اقل في التعامل مع الكمبيوتر وخصوصا السوفت وير منها
  • في حالة اعتماد برامجك علي برامج اخري لا تعمل بدونها مثل MS SQL Server او MS Access او غيره

خاتمة

ختاما اتمنا اكون وفقت في تقديم بعض المعلومات عن هذا الموضوع

وان شاء الله سوف نتابع في الاجزاء القادمة من تلك الدورة ونتناول فيها طرق التحزيم بعون الله وقوته فتقبلوا خالص تحياتي

مبدأ المسئولية الواحدة

The Single Responsibility Principle
SRP

المبدأ

يجب ان يكون هناك سبب واحد فقط لتغيير الكلاس

الشرح

عندما نقوم بتصميم الفئات في النظام ، فإننا نقوم بتوزيع المسئوليات ، وعلينا خلال هذه المرحلة ان لا نضع اكثر من مسئولية في نفس الفئة لأن كل مسئولية تعتبر محورا من محاور التغيير . فإذا تغيرت المتطلبات فيما بعد ، فإن علينا ان ان نقوم بتغيير المسئولية في الفئة ، وبالتالي فإن الفئة التي تكون لها اكثر من مسئولية سوف يكون لها اكثر من سبب للتغيير.

إضافة إلى ذلك فإن المسئوليات داخل الفئة الواحدة يمكن ان تقترن فيما بينها ، وتعديل فئة من اجل مسئولية معينة يمكن ان يفسد قدرة الفئة على تحقيق بقية المسئوليات.

للتوضيح ، تأمل في المثال التالي:

Computational Geometry Application 1حيث قمنا بتعريف فئة المستطيل تحتوي على الدالة Area() لحساب مساحة المستطيل ، والدالة Draw لرسم المستطيل على الشاشة . ايضاً قمنا باستخدام تلك الفئة في تطبيقين مختلفين ، الأول Computational Geometry Application يقوم بالحسابات الهندسية ويستخدم فئة المستطيل لهذا الغرض ، ولا يقوم اطلاقاً باي عملية رسم على الشاشة.

والتطبيق الآخر GraphicalApplication يقوم ايضاً ببعض الحسابات الهندسية لكنه اساساً مهتم برسم المستطيل على الشاشة.

واضح ان مثل هذا التصميم يخرق مبدأ SRP فالمستطيل له مسئوليتان الأولى القيام بالحسابات الهندسية ، والأخرى القيام بعمليات الرسم. ومثل هذا الخرق يؤدي إلى العديد من المشاكل ، اولها انه لكي يقوم التطبيق Computational Geometry Application باستيراد مكتبة المستطيل فإنه يجب ان يقوم ايضاً باستيراد المكتبة GUI حتى وان لم يستخدمها.

المشكلة الثانية ، انه إذا تغيرات المتطلبات في Graphical Application بالشكل الذي يتوجب معه تغيير فئة المستطيل فإن علينا ان نقوم ايضاً بإعادة بناء واختبار وتحزيم Computational Geometry Application حتى نضمن انه سوف يعمل بالشكل السليم.

التصميم الجيد يكون عن طريق فصل المسئوليتين في فئتين مختلفتين كلياً كما هو موضح في الشكل التالي:

Computational Geometry Application 2بهذا لم يعد Computational Geometry Application بحاجة إلى استيراد GUI ولن يتأثر بالتعيرات المتعلقة بالرسم.

تعريف المسئولية

المسئولية هي السبب في التغيير

تعرف المسئولية في سياق مبدأ SRP، بإنها السبب في التغيير ، فإذا كنت تعتقد ان هناك اكثر من حافز لتغيير الفئة، فإن لتلك الفئة اكثر من مسئولية.

احيانا يكون من الصعب رؤية هذا ، فنحن معتادون على مزج المسئوليات والتعامل معها على شكل مجموعات ، على سبيل المثال تأمل في واجهة المودم التالية:

// C# Code
public interface Modem
{
    void Dial(string pno);
    void Hangup();
    void Send(char c);
    char Recv();
}

'VB.NET

Public Interface Modem
    Sub Dial(pno As String);
    Sub Hangup();
    Sub Send(c As Char);
    Function Recv() As Char;
End Interface

البعض قد ينظر اليها على انها واجهة ممتازة فجميع الدوال فعلا تنتمي إلى المودم .

في الحقيقة ان هذا التصميم يعرض مسئوليتين الأولى هي إدارة الأتصال (Dial و Hangup من اجل فتج واغلاق الأتصال) ، والثانية هي تراسل البيانات (Send و Recv من اجل استقبال وارسال البيانات).

والسؤال الذي يطرح نفسه هنا، هل يجب فصل المسئوليتين؟ إجابة هذا السؤال تتوقف على الكيفية التي يمكن ان يتغير بها التطبيق.

فإذا كان التطبيق يمكن ان ان يتغير بالشكل الذي يؤثر على توقيع الدوال المتعلقة بإدارة الأتصال ، فإن هذا سوف يؤثر على الزبائن المهتمة بتراسل البيانات ايضاً.

في هذه الحالة يجب الفصل بين المسئوليتين كما يوضح الشكل التالي:

Modem Implementationمن ناحية اخرى إذا كان تغيير في مسئولية يعني التغيير في الأخرى ، فلا حاجة إلى فصل المسئوليتين ، حيث ان مثل هذا الفصل لن يؤدي إلا إلى مزيد من التعقيد.

فصل المسئوليات المقترنة

لو عدنا إلى الشكل الأخير، سوف تجد اننا وضعنا المسئوليتين في الفئة Modem Implementation ، ورغم ان هذا غير مستحب ، إلا انه ضروري احيانا ، فهناك العديد من العوامل المتعلقة بتفاصيل العتاد ونظام التشغيل والتي تجبرنا على القيام بمثل هذا الأقتران ، رغم هذا وعن طريق فصل الواجهات فإننا قمنا بفك هذا الأقتران بالدرجة المطلوبة تماماً ، وعليه فإن Modem Implementation هو عنصر مجهول لبقية العناصر لا يتم التعامل معه مباشرة ، باستثناء الدالة main في التطبيق.

المراجع

كيفية برمجة Microsoft Agent

قمت بكتابة هذا الدرس سابقا باللغة الإنجليزية في موقعي Just Like a Magic. قم بزيارة الموقع لمزيد من التفاصيل.

المحتويات

  • المحتويات
  • نظرة خاطفة
  • مقدمة
  • شخصيات Microsoft Agent
  • متطلبات Microsoft Agent
  • ملفات Microsoft Agent
  • استخدام Microsoft Agent
  • إظهار الشخصية
  • جعل الشخصية تتكلم
  • تحريك الشخصية
  • حركات الشخصية
  • خواص عنصر الشخصية
  • أحداث أداة Microsoft Agent
  • إنشاء مدير الشخصيات
  • إنهاء Microsoft Agent
  • المراجع
  • المثال
  • خاتمة

نظرة خاطفة

درسنا في هذا اليوم يتكلم عن طريقة برمجة Microsoft Agent في برنامج الويندوز Windows Application الخاص بك. تعتبر Microsoft Agent تقنية ليس لها مثيل فهي تمكنك من خلق شخصيات متحركة تقوم بالتحادث مع المستخدم وإرشاده. مثال على شخصيات Microsoft Agent هو مساعد الأوفيس Office Assistant وهو الذي يظهر في أوفيس 2003 والإصدارات السابقة. وهو عبارة عن شخصية تقوم بمساعدة المستخدم وتشرح لها النوافذ والشاشات والأوامر التي يتعرض لها.

سنبدأ أولا بشرح مقدمة عن Microsoft Agent. ثم سنتكلم عن الشخصيات المتوفرة والتي يمكن استخدامها. بعد ذلك سوف نتطرق عن المكتبات التي ستحتاجها وكيفية استخدامها في برنامجك. بعد ذلك سوف نشرع في شرح كل نقاط Microsoft Agent من أوامر وأحداث. سنتعلم كيفية تحريكه وكيفية جعله يتكلم وغيرها.

بالإضافة إلى هذا كله، يحتوي هذا الدرس على برنامج يقوم بتقسيم الملفات كبيرة الحجم إلى أكثر من جزء ثم يقوم بدمجها بعد ذلك. يحتوي هذا البرنامج على شخصية ظريفة من شخصيات Microsoft Agent تتفاعل مع المستخدم ومع حركاته خلال البرنامج وتقوم بشرح أجزاء البرنامج بطريقة مبسطة وظريفة.

مقدمة

تعتبر تقنية Microsoft Agent هي أحد التقنيات الفريدة التي تساعدك على إنشاء واجهة Interface ذكية تقوم بالتفاعل مع المستخدم، فتقوم بالتحرك والتحادث معه. وتعتبر هذه التقنية سهلة من خلال التعامل معها برمجيا حيث توفر لك العديد من الأوامر البسيطة التي تمكنك من التحكم الكامل بهذه التقنية بطريقة بسيطة وسهلة.

توفر لك تقنية Microsoft Agent إمكانية استخدام شخصيات متحركة سواء في برامج الويندوز أو مواقع الويب. حيث يمكنك تحريك هذه الشخصيات وجعلها تتكلم سواء بكلام تحدده أنت أو تقوم بتسجيله بصوتك. كما يمكنك التفاعل أكثر مع المستخدم من خلال الأحداث الخاصة بهذه الشخصيات. حيث يمكنك جعل هذه الشخصيات تقوم بالتحدث مع المستخدم مثلا عند الضغط عليها. كما يمكنك استخدام الأحداث الخاصة بالبرنامج مثلا في جعل هذه الشخصيات تقوم بشرح نقطة معينة في البرنامج عند وصول المستخدم لها أو عند ضغوطه زر المساعدة F1.

كما تلاحظ أنه يمكننا استخدام Microsoft Agent وشخصياتها في العديد من الأماكن والأدوار التي لا حصر لها ومن هذه الأماكن التي يمكنك استخدام هذه التقنية فيها هو إنشاء مساعد للمستخدم وهذا المساعد Assistant يمكننا استغلاله في أدوار كثيرة منها:

  • القيام بشرح البرنامج أو الموقع للمستخدم عند دخوله أول مرة عليه.
  • القيام بشرح عمليات خاصة في البرنامج ومساعدة المستخدم على استكمالها خطوة بخطوة.
  • القيام بإبلاغ المستخدم عند حدوث أمر معين مثلا عند استقباله بريدا إلكترونيا جديدا مثلا.
  • القيام بعمل عمليات معينة مثلا البحث على الإنترنت بدلا من المستخدم حيث يقوم المستخدم مثلا بإعطائه نص البحث ويقوم هذا المساعد بالبحث تلقائيا. أو أن يقوم المساعد بالتفاعل مع المستخدم فعند دخوله شاشة أو نافذة معينة مثلا يقوم المساعد بعمل بحث عن المواقع أو الملفات التي يمكنها مساعدة المستخدم.

شخصيات Microsoft Agent

يمكنك مشاهدة تقنية Microsoft Agent وشخصياتها Characters في العديد من الأماكن والبرامج، ومن أشهرها الـ Office الإصدار 2003 أو الإصدارات السابقة حيث يقوم باستغلال تقنية Microsoft Agent في إنشاء مساعد الأوفيس Office Assistant الشخصية التي تقوم بمساعدة المستخدم أثناء تعامله مع برامج الأوفيس. كما تجد أيضا هذه الشخصيات وهذه التقنية في العديد من مواقع الإنترنت. شكل 1 و 2 عبارة عن اثنان من شخصيات Microsoft Agent الشهيرة والاثنان تجدهم مع الأوفيس الأول هو المشبك Clippit والثاني هو الساحر Merlin.

شكل 1 - المشبك Clippit

شكل 1 - المشبك Clippit

شكل 2 - الساحر Merlin

شكل 2 - الساحر Merlin

هناك العديد من الشخصيات التي يمكنها الوصول إلى أكثر من 50 شخصية موجودين وجاهزين للتحميل من مواقع الإنترنت ومن الأماكن المختصة. ومن هذه الشخصيات الساحر Merlin وهو يأتي بشكل أساسي مع الويندوز. كما أن هناك العديد من الشخصيات التي تأتي مع الأوفيس.

هذه الشخصيات عبارة عن ملفات لها الامتداد acs.

الموقع الرسمي لتقنية Microsoft Agent والذي سوف تجد به العديد والعديد من الشخصيات الجاهزة للتحميل مجانا هو Microsoft Agent Ring.

متطلبات Microsoft Agent

كي يمكنك استخدام تقنية Microsoft Agent في برنامجك يجب أن تكون بعض الأشياء متوفرة:

  1. المكتبات الأساسية Microsoft Agent SDK
    وهي التي تحتوي على جميع الأوامر من إظهار الشخصيات وتحريكها وغيرها من الأوامر الهامة الخاصة بالشخصية والتقنية نفسها Microsoft Agent كما تسمى هذه المكتبات -أو يمكننا القول التقنية- تسمى الخادم Agent Server لأن التقنية نفسها عبارة عن خادم لبرنامج حيث تقوم أنت بإعطائه الأمر كي يقوم بإظهار الشخصية أو تحريكها وهكذا.
  2. الشخصية Character
    وهي عبارة عن ملف من نوع acs يحتوي هذه الشخصية. بالطبع يمكنك تحميل الشخصيات التي تريدها أو حتى إنشاء الشخصيات بنفسك. وفكرة فصل الخادم Server (التقنية) عن الشخصية تعتبر فكرة فريدة من نوعها حيث أنه توفر لنا طريقة واحدة للتعامل مع كل الشخصيات بغض النظر عن إمكانيات كل شخصية أو شكلها أو الحركات الخاصة بها.
  3. محرك تحويل النص إلى صوت Text-to-Speech (TTS) Engine
    وهذا في حالة أنك تريد أن تقوم الشخصية بقول نص معين تقوم أنت بتحديده. وهذا المحرك يختلف باختلاف اللغة. فهناك مثلا محرك للغة الإنجليزية ومحرك آخر للفرنسية وآخر للإسبانية وآخر لليابانية وهكذا. فإذا كنت تريد استخدام لغة معينة فحينها يمكنك تنصيب محرك هذا اللغة على جهاز المستخدم. لاحظ أنه للأسف ليس هناك محركا للغة العربية. (سوف نتعلم كيفية التغلب على هذه المشكلة لاحقا)

هذه المعطيات التي حددناها يجب توافرها على جهاز المستخدم وكذلك على جهاز المطور أثناء استخدامه لهذه التقنية. ولحسن الحظ، فهي متوفرة للتحميل من خلال صفحة Microsoft Agent الموجودة على موقع Microsoft والتي يمكنك زيارتها من هنا. كما يمكنك أيضا من خلال هذه الصفحة تحميل برنامج Microsoft Agent Editor والذي يمكنك من خلاله إنشاء الشخصيات الخاصة بك.

كما يمكنك أيضا من خلال هذه الصفحة تحميل المساعدة Documentation الخاصة بـ Microsoft Agent وهي ستشرح لك كل شيئ عن هذه التقنية وكيفية استخدامها.

بالطبع يمكنك إضافة هذه المتطلبات إلى المنصب Installer الخاص ببرنامجك كي يقوم بتنصيبها تلقائيا.

ملفات Microsoft Agent

عند تنصيبك لمكتبات Microsoft Agent ستجد أنها تقوم بالتنصيب تلقائيا في المسار %windir%\MSAgent. هناك ستجد جميع الملفات الخاصة بهذه التقنية. لحسن الحظ، لا نحتاج منها إلا الملف AgentCtl.dll ولكن بالطبع الخادم Agent Server (التقنية) يحتاج إلى كل هذه الملفات.

في هذه الملفات أيضا ستجد البرنامج AgentSvr.exe وهو المسؤول كما قلنا عن التقنية كلها أي هو المسؤول عن الأوامر الخاصة بجميع الشخصيات التي تعمل على الجهاز. ولهذا عند تشغيلك لأي برنامج يحتوي على هذه التقنية ستجد أن هذا الملف يعمل في خلفية الجهاز Background فلهذا يمكنك مشاهدة عمله من خلال التبويب Process الموجود في Task Manager. وعند إغلاقك لهذا الملف تغلق جميع الشخصيات المفتوحة.

لاحظ أن المجلد %windir%\MSAgent يحتوي على مجلد داخلي آخر وهو chars وهو يحتوي على جميع الشخصيات الأساسية التي تم تحميلها مع المكتبات. وهي في الغالب شخصية واحدة وهي الساحر Merlin.

استخدام Microsoft Agent

درسنا في هذا اليوم يتكلم عن طريقة استخدام Microsoft Agent في مشروع الويندوز Windows Application الخاص بك. للأسف درسنا لا يتكلم عن كيفية استخدامه في مشروع الويب Web Application. ولكن طريقة استخدامه بسيطة ومماثلة كثيرا. تختلف فقط في كيفية إضافة المكتبة إلى المشروع. يمكنك القراءة عن مشاريع الويب في المساعدة Documentation الخاص بـ Microsoft Agent. أما مشاريع XAML Application فهي شبيهة جدا بالويندوز Windows Application ولكن الفرق هو في كيفية إضافة أداة Microsoft Agent إلى النموذج Form أو النافذة Window التي تريدها. فقط قم بإضافة عنصر من النوع WindowsFormsHost كي يحوي هذه الأداة.

الآن قم بفتح مشروع جديد من نوع Windows Application. بعد فتح المشروع الجديد قم بإضافة أداة Microsoft Agent إلى شريط الأدوات Toolbox عن طريق الضغط عليه بالزر الأيمن واختيار Choose Toolbox Items. لتظر شاشة Choose Toolbox Items شكل 3.

شكل 3 - الأداة Microsoft Agent Control في شاشة اختيار الأدوات

شكل 3 - الأداة Microsoft Agent Control في شاشة اختيار الأدوات

بما أن الأداة Microsoft Agent موجودة في المكتبة AgentCtl.dll كما قلنا. فإن المكتبة AgentCtl.dll ليست مكتبة دوت نت أي لم يتم إنشائها باستخدام بيئة .NET Framework. بدلا من ذلك فإنه تم إنشائها باستخدام تقنيات سابقة ولذلك تسمى هذه المكتبة COM Library. ولهذا فلإضافة هذه الأداة سوف نقوم بالتحويل إلى التبويب COM Components ثم سوف نبحث في القائمة عن الأداة Microsoft Agent Control ونختارها كي يتم إضافتها. انظر الشكل السابق. إن لم تجد الأداة في القائمة قم بضغط زر Browse لتحديد مكان المكتبة وهي موجودة في %windir%\MSAgent.

بعد إضافة الأداة Microsoft Agent Control إلى المشروع ستجد أنه تمت إضافة مكتبتين إلى مراجع References المشروع الخاصة بك وهما AgentObjects و AxAgentObjects ولكن لم تتم إضافة المكتبة AgentCtl.dll! لماذا؟ هذا لأن كما قلنا هذه المكتبة هي COM Library ولذلك فإنك لا تستطيع التعامل معها مباشرة من خلال الكود. بدلا من ذلك فإن Visual Studio يقوم بإنشاء مكتبتين دوت نت عبارة عن وسطاء بينك وبين المكتبة الأصلية وبهما جميع عناصر المكتبة الأصلية. ولهذا يمكنك التعامل معها مباشرة كأنك تتعامل مع المكتبة الأصلية.

الآن قم بإضافة الأداة إلى النموذج Form الذي تريده. انظر شكل 4.

شكل 4 - الأداة Microsoft Agent Control في النموذج

شكل 4 - الأداة Microsoft Agent Control في النموذج

لاحظ أنك لا تحتاج إلى إضافة هذه الأداة في كل النماذج. بل يكفي إضافتها إلى نموذج واحد فقط (وهو في الغالب شاشة البرنامج الرئيسية) وبالتالي ستعمل الشخصية في كل النماذج والشاشات الخاصة بالمشروع.

إذا كنت تستخدم C# قم بإضافة هذا السطر إلى الدالة Main():

[System.STAThreadAttribute]
void Main()
{
   ...
}

قمنا بإضافة الخاصية Attribute المسماة بـ STAThreadAttribute إلى الدالة Main() وذلك لأن المكتبة الخاصة بنا هي لها الخاصية STA (Single-Threaded Apartment) وهذا معناه أنه لا يمكنك التعامل مع هذه المكتبة إلا بـ Thread واحد فقط. وهذا بعكس المكتبات MDA (Multi-Threaded Apartment).

إظهار الشخصية

يمكننا الآن جعل Agent Server أو يمكننا القول تقنية أو أداة Microsoft Agent يمكننا الآن إظهار الشخصية أو الشخصيات التي نريدها وذلك عن طريق الخاصية Characters الخاصة بهذه الأداة. لاحظ الكود التالي والذي يقوم بتحميل شخصية الساحر Merlin من الملف الخاص بها وإعطائها الاسم Merlin كي نستطيع التعامل معها ولكي نميزها عن الشخصيات الأخرى في حالة استعمالنا لأكثر من شخصية. لاحظ أيضا كيفية الحصول على الشخصية المحملة وكيفية إظهارها. لاحظ أنه هناك فرق بين تحميلها في الذاكرة وبين إظهارها. فيجب تحميل الشخصية أولا ثم إظهارها.

// C# Code

this.axAgent1.Characters.Load("Merlin",
    @"C:\Windows\MSAgent\chars\merlin.acs");

AgentObjects.IAgentCtlCharacterEx character =
    this.axAgent1.Characters.Character("Merlin");

character.Show(null);

' VB.NET Code

Me.axAgent1.Characters.Load("Merlin", _
    "C:\Windows\MSAgent\chars\merlin.acs")

Dim character As AgentObjects.IAgentCtlCharacterEx = _
    Me.axAgent1.Characters.Character("Merlin")

character.Show(Nothing)

يحتوي عنصر الشخصية Character على الدالة Hide وذلك لإخفائه. كما تحتوي الأداة Microsoft Agent على الدالة Unload الموجودة في الخاصية Characters وذلك لإلغاء العنصر وإغلاقه. لاحظ أيضا أنه هناك فرق بين مجرد الإخفاء وبين الإغلاق تماما.

جعل الشخصية تتكلم

يمكنك جعل الشخصية تتكلم إن نص تحدده أنت وإما ملف صوتي موجود على الجهاز. وفي حالة تحديد نص سوف يقوم Agent Server باستخدام Text-to-Speech (TTS) Engine لتحويل النص إلى صوت. وسوف يقوم بتحريك فم الشخصية حتى تحاكي عملية التكلم. وهذا أيضا في حالة الملف الصوتي. لاحظ أنه كما قلنا هناك TTS Engine لأكثر من لغة. ولكن للأسف ليس من ضمنها اللغة العربية فلهذا يمكننا التغلب على هذا عن طريق تسجيل الصوت وجعل الشخصية تقوم هذا الصوت. لاحظ الكود التالي والذي يقوم في السطر الأول بجعل الشخصية تتكلم بنص معين وفي السطر الثاني بجعل الشخصية تتكلم بملف صوتي.

// C# Code

character.Speak(
    "Hello everybody, I'm Merlin. " +
    "I'll guide you throuh the application windows.",
    null);

character.Speak(null, @"D:\Recorded Introduction.wav");

' VB.NET Code

character.Speak( _
    "Hello everybody, I'm Merlin. " & _
    "I'll guide you throuh the application windows.", _
    Nothing)

character.Speak(Nothing, "D:\Recorded Introduction.wav")

لاحظ شكل 5 والذي يظهر الساحر Merlin أثناء التكلم.

شكل 5 - الساحر Merlin يتكلم

شكل 5 - الساحر Merlin يتكلم

هناك أيضا الدالة Think() والتي تجعل الشخصية تظهر كأنها تفكر وليس تتكلم.

تحريك الشخصية

السطر التالي يوضح كيفية تحريك الشخصية. إلى النقطة 100، 100 (س، ص) من الشاشة من أعلى أيسر الشاشة.

// C# Code

character.MoveTo(100, 100, null);

' VB.NET Code

character.MoveTo(100, 100, Nothing)

لاحظ أن الدالة MoveTo لها 3 مدخلات الأول هو الإحداثي س (x) من يسار الشاشة. الثاني هو الإحداثي ص (y) من أعلى الشاشة. الثالث هو سرعة الحركة. إذا قمت بتحديد السرعة كـ null (Nothing في VB.NET) كما في المثال فهذا معناه أن السرعة سوف تكون متوسطة أي 1000. إذا قمت بتحديد رقم أعلى من 1000 سوف تكون السرعة أعلى. أو رقم أقل سوف تكون السرعة أبطأ. إذا قمت بتحديد 0 سوف تختفي الشخصية من مكانها وتظهر في المكان الجديد بدلا من انتقالها تدريجيا.

حركات الشخصية

لاحظ الفرق الذي نريده بين التحريك Move وهو عبارة عن انتقال الشخصية من مكانها وبين الحركة Animation وهي المقصودة هنا وهي أي حركة تقوم الشخصية بعملها مثلا كما في المشبك Clippit عندما يتحول إلى شكل دراجة أثناء ظهوره وهكذا. فالـ Animation لا تجعل الشخصية تتحرك من مكانها.

لكل حركة Animation اسم. ولكل شخصية الحركات الخاصة بها ولكنها قد تتشابه في بعض الحركات.ولكي تقوم بالبرمجة باستخدام شخصية معينة يجب عليك معرفة أسماء الحركات الخاصة بها وذلك لتفادي محاولتك لتشغيل حركة معينة قد تكون هذه الشخصية لا تدعمها أو حتى لا تكون باسم مختلق. فلكي تحصل على أسماء جميع الحركات يمكنك ذلك عن طريق الخاصة AnimationNames الخاصة بالشخصية. الكود التالي يوضح كيفية إضافة أسماء جميع الحركات الخاصة بالشخصية إلى قائمة ListBox موجودة على الفورمة.

// C# Code

IEnumerator enumerator =
    character.AnimationNames.GetEnumerator();
while (enumerator.MoveNext())
    this.listBox1.Items.Add(enumerator.Current.ToString());

' VB.NET Code

Dim enumerator As IEnumerator = _
    character.AnimationNames.GetEnumerator()
While enumerator.MoveNext()
    Me.listBox1.Items.Add(enumerator.Current.ToString())
End While

والآن سوف نقوم بعمل بعض السحر باستخدام الساحر Merlin وسوف نقوم بجعل الساحر يتكلم أثناء عمله لهذه الحركة. لاحظ أن الحركة التي نريدها تسمى DoMagic1.

// C# Code

character.Play("DoMagic1");
character.Speak("I'm better than Harry Potter. Am not I?", null);

' VB.NET Code

character.Play("DoMagic1");
character.Speak("I'm better than Harry Potter. Am not I?", _
    Nothing)

شكل 6 يظهر Merlin وهو يستعرض مواهبه. لاحظ أن الحركة سوف تنتهي في خلال ثواني.

شكل 6 - الساحر Merlin يقوم بالسحر

شكل 6 - الساحر Merlin يقوم بالسحر

لاحظ أن هناك بعض الحركات التي تحتوي على اتجاهات مثلا Left و Right مثل الحركتين GestureRight و GestureLeft والتي تجعل الشخصية تنظر إلى اليمين واليسار. لاحظ أن اليمين واليسار هنا هما يمين ويسار الشخصية وليس أنت. فلهذا يسار الشخصية يعتبر يمينك ويمينها هو يسارك!

هناك أيضا الدالة GestureAt() وهي تقوم بجعل الشخصية تنظر إلى اتجاه معين.

هناك أيضا الدوال Stop() و StopAll() الخاصة بالشخصية. الأولى تقوم بإيقاف حركة معينة. والثانية تقوم بإيقاف حركات من نوع خاص. فالدالة StopAll() تأخذ مدخلا واحدا وهو إما “Move” وهذا إذا كنت تريد إيقاف التحركات Movement الخاصة بالشخصية، أو “Play” وذلك لإيقاف الحركات Animations، أو “Speak” وذلك لإيقاف التكلم، أو null (Nothing في VB.NET) وذلك لإيقاف أي حركات من أي نوع.

خواص عنصر الشخصية

التالي هو قائمة بأشهر الخواص Properties الموجودة في عنصر Object الشخصية:

  • AutoPopupMenu
    إذا قمت بإعطائها القيمة True فإن القائمة المنسدلة Popup Menu (Context Menu) الخاصة بالشخصية سوف تظهر في حالة ضغط المستخدم للزر الأيمن على الشخصية. هذه القائمة لا تحوي إلا الأمر Hide لإخفاء الشخصية.
  • Balloon
    هي خاصية للقراءة فقط Read-Only أي أنه لا يمكنك تعديلها. وتحتوي على خواص البالون الذي يظهر فوق الشخصية عند التكلم. وهذه الخواص منها لون الخلفية ولون نص الكتابة. طبعا لتعديل هذه الخواص يمكنك ذلك عن طريق خصائص سطح المكتب من اختيارات Appearance. فقط قم بتغيير خصائص الملاحظات Tooltip.
  • Left و Top
    تحتوي على الإحداثيات، بمعنى آخر مكان، الشخصية على الشاشة. قم باستخدام الدالة MoveTo() أفضل وذلك لعمل حركة Animation أثناء التحرك Move.
  • SoundEffectsOn
    قم بإعطاء هذه الخاصية True لتشغيل أصوات التأثيرات Effects للشخصية أو False لإلغائها. لاحظ أن أصوات التأثيرات مختلفة عن أصوات التكلم.
  • Speedهي خاصية للقراءة فقط Read-Only أي أنه لا يمكنك تعديلها. وتحتوي على سرعة الشخصية. بعض الدوال مثل MoveTo() تأخذ قيمة لتحديد السرعة.

أحداث أداة Microsoft Agent

الآن سوف نلقي الضوء على بعض الأحداث Events الخاصة بالأداة Microsoft Agent Control. بشكل غريب، تحتوي هذه الأداة على أحداث من المفترض أن تكون موجودة في عنصر الشخصية نفسه ولكنها موجودة في الأداة نفسها وهذا يدل على أن المسؤول عن التحركات وجميع عمليات الشخصية ليست الشخصية نفسها بل هو الخادم Agent Server.

  • BallonShow و BallonHide
    تحدث عند ظهور البالون فوق شخصية أو إختفائه.
  • ClickEvent و DblClick
    تحدث عند الضغط بالفأرة ضغطة واحدة أو مرتان على شخصية.
  • DragStart و DragComplete
    تحدث عند بدأ تحريك شخصية وعند الانتهاء.
  • ShowEvent و HideEvent
    تحدث عند ظهور شخصية وعند اختفائها.
  • MoveEvent
    تحدث عند تحريك شخصية

إنشاء مدير الشخصيات

عند تعاملك مع Microsoft Agent وخصوصا عندما يكون عندك نوافذ عدة في برنامجك وعندما يكثر التعامل مع الشخصية ستجد انه من الصعوبة أن تقوم كل مرة باسترجاع الشخصية أو الشخصيات والتعامل معها بسهولة وخصوصا مع كثرة الدوال وكثرة الحركات التي تريدها فستجد أنه من الصعوبة تذكر أسماء الحركات كاملة. ولذلك فإن الحل هو في أن تقوم بإنشاء عنصر Class معينة تقوم باحتواء هذه الشخصية وتقوم بإضافة الأوامر الخاصة بك إليها وكذلك الأوامر التي تحوي أوامر متعددة. ونسمي هذه الكلاس المدير أو المتحكم Controller. لاحظ الكود التالي والذي يظهر لنا Controller متميز لهذه الشخصية.

// C# Code

internal sealed class AgentController
{
    private AgentObjects.IAgentCtlCharacterEx _char;
    private AxAgentObjects.AxAgent _agent;

    public AgentController(AxAgentObjects.AxAgent agent,
        string characterLocation)
    {
        _agent = agent;
        _agent.Characters.Load("CHAR", characterLocation);
        _char = _agent.Characters.Character("CHAR");
        _char.Show(null);
    }

    public bool IsVisible() { return _char.Visible; }
    public void Animate(string animation, bool stopAll)
    {
        if (stopAll)
            _char.StopAll(null);
        _char.Play(animation);
    }
    public void Speak(string text, bool stopAll)
    {
        if (stopAll)
            _char.StopAll(null);
        _char.Speak(text, null);
    }
    public void MoveTo(int x, int y, bool stopAll)
    {
        if (stopAll)
            _char.StopAll(null);
        _char.MoveTo((short)x, (short)y, null);
    }

    public void Surprise()
    {
        _char.StopAll(null);
        _char.Play("Alert");
        _char.Play("Sad");
    }
}

' VB.NET Code

Friend NotInheritable Class AgentController
    Private _char As AgentObjects.IAgentCtlCharacterEx
    Private _agent As AxAgentObjects.AxAgent

    Public Sub New(ByVal agent As AxAgentObjects.AxAgent, _
         ByVal characterLocation As String)

        _agent = agent
        _agent.Characters.Load("CHAR", characterLocation)
        _char = _agent.Characters.Character("CHAR")
        _char.Show(Nothing)
    End Sub

    Public Function IsVisible() As Boolean
        Return _char.Visible
    End Function

    Public Sub Animate(ByVal animation As String, _
        ByVal stopAll As Boolean)
        If (stopAll) Then
            _char.StopAll(Nothing)
        End If
        _char.Play(animation)
    End Sub

    Public Sub Speak(ByVal text As String, _
        ByVal stopAll As Boolean)
        If (stopAll) Then
            _char.StopAll(Nothing)
        End If
        _char.Speak(text, Nothing)
    End Sub

    Public Sub MoveTo(ByVal x As Integer, _
        ByVal y As Integer, ByVal stopAll As Boolean)
        If (stopAll) Then
            _char.StopAll(Nothing)
        End If
        _char.MoveTo(x, y, Nothing)
    End Sub

    Public Sub Surprise()
        _char.StopAll(Nothing)
        _char.Play("Alert")
        _char.Play("Sad")
    End Sub
End Class

لاحظ في الكود السابق أنه لجعل الشخصية تقوم بعمل حركة كأنها تفاجأت بشيئ، يجب أن نقوم بإيقاف الحركة السابقة -إن كانت تعمل- ثم تشغيل الحركة Alert ثم تشغيل الحركة Sad وبالتالي سنجعل الشخصية تنتبه ثم تقوم تحزن كأنها تفاجأت بشيئ كأن خطأ معين مثلا حدث في البرنامج.

لاحظ بالطبع أنه من الأسهل عليك النداء على الدالة Surprise بدلا من النداء على الثلاثة دوال التي بداخلها في حالة أنك تريد عمل هذه الحركة.

لاحظ أيضا أنك يمكنك إضافة دوالك وأحداثك.

إنهاء Microsoft Agent

بما أن مكتبات Microsoft Agent كما قلنا هي COM Libraries وبما أن Microsoft Agent تقنية تقوم باستهلاك موارد من الجهاز فإنك تريد حالة انتهائك من استخدامها إزالة هذه الموارد حتى لا تبقى في الذاكرة لفترات طويلة. بالطبع وكما تعرف أنه يمكنك استخدام الدالة Characters.Unload الموجودة في الأداة Microsoft Agent Control وذلك لإنهاء الشخصية من الذاكرة بعد انتهاء التعامل معها. بالإضافة إلى ذلك يجب عليك إنهاء الأداة نفسها من الذاكرة وهي عبارة عن كود بسيط وهو كالتالي:

// C# Code

while (System.Runtime.InteropServices.
    Marshal.FinalReleaseComObject(_char) > 0);

' VB.NET Code

While System.Runtime.InteropServices.
    Marshal.FinalReleaseComObject(_char) > 0
End While

يمكنك إضافة هذه الأسطر إلى أي مكان لإنهاء الأداة كما يمكنك إضافتها إلى المدير Controller الخاص بك. حيث يمكنك تطبيق الـ Interface المسماة بـ IDisposable على المدير وتطبيق الدالة الوحيدة الخاصة بها وهي Dispose() وإضافة هذا الكود بها. ويمكنك بعد ذلك إضافة سطر النداء على الدالة Dispose() بدلا من الأسطر نفسها في المكان الذي ترغب فيه في الانتهاء من الأداة والشخصيات. لاحظ الكود التالي.

الكود مختصر للوضوح.

// C# Code

internal sealed class AgentController : IDisposable
{

    ............

    public void Dispose()
    {
        _char.StopAll(null);
        _char.Hide(null);
        _agent.Characters.Unload("CHAR");
        while (System.Runtime.InteropServices.
            Marshal.FinalReleaseComObject(_char) > 0) ;
    }
}

' VB.NET Code

Friend NotInheritable Class AgentController
    Implements IDisposable

    ............

    Public Sub Dispose() _
            Implements IDisposable.Dispose
        _char.StopAll(Nothing)
        _char.Hide(Nothing)
        _agent.Characters.Unload("CHAR")
        While System.Runtime.InteropServices.
            Marshal.FinalReleaseComObject(_char) > 0
        End While
    End Sub
End Class

كما يمكنك إضافة كود النداء على هذه الدالة في كود الدالة Dispose() أيضا ولكن الخاصة بالنموذج وهكذا تضمن إزالة الأداة من الذاكرة فور انتهاء النموذج أي انتهاء برنامجك (في الغالب). لاحظ الكود التالي في النموذج:

// C# Code

Protected override void Dispose(bool disposing)
{
    try
    {
        if (disposing)
        {
            this.axAgent1.Dispose();
            if (components != null)
                components.Dispose();
        }
    }
    finally { base.Dispose(disposing); }
}

' VB.NET Code

Protected Overrides Sub Dispose(ByVal disposing As Boolean)
    Try
        If (disposing) Then
            Me.axAgent1.Dispose()
            If (components IsNot Nothing) Then
                components.Dispose()
            End If
        End If
    Finally
        base.Dispose(disposing)
    End Try
End Sub

يمكنك الوصول إلى الدالة Dispose() الخاصة بالنموذج من خلال شاشة تحرير الكود Code Editor من خلال القائمة Members الموجودة في أعلى يمين الشاشة.

ليس فقط إزالة هذا العنصر من الذاكرة هو المطلوب، بل هذا مطلوبا في جميع العناصر بجميع الأنواع وهي التي توفر لنا الخاصية Dispose() أما عناصر COM فيمكننا إزالتها بالطريقة السابقة.

المراجع

بعض المراجع الهامة في تقنية Microsoft Agent:

  • مساعدة Microsoft Agent
    يمكنك تحميلها من صفحتها بـ Microsoft. انظر القسم “استخدام Microsoft Agent”.
  • كتاب: Microsoft Agent Software Development Kit
    ISBN: 0-7356-0567-X
  • كتاب: Developing for Microsoft Agent
    ISBN: 1-5723-1720-5

المثال

المثال ليس عبارة عن فقط مثال. بل هو برنامج كامل يقوم بتقسيم الملفات كي يتم حفظها على أقراص مرنة Floppy Disks أو اسطوانات مدمجة CDs أو حتى إرسالها بالبريد الإلكتروني أو غيرها. كما يقوم البرنامج بدمجها مرة أخرى.

يحتوي هذا البرنامج برنامج Geming PartIt! على شخصية الساحر Merlin والذي يقوم بشرح خطوات البرنامج بطريقة بسيطة وسلسة.

قم بتحميل البرنامج من هنا

قم بتحميل الكود من هنا

تنشرف باستقبال آرائكم واقتراحتكم حول الكود.

خاتمة

تعلمنا في هذا الدرس الكثير والكثير من الأفكار المدهشة والتي يمكنك تطبيقها في برنامجك لتوفير واجهة Interface جميلة وظريفة يمكن للمستخدم التعامل معها ببساطة. في دروس أخرى سنتعلم أفكار أخرى وتقنيات أخرى.

قمت بكتابة هذا الدرس سابقا باللغة الإنجليزية في موقعي Just Like a Magic.

المحتويات

  • المحتويات
  • نظرة خاطفة
  • مقدمة
  • نظرة على الأصناف
    • الأصناف في System.Net.Mail
    • الأصناف في System.Web.Mail
  • سيرفرات SMTP
  • التطبيق
  • تغيير طريقة إيصال الرسالة
  • مثال
  • خاتمة

نظرة خاطفة

نقوم في هذا الدرس بإذن الله تعالى بشرح كيفية إرسال رسائل البريد الإلكتروني من خلال بيئة الدوت نت. ويتم ذلك عن طريق خدمة SMTP.

بداية، سوف نقوم بشرح خدمة SMTP ثم سوف نقوم بشرح الأنواع Types التي توفرها لنا بيئة الدوت نت للتعامل مع SMTP. بعد ذلك سوف نقوم بسرد بعض أشهر سيرفرات SMTP ثم سوف نقوم بتطبيق ما تعلمناه من خلال كود مبسط وسهل.

مع الدرس مثال عبارة عن برنامج يقوم بإرسال رسائل البريد الإلكتروني من خلال أكثر من مزود لخدمة البريد الإلكتروني E-Mail Service Provider.

مقدمة

بروتوكول نقل البريد Simple Mail Transfer Protocol أو كما يختصر إلى SMTP هو أحد الطرق المميزة والسهلة التي توفر خدمة إرسال رسائل البريد الإلكتروني عن طريق خادم (سيرفر) Server معين يسمح لهذا البروتوكول بالعمل.

منذ ظهور بيئة الدوت نت وهي تسمح للمبرمج بإرسال رسائل البريد الإلكتروني عن طريق الأصناف Classes الموجودة في الـ Namespace المسمى System.Web.Mail وهو موجود في المكتبة System.Web.dll.

مع ذلك، وبظهور بيئة الدوت نت 2.0، ظهرت Namespace جديدة توفر للمبرمج ميزات أعلى وأكثر وأكبر تمكنه من استغلال كافة إمكانات بروتوكول SMTP لإرسال الإيميلات، وهذه الـ Namespace هي System.Net.Mail وهي موجودة في المكتبة System.dll لذلك سوف تجدها دائما في كل برامجك لأنه وكما تعلم ليس هناك استغناء عن المكتبة System.dll.

لهذا، فإنه يمكنك استخدام إما الأصناف الموجودة في System.Web.Mail أو الأصناف الموجودة في System.Net.Mail. مع ذلك الثانية أفضل وأقوى. حتى أنه عند استخدامك لـ System.Web.Mail سوف يقوم الـ Compiler بتحذيرك.

في القسم التالي نتكلم عن أشهر الأصناف Classes والأنواع Types التي ستحتاجها عند تعاملك مع بروتوكول SMTP. لاحظ تشابه الأصناف إلى حد كبير في System.Net.Mail إلى System.Web.Mail. في الكود سوف تلاحظ أيضا أن الفرق بسيط في التعامل لذلك إذا كنت تتعامل مع الدوت نت 1.0 أو حتى أي إصدار تستطيع قراءة هذا الدرس.

نظرة على الأصناف

الأصناف في System.Net.Mail

توفر لك الـ Namespace المسمى System.Net.Mail عدد، ليس بالعدد الكبير، بل عدد مناسب من الأصناف والأنواع التي توفر لك التحكم الكامل في رسالتك وفي طريقة إرسالها باستخدام بروتوكول SMTP بل وتوفر لك خيارات مُفصَّلة جدا في رسالتك.

بالطبع لن تحتاج إلى معرفة جميع الأصناف في هذه الـ Namespace ولكن يجب عليك معرفة ما توفره لك بيئة الدوت نت عند التعامل مع بروتوكول SMTP وهذا يجعلك قادر على تطوير برنامجك في جميع الأبعاد.

التالي هو قائمة بأشهر الأنواع -ربما جميعها- الموجودة في هذه الـ Namespace:

  • SmtpClient:
    واحدة من الأصناف الهامة جدا والأساسية وهي التي توفر لك الإمكانيات لإرسال الرسال. بداية، يجب عليك توفير البيانات المناسبة لهذه الكلاس من خلال الخصائص Host، Port، و EnableSsl والتي تمكنك من خلالها تحديد إعدادات السيرفر Server والذي سوف تقوم بإرسال الرسائل من خلاله. فعلى سبيل المثال إذا كنت تريد إرسال رسائل من بريد إلكتروني Hotmail مثلا سوف تقوم بتحديد إعداداته من خلال هذه الخصائص. سوف نشرح كيفية تحديد الإعدادات لاحقا. بجانب هذا، توفر لك هذه الكلاس الدالة Send() لإرسال الرسائل والدالة SendAsync() لإرسال الرسائل بشكل غير متزامن Asynchronously أي باستخدام الـ Threading. سوف نشرح هذا لاحقا.
  • MailMessage:
    الرسالة التي سوف يتم إرسالها. تحتوي هذه الكلاس على الحقول الخاصة بالرسالة مثل To، CC، Bcc، Subject (عنوان الرسالة)، و Body (نص الرسالة).
  • MailAddress:
    عبارة عن عنوان بريد إلكتروني. تستخدم هذه الكلاس في حقول الرسالة مثل حقل To لتحديد البريد الإلكتروني الخاص بمستلم الرسالة. تحتوي هذه الكلاس على حقلين الأول هو اسم صاحب البريد والثاني هو البريد نفسه.
  • MailAddressCollection:
    عبارة عن مجموعة Collection من عنواين البريد الإلكتروني أي عبارة عن مجموعة من عناصر MailAddress. تستخدم هذه الكلاس في حقول البريد الإلكتروني للرسالة مثل To، CC، Bcc. وهذا إذا كنت تريد إرسال الرسال لأكثر من شخص.
  • Attachment:
    عبارة عن ملف مرفق مع الرسالة.
  • AttachmentCollection:
    عبارة عن مجموعة Collection من الملفات المرفقة أي من الكلاس Attachment. تستخدم هذه الكلاس في الحقل Attachments الخاص بالرسالة.
  • SmtpException:
    عند حدوث خطأ أثناء إرسال الرسالة يقوم SmtpClient بإصدار هذا الخطأ. يحتوي هذا الكلاس على الخاصية StatusCode والتي توفر لك طريقة معرفة ما هو الخطأ الذي حدث.
  • SmtpFailedRecipientException و SmtpFailedRecipientsException:
    الاثنان هما أيضا عناصر خطأ Exception ينحدرا Inherit من العنصر SmtpException. إذا لم يستطع SmtpClient إرسال الرسالة إلى شخص معين سوف يقوم بإصدار الخطأ SmtpFailedRecipientException والذي يحتوي على عنوان بريد إلكتروني هذا الشخص. أما إذا فشل SmtpClient في إرسال الرسالة لعدة أشخاص سوف يقوم بإصدار الخطأ SmtpFailedRecipientsException والذي بدوره يحتوي على عناوين الأشخاص الذين لم ترسل إليهم الرسالة.

بالإضافة إلى هذه التصنيفات تحتوي هذه الـ Namespace وهي System.Net.Mail على العديد من الـ Enumerations لأغراض معينة مثل MailPriority وتمثل أهمية الرسالة إما Low أ، Normal أ، High. وتستخدم هذه الـ Enumeration في الحقل Priority الخاص بالرسالة أي في الكلاس MailMessage.

الأصناف في System.Web.Mail

إذا كنت مهتما بالأصناف Classes الموجودة في System.Web.Mail ستجد أنها شبيهة جدا -إن لم تكن مماثلة- للأصناف في System.Net.Mail. ستجد أيضا أنها عبارة عن ثلاثة أصناف Classes وثلاثة Enumerations فقط!

لا تنسى أن هذه الأصناف موجودة في المكتبة System.Web.dll ولذلك يتوجب عليك إضافتها إلى مشروعك قبل البدأ في التعامل مع هذه الأصناف.

التالي هو قائمة بالأصناف في System.Web.Mail:

  • SmtpMail:
    هذا العنصر شبيها جدا -بل مماثل- للعنصر System.Net.Mail.SmtpClient ويعمل نفس العمل وله نفس الحقول Fields.
  • MailMessage:
    هو عنصر مماثل للعنصر System.Net.Mail.MailMessage ويمثل الرسالة التي سوف ترسل باستخدام SmtpMail.
  • MailAttachment:
    عبارة عن ملف مرفق مع الرسالة. مماثل للعنصر System.Net.Mail.Attachment.

أما الـ Enumerations الموجودة في هذه الـ Namespace فهي MailEncoding، MailFormat و MailPriority. تمثل الأولى نظام الـ Encoding الخاص بالرسالة وهو في الغالب لن تحتاج إلى تغييره. وتمثل الثانية MailFormat نوع الرسالة هل هي رسالة HTML أم رسالة نصية عادية. سوف نقوم بشرح كيفية إرسال الرسائل HTML لاحقا. والثالثة MailPriority تمثل أهمية الرسالة Low أ، Normal أ، High.

نتكلم اليوم عن الطريقة الأفضل والأقوى وهي باستخدام العناصر من System.Net.Mail. إذا كنت تفضل System.Web.Mail فالكود سوف يكون شبيها جدا. فقط قم بتغيير أسماء العناصر وعمل اللازم.

سيرفرات SMTP

تعرف بالطبع أنه لكي يمكنك إرسال رسالة بريد إلكتروني باستخدام بروتوكول SMTP يجب عليك أن تقوم بتحديد السيرفر Server المناسب لإرسال هذه الرسالة. ويجب أيضا أن يكون هذا السيرفر يوفر لك خدمة SMTP وإلا لن تستطيع التعامل معه.

بالطبع يمكنك إنشاء السيرفر الخاص بك والذي يوفر خدمة SMTP. وبالطبع أيضا يمكنك شراء سيرفر أو على الأقل الاشتراك في سيرفر يوفر لك هذه الخدمة. فتستطيع مثلا من خلال خدمة الاستضافة Hosting الخاصة بموقعك أن تقوم بإنشاء بريد إلكتروني مثلا yourname@yoursite.com وتقوم بإرسال الرسائل من خلاله. ولكن يشترط في هذه الاستضافة أن يكون السيرفر يوفر لك كما قلنا خدمة SMTP.

ولكي تستطيع التعامل مع أي سيرفر SMTP يجب عليك أن تعرف عن هذه السيرفر أربعة أشياء:

  • العنوان Address:
    عنوان السيرفر مثلا smtp.example.com.
  • المخرج Port:
    بالطبع يوفر لك السيرفر الخدمة SMTP عن طريق مخرج معين وهو في الغالب 25 وأحيانا يكون 465 وأحيانا يختلف.
  • هل هذا السيرفر يدعم تأمين SSL؟
    معظم السيرفرات تدعم تأمين SSL وهو Secure Socket Layer. تأمين SSL يقوم بتشفير البيانات أثناء نقلها وبالتالي يمنح الرسالة حماية ضد أن يقوم شخص Hacker بالحصول عليها وفهمها أو تغييرها قبل وصولها إلى المستخدم.
  • الاعتمادات Credentials:
    أي اسم المستخدم وكلمة المرور.

بالطبع تعرف أن هذه الإعدادات الخاصة بالسيرفر سوف تقوم بتحديدها عن طريق حقول System.Net.Mail.SmtpClient أو System.Web.Mail.SmtpMail.

التالي هي قائمة بأشهر أربعة موفروا لخدمة البريد الإلكتروني E-Mail Service Providers والإعدادات الخاصة بهم:

Live
العنوان Address smtp.live.com
المخرج Port 25
يدعم SSL؟ نعم
 
GMail
العنوان Address smtp.gmail.com
يدعم SSL؟ 25 أ، 465
  نعم
Yahoo!
العنوان Address plus.smtp.mail.yahoo.com
المخرج Port 465 أ، 25 أ، 587
يدعم SSL؟ نعم/لا
هذه الخدمة متاحة فقط لمشتركي Yahoo Plus! أي غير المشتركين المجانيين.
 
GMX
العنوان Address mail.gmx.com
المخرج Port 25
يدعم SSL؟ لا
 

كما تعلم أن Live يمثل جميع خدمات البريد الإلكتروني لـ Live مثل Live، Hotmail، MSN، … وغيرها. وأيضا Gmail يمثل Gmail و GoogleMail فهي مجرد أسماء فقط.

أي سيرفر أو موفر للبريد الإلكتروني في الغالب يوفر SMTP. فقط قم بزيارة صفحات الدعم Support الخاصة بهذا السيرفر أو الموفر وعمل بحث عن خدمة SMTP.

بعض السيرفرات مثل Gmail و Yahoo! توفر لك أكثر من مخرج Port. فقط قم باختيار أي منها ليس هناك فرق. وبعض السيرفرات مثل Yahoo! لا تضع شروطا لدعم SSL فلهذا يمكنك اختياره أو تركه ولكن يفضل اختيار SSL.

بعض السيرفرات تجعل لك حدا يوميا لإرسال الرسائل باستخدام SMTP. فعلى سبيل المثال Gmail يضع حدا أعلى للرسائل من على SMTP وهو 100 رسالة يوميا. أما من خلال الموقع أو البرنامج الخاص بـ Gmail الحد الأعلى له هو 400 رسالة يوميا.

التطبيق

الكود التالي يقوم بإرسال رسالة من حساب GMX إلى حساب آخر باستخدام العناصر من System.Net.Mail. لاحظ الكود جيدا. لاحظ أن الرسالة هي عبارة عن رسالة HTML. ولاحظ أن المثال يقوم بإضافة ملف مرفق إلى الرسالة وبالطبع سوف يحدث خطأ إن لم يكن هذا الملف موجود على جهازك. ولاحظ أيضا كيفية تحديد إعدادات السيرفر.

يمكنك زيارة موقع GMX وإنشاء حساب خاص عليه.

عناوين البريد الإلكتروني هذه هي عبارة عن عناوين وهمية وليست موجودة وهي فقط للمثال وليست للعمل. قم بتغيير العناوين لتصبح عناوين حقيقية. كما يمكنك تغيير إعدادات السيرفر لتوافق إعدادات أي سيرفر تريده إن لم يكن عندك حساب على GMX. وأيضا لا يشترط إرسال رسالة إلى نفس السيرفر. بمعنى أنه لا يشترط بالطبع إرسال رسالة من Hotmail إلى Hotmail آخر. ولكن يجب أن يكون بريد الراسل على نفس السيرفر المحدد. أما المستلم فلا يشترط له هذا أبدا.

تأكد أن الملف الذي يريد الكود إضافته موجود على الجهاز. قم بتغيير مساره ليكون موافقا لملف موجود، أو حتى قم بإزالته. بالطبع يمكنك تغيير الكود كما تحب وأيضا تغيير عنوان الرسالة ونصها.

قمت بوضع بعض الملاحظات حول الكود لتوضيحه.

يجب عليك إضافة جملة using (Imports في VB.NET) للـ Namespace المسمى System.Net.Mail.

    // C# Code

    MailMessage msg = new MailMessage();

    // Your mail address and display name.
    // This what will appear on the From field.
    // If you used another credentials to access
    // the SMTP server, the mail message would be
    // sent from the mail specified in the From
    // field on behalf of the real sender.
    msg.From =
        new MailAddress("example@GMX.com", "Example");

    // To addresses
    msg.To.Add("friend_a@example.com");
    msg.To.Add(
        new MailAddress("friend_b@example.com",
            "Friend A"));

    // You can specify CC and BCC addresses also

    // Set to high priority
    msg.Priority = MailPriority.High;

    msg.Subject = "Hey, a fabulous site!";

    // You can specify a plain text or HTML contents
    msg.Body =
        "Hello everybody,<br />" +
        "<br />" +
        "I found an interesting site called " +
        "<a href=\"http://WithDotNet.WordPress.com\">" +
        "مع الدوت نت</a>. Be sure to visit it soon.";
    // In order for the mail client to interpret message
    // body correctly, we mark the body as HTML
    // because we set the body to HTML contents.
    msg.IsBodyHtml = true;

    // Attaching some data
    msg.Attachments.Add(new Attachment("C:\\Site.lnk"));

    // Connecting to the server and configuring it
    SmtpClient client = new SmtpClient();
    client.Host = "mail.gmx.com";
    client.Port = 25;
    client.EnableSsl = false;
    // The server requires user's credentials
    // not the default credentials
    client.UseDefaultCredentials = false;
    // Provide your credentials
    client.Credentials =
        new System.Net.NetworkCredential(
            "example@GMX.com", "buzzwrd");

    // Use SendAsync to send the message asynchronously
    client.Send(msg);

    ' VB.NET Code

    Dim msg As New MailMessage()

    ' Your mail address and display name.
    ' This what will appear on the From field.
    ' If you used another credentials to access
    ' the SMTP server, the mail message would be
    ' sent from the mail specified in the From
    ' field on behalf of the real sender.
    msg.From = _
        New MailAddress("example@GMX.com", "Example")

    ' To addresses
    msg.To.Add("friend_a@example.com")
    msg.To.Add( _
        New MailAddress("friend_b@example.com", _
        "Friend A"))

    ' You can specify CC and BCC addresses also

    ' Set to high priority
    msg.Priority = MailPriority.High

    msg.Subject = "Hey, a fabulous site!"

    ' You can specify a plain text or HTML contents
    msg.Body = _
        "Hello everybody,<br />" & _
        "<br />" & _
        "I found an interesting site called " & _
        "<a href=""http://WithDotNet.WordPress.com"">" & _
        "مع الدوت نت</a>. Be sure to visit it soon."
    ' In order for the mail client to interpret message
    ' body correctly, we mark the body as HTML
    ' because we set the body to HTML contents.
    msg.IsBodyHtml = True

    ' Attaching some data
    msg.Attachments.Add(New Attachment("D:\Site.lnk"))

    ' Connecting to the server and configuring it
    Dim client As New SmtpClient()
    client.Host = "mail.gmx.com"
    client.Port = 25
    client.EnableSsl = False
    ' The server requires user's credentials
    ' not the default credentials
    client.UseDefaultCredentials = False
    ' Provide your credentials
    client.Credentials = _
        New System.Net.NetworkCredential( _
            "example@GMX.com", "buzzwrd")

    ' Use SendAsync to send the message asynchronously
    client.Send(msg)

تغيير طريقة إيصال الرسالة

ماذا إن كنت تريد تجربة برنامجك فقط؟ ماذا إن كنت تريد مشاهدة الرسالة ولكن لا تريد إرسالها؟

يوفر لك الدوت نت طريقة لإرسال الرسالة ليس من سيرفر معين إلى مستلم! بل إنك ترسل الرسالة إلى مسار على جهازك مسار تقوم بتحديده. وهذه الرسالة لا تشترط أن تكون متصلا بالإنترنت أو الشبكة أو تكون محددا لبيانات صحيحة للسيرفر. فهي فقط ترسل الرسالة إلى مسار على جهازك.

لتغيير طريقة إيصال الرسالة Delivery Method يوفر لك العنصر SmtpClient خاصيتان هما DeliveryMethod و PickupDirectoryLocation. الخاصية DeliveryMethod تحدد طريقة وصول الرسالة وهي عبارة عن عنصر من الـ Enumeration المسمى SmtpDeliveryMethod وبالتالي تأخذ أحد القيم التالية:

  • Network (الأساسي):
    ترسل الرسالة عن طريق الشبكة. أي أن الرسالة ترسل وتصل إلى المستلم الحقيقي.
  • PickupDirectoryFromIis:
    ترسل الرسالة إلى مجلد على الجهاز تستطيع تحديده من خلال Internet Information Services (IIS).
  • SpecifiedPickupDirectory:
    ترسل الرسالة إلى مجلد على الجهاز تستطيع تحديده من الخاصية الأخرى وهي PickupDirectoryLocation.

قم بزيارة الدعم Support الخاص بـ IIS لمعرفة طريقة تغيير مجلد وصول الرسالة في حالة اختيارك SmtpDeliveryMethod.PickupDirectoryFromIis.

الكود التالي يوضح كيفية جعل الرسائل ترسل إلى مجلد معين على الجهاز. لاحظ أنه إذا لم يكن المجلد المحدد موجودا على الجهاز سوف يحدث خطأ ولن يتم إرسال الرسالة. قم بإضافة هذا الكود قبل النداء على دالة Send() أو SendAsync().

    // C# Code

    client.DeliveryMethod =
        SmtpDeliveryMethod.SpecifiedPickupDirectory;
    client.PickupDirectoryLocation = "C:\\mails";

    ' VB.NET Code

    client.DeliveryMethod =
        SmtpDeliveryMethod.SpecifiedPickupDirectory
    client.PickupDirectoryLocation = "C:\mails"

مثال

برنامج Geming Mail+ عبارة عن برنامج يقوم بإرسال رسائل البريد الإلكتروني من خلال أكثر من مزود لخدمة البريد الإلكتروني E-Mail Service Provider والذي يدعم خدمة SMTP.

تم تطوير البرنامج باستخدام C# والدوت نت 2.0. ليس هناك متطلبات لتشغيل الكود فقط Visual Studio 2005 أو إصدار أعلى. التالي هو بعض صور من البرنامج.

Geming Mail+ Splash

Geming Mail+

قم بتحميل البرنامج من هنا

قم بتحميل الكود من هنا

خاتمة

تعلمنا في هذا الدرس كيفية إرسال الرسائل من بيئة الدوت نت. في دروس أخرى بإذن الله تعالى سنتعلم كيفية استقبال الرسائل وعمل إدارة لحساب البريد الإلكتروني بالكامل.

قمت بكتابة هذا الدرس سابقا باللغة الإنجليزية في موقعي Just Like a Magic.

المحتويات

  • المحتويات
  • نظرة خاطفة
  • مقدمة
  • الخلفية
  • استخدام DirectX لتغيير إعدادات الشاشة برمجيا
  • الحصول على الإعدادات الحالية للشاشة
  • معرفة الأوضاع المتاحة للشاشة
  • تغيير الإعدادات الحالية للشاشة
  • خاتمة

نظرة خاطفة

درسنا اليوم يتكلم عن طريقة الحصول وتغيير إعدادات الشاشة Display Settings برمجيا ويتم ذلك باستخدام عدة طرق أحدها مكتبات DirectX وهذا ما سوف نقوم بشرحه اليوم. سوف نشرح أولا ما المقصود بإعدادات الشاشة ثم سوف نشرح كيفية الحصول على إعدادات الشاشة وأيضا كيفية معرفة الأوضاع المتاحة للشاشة. ثم أخيرا كيفية تغيير هذه الإعدادات.

بجانب هذا سنتعلم المزيد داخل الشرح مثل كيفية التعامل مع مكتبات COM وكيفية تطبيق الـ Callbacks.

مقدمة

تحتاج كثيرا إلى تغيير إعدادات الشاشة ربما عند استخدام برنامج معين وربما بل وفي الأغلب عند التعامل مع الألعاب فالألعاب دائما تقوم بتغيير إعدادات الشاشة تلقائيا.

إذا فماذا المقصود بإعدادات الشاشة؟

إعدادات الشاشة أو Display Settings وربما نسميها وضع الشاشة Display Mode هي عبارة عن مجموعة من الإعدادات الخاصة بالشاشة وهذه الإعدادات هي:

  • الأبعاد Bounds أو كما تسمى Resolution
    وهي الأبعاد العرض والعرض Width and Height بالبيكسل Pixel. وهي على الأفضل 1024 × 768 في الشاشات العادية أو 1280 × 720 في الشاشة العريضة Wide Screen مثل شاشات الأجهزة المحمولة. لاحظ أن الألعاب تقوم بتغيير الشاشة ربما إلى 640 × 480 وهذا لجعل أداء اللعبة أفضل وأسرع.
  • نظام الألوان Color System أو كما يسمى Bit Count
    وهو نظام الألوان في الشاشة أي عدد أو كمية الألوان التي يمكن للشاشة التعامل معها.وهي على الأفضل 32-bit أو 16-bit. وهكذا كلما تزيد عدد البتات Bits تصبح الألوان أفضل ولكن تطلب الشاشة ذاكرة أكبر وبالتالي يصبح النظام أبطأ. تقوم الألعاب بتقليل هذا الرقم ربما إلى 256 لون أي 8-bit. وربما في الوضع Safe Mode للويندوز ستجدها 256 لون أيضا. لاحظ أن الويندوز يسمي هذه الأرقام بأسماء معينة. فعلى سبيل المثال تجد أن الويندوز يسمي النظام 32-bit يسميه High Color والنظام 16-bit يسميه True Color. وهناك بعض الشاشات التي لا تدعم النظام 32-bit بل تدعم نظام أقل منه وهو 24-bit فحينها ستجد النظام يسمي 24-bit يسميه High Color بينما النظام 16-bit يسميه True Color. وأحيانا أخرى تجد أن الشاشة تدعم النظامين 32-bit و 24-bit وحينها تجد True Color هو النظام 24-bit.
  • المعدل Refresh Rate أو كما يسمى Frequency أو Monitor Flicker
    وهو معدل تغيير الشاشة أي أن الشاشة تقوم بعمل Refresh لنفسها بنسبة معينة. وهذا المعدل كلما زاد كلما سبب للمستخدم صُداع وإجهاد للعين. في الأفضل يكون هذا المعدل 75-Hertz فأقل.
  • التدوير Rotation أو بمعنى آخر Orientation
    وهو كيفية تدوير الشاشة. فربما في بعض الشاشات تجد إمكانية لتدوير الشاشة فتجعلها بزاوية °90 مثلا باتجاه عقارب الساعة وهكذا.

وهكذا فإن الأربعة إعدادات وهي الأبعاد Bounds، نظام الألوان Color System، المعدل Refresh Rate، والتدوير Rotation تمثل جميعها إعدادات الشاشة Display Settings وربما نسميها وضع الشاشة Display Mode.

ولكن يجب عليك معرفة أنه ليست جميع الشاشات وكذلك كروت الشاشة تسمح لك بجميع الإعدادات. فعلى سبيل المثال هناك شاشات لا تدعم التدوير Rotation. وهناك شاشات لا تدعم مثلا نظام الألوان 32-bit بل تدعم بدلا منه 24-bit. وهناك شاشات لا تدعم أبعاد أعلى من 1024 × 768 أو في الشاشات العريضة Wide Screen هناك شاشات لا تدعم أبعاد أعلى من 1280 × 720 (مثل شاشتي)، وهكذا…

وأيضا عليك معرفة أن لكل شاشة مجموعة من الأوضاع التي تدعمها Display Supported Mode. فهناك شاشات مثلا لا تدعم الأبعاد 1024 × 786 بينما نظام الألوان هو 32-bit مثلا.

بجانب هذا كله يجب عليك معرفة أن الشاشة لا يمكنها العمل على جميع أوضاعها بطريقة صحيحة إلا إذا كان تعريف الشاشة منصب ويجب أن يكون منصب بطريقة صحيحة.

وهنا يظهر سؤال! لماذا تقوم الألعاب بتغيير دقة الشاشة Resolution و نظام الألوان Color System إلى الأقل؟ لماذا يسبب هذا في تحسين أداء وسرعة اللعبة؟

الفكرة هي أن كي تظهر كل نقطة -بيكسل بمعنى أصح- على الشاشة تحتاج إلى جزء من الذاكرة وهذا الجزء هو لكل نقطة هو نظام الألوان الخاص بها. كيف؟ نفترض مثلا أن نظام الألوان هو 32-bit. فهذا معناه أن كل نقطة (بيكسل) على الشاشة تحتاج إلى 32-bit من الذاكرة. إذا فإذا تخيلنا الدقة 1024 × 768 بيكسل فإن المساحة تكون 786432 بيكسل (نقطة). وبالضرب في نظام الألوان 32-bit لكل نقطة إذا فإن 786432 × 32 = 25165824 بت. وكما تعلم أن البايت Byte هو 8 بت Bit وكل كيلوبايت Kilobyte هو 1024 بايت. وكل ميجابايت Megabyte هي 1024 كيلوبايت. إذا يمكننا تحويل البتات Bits 25165824 إلى ميجابايت عن طريق عملية حسابية بسيطة وهي 25165824 / (8 * 1024 * 1024) فيكون الناتج هو أن الشاشة في الدقة 1024 × 768 ونظام الألوان 32-bit تحتاج إلى 3 ميجابايت من الذاكرة في كل نقطة. فتخيل معي أن لعبة مثلا تحتاج في كل لحظة إلى إزالة 3 ميجابايت من الذاكرة وإنشاء 3 غيرها وهكذا في كل لقطة!!! إذا فالحل هو أننا نقوم بتقليص حجم الشاشة ونظام الألوان كي يتقلص معنا الحجم الذي نريده لكل لقطة.

بالطبع مطورو الألعاب يستخدمون طرق عدة لتسريع أداء اللعبة.

الخلفية

في الويندوز يمكنك تغيير إعدادات الشاشة عن طريق فتح شاشة إعدادات الشاشة Display Settings. شكل 1 يوضح شاشة إعدادات الشاشة Display Settings Dialog.

شكل 1 - Display Settings Dialog

شكل 1 - Display Settings Dialog

نلاحظ أن هذه الشاشة تسمح لك بتغيير الأبعاد Resolution ونظام الألوان Color System فقط. مع ذلك يمكنك تغيير باقي أوضاع الشاشة عن طريق الدخول إلى الخيارات المتقدمة عن طريق الزر Advanced Settings لتظهر لك شاشة الخيارات المتقدمة. شكل 2 يوضح شاشة الخيارات المتقدمة للشاشة Advanced Settings Dialog.

شكل 2 - Display Advanced Settings Dialog

شكل 2 - Display Advanced Settings Dialog

من هذه الشاشة يمكنك الضغط على زر List All Modes لتظهر لك شاشة الأوضاح المتاحة للشاشة والتي يمكن للشاشة العمل على أحدها. شكل 3 يوضح شاشة الأوضاع المتاحة للشاشة Display Supported Modes.

شكل 3 - List All Modes Dialog

شكل 3 - List All Modes Dialog

نلاحظ أن هذه الشاشة عبارة عن قائمة بالأوضاع Modes أو بمعنى آخر مجموعة الإعدادات التي تستطيع الشاشة أن تعمل على أحدها. لاحظ أنه يمكنك اختيار الوضع الذي تريده من هذه القائمة. لاحظ أيضا أن هناك بعض الأوضاع التي لن تجدها في شاشة تغيير إعدادات الشاشة العادية شكل 1. فمثلا لن تجد في الشاشة الأخرى طريقة لتغيير المعدل Frequency أو نظام الألوان Color System إلى 256 لون (8-bit) مثلا.

يلاحظ أيضا في هذه الشاشة أن الأبعاد الخاصة بشاشتي وهي 1280 × 720 لا تعمل إلى مع المعدل 60-Hertz فقط وكذلك الأبعاد 1280 × 600. بينما الأبعاد 800 × 600 تسمح لي بالمعدل 75-Hertz.

نرجع إلى شاشة إعدادات الشاشة المتقدمة شكل 2. يمكنك من هذه الشاشة الانتقال إلى التبويب Monitor لتغيير المعدل Refresh Rate أو كما يسمى Frequency أو Monitor Flicker. بالنسبة لشاشتي لا أجد إلا المعدل 60-Hertz وذلك كما ذكرت سابقا أن الأبعاد 1280 × 720 والذي أعمل عليهت لا تدعم معها إلا المعدل 60-Hertz. أما إذا قمت باختيار أبعاد مثل 800 × 600 فحينئد أجد الوضع 75-Hertz أيضا.

لاحظ أيضا أنه كلما زدت عن المعدل 75-Hertz فإن ذلك سوف يسبب لك صُداع في الرأس وإجهاد للعين.

نرجع إلى شاشة إعدادات الشاشة المتقدمة شكل 2. يمكنك من هذه الشاشة أيضا تغيير التدوير Rotation أو Orientation -بالطبع إذا كانت تدعمه شاشتك عن طريق الانتقال إلى تبويب الإعدادات الخاصة بكارت الشاشة الخاص بك من هذه الشاشة واتباع الخطوات المناسبة. شكل 4 يوضح شاشة إعدادات الشاشة بعد تدويرها بزاوية °90 باتجاه عقارب الساعة.

شكل 5 - Display Settings Dialog Rotated 90degree Clockwise

شكل 4 - Display Settings Dialog Rotated 90degree Clockwise

لاحظ أنه نظام عقارب الساعة هذا بالنسبة للشاشة وليس بالنسبة لك. فكما في الشكل السابق أن التدوير كان باتجاه عقارب الساعة بالنسبة للشاشة ولكنه بالنسبة لك بعكس اتجاه عقارب الساعة.

لاحظ أيضا أنه ليست كل كروت الشاشة لها خيارات خاصة بها وليست كل الشاشات أيضا تدعم التدوير Rotationأو Orientation.

والآن نحن بصدد أبسط طريقة لتغيير إعدادات الشاشة برمجيا وهي عن طريق استخدام دوال DirectX.

استخدام DirectX لتغيير إعدادات الشاشة برمجيا

DirectX هو عبارة عن مجموعة من المكتبات Libraries التي توفر لك استغلال جميع إمكانيات الشاشة وكارت الشاشة لتناسب احتياجات برنامجك أو لعبتك من الوسائط المتعددة Multimedia. فـ DirectX هو عبارة عن مجموعة من المكتبات التي تكون منصبة تلقائيا عند تنصيب الويندوز ولكنك يمكنك تنصيب نسخ أحدث منها لاستغلال المميزات الأعلى لهذه النسخ.

يمكنك استخدام DirectX لاستغلال جميع إمكانيات الجهاز من الوسائط المتعددة Multimedia مثل الصوت والصورة. فمثلا في درسنا هذا سوف نستخدم أحد مكتبات DirectX لتغيير إعدادات الشاشة من خلال برنامجنا. وهذه المكتبة هي المكتبة dx3j.dll وتسمى Direct 1.0 Type Library وهي موجودة في المجلد %windir%\System32.

كي يمكنك التعامل مع هذه المكتبة يجب عليك إضافتها إلى المراجع References الخاصة بمشروعك لكي تستطيع لك التعامل معها. ويتم ذلك عن طريق اختيار Add Reference من قائمة Project أو عن طريق ضغطة بالزر الأيمن للفأرة على المشروع واختيار Add Reference لتظهر لك شاشة إضافة المرجع Add Reference شكل 5 والتي توفر لك طريقة لإضافة المكتبات إلى مشروعك كي يمكنك التعامل معها.

شكل 5 - Add Reference Dialog - Direct 1.0 Type Library

شكل 5 - Add Reference Dialog - Direct 1.0 Type Library

من هذه الشاشة اختر التبويب COM وذلك لأن مكتبتنا هي عبارة عن COM Library أي أنها لم تطور باستخدام بيئة الدوت نت. بعد اختيارك التبويب COM ابحث عن المكتبة وهي بعنوان Direct 1.0 Type Library وقم بإضافتها. وهذا سوف يضيف إلى مراجع References الخاصة بمشروعك هذه المكتبة ولكنها ستظهر باسم DIRECTLIB.dll وهذا لأن الدوت نت لا يستطيع التعامل مباشرة مع المكتبات الأخرى مكتبات COM ولهذا فإنك لا تقوم بإضافة المكتبة COM نفسها بل تقوم بإضافة مكتبة دوت نت تعمل كوسيط بينك وبين مكتبة COM وتسمى هذه المكتبة Interop Library كما تسمى أيضا Runtime Callable Wrapper أو RCW.

فالمكتبة التي أضفتها ليست حقيقة مكتبة COM المطلوبة بل هي مكتبة دوت نت تعمل كوسيط حيث تكون لها نفس تركيب المكتبة الأصلية COM ولكنك عند النداء على أحد دوالها تقوم هذه المكتبة بالنداء على الدالة الأصلية الموجودة في المكتبة COM.

لاحظ أن عملية تعامل الدوت نت مع التكنولوجيات الأخرى مثل COM تسمى Interoperability وتختصر إلى Interop.

بعد إضافة المكتبة إلى المشروع يمكنك الآن ببساطة التعامل معها. في الحقيقية، نحن نحتاج فقط من هذه المكتبة عنصر Class واحد هو الذي نحتاجه للتعامل مع إعدادات الشاشة وهو _dxj_DirectDrawClass.

للأسف DirectX لا يسمح لنا بتغيير جميع إعدادات الشاشة بل يسمح لنا بتغيير فقط الأبعاد ونظام الألوان. على الناحية الأخرى لا يمكننا أيضا من معرفة جميع إعدادات الشاشة بل يمكننا من معرفة الأبعاد ونظام الألوان والمعدل Frequency.

الحصول على الإعدادات الحالية للشاشة

كما قلنا أن الـ DirectX يسمح لنا بالحصول على بعض إعدادات الشاشة وهي الأبعاد Bounds ونظام الألوان Color System والمعدل Frequency. وتتم عمليات إعدادات الشاشة كما قلنا من العنصر _dxj_DirectDrawClass الخاص بمكتبة DirectX المختارة.

للحصول على إعدادات الشاشة حاليا يمكنك استخدام الدالة getDisplayMode() للحصول على هذه الإعدادات. تقوم هذه الدالة بأخذ مدخل Argument واحد وهو من النوع DDSurfaceDesc وهو عبارة عن Structure موجود أيضا في مكتبة DirectX.

النوع DDSurfaceDesc يحتوي على جميع معلومات الشاشة ولكننا يهمنا في هذا الدرس أربعة فقط (يمكنك اكتشاف الباقي بنفسك) وهي:

  • width and height:
    أبعاد الشاشة Bounds، العرض والطول.
  • rgbBitCount:
    نظام الألوان أي عدد البتات bits.
  • refreshRate:
    المعدل Frequency.

الكود التالي يوضح كيفية الحصول على إعدادات الشاشة الحالية. لاحظ أن الكود يعتمد على أنك قمت بإضافة المكتبة dx3j.dll وأنك تعمل على مشروع Console Application. بالطبع يمكنك تغيير نوع المشروع. لاحظ أيضا أن جميع عناصر المكتبة موجودة في الـ Namespace المسمى DIRECTLIB.

// C# Code

static void Main()
{
    DIRECTLib._dxj_DirectDrawClass ddraw =
        new DIRECTLib._dxj_DirectDrawClass();

    DIRECTLib.DDSurfaceDesc desc;

    ddraw.getDisplayMode(out desc);

    Console.WriteLine("{0} by {1}, {2} bit, {3} Hertz",
        desc.width, desc.height,
        desc.rgbBitCount, desc.refreshRate);
}

' VB.NET Code

Sub Main()
    Dim ddraw As _
        New DIRECTLib._dxj_DirectDrawClass()

    Dim desc As DIRECTLib.DDSurfaceDesc

    ddraw.getDisplayMode(desc)

    Console.WriteLine("{0} by {1}, {2} bit, {3} Hertz", _
        desc.width, desc.height, _
        desc.rgbBitCount, desc.refreshRate)
End Sub

يقوم البرنامج بطباعة التالي على جهازي (بالطبع يكون المطبوع هو الإعدادات الخاصة بشاشتك):

1280 by 720, 32 bit, 60 Hertz

معرفة الأوضاع المتاحة للشاشة

لمعرفة الأوضاع المتاحة للشاشة Display Supported Modes التي تستطيع العمل عليها يمكنك استخدام الدالة enumDisplayModes() والموجودة أيضا في نفس العنصر _dxj_DirectDrawClass الخاص بالشاشة.

تقوم هذه الدالة بأخذ أربعة مدخلات Arguments وهي على الترتيب:

  • d:
    وهي عبارة عن اختيارات يمكنك تحديدها. في مثالنا هذا سوف نحدد الاختيار DDEDM_REFRESHRATES كي نقوم باسترجاع المعدل Refresh Rate أيضا وإلا عند استرجاعنا للأوضاع المتاحة لن يتم استرجاع المعدل Freq.
  • desc:
    عبارة عن عنصر من نوع DDSurfaceDesc يمثل الوضع.
  • args:
    بياانات أخرى خاصة بالمبرمج يمكنه الرجوع إليها في دالة الـ Callback. سوف يتم الشرح قريبا.
  • fn:
    أي عنصر يقوم المستخدم بإنشائه يدعم الـ Interface المسماة IEnumModesCallback.

ما هي فكرة هذه الدالة؟

فكرة هذه الدالة هي أنها تستخدم دالة أخرى دالة Callback لكي تقوم بإرجاع الأوضاع المتاحة Supported Modes للمبرمج.

وهنا سؤال آخر! ما هي دوال الـ Callback؟ دوال الـ Callback هي عبارة عن دوال يتم النداء عليها عند حدوث أمر معين.

فدالتنا التي بأيدينا الآن وهي enumDisplayModes() تستخدم المدخل الثالث والرابع في تطبيق الـ Callback Mechanism. كيف؟

تقوم دالة enumDisplayModes() داخليا باسترجاع جميع الأوضاع المتاحة ثم تقوم بإرجاعها لنا واحدة تلو الأخرى. حيث تقوم بالنداء على دالة الـ Callback تكراريا لكل وضع.

وبالنسبة لهذه الدالة enumDisplayModes() فإنها تطلب من المبرمج إنشاء دالة الـ Callback بنفسه. وكي يستطيع المبرمج إنشائها فإنه يقوم بإنشاء عنصر Class معين وهذا العنصر يقوم بالاعتماد على الـ Interface المسماة IEnumModesCallback المحددة من دالة enumDisplayModes().

تحتوي الـ Interface المسماة IEnumModesCallback على دالة واحدة فقط -بالطبع يجب على العنصر الذي أنشأه المبرمج تطبيقها-، هذه الدالة لها مدخلين الأول هو العنصر DDSurfaceDesc يحتوي على بيانات الوضع المحدد والثاني هو عنصر بيانات أخرى وهو الذي أدخله المستخدم عند ندائه على دالة enumDisplayModes().

تشبه فكرة الـ Callback إلى حد كبير فكرة Delegates الموجودة في بيئة الدوت نت.

لاحظ الشكل التالي شكل 6 والذي يوضح كيفية عمل الدالة enumDisplayModes().

شكل 6 - enumDisplayModes() callback

شكل 6 - enumDisplayModes() callback

المثال التالي يوضح كيفية معرفة الأوضاع المتاحة للشاشة. لاحظ الكود جيدا.

    // C# Code

    static void Main()
    {
        DIRECTLib._dxj_DirectDrawClass ddraw =
            new DIRECTLib._dxj_DirectDrawClass();

        DIRECTLib.DDSurfaceDesc desc =
            new DIRECTLib.DDSurfaceDesc();

        ModeCallback callback = new ModeCallback();
        const uint DDEDM_REFRESHRATES = 3;
        string format = "{0} by {1}, {2} bit, {3} Hertz";
        ddraw.enumDisplayModes
            (DDEDM_REFRESHRATES, ref desc, format, callback);
    }

    class ModeCallback : DIRECTLib.IEnumModesCallback
    {
        public void callbackEnumModes
            (ref DIRECTLib.DDSurfaceDesc surfDesc, object ctxt)
        {
            Console.WriteLine(ctxt.ToString(),
                surfDesc.width, surfDesc.height,
                surfDesc.rgbBitCount, surfDesc.refreshRate);
        }
    }

    ' VB.NET Code

    Sub Main()

        Dim ddraw As New DIRECTLib._dxj_DirectDrawClass()

        Dim desc As New DIRECTLib.DDSurfaceDesc()

        Dim callback As New ModeCallback()
        Const DDEDM_REFRESHRATES As UInt32 = 3
        Dim format As String = "{0} by {1}, {2} bit, {3} Hertz"
        ddraw.enumDisplayModes _
            (DDEDM_REFRESHRATES, desc, format, callback)
    End Sub

    Class ModeCallback
        Implements DIRECTLib.IEnumModesCallback

        Public Sub callbackEnumModes _
            (ByRef surfDesc As DIRECTLib.DDSurfaceDesc, _
            ByVal ctxt As Object) _
            Implements _
            DIRECTLib.IEnumModesCallback.callbackEnumModes
            Console.WriteLine(ctxt.ToString(), _
                surfDesc.width, surfDesc.height, _
                surfDesc.rgbBitCount, surfDesc.refreshRate)
        End Sub
    End Class

من البيانات المطبوعة على جهازي باختصار:

320 by 200, 8 bit, 70 Hertz
320 by 200, 16 bit, 70 Hertz
320 by 200, 32 bit, 70 Hertz
. . .
640 by 480, 8 bit, 59 Hertz
640 by 480, 8 bit, 60 Hertz
. . .
800 by 600, 8 bit, 60 Hertz
800 by 600, 8 bit, 72 Hertz
800 by 600, 8 bit, 75 Hertz
. . .
1280 by 720, 8 bit, 60 Hertz
1280 by 720, 16 bit, 60 Hertz
1280 by 720, 32 bit, 60 Hertz

في الكود السابق قمنا بإنشاء الكلاس ModeCallback والتي تقوم بتطبيق الـ Interface المسماة IEnumModesCallback كي نستطيع تطبيق آلية Callback.

تغيير الإعدادات الحالية للشاشة

تعلمنا كيفية الحصول على إعدادات الشاشة وكيفية الحصول على جميع أوضاع الشاشة المتاحة. في هذا القسم سنتعلم كيفية تغيير الإعدادات الحالية للشاشة.

لسوء الحظ لا يسمح لنا DirectX إلا بتغيير الأبعاد ونظام الألوان ويتم ذلك عن عن طريق الدالة setDisplayMode() وهي أيضا موجودة في الكلاس _dxj_DirectDrawClass. وتأخذ هذه الدالة خمس مدخلات Arguments ولكننا يهمنا فقط أول ثلاثة وهم على الترتيب:

  • w:
    عرض الشاشة بالبيكسل Pixel.
  • h:
    طول الشاشة بالبيكسل Pixel.
  • bpp:
    نظام الألوان أي عدد البتات في كل بيكسل Bits Per Pixel.

بالطبع لا يمكنك تغيير إعدادات الشاشة إلى وضع غير مدعوم من شاشتك. فعلى سبيل المثال، إذا كنت تمتلك شاشة عادية وليست عريضة Wide Screen فإنك لن تستطيع تحويل الأبعاد إلى 1280 × 720 بيكسل مثلا. وإذا حاولت سيحدث الخطأ System.NotImplementedException.

يعد تغيير الإعدادات باستخدام الدالة setDisplayMode() مؤقتا حيث أنك عند انتهائك من البرنامج ترجع الأوضاع كما هي قبل بدأك في تنفيذ كود التغيير. كما يمكنك أيضا النداء على الدالة restoreDisplayMode() إذا كنت تريد استرجاع الأوضاع كما كانت عند نقطة معينة.

فباختصار، إذا لم تقم بالنداء على الدالة restoreDisplayMode() لاسترجاع الإعدادات قبل تنفيذ الكود، سوف يقوم DirectX باسترجاع الإعدادات تلقائيا عند انتهاء البرنامج.

الكود التالي يوضح كيفية تغيير إعدادات الشاشة كي تصبح الأبعاد 640 × 480 بيكسل ويصبح نظام الألوان 8-bit أي 256 لون. وسوف نعتمد على الـ DirectX كي يقوم باسترجاع الأوضاع الأصلية تلقائيا فور انتهاء البرنامج. لاحظ أننا أضفنا سطر إلى نهاية الكود كي يمنع البرنامج من الانتهاء حتى يقوم المستخدم بضغط أي زر.

// C# Code

static void Main()
{
    DIRECTLib._dxj_DirectDrawClass ddraw =
        new DIRECTLib._dxj_DirectDrawClass();

    ddraw.setDisplayMode(640, 480, 8, 0, 0);

    Console.WriteLine("Press any key to continue . . .");
    Console.ReadKey(true);
}

' VB.NET Code

Sub Main()

    Dim ddraw As New DIRECTLib._dxj_DirectDrawClass()

    ddraw.setDisplayMode(640, 480, 8, 0, 0)

    Console.WriteLine("Press any key to continue . . .")
    Console.ReadKey(True)
End Sub

خاتمة

تعلمنا في هذا الدرس…

  • ما هي إعدادات الشاشة وكيفية تغييرها من خلال الويندوز.
  • ما هي الأوضاع Modes وكيفية الحصول على الأوضاع المتاحة للشاشة أيضا من خلال الويندوز.
  • ما هو DirectX وكيفية استخدام مكتباته.
  • كيفية التعامل مع مكتبات COM من خلال بيئة الدوت نت.
  • كيفية الحصول على الإعدادات الحالية للشاشة برمجيا.
  • كيفية الحصول على الأوضاع المتاحة للشاشة برمجيا.
  • كيفية تطبيق الـ Callback وآليته.
  • كيفية تغيير الإعدادات الحالية للشاشة برمجيا.

وهذا كله تعلمناه من خلال DirectX. وفي دروس أخرى سنتعلم أكثر وأكثر. سنتعلم كيفية تغيير جميع الإعدادات ليس فقط الأبعاد ونظام الألوان، بل سنتعلم كيفية تغيير المعدل Frequency وأيضا كيفية تدوير الشاشة Rotation وسوف يتم هذا عن طريق دوال ويندوز أي Win32 API Functions.

قمت بكتابة هذا الدرس سابقا باللغة الإنجليزية في موقعي Just Like a Magic.

نظرة خاطفة

درسنا اليوم يتكلم عن طريقة تشغيل حافظة الشاشة Screen Saver برمجيا. هناك عدة طرق لتشغيل حافظة الشاشة سوف نقوم بسردها جميعا بإذن الله تعالى.

مقدمة

في الويندوز يمكنك تشغيل حافظة الشاشة Screen Saver عن طريق ترك جهازك بدون تحريك الفأرة أو لوحة المفاتيح لمدة فترة زمنية معينة تقوم بتحديدها من خلال خيارات Screen Saver من خصائص سطح المكتب.

Screen Saver Settings

ماذا لو أردت فعل ذلك برمجيا؟ ماذا لو أردت تشغيل حافظة الشاشة عند نقطة معينة من البرنامج؟ بالطبع أنت لا تريد منع المستخدم من تحريك الفأرة أو استخدام لوحة المفاتيح حتى تنتهي الفترة المحددة بل تريد تشغيلها تلقائيا وهذا يمكنك عمله بأحد طرق ثلاثة:

  1. تشغيل ملف حافظة الشاشة برمجيا. حيث أن حافظات الشاشة هي عبارة عن ملفات لها الامتداد SCR. فيمكنك برمجيا على سبيل المثال باستخدام الدالة System.Diagnostics.Process.Start() تشغيل ملف SCR المحدد. ملاحظة: غالبية حافظات الشاشة ستجدها في المسار %windir%\System32.
  2. المشكلة في الطريقة السابقة أنها لا توفر لك طريقة لمعرفة أي من حافظات الشاشة هو الذي اختاره المستخدم في شاشة Screen Saver Settings. انظر الشكل السابق. فيمكنك التغلب على هذه المشكلة عن طريق معرفة حافظة الشاشة التي حددها المستخدم عن طريق مكتب التسجيل Registry. فحافظة الشاشة يتم تسجيل المسار الخاص بها في المفتاح Key المسمى SCRNSAVE.EXE الموجود في HKEY_CURRENT_USER\Control Panel\Desktop. فيمكنك دمج هذه الطريقة مع الطريقة السابقة لتشغيل الحافظة المحددة من المستخدم.
  3. الطريقة الأخيرة وهي الأصعب ولكنها الأقوى وهي أن تقوم باستخدام دوال الويندوز أي Win32 API Functions لتشغيل حافظة الشاشة ويتم ذلك عن طريق الدالة SendMessage() والتي يمكنك إعطائها مدخلات معينة لتشغيل حافظة الشاشة.

الطريقة الأولى والثانية هي ليست طرق لتشغيل الحافظة فعليا بل هي لتجربتها كأنك تقوم بالضغط على زر Preview في لوحة تحكم حافظة الشاشة. أما الطريقة الثالثة فهي تقوم بتشغيلها فعليا حتى أنك عند الخروج منها يطلب منك إدخال كلمة سر النظام واسم المستخدم وهذا إذا كان لك كلمة سر وأنك قمت باختيار هذه الخاصية من لوحة التحكم.

التطبيق

تشغيل حافظة الشاشة باستخدام المسار

وهذه هي الطريقة الثانية مدمجة مع الأولى أي أننا سوف نقوم بقراءة مسار ملف حافظة الشاشة المختارة من الـ Registry ثم سوف نقوم بتشغيله. لاحظ الكود.

// C# Code

using System;
using Microsoft.Win32;

class Program
{
    static void Main()
    {
        string path = Registry.CurrentUser
            .OpenSubKey("Control Panel")
            .OpenSubKey("Desktop")
            .GetValue("SCRNSAVE.EXE").ToString();

        Console.WriteLine("Current Screen Saver: \n{0}",
            path);

        Console.WriteLine("Press any key to continue . . .");
        Console.ReadKey(true);

        System.Diagnostics.Process.Start(path);

        Console.WriteLine("Press any key to continue . . .");
        Console.ReadKey(true);  // Wait for the Screen Saver
    }
}

-----------------------------

' VB.NET Code

Imports System
Imports Microsoft.Win32

Module Program
    Sub Main()
        Dim path As String = Registry.CurrentUser _
        .OpenSubKey("Control Panel") _
        .OpenSubKey("Desktop") _
        .GetValue("SCRNSAVE.EXE").ToString()

        Console.WriteLine("Current Screen Saver: {0}{1}", _
            Environment.NewLine, path)

        Console.WriteLine("Press any key to continue . . .")
        Console.ReadKey(True)

        System.Diagnostics.Process.Start(path)

        Console.WriteLine("Press any key to continue . . .")
        Console.ReadKey(True)  ' Wait for the Screen Saver
    End Sub
End Module

تشغيل حافظة الشاشة باستخدام دوال ويندوز

هذه هي الطريقة الثالثة التي تكلمنا عنها. فالدالة التي نريدها هي دالة SendMessage() وهي موجودة في المكتبة الخاصة بالويندوز المسماة user32.dll. وهذه الدالة ترسل أمرا معينا إلى عنصر معين. فعلى سبيل المثال يمكنك إرسال أمر تصغير الشاشة Minimize إلى نافذة معينة أو حتى إلى جميع النوافذ وهكذا. وهذه الدالة معرفة كالتالي:

LRESULT SendMessage(
    HWND hWnd,
    UINT Msg,
    WPARAM wParam,
    LPARAM lParam
);

تأخذ هذه الدالة 4 مدخلات وهي على الترتيب:

  • hWnd:
    العنصر الذي سوف يتم إرسال الأمر إليه. في مثالنا هذا سوف نحدد HWND_BROADCAST وهذا لتحديد جميع النوافذ لنرسل إليها الأمر حتى لا تعرقل أحدها أمر التشغيل.
  • Msg:
    نوع الأمر لإرساله. في مثالنا هذا سوف نقوم بتحديد WM_SYSCOMMAND وهي معناها أمر خاص بالويندوز.
  • wParam:
    خصائص خاصة بالأمر. في مثالنا هذا سوف نقوم بتحديد الخاصية SC_SCREENSAVE وهي معناها تشغيل حافظة الشاشة.
  • lParam:
    خصائص أخرى خاصة بالأمر. في مثالنا هذا لن نقوم بتحديدها.

إذا لتشغيل حافظة الشاشة سوف نقوم بتحديد الأمر WM_SYSCOMMAND وهو معناه أننا نريد أن نرسل أمرا خاصا بالويندوز. وسوف نقوم بتحديد الخاصية SC_SCREENSAVE وهي معاناها أن هذه الأمر هو أمر تشغيل حافظة الشاشة. وبالطبع سوف يرسل هذا الأمر ومعه هذه الخاصية إلى جميع النوافذ وذلك لكي لا تعرقل أحدها هذا الأمر.

بداية وقبل النداء على الدالة تعرف بالطبع أنه لا يمكنك النداء على دوال الويندوز Win32 API Functions مباشرة بل يجب أن تقوم بتعريفها في بيئة الدوت نت أولا ثم النداء عليها. والتعريف في بيئة الدوت نت يسمى Platform Invokation أو PInvoke. لاحظ دالة التعريف ومعها الخصائص والأوامر التي نريدها.  لاحظ أيضا أن نظام التعريف ثابت في جميع دوال الويندوز فقط قم بتغيير اسم الدالة ومكانها والمدخلات الخاصة بها في الدوال الأخرى.

إذا كنت في C# يجب عليك إضافة أمر using للـ Namespace المسماة System.Runtime.InteropServices وذلك لأجل العنصر DllImport.

// C# Code

[DllImport("User32.dll")]
static extern int SendMessage
    (IntPtr hWnd,
    uint Msg,
    uint wParam,
    uint lParam);

const uint WM_SYSCOMMAND = 0x112;
const uint SC_SCREENSAVE = 0xF140;
const uint HWND_BROADCAST = 0xFFFF;

-----------------------------

' VB.NET Code

Declare Auto Function SendMessage Lib "user32.dll" _
    (ByVal hWnd As IntPtr, _
    ByVal Msg As UInt32, _
    ByVal wParam As UInt32, _
    ByVal lParam As UInt32) As Int32

Const WM_SYSCOMMAND As UInt32 = &H112
Const SC_SCREENSAVE As UInt32 = &HF140
Const HWND_BROADCAST As UInt32 = &HFFFF

والآن قم بالنداء بسهولة جدا على الدالة كي تقوم بتشغيل حافظة الشاشة:

// C# Code

static void Main()
{
    SendMessage(
        new IntPtr((int)HWND_BROADCAST),
        WM_SYSCOMMAND,
        SC_SCREENSAVE,
        0);
}

-----------------------------

' VB.NET Code

Sub Main()
    SendMessage( _
        New IntPtr(CInt(HWND_BROADCAST)), _
        WM_SYSCOMMAND, _
        SC_SCREENSAVE, _
        0)
End Sub

لاحظ أن هذه هي الطريقة الأصح لتشغيل حافظة الشاشة وذلك لأن الطريقة السابقة عبارة عن تشغيل برنامج أي أنك تستعرض حافظة الشاشة ولست تشغلها فعليا حتى أنك لا يطلب منك إدخال كلمة سر النظام عن الخروج من حافظة الشاشة وذلك إذا كنت محدد هذا الاختيار.

خاتمة

تعلمنا في هذا الدرس ليس فقط كيفية تشغيل حافظة الشاشة بل تعلمنا أيضا كيفية النداء على دوال الويندوز وأحد الدوال المهمة جدا وهي دالة إرسال الأوامر الدالة SendMessage(). وفي الدروس القادمة بإذن الله تعالى سنتعلم كيفية استخدام هذه الدالة لتنفيذ أوامر أكثر.

قمت بكتابة هذا الدرس سابقا باللغة الإنجليزية في موقعي Just Like a Magic.

نظرة خاطفة

درسنا اليوم يتكلم عن طريقة التبديل بين زري الفأرة عن طريق جعل زر الفأرة الأيسر يعمل عمل الأيمن والأيمن يعمل عمل الأيسر وهكذا.

مقدمة

يمكنك التبديل بين زري الفأرة عن طريق لوحة تحكم الويندوز Control Panel عن طريق خيارات الفأرة Mouse. نلاحظ في هذه الشاشة أنه يمكنك التبديل بين زري الفأرة عن طريق اختيار “Switch primary and secondary buttons”.

Swap Mouse Buttons

عن طريق هذا الاختيار يمكنك التبديل بين الزرين وبالتالي يعمل عمل الزر الأيسر عمل الأيمن فيمكنك من خلال الأيسر فتح القوائم المنسدلة وغيرها. ويقوم الزر الأيمن بعمل الأيسر فيفتح البرامج وهكذا. ويسمى الزر الذي يقوم بفتح البرامج وغيرها والشاشات وهو الأيسر في وضعنا هذا يسمى الزر الأساسي Primary أما الآخر فيسمى الزر الثانوي Secondary، ولهذا إذا بدلنا الزرين يصبح الأيسر هو الثانوي والأيمن هو الـ Primary وهكذا.

لعمل ذلك برمجيا ستحتاج إلى استخدام دالة من دوال الويندوز أي Win32 API Function فإنه -للأسف- بيئة الدوت نت لا توفر لك إمكانية للتبيدل بين الزرين فلهذا سوف نستخدم دالة من دوال الويندوز وهذه الدالة هي SwapMouseButtons. وهذه الدالة معرفة كالتالي:

BOOL SwapMouseButton(
    BOOL fSwap
);

نلاحظ أن هذه الدالة من الدوال البسيطة جدا فهي تأخذ مدخل Argument واحد وهو منطقي Boolean يأخذ قيمة إما True وذلك للتبديل أو False للاسترجاع. وتقوم هذه الدالة بإرجاع أيضا إما True وذلك إذا نجحت الدالة ولم يحدث أي خطأ، أو False إذا حدث خطأ في الدالة وفشلت العملية.

التطبيق

لكي يمكنك النداء على دالة من دوال الويندوز يجب عليك الأول كتابة تعريفها قبل النداء عليها. ويسمى تعريفها بـ PInvoke Method ويكون تعريفها كالتالي:

إذا كنت تستخدم C# يجب عليك إضافة جملة using للـ Namespace المسمى بـ System.Runtime.InteropServicesو ذلك للعنصر DllImport أو على الأقل قم بكتابة اسم العنصر بالكامل أي مع الـ Namespace الخاصة به.

لاحظ أن السطور التالية لا يمكن تغييرها بل هي قاعدة ثابتة في تعريف دوال الويندوز في بيئة الدوت نت.

// C# Code
[DllImport("user32.dll")]
static extern bool SwapMouseButton(bool fSwap);

-----------------------------

' VB.NET
Declare Auto Function SwapMouseButton Lib "user32.dll" _
    (ByVal fSwap As Boolean) As Boolean

الكود التالي هو عبارة عن دالتين الأول تبدل الأزرار فتجعل الزر الأيمن هو الأساسي Primary أي بدلا من الأيسر والأيسر بدلا من الأيمن، والدالة الأخرى تعكس هذا فتجعله طبيعيا الأيسر هو الأساسي والأيمن هو الثانوي Secondary.

// C# Code

public void MakeRightButtonPrimary()
{
    SwapMouseButton(true);
}

public void MakeLeftButtonPrimary()
{
    SwapMouseButton(false);
}

-----------------------------

' VB.NET Code

Public Sub MakeRightButtonPrimary()
    SwapMouseButton(True)
End Sub

Public Sub MakeLeftButtonPrimary()
    SwapMouseButton(False)
End Sub

خاتمة

تعلمنا في هذ الدرس بعض الأشياء:

  1. كيفية التبديل بين زري الفأرة.
  2. دالة التبديل الخاصة بالويندوز.
  3. كيفية النداء على دوال الويندوز.

وفي الدروس القادمة بإذن الله تعالى نشرح أكثر وأكثر في دوال الويندوز وغيرها ونقدم معلومات مختلفة وجديدة.

قمت بكتابة هذا الدرس سابقا باللغة الإنجليزية في موقعي Just Like a Magic.

نظرة خاطفة

درسنا اليوم يتكلم عن طريقة تسجيل البيانات Binary Data في قاعدة بيانات SQL Server. فمثلا بدلا من تسجيل النصوص والأرقام فقط، درسنا اليوم يتكلم عن طريقة أخرى لتسجيل البيانات وهي تسجيل ملفات أو بالأصح بيانات ثنائية Binary Data مثل ملفات الصور والفيديو والـ Word وملفات الكتابة وجميع الملفات. فجميع الملفات كما نعرف هي ملفات ثنائية Binary. يشرح الدرس أولا مقدمة عن هذه الملفات وكيفية التعامل معها في SQL Server ثم ندخل في طريقة تسجيل هذه البيانات واسترجاعها.

مع الدرس مثال يشرح كيفية تسجيل الملفات واسترجاعها من قاعدة بيانات SQL Server.

مقدمة

جميع الملفات على الجهاز هي كما نعرف ملفات ثنائية أو Binary، فكما تعرف أن جميع البيانات يتم تمثيلها داخل الجهاز بنظام ثنائي فمثلا الرقم 5 يتم تمثيلها هكذا 101 والحرف a له الكود 65 فلهذا يتم تمثيله هكذا 1000001.

يمكنك استخدام الآلة الحاسبة Calculator في التحويلات. فقط قم بالتغيير إلى الوضع “علمي Scientific”.

وهذه البيانات يطلق عليها لفظ BLOBs أي Binary Large Objects أي العناصر الثنائية الكبيرة. ونرمز بمصطلح BLOB إلى جميع البينات من ملفات كتابة وصور وفيديو وألعاب وبرامج وغيرها فهي جميعها ملفات (أو عناصر) ثنائية كبيرة الحجم.

لماذا يختلف التعامل مع بيانات BLOB عن غيرها من البيانات الأخرى في SQL Server؟

بالطبع البيانات BLOB يتم تسجيلها بطريقة مختلفة عن طريقة تسجيل البينات من الأنواع الأخرى مثل النصوص والأرقام وذلك بسبب شيئين:

  1. في الغالب أنت لا تعرف حجم البيانات التي يتم تسجيلها. بعكس مثلا إذا كنت تحاول تسجيل نص مثلا اسم شخص في قاعدة البيانات فأنت تطلب من المستخدم إدخال اسمه والذي يجب أن لا يزيد عن 30 حرف مثلا.
  2. السبب الثاني وهو أن البيانات هذه تكون أحيانا حجمها كبير جدا فمثلا تخيل أنك تحاول أن تقوم بتسجيل ملف فيديو حجمه 50 ميجا بايت! فماذا الحل؟ بالطبع أولا، سوف يزيد حجم قاعدة البيانات بحجم هذا الملف المراد تسجيله. وثانيا عند التعامل معه بالطبع أنت لا يمكنك التعامل مع 50 ميجا بايت مرة واحدة فهذا سوف يسبب بطء وفي الغالب توقف برنامجك، ولهذا سوف تحتاج إلى تسجيله واسترجاعه دفعة Chunk بدفعة أخرى. فمثلا بدلا من تسجيل 50 ميجا بايت مرة واحدة يمكنك عمل دوارة Loop تقوم بتسجيل 100 ك.ب. في المرة مثلا. (وهذا أيضا يعتبر إلى حد ما رقم كبير جدا).

للأسف Access لا يمكنه تسجيل بيانات من هذا النوع.

في شرحنا في هذا الدرس سوف نشرح على قاعدة بيانات SQL Server بسيطة جدا يمكنك إنشائها بنفسك وتسمى FileStore. هذه القاعدة لا تحتوي إلا على جدول واحد اسمه MyFiles وهذا الجدول يحتوي على فقط عمودين الأول اسمه Filename ويحتوي على اسم الملف وهو من نوع نصي أي nvarchar بالحجم 260 وبالطبع هو المفتاح الأساسي Primary Key (PK) لهذا الجدول. أما العمود الآخر فهو يسمى Data وهو من نوع image ويستخدم هذا العمود لتسجيل بيانات الملف. لاحظ أنه النوع image للعمود يستخدم لتسجيل البيانات binary أي بيانات BLOB.

تسجيل بيانات BLOB

الكود التالي هو كود بسيط جدا عبارة عن دالة لها مدخل Argument واحد وهو عبارة عن اسم الملف المراد تسجيله. تقوم هذه الدالة بقراءة هذا الملف وتسجيله في قاعدة البيانات. لاحظ الكود.

بالطبع لا تنسى إضافة Reference للمكتبة System.Data.dll وبالطبع يتبعها System.Xml.dll. ولا تنسى أيضا إضافة جملتي using (أو Imports في VB.NET) للـ Namespaces المطلوبة وهي System.Data.SqlClient و System.IO.

// C# Code
static void StoreFile(string filename)
{
    SqlConnection connection = new SqlConnection
        ("Server=(local) ; Initial Catalog = FileStore ; " +
        "Integrated Security = SSPI");

    SqlCommand command = new SqlCommand
        ("INSERT INTO MyFiles VALUES " +
        "(@Filename, @Data)", connection);

    command.Parameters.AddWithValue("@Filename",
        Path.GetFileName(filename));
    command.Parameters.AddWithValue("@Data",
        File.ReadAllBytes(filename));

    connection.Open();

    command.ExecuteNonQuery();

    connection.Close();
}

-------------------------------------------

' VB.NET Code
Sub StoreFile(ByVal filename As String)
    Dim connection As New SqlConnection( _
        "Server=(local) ; Initial Catalog = FileStore ; " & _
        "Integrated Security = SSPI")

    Dim command As New SqlCommand( _
        "INSERT INTO MyFiles VALUES " & _
        "(@Filename, @Data)", connection)

    command.Parameters.AddWithValue("@Filename", _
        Path.GetFileName(filename))
    command.Parameters.AddWithValue("@Data", _
        File.ReadAllBytes(filename))

    connection.Open()

    command.ExecuteNonQuery()

    connection.Close()
End Sub

شرح الكود

قمنا أولا بإنشاء Connection لقاعدة البيانات ثم قمنا بعد ذلك بإنشاء العنصر SqlCommand والذي سيحوي أمر SQL المراد تنفيذه لإضافة البيانات.

بالطبع، ولأن هذا هو التطبيق الأفضل، قمنا بإستخدام مدخلات Parameters لأمر SQL بدلا من كتابتها في الأمر نفسه وذلك أأمن وأفضل وأسرع ولهذا استخدمنا عناصر من نوع SqlParameter.

أخيرا وبعد فتح الاتصال مع القاعدة وتنفيذ الأوامر قمنا بإغلاق الاتصال مع القاعدة.

يفضل إغلاق جميع الاتصالات Connections وجميع الأوامر Commands التي تم إنشائها وبالطبع جميع العناصر التي تتعامل مع قواعد البيانات وتوفر لنا أمر للإغلاق مثل أمر Close() أو Dispose() وذلك توفيرا لموراد الجهاز والحفاظ على أداء برنامجك.

استرجاع بيانات BLOB

الكود التالي يوضح كيفية استرجاع البينات التي قمنا بإدخالها مسبقا. لاحظ أن الكود يأخذ اسم الملف كمدخلات ويقوم بإرجاع بيانات هذا الملف في هيئة مصفوفة Array من نوع Byte.

// C# Code
static byte[] RetrieveFile(string filename)
{
    SqlConnection connection = new SqlConnection
       ("Server=(local) ; Initial Catalog = FileStore ; " +
       "Integrated Security = SSPI");

    SqlCommand command = new SqlCommand
        ("SELECT * FROM MyFiles " +
        "WHERE Filename=@Filename", connection);

    command.Parameters.AddWithValue("@Filename", filename);

    connection.Open();

    SqlDataReader reader = command.ExecuteReader
        (System.Data.CommandBehavior.SequentialAccess);

    reader.Read();

    MemoryStream memory = new MemoryStream();

    long startIndex = 0;
    const int ChunkSize = 256;
    while (true)
    {
        byte[] buffer = new byte[ChunkSize];

        long retrievedBytes =
            reader.GetBytes(1, startIndex, buffer, 0, ChunkSize);

        memory.Write(buffer, 0, (int)retrievedBytes);

        startIndex += retrievedBytes;

        if (retrievedBytes != ChunkSize)
            break;
    }

    connection.Close();

    byte[] data = memory.ToArray();

    memory.Dispose();

    return data;
}

-------------------------------------------

' VB.NET Code
Function RetrieveFile(ByVal filename As String) As Byte()
    Dim connection As New SqlConnection( _
        "Server=(local) ; Initial Catalog = FileStore ; " & _
        "Integrated Security = SSPI")

    Dim command As New SqlCommand( _
        "SELECT * FROM MyFiles " & _
        "WHERE Filename=@Filename", connection)

    command.Parameters.AddWithValue("@Filename", filename)

    connection.Open()

    Dim reader As SqlDataReader = command.ExecuteReader _
        (System.Data.CommandBehavior.SequentialAccess)

    reader.Read()

    Dim memory As New MemoryStream()

    Dim startIndex As Long = 0
    Const ChunkSize As Integer = 256
    While (True)
        Dim buffer(ChunkSize) As Byte

        Dim retrievedBytes As Long = _
            reader.GetBytes(1, startIndex, buffer, 0, ChunkSize)

        memory.Write(buffer, 0, CInt(retrievedBytes))

        startIndex += retrievedBytes

        If (retrievedBytes <> ChunkSize) Then
            Exit While
        End If
    End While

    connection.Close()

    Dim data() As Byte = memory.ToArray()

    memory.Dispose()

    Return data
End Function

شرح الكود

بعد فتح الوصلة Connection إلى قاعدة البيانات وكتابة الأمر SQL نقوم بتنفيذه عن طريق الدالة ExecuteReader وهذه الدالة تقوم بتنفيذ الأوامر وإرجاع عنصر SqlDataReader يتم عن طريقه استرجاع البيانات كما قلنا Chunk by Chunk او دفعة بدفعة. لاحظ أنه يجب تحديد الأمر CommandBehavior.SequentialAccess للدالة ExecuteReader وهذا الأمر يسمح لنا بعمل Sequential Access أو وصول تتابعي للبيانات أي أننا لا نأخذها كلها مرة واحدة بل دفعة بدفعة بشكل تتابعي.

قمنا بعد ذلك بالنداء على دالة Read الخاصة بالعنصر SqlDataReader وذلك لنجعلها تقرأ أول صف في الصفوف وهكذا كل نداء على هذه الدالة يجعلها تقرأ صف بصف وهكذا. تقوم هذه الدالة بإرجاع True إذا كان هناك صف موجود أو False إذا كان آخر صف قرأناه هو آخر صف في البينات المسترجعة.

ثم نقوم بعد ذلك باسترجاع البينات دفعة بدفعة عن طريق الدالة GetBytes الخاصة بالعنصر SqlDataReader. وهذه الدالة تأخذ خمس مدخلات Arguments وهي على الترتيب:

  1. رقم العمود. وفي هذا المثال الرقم هو 1 أي العمود الثاني.
  2. المكان الذي سوف يتم القراءة من بدايته. وهو المكان داخل البيانات Binary المخزنة وهو بالبايت. فعلى سبيل المثال إذا قمنا بكتابة المكان 1024 معنى هذا أننا سوف نقوم بالقراءة من أول الكيلو بايت الثاني وهكذا. بالطبع قمنا باستخدام العنصر startIndex لمعرفة مكان القراءة لأننا نقرأ كل مرة دفعة بدفعة.
  3. العنصر الذي سوف يتم تسجيل البينات المدخلة فيه. قمنا باستخدام مصفوفة من النوع Byte حجمها حجم الدفعة Chunk التي نقوم باسترجاعها في المرة.
  4. حجم البيانات أي الدفعة لاسترجاعها. في هذا المثال قمنا بتحديد 256 بايت كحجم للدفعة Chunk.

تقوم هذه الدالة بإرجاع عدد البايتات التي تم استرجاعها ونقوم بتسجيل هذا العدد في المتغير retrievedBytes ويساعدنا هذا العدد في معرفة إذا كنا في آخر البيانات أم لا لأن إذا كان العدد المسترجع غير العدد المطلوب (وهو 256 بايت كما حددنا) إذا فهذه آخر بيانات في الملف.

قمنا أيضا باستخدام عنصر من نوع MemoryStream لتسجيل البيانات المسترجعة فيه وهذا العنصر يقوم بتسجيل البيانات في الذاكرة وبالطبع هذا ليس حلا مثاليا لأنه ربما تكون هناك بيانات كبيرة الحجم جدا فلذلك يفضل التسجيل على ملف على الجهاز أفضل ولكن لنجعل المثال أبسط قمنا باستخدام MemoryStream.

أخيرا قمنا باسترجاع البيانات من الذاكرة عن طريق الدالة ToArray().

بالطبع جميع العناصر التي لها الدالة Close أو Dispose يجب النداء على هذه الدوال متى تنتهي من استخدامها وذلك لأن هذه العناصر تستخدم موارد كبيرة للجهاز فلذلك يجب إلغاء هذه الموارد متى يتم الانتهاء من التعامل معها. تمسى هذه العناصر Disposable Objects وهي تقوم بتطبيق الـ Interface المسماة بـ IDisposable. من هذه العناصر معظم (ربما جميع) العناصر المستخدمة مع قواعد البيانات وأيضا معظم (ربما جميع أيضا) العناصر المستخدمة مع الملفات.

المثال

مع هذا الدرس مثال يوضح كيفية تسجيل الملفات واسترجاعها من قاعدة البيانات. هذا المثال بالـ C# ويعمل على قاعدة بيانات تصميمها كالتالي:

تصميم قاعدة البيانات

تصميم قاعدة البيانات

 لإنشاء قاعدة البيانات إما أن تقوم بإنشائها يدويا أو باستخدام ملف الأوامر الموجود مع المثال.

قم بتحميل المثال

خاتمة

تعلمنا في هذا المثال كيفية تسجيل ملفات في قاعدة بيانات SQL Server. كما تعرف أن حجم قاعدة البيانات سوف يزيد بحجم البيانات المسجلة فيها فلهذا يفضل أن تقوم بضغط الملفات قبل تسجيلها وأيضا يفضل ألا تقوم بتسجيل ملفات إلا إذا كانت ذا أهمية كبيرة. ويفضل أيضا وضح حد أقصى لحجم الملفات المسجلة. كما يفضل تسجيل الملفات في جدول خاص بها وألا تسجل في نفس الجدول الذي يحوي البيانات المتعلقة بها. فعلى سبيل المثال، إذا كنا نقوم ببرمجة برنامج للموارد البشرية HR لشركة يفضل ألا يتم تسجيل بيانات الشخص المتقدم للوظيفة مع السيرة الذاتية CV الخاصة به. بل يفضل وضع السير الذاتية في جدول والبيانات بجدول آخر وربط الجدولين ببعضهم وذلك حفاظا على أنه إذا كنا نريد مثلا تفحص البيانات وليست السير الذاتية لأشخاص معينين نضمن بهذا سرعة استرجاع البيانات وذلك لأن بيانات الملفات تأخذ مساحة ووقت أكبر. وهكذا…

اقرأ عن ضغط وفك ضغط الملفات برمجيا.

قمت بكتابة هذا الدرس سابقا باللغة الإنجليزية في موقعي Just Like a Magic.

نظرة خاطفة

هذا الدرس يركز على كيفية ضغط وفك ضغط الملفات برمجيا في بيئة الدوت نت. سوف نقوم بداية بشرح آليات الضغط التي توفرها لنا بيئة الدوت نت. ثم سوف نقوم بعد ذلك بتطبيق الأفكار التي تعلمناها.

مقدمة

توفر لك بيئة الدوت نت إمكانيات للتعامل مع اثنان فقط من آليات الضغط الموجودة حاليا ولكنهما اثنان من أقوى وأشهر الآليات الموجودة الآن. وهذان الاثنان هما:

  • Deflate:
    هذه الآلية تعتبر من الآليات البسيطة جدا ويفضل عدم استخدامها إلا إذا كنت لا تنوي استعمالها إلا في برنامجك فقط وذلك لأن الملفات المضغوطة بهذه الآلية لا يمكن فكها ببرامج ضغط الملفات المعروفة مثل WinRAR و WinZip وذلك لأن هذه الآلية لا تقوم بإضافة البيانات Headers التي تساعد برامج ضغط الملفات على فكها وبالتالي فلا تستطيع هذه البرامج التعامل معها. وأيضا بسبب أن هذه الآلية لا تقوم بإضافة بيانات Headers إلى الملفات المضغوطة فإن الملفات المضغوطة يصبح حجمها أقل من الملفات المضافة إليها هذه البيانات.
  • GZIP:
    تعتبر هذه الآلية أفضل من سابقتها. بجانب أن هذه الآلية تقوم بإضافة بيانات Headers إلى الملفات المضغوطة والذي يسمح لبرامج الضغط بالتعامل معها، لهذه الآلية ميزة عظيمة جدا وهي أنها تقوم بعمل فحص Check للبيانات حتى لا تتعرض للتلف أثناء ضغطها أو فكها بعكس الآلية السابقة.

لمعرفة المزيد من التفاصيل حول آلية GZIP اقرأ GZIP File Format Specification.

لحسن الحظ أنه في بيئة الدوت نت طريقة التعامل مع هاتين الآليتين هي نفسها بالضبط ولا يوجد اختلاف غير أنك تقوم بتغيير اسماء الأنواع Classes فقط. فبيئة الدوت توفر لك جميع الأنواع Classes التي تحتاجها في الـ Namespace المسمى بـ System.IO.Compression وهو موجود في المكتبة System.dll ولهذا لا تحتاج إلى خطوات إضافية قبل التعامل مع الكود. هذه الـ Namespace المسماة بـ System.IO.Compression تحتوي على الأنواع التي تحتاجها لضغط وفك ضغط الملفات وهي -لحسن الحظ- ثلاثة أنواع فقط وهي:

  • DeflateStream:
    وهذا النوع يمثل آلية Deflate.
  • GZipStream:
    وهذا النوع يمثل الآلية GZIP.
  • CompressionMode:
    وهذه Enumeration تحدد نوع العملية إما ضغط Compress أو فك ضغط Decompress.

وكما ذكرنا سابقا أن سواء تعاملك مع آلية Deflate بواسطة العنصر DeflateStream أو الآلية الأخرى لا يوجد فرق في التعامل غير أنك إذا أردت التعامل مثلا مع آلية GZIP تقوم بتغيير اسم العنصر إلى GZipStream والعكس بالعكس.

ضغط ملف

الكود التالي يوضح كيفية ضغط ملف وهو كود بسيط وسهل جدا ولا يحتاج إلى شرح. يقوم هذا الكود بضغط الملف D:\My Great Word Document.doc ليصبح ملفا مضغوطا D:\CompressedGreatDocument.zip. بالطبع يمكنك تغيير أسماء الملفات إلى الملفات التي تريدها.

قبل تنفيذ هذه الكود يجب عليك إضافة أمرين using (في C#) أو Imports (في VB.NET) للـ Namespaces المطلوبة وهي System.IO و System.IO.Compression.

// C# Code

string fileToBeCompressed = "D:\\My Great Word Document.doc";
string zipFilename = "D:\\CompressedGreatDocument.zip";

FileStream target = new FileStream(zipFilename,
        FileMode.Create, FileAccess.Write);
GZipStream alg =
    new GZipStream(target, CompressionMode.Compress);

byte[] data = File.ReadAllBytes(fileToBeCompressed);
alg.Write(data, 0, data.Length);
alg.Flush();

-------------------------------

// VB.NET Code

Dim fileToBeCompressed As String = _
    "D:\\My Great Word Document.doc"
Dim zipFilename As String = _
    "D:\\CompressedGreatDocument.zip"

Dim target As New FileStream(zipFilename, _
        FileMode.Create, FileAccess.Write)
Dim alg As New GZipStream(target, CompressionMode.Compress)

Dim data() As Byte = File.ReadAllBytes(fileToBeCompressed)
alg.Write(data, 0, data.Length)
alg.Flush()

إذا كنت تريد ضغط ملف يجب عليك تحديد CompressionMode.Compress ويتم الضغط باستخدام الدالة Write وأيضا يجب عليك فتح الملف الذي يجب الكتابة فيه.

مرة أخرى، إذا كنت تريد استخدام الآلية Deflate فقط قم بتغيير اسم النوع Class إلى DeflateStream.

فك ضغط ملف

يقوم الكود التالي بفك ضغط الملف الذي قمنا بضغطه سابقا.

// C# Code

string compressedFile = "D:\\CompressedGreatDocument.zip";
string originalFileName = "D:\\My Great Word Document.doc";

FileStream zipFile = new FileStream(compressedFile,
        FileMode.Open, FileAccess.Read);
FileStream originalFile = new FileStream(originalFileName,
        FileMode.Create, FileAccess.Write);
GZipStream alg =
    new GZipStream(zipFile, CompressionMode.Decompress);

while (true)
{
    // قراءة 100 بايت في كل مرة
    byte[] buffer = new byte[100];
    // تقوم الدالة التالية بإعطائنا عدد البايتات التي تمت قراءتها
    int bytesRead = alg.Read(buffer, 0, buffer.Length);
    originalFile.Write(buffer, 0, bytesRead);
    if (bytesRead != buffer.Length)
        break;
}

-------------------------------

' VB.NET Code

Dim compressedFile As String = _
    "D:\\CompressedGreatDocument.zip"
Dim originalFileName As String = _
    "D:\\My Great Word Document.doc"

Dim zipFile As New FileStream(compressedFile, _
    FileMode.Open, FileAccess.Read)
Dim originalFile As New FileStream(originalFileName, _
    FileMode.Create, FileAccess.Write)
Dim alg As New GZipStream(zipFile, _
    CompressionMode.Decompress)

While (True)
    ' قراءة 100 بايت في كل مرة
    Dim buffer(100) As Byte
    ' تقوم الدالة التالية بإعطائنا عدد البايتات التي تمت قراءتها
    Dim bytesRead As Integer = _
        alg.Read(buffer, 0, buffer.Length)

    originalFile.Write(buffer, 0, bytesRead)

    If (bytesRead <> buffer.Length) Then
        Exit While
    End If
End While

قمنا بإنشاء عنصري FileStream الأول للقراءة من الملف المضغوط والثاني للكتابة فيه أي للفك إليه. ثم قمنا بالقراءة من الملف المضغوط في كل مرة 100 بايت وفك هذه البيانات وكتابتها في الملف المفكوك. وهكذا.

للتدريب

جرب إنشاء برنامج يقوم بضغط الملفات مثل برنامج WinRAR مثلا. يمكنك الاعتماد على الأنواع BinaryReader و BinaryWriter للتعامل الدقيق مع الملفات.

خاتمة

يمكنك استخدام هذه الطريقة لضغط الملفات قبل تسجيلها على الجهاز وتعتبر هذه الآليات مفيدة جدا إذا كنت ستحتاج إلى تسجيل هذه البيانات في قاعدة بيانات. فحينها تقوم بضغط الملفات قبل تسجيلها في القاعدة كي لا تزيد حجم القاعدة بشكل خرافي. في دروس أخرى بإذن الله تعالى نشرح كيفية تسجيل ملفات في قاعدة بيانات SQL Server.

قمت بكتابة هذا الدرس سابقا باللغة الإنجليزية في موقعي Just Like a Magic.

نظرة خاطفة

درسنا في هذا اليوم يتكلم عن أحد الطرق لتحسين الكود أو ما نسميها بعملية Refactoring. يتكلم عن طريقة استخدام Magic Numbers لجعل الكود أسهل وأبسط في القراءة والكتابة.

مقدمة

تحسين الكود أو ما نسميه علميا بـ Refactoring هي أفكار أو طرق يتم تطبيقها على الكود لجعله أبسط وأسهل في القراءة والكتابة ولجعل عملية التنقيح Debugging أو مراجعة Revising الكود أسهل وأبسط.

هناك طرق عديدة جدا للـ Refactoring ومنها استخدام الـ Magic Numbers.

أرقام Magic Numbers هي عبارة عن أرقام لها دلالات معينة. فعلى سبيل المثال إذا كنا نريد تحويل عدد من المترات إلى سنتيميترات فسوف نقوم بالضرب في 100. وبالعكس، إذا كنا نريد تحويل السنتيميترات إلى مترات فسوف نقوم بالقسمة على 100. ولذلك يعتبر 100 هو رقم سحري Magic Number. فبدلا من كتابتك لهذا الرقم مرات عديدة في الكود يمكنك جعل هذا الكود في متغير  Variable أو حتى في متغير ثابت Constant وتقوم بدلا من كتابة الرقم 100 تقوم بكتابة اسم المتغير أو هذا الثابت.

لنأخذ مثال آخر أكثر تعقيدا فربما الرقم 100 هو رقم بسيط وسهل. إذا كنا نريد عمل برنامج يقوم بالتحويل بين المترات والأقدام فسوف يكون عندنا رقم سحري Magic Number وهو 3.2808398950131233595800524934383 (هذه أعلى دقة لهذا الرقم.) فإنك إذا كنت تريد التحويل إلى الأقدام فسوم تقوم بالضرب في هذا الرقم وإذا كنت تريد العكس بالتحويل على المترات فسوف تقوم بالقسمة على هذا الرقم. فبدلا من أن تقوم بكتابة هذا الرقم بهذه الدقة في كل مكان في الكود وإذا كنت تريد تغييره فسوف يكون هذا شبه مستحيل أن تبحث في كل الكود عن هذا الرقم وتقوم بتغييره. ولكن الحل الأفضل هو أن تقوم بتسجيل هذا الرقم في متغير وتقوم بكتابة فقط اسم المتغير في كل دالة بدلا من الرقم وهكذا تستطيع التعامل مع الكود الأسهل وأبسط بالإضافة إلى أنه سيكون أسهل في القراءة.

التطبيق

الكود التالي يوضح دالتين واحدة للتحويل من المتر إلى القدم والأخرى للتحويل بالعكس من القدم إلى المتر. لاحظ كيفية قراءة وكتابة الكود.

// C# Code

public static double ConvertMetersToFeet(double meters)
{
    return meters * 3.2808398950131233595800524934383;
}

public static double ConvertFeetToMeters(double feet)
{
    return feet / 3.2808398950131233595800524934383;
}

------------------------------

' VB.NET Code

Public Function ConvertMetersToFeet(meters As Double) As Double
    Return meters * 3.2808398950131233595800524934383
End Function

Public Function ConvertFeetToMeters(feet As Double) As Double
    Return feet / 3.2808398950131233595800524934383
End Function

بعد أن نقوم بتحسين الكود أو بمعنى أصح عمل Refactoring من نوع Magic Number يصبح كودنا بالشكل التالي:

// C# Code

public const double MeterFeet = 3.2808398950131233595800524934383;

public static double ConvertMetersToFeet(double meters)
{
    return meters * MeterFeet;
}

public static double ConvertFeetToMeters(double feet)
{
    return feet / MeterFeet;
}

------------------------------

' VB.NET Code

Public Const MeterFeet As Double _
    = 3.2808398950131233595800524934383

Public Function ConvertMetersToFeet(meters As Double) As Double
    return meters * MeterFeet
End Function

Public Function ConvertFeetToMeters(feet As Double) As Double
    return feet / MeterFeet
End Function

خاتمة

تعلمنا في هذا الدرس كيفية تحسين الكود Refactoring عن طريق استخدام Magic Numbers لجعل الكود أسهل وأيسر وأسهل في عملية التنقيح والمراجعة. سوف نقوم بإذن الله تعالى بشرح طرق أكثر لتحسين الكود في الدروس القادمة.

Older Posts »