7. واجهات المستخدم الرسومية باستخدام C# و VS.NET
7.1. أساسيات واجهات المستخدم الرسومية
7.1.1. مشروع أول
لنقم بإنشاء مشروع أول من نوع "تطبيق ويندوز":
![]() |
- [1]: إنشاء مشروع جديد
- [2]: من نوع تطبيق ويندوز
- [3]: لا يهم اسم المشروع في الوقت الحالي
- [4]: تم إنشاء المشروع
![]() |
- [5]: يتم حفظ الحل الحالي
- [6]: اسم المشروع
- [7]: مجلد الحل
- [8]: اسم الحل
- [9]: سيتم إنشاء مجلد للحل [Chap5]. وستكون مشاريع هذا الحل في مجلدات فرعية.
![]() |
- [10]: المشروع [01] في الحل [Chap5]:
- [Program.cs] هي الفئة الرئيسية للمشروع
- [Form1.cs] هو ملف المصدر الذي سيدير سلوك النافذة [11]
- [Form1.Designer.cs] هو الملف المصدر الذي سيقوم بتغليف المعلومات حول مكونات النافذة [11]
- [11]: ملف [Form1.cs] في وضع "التصميم" (design)
- [12]: يمكن تشغيل التطبيق الذي تم إنشاؤه بواسطة (Ctrl-F5). تظهر النافذة [Form1]. يمكن تحريكها وتغيير حجمها وإغلاقها. وبذلك يكون لدينا العناصر الأساسية لنافذة رسومية.
الفئة الرئيسية [Program.cs] هي كما يلي:
using System;
using System.Windows.Forms;
namespace Chap5 {
static class Program {
/// <summary>
/// نقطة الدخول الرئيسية للتطبيق.
/// </summary>
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
- السطر 2: تستخدم التطبيقات التي تحتوي على نماذج مساحة الأسماء System.Windows.Forms.
- السطر 4: تم تغيير اسم مساحة الأسماء الأصلية إلى Chap5.
- السطر 10: عند تشغيل المشروع (Ctrl-F5)، يتم تنفيذ الأسلوب [Main].
- الأسطر 11-13: تنتمي الفئة Application إلى مساحة الاسم System.Windows.Forms. وهي تحتوي على طرق ثابتة لتشغيل/إيقاف تشغيل تطبيقات Windows الرسومية.
- السطر 11: اختياري - يسمح بإعطاء أنماط مرئية مختلفة للعناصر الموجودة في النموذج
- السطر 12: اختياري - يحدد محرك عرض نصوص عناصر التحكم: GDI+ (صحيح)، GDI (خطأ)
- السطر 13: السطر الوحيد الضروري في الطريقة [Main]: يقوم بإنشاء مثيل للفئة [Form1] وهي فئة النموذج ويطلب منها التنفيذ.
ملف المصدر [Form1.cs] هو التالي:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
}
}
- السطر 5: الفئة Form1 مشتقة من الفئة [System.Windows.Forms.Form]، وهي الفئة الأم لجميع النوافذ. تشير الكلمة الرئيسية partial إلى أن الفئة جزئية ويمكن استكمالها بملفات مصدر أخرى. وهذا هو الحال هنا، حيث تنقسم الفئة Form1 إلى ملفين:
- [Form1.cs]: حيث نجد سلوك النموذج، ولا سيما معالجات الأحداث الخاصة به
- [Form1.Designer.cs]: حيث نجد مكونات النموذج وخصائصها. يتميز هذا الملف بأنه يتم إعادة إنشاؤه في كل مرة يقوم فيها المستخدم بتعديل النافذة في وضع [conception].
- الأسطر 6-8: منشئ الفئة Form1
- السطر 7: يستدعي الطريقة InitializeComponent. نلاحظ أن هذه الطريقة غير موجودة في [Form1.cs]. نجدها في [Form1.Designer.cs].
ملف المصدر [Form1.Designer.cs] هو التالي:
namespace Chap5 {
partial class Form1 {
/// <summary>
/// متغير مصمم مطلوب.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// قم بتنظيف أي موارد قيد الاستخدام.
/// </summary>
/// <param name="disposing">true إذا كان يجب التخلص من الموارد المُدارة؛ وإلا، false.</param>
protected override void Dispose(bool disposing) {
if (disposing && (components != null)) {
components.Dispose();
}
base.Dispose(disposing);
}
#منطقة كود تم إنشاؤه بواسطة Windows Form Designer
/// <summary>
/// الطريقة المطلوبة لدعم المصمم - لا تقم بتعديل
/// محتويات هذه الطريقة باستخدام محرر الكود.
/// </summary>
private void InitializeComponent() {
this.SuspendLayout();
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(196, 98);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
}
}
- السطر 2: لا يزال الأمر يتعلق بالفئة Form1. تجدر الإشارة إلى أنه لم يعد هناك داعٍ لتكرار أنها مشتقة من الفئة Form.
- الأسطر 25-37: الطريقة InitializeComponent التي يتم استدعاؤها بواسطة منشئ الفئة [Form1]. ستقوم هذه الطريقة بإنشاء وتهيئة جميع مكونات النموذج. يتم إعادة إنشاؤها عند كل تغيير في النموذج في الوضع [conception]. يتم إنشاء قسم، يُسمى région، لتحديدها في الأسطر 19-39. يجب على المطور عدم إضافة أي كود في هذه المنطقة: سيتم استبداله عند إعادة الإنشاء التالية.
من الأسهل في البداية عدم الاهتمام برمز [Form1.Designer.cs]. يتم إنشاؤه تلقائيًا وهو ترجمة بلغة C# للاختيارات التي يقوم بها المطور في الوضع [conception]. لنأخذ مثالًا أول:
![]() |
- [1]: حدد الوضع [conception] بالنقر المزدوج على الملف [Form1.cs]
- [2]: انقر بزر الماوس الأيمن على النموذج واختر [Properties]
- [3]: نافذة خصائص [Form1]
- [4]: تمثل الخاصية [Text] عنوان النافذة
- [5]: يتم أخذ تغيير الخاصية [Text] في الاعتبار في الوضع [conception] وكذلك في شفرة المصدر [Form1.Designer.cs]:
private void InitializeComponent() {
this.SuspendLayout();
...
this.Text = "Mon 1er formulaire";
...
}
7.1.2. مشروع ثانٍ
7.1.2.1. النموذج
نبدأ مشروعًا جديدًا يسمى 02. وللقيام بذلك، نتبع الإجراء الموضح سابقًا لإنشاء مشروع. النافذة المطلوب إنشاؤها هي التالية:
![]() |
مكونات النموذج هي كما يلي:
رقم | الاسم | النوع | الدور |
1 | labelSaisie | العلامة | وصف |
2 | textBoxSaisie | TextBox | حقل إدخال |
3 | buttonAfficher | زر | لعرض محتوى منطقة الإدخال في مربع حوار textBoxSaisie |
يمكن اتباع الخطوات التالية لإنشاء هذه النافذة:
![]() |
- [1]: انقر بزر الماوس الأيمن على النموذج خارج أي مكون واختر الخيار [Properties]
- [2]: تظهر ورقة خصائص النافذة في الزاوية اليمنى السفلية من Visual Studio
من بين خصائص النموذج التي تجدر الإشارة إليها:
لتحديد لون خلفية النافذة | |
لتحديد لون الرسومات أو النص في النافذة | |
لربط قائمة بالنافذة | |
لإعطاء عنوان للنافذة | |
لتحديد نوع النافذة | |
لتحديد نوع الخط المستخدم في النصوص داخل النافذة | |
لتحديد اسم النافذة |
هنا، نحدد الخصائص Text و Name:
المدخلات والأزرار - 1 | |
frmSaisiesBoutons |
![]() |
- [1]: اختر مجموعة أدوات [Common Controls] من بين مجموعات الأدوات التي يقدمها Visual Studio
- [2, 3, 4]: انقر نقرًا مزدوجًا على المكونات [Label] و [Button] و [TextBox]
- [5]: المكونات الثلاثة موجودة في النموذج
لمحاذاة المكونات وتحديد أبعادها بشكل صحيح، يمكن استخدام عناصر شريط الأدوات:
![]() | |||||||||
مبدأ التنسيق هو كما يلي:
- حدد المكونات المختلفة التي تريد تنسيقها معًا (اضغط على مفتاح Ctrl أثناء النقر لتحديد المكونات)
- حدد نوع التنسيق المطلوب:
- (تابع)
- تسمح الخيارات Align بمحاذاة المكونات من الأعلى أو الأسفل أو الجانب الأيسر أو الأيمن أو الوسط
- تسمح خيارات Make Same Size بأن يكون للمكونات نفس الارتفاع أو نفس العرض
- تسمح خيار Horizontal Spacing بمحاذاة المكونات أفقيًا مع ترك مسافات بينها بنفس العرض. وينطبق الأمر نفسه على خيار Vertical Spacing للمحاذاة عموديًا.
- تسمح الخيار Center بتوسيط مكون أفقيًا (Horizontally) أو عموديًا (Vertically) في النافذة
بمجرد وضع المكونات، نقوم بتحديد خصائصها. للقيام بذلك، انقر بزر الماوس الأيمن على المكون واختر الخيار Properties:
![]() |
- [1]: حدد المكون لفتح نافذة خصائصه. في هذه النافذة، قم بتعديل الخصائص التالية: name: labelSaisie، text: Saisie
- [2]: اتبع نفس الخطوات: الاسم: textBoxSaisie، النص: لا تضع أي شيء
- [3]: الاسم: buttonAfficher، النص: Afficher
- [4]: النافذة نفسها: الاسم: frmSaisiesBoutons، النص: الحقول والأزرار - 1
- [5]: قم بتشغيل (Ctrl-F5) المشروع للحصول على نظرة أولية على النافذة أثناء العمل.
تمت ترجمة ما تم إنجازه في الوضع [conception] إلى كود [Form1.Designer.cs]:
namespace Chap5 {
partial class frmSaisiesBoutons {
...
private System.ComponentModel.IContainer components = null;
...
private void InitializeComponent() {
this.labelSaisie = new System.Windows.Forms.Label();
this.buttonAfficher = new System.Windows.Forms.Button();
this.textBoxSaisie = new System.Windows.Forms.TextBox();
this.SuspendLayout();
//
// labelSaisie
//
this.labelSaisie.AutoSize = true;
this.labelSaisie.Location = new System.Drawing.Point(12, 19);
this.labelSaisie.Name = "labelSaisie";
this.labelSaisie.Size = new System.Drawing.Size(35, 13);
this.labelSaisie.TabIndex = 0;
this.labelSaisie.Text = "Saisie";
//
// buttonAfficher
//
this.buttonAfficher.Location = new System.Drawing.Point(80, 49);
this.buttonAfficher.Name = "buttonAfficher";
this.buttonAfficher.Size = new System.Drawing.Size(75, 23);
this.buttonAfficher.TabIndex = 1;
this.buttonAfficher.Text = "Afficher";
this.buttonAfficher.UseVisualStyleBackColor = true;
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
//
// textBoxSaisie
//
this.textBoxSaisie.Location = new System.Drawing.Point(80, 19);
this.textBoxSaisie.Name = "textBoxSaisie";
this.textBoxSaisie.Size = new System.Drawing.Size(100, 20);
this.textBoxSaisie.TabIndex = 2;
//
// frmSaisiesBoutons
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(292, 118);
this.Controls.Add(this.textBoxSaisie);
this.Controls.Add(this.buttonAfficher);
this.Controls.Add(this.labelSaisie);
this.Name = "frmSaisiesBoutons";
this.Text = "Saisies et boutons - 1";
this.ResumeLayout(false);
this.PerformLayout();
}
private System.Windows.Forms.Label labelSaisie;
private System.Windows.Forms.Button buttonAfficher;
private System.Windows.Forms.TextBox textBoxSaisie;
}
}
- الأسطر 53-55: أنتجت المكونات الثلاثة ثلاثة حقول خاصة للفئة [Form1]. تجدر الإشارة إلى أن أسماء هذه الحقول هي الأسماء التي أُعطيت للمكونات في وضع [conception]. وينطبق الأمر نفسه على النموذج في السطر 2 الذي يمثل الفئة نفسها.
- الأسطر 7-9: تم إنشاء الكائنات الثلاثة من النوع [Label] و [TextBox] و [Button]. يتم إدارة المكونات المرئية من خلالها.
- الأسطر 14-19: تكوين التسمية labelSaisie
- الأسطر 23-29: تكوين الزر buttonAfficher
- الأسطر 33-36: تكوين حقل الإدخال textBoxSaisie
- الأسطر 40-47: تكوين النموذج frmSaisiesBoutons. تجدر الإشارة، في الأسطر 43-45، إلى طريقة إضافة المكونات إلى النموذج.
هذا الرمز سهل الفهم. وبذلك يمكن إنشاء نماذج برمجياً دون استخدام الوضع [conception]. توجد أمثلة عديدة على ذلك في وثائق Visual Studio الخاصة بـ MSDN. إتقان هذا الكود يسمح بإنشاء نماذج أثناء التشغيل: على سبيل المثال، إنشاء نموذج على الفور يسمح بتحديث جدول قاعدة بيانات، حيث لا يتم اكتشاف بنية هذا الجدول إلا عند التنفيذ.
يبقى لنا كتابة الإجراء الخاص بإدارة النقر على الزر Afficher. حدد الزر للوصول إلى نافذة خصائصه. تحتوي هذه النافذة على عدة علامات تبويب:
![]() |
- [1]: قائمة الخصائص مرتبة أبجديًا
- [2]: الأحداث المرتبطة بالعنصر
يمكن الوصول إلى خصائص وأحداث عنصر التحكم حسب الفئات أو حسب الترتيب الأبجدي:
- [3]: الخصائص أو الأحداث حسب الفئة
- [4]: الخصائص أو الأحداث حسب الترتيب الأبجدي
علامة التبويب Events في الوضع Catégories للزر buttonAfficher هي كما يلي:
![]() |
- [1]: يسرد العمود الأيسر من النافذة الأحداث المحتملة على الزر. النقر على زر ما يتوافق مع الحدث Click.
- [2]: يحتوي العمود الأيمن على اسم الإجراء الذي يتم استدعاؤه عند حدوث الحدث المقابل.
- [3]: إذا نقرت مرتين على خلية الحدث Click، فستنتقل تلقائيًا إلى نافذة الكود لكتابة معالج الحدث Click على الزر buttonAfficher:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class frmSaisiesBoutons : Form {
public frmSaisiesBoutons() {
InitializeComponent();
}
private void buttonAfficher_Click(object sender, EventArgs e) {
}
}
}
- الأسطر 10-12: الهيكل الأساسي لمعالج الحدث Click على الزر المسمى buttonAfficher. تجدر الإشارة إلى النقاط التالية:
- يتم تسمية الطريقة وفقًا للنمط nomDuComposant_NomEvénement
- الطريقة خاصة. تتلقى معلمتين:
- sender: هو الكائن الذي تسبب في الحدث. إذا تم تنفيذ الإجراء بعد النقر على الزر buttonAfficher، فسيكون sender مساوياً لـ buttonAfficher. يمكننا أن نتخيل أن الإجراء buttonAfficher_Click يتم تنفيذه من إجراء آخر. عندئذٍ سيكون لهذا الإجراء كل الحرية في تعيين الكائن sender الذي يختاره كمعلمة أولى.
- EventArgs: كائن يحتوي على معلومات عن الحدث. بالنسبة لحدث Click، لا يحتوي على أي شيء. بالنسبة لحدث يتعلق بحركات الماوس، سنجد فيه إحداثيات (X، Y) الماوس.
- لن نستخدم أيًا من هذه المعلمات هنا.
تتمثل كتابة معالج الأحداث في استكمال هيكل الكود السابق. هنا، نريد عرض مربع حوار يحتوي على محتوى الحقل textBoxSaisie إذا كان غير فارغ [1]، أو رسالة خطأ في حالة عدم وجود محتوى [2]:
![]() |
قد يكون الكود الذي يحقق ذلك كما يلي:
private void buttonAfficher_Click(object sender, EventArgs e) {
// يتم عرض النص الذي تم إدخاله في TextBox textboxSaisie
string texte = textBoxSaisie.Text.Trim();
if (texte.Length != 0) {
MessageBox.Show("Texte saisi= " + texte, "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Information);
} else {
MessageBox.Show("Saissez un texte...", "Vérification de la saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
تُستخدم الفئة MessageBox لعرض الرسائل في نافذة. وقد استخدمنا هنا الطريقة Show التالية:
public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon);
مع
الرسالة المراد عرضها | |
عنوان النافذة | |
الأزرار الموجودة في النافذة | |
الرمز الموجود في النافذة |
يمكن أن تأخذ المعلمة buttons قيمها من بين الثوابت التالية (المسبوقة بـ MessageBoxButtons كما هو موضح في السطر 7) أعلاه:
أزرار | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() | |
![]() |
يمكن أن تأخذ المعلمة icon قيمها من بين الثوابت التالية (المسبوقة بـ MessageBoxIcon كما هو موضح في السطر 10) أعلاه:
![]() | نفس الشيء توقف | ||
نفس الشيء تحذير | ![]() | ||
نفس الشيء Asterisk | ![]() | ||
![]() | نفس الشيء يدويًا | ||
![]() |
الطريقة Show هي طريقة ثابتة تُرجع نتيجة من النوع [System.Windows.Forms.DialogResult] وهي عبارة عن تعداد:

لمعرفة الزر الذي ضغط عليه المستخدم لإغلاق النافذة من النوع MessageBox، سنكتب:
7.1.2.2. الرمز المرتبط بإدارة الأحداث
بالإضافة إلى الدالة buttonAfficher_Click التي كتبناها، قام Visual Studio بإنشاء السطر التالي في الأسلوب InitializeComponents التابع لـ [Form1.Designer.cs] الذي يقوم بإنشاء وتهيئة مكونات النموذج:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
Click هو حدث من فئة Button [1, 2, 3]:
![]() |
- [5]: إعلان الحدث [Control.Click] [4]. وهكذا نرى أن الحدث Click ليس خاصًا بالفئة [Button]. إنه ينتمي إلى الفئة [Control]، وهي الفئة الأم للفئة [Button].
- EventHandler هو نموذج أولي (prototype) لطريقة تسمى delegate. سنعود إلى هذا الموضوع لاحقًا.
- event هي كلمة رئيسية تقيد وظائف delegate EventHandler: يتمتع كائن delegate بوظائف أكثر ثراءً من كائن event.
يتم تعريف delegate EventHandler على النحو التالي:
![]() |
يشير delegate EventHandler إلى نموذج طريقة:
- الذي يكون المعلمة الأولى له من نوع Object
- والمعلمة الثانية من النوع EventArgs
- لا يعطي أي نتيجة
وهذا هو الحال بالنسبة لطريقة إدارة النقر على الزر buttonAfficher التي تم إنشاؤها بواسطة Visual Studio:
private void buttonAfficher_Click(object sender, EventArgs e);
وبالتالي، فإن الطريقة buttonAfficher_Click تتوافق مع النموذج الأولي المحدد بواسطة النوع EventHandler. لإنشاء كائن من النوع EventHandler، يتم اتباع الخطوات التالية:
EventHandler evtHandler=new EventHandler(méthode correspondant au prototype défini par le type EventHandler);
نظرًا لأن الطريقة buttonAfficher_Click تتوافق مع النموذج الأولي المحدد بواسطة النوع EventHandler، يمكننا كتابة:
المتغير من النوع delegate هو في الواقع قائمة من المراجع إلى طرق من النوع delegate. لإضافة طريقة جديدة M إلى المتغير evtHandler أعلاه، سنستخدم الصيغة:
يمكن استخدام الترميز += حتى لو كانت evtHandler قائمة فارغة.
لنعد إلى سطر [InitializeComponent] الذي يضيف معالج أحداث إلى الحدث Click للكائن buttonAfficher:
this.buttonAfficher.Click += new System.EventHandler(this.buttonAfficher_Click);
تضيف هذه التعليمات طريقة من النوع EventHandler إلى قائمة طرق الحقل buttonAfficher.Click. سيتم استدعاء هذه الطرق في كل مرة يتم فيها اكتشاف الحدث Click على المكون buttonAfficher. غالبًا ما يكون هناك طريقة واحدة فقط. ويُطلق عليها اسم "مدير الحدث".
لنعد إلى توقيع EventHandler:
private delegate void EventHandler(object sender, EventArgs e);
المعلمة الثانية لـ delegate هي كائن من النوع EventArgs أو من فئة مشتقة. النوع EventArgs عام جدًا ولا يقدم في الواقع أي معلومات عن الحدث الذي وقع. بالنسبة للنقر على زر، هذا كافٍ. بالنسبة لتحريك الماوس على نموذج، سيكون لدينا حدث MouseMove من الفئة [Form] المحددة بواسطة:
يتم تعريف delegate MouseEventHandler على النحو التالي:
![]() |
وهي دالة تفويضية (delegate) للتوقيع void f (object, MouseEventArgs). أما الفئة MouseEventArgs فهي محددة بواسطة:
![]() |
الفئة MouseEventArgs أكثر ثراءً من الفئة EventArgs. يمكننا، على سبيل المثال، معرفة إحداثيات الماوس X و Y في اللحظة التي يحدث فيها الحدث.
7.1.2.3. Conclusion
من المشروعين اللذين تمت دراستهما، يمكننا أن نستنتج أنه بمجرد إنشاء واجهة المستخدم الرسومية باستخدام Visual Studio، فإن عمل المطور يتمثل بشكل أساسي في كتابة معالجات الأحداث التي يريد إدارتها لهذه الواجهة. يتم إنشاء الكود تلقائيًا بواسطة Visual Studio. هذا الكود، الذي قد يكون معقدًا، يمكن تجاهله في البداية. لاحقًا، قد تسمح دراسته بفهم أفضل لإنشاء النماذج وإدارتها.
7.2. المكونات الأساسية
نقدم الآن تطبيقات متنوعة تستخدم المكونات الأكثر شيوعًا لاكتشاف الطرق والخصائص الرئيسية لهذه المكونات. لكل تطبيق، نقدم الواجهة الرسومية والكود ذي الصلة، وبشكل أساسي كود معالجات الأحداث.
7.2.1. نموذج Form
نبدأ بتقديم المكون الأساسي، وهو النموذج الذي توضع عليه المكونات. وقد قدمنا بالفعل بعض خصائصه الأساسية. وسنتناول هنا بعض الأحداث المهمة في النموذج.
يتم تحميل النموذج | |
النموذج قيد الإغلاق | |
تم إغلاق النموذج |
يحدث الحدث Load قبل حتى عرض النموذج. يحدث الحدث Closing عندما يكون النموذج قيد الإغلاق. لا يزال من الممكن إيقاف هذا الإغلاق برمجيًا.
نقوم بإنشاء نموذج باسم Form1 بدون مكونات:
![]() |
- [1]: النموذج
- [2]: الأحداث الثلاثة التي تمت معالجتها
رمز [Form1.cs] هو التالي:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
// التحميل الأولي للنموذج
MessageBox.Show("Evt Load", "Load");
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
// النموذج قيد الإغلاق
MessageBox.Show("Evt FormClosing", "FormClosing");
// يُطلب التأكيد
DialogResult réponse = MessageBox.Show("Voulez-vous vraiment quitter l'application", "Closing", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (réponse == DialogResult.No)
e.Cancel = true;
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
// سيتم إغلاق النموذج
MessageBox.Show("Evt FormClosed", "FormClosed");
}
}
}
نستخدم الوظيفة MessageBox لتلقي إشعارات بالأحداث المختلفة.
السطر 10: سيحدث الحدث Load عند بدء تشغيل التطبيق قبل حتى ظهور النموذج: | ![]() |
السطر 15: سيحدث الحدث FormClosing عندما يغلق المستخدم النافذة. | ![]() |
السطر 19: نسأله عندئذٍ عما إذا كان يريد حقًا الخروج التطبيق: | ![]() |
السطر 20: إذا أجاب بـ "لا"، فإننا نعيّن الخاصية Cancel الحدث CancelEventArgs الذي تلقتها الطريقة كمعلمة معلمة. إذا قمنا بتعيين هذه الخاصية إلى False، يتم إلغاء النافذة، وإلا فسيستمر. سيحدث عندئذٍ الحدث FormClosed: | ![]() |
7.2.2. العلامات Label ومربعات الإدخال TextBox
لقد سبق أن تعرفنا على هذين المكونين. Label هو مكون نصي و TextBox هو مكون حقل إدخال. خاصيتهما الرئيسية هي Text التي تشير إما إلى محتوى حقل الإدخال أو نص التسمية. هذه الخاصية قابلة للقراءة والكتابة.
الحدث الذي يُستخدم عادةً لـ TextBox هو TextChanged الذي يشير إلى أن المستخدم قد قام بتعديل حقل الإدخال. فيما يلي مثال يستخدم الحدث TextChanged لتتبع التغييرات في حقل الإدخال:
![]() |
رقم | النوع | الاسم | الدور |
1 | TextBox | textBoxSaisie | حقل الإدخال |
2 | التسمية | labelControle | يعرض نص 1 في الوقت الفعلي AutoSize=False، Text=(لا شيء) |
3 | زر | buttonEffacer | لمسح الحقلين 1 و 2 |
4 | زر | buttonQuitter | للخروج من التطبيق |
رمز هذا التطبيق هو التالي:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// تغير محتوى TextBox - يتم نسخه إلى التسمية labelControle
labelControle.Text = textBoxSaisie.Text;
}
private void buttonEffacer_Click(object sender, System.EventArgs e) {
// يتم مسح محتوى مربع الإدخال
textBoxSaisie.Text = "";
}
private void buttonQuitter_Click(object sender, System.EventArgs e) {
// النقر على زر الخروج - الخروج من التطبيق
Application.Exit();
}
private void Form1_Shown(object sender, System.EventArgs e) {
// نضع التركيز على حقل الإدخال
textBoxSaisie.Focus();
}
}
}
- السطر 24: يحدث الحدث [Form].Shown عند عرض النموذج
- السطر 26: يتم وضع التركيز (للإدخال) على المكون textBoxSaisie.
- السطر 9: يحدث الحدث [TextBox].TextChanged في كل مرة يتغير فيها محتوى مكون TextBox
- السطر 11: يتم نسخ محتوى المكون [TextBox] إلى المكون [Label]
- السطر 14: يدير النقر على الزر [Effacer]
- السطر 16: يتم وضع السلسلة الفارغة في المكون [TextBox]
- السطر 19: يدير النقر على الزر [Quitter]
- السطر 21: لإيقاف التطبيق قيد التشغيل. نتذكر أن الكائن Application يستخدم لتشغيل التطبيق في الطريقة [Main] من [Form1.cs]:
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
يستخدم المثال التالي كائن TextBox متعدد الأسطر:
![]() |
قائمة عناصر التحكم هي كما يلي:
رقم | النوع | الاسم | الدور |
1 | TextBox | textBoxLignes | حقل إدخال متعدد الأسطر Multiline=true، ScrollBars=Both، AcceptReturn=True، AcceptTab=True |
2 | TextBox | textBoxLigne | حقل إدخال أحادي السطر |
3 | زر | buttonAjouter | يضيف المحتوى من 2 إلى 1 |
لكي يصبح TextBox متعدد الأسطر، يتم ضبط الخصائص التالية للعنصر:
لقبول عدة أسطر من النص | |
لطلب أن يكون للعنصر أشرطة تمرير (أفقية، عمودية، كلاهما) أو لا (لا شيء) | |
إذا كانت القيمة true، فستؤدي مفتاح Enter إلى الانتقال إلى السطر التالي | |
إذا كانت القيمة true، فستؤدي مفتاح Tab إلى إنشاء فاصل في النص |
يتيح التطبيق كتابة الأسطر مباشرة في [1] أو إضافتها عبر [2] و [3].
رمز التطبيق هو التالي:
using System.Windows.Forms;
using System;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void buttonAjouter_Click(object sender, System.EventArgs e) {
// إضافة محتوى textBoxLigne إلى محتوى textBoxLignes
textBoxLignes.Text += textBoxLigne.Text+Environment.NewLine;
textBoxLigne.Text = "";
}
private void Form1_Shown(object sender, EventArgs e) {
// وضع التركيز على حقل الإدخال
textBoxLigne.Focus();
}
}
}
- السطر 18: عند عرض النموذج (ربما Shown)، يتم وضع التركيز على حقل الإدخال textBoxLigne
- السطر 10: يدير النقر على الزر [Ajouter]
- السطر 12: يتم إضافة نص حقل الإدخال textBoxLigne إلى نص حقل الإدخال textBoxLignes متبوعًا بفاصل أسطر.
- السطر 13: يتم مسح حقل الإدخال textBoxLigne
7.2.3. قوائم منسدلة ComboBox
نقوم بإنشاء النموذج التالي:
![]() |
رقم | النوع | الاسم | الدور |
1 | ComboBox | comboNombres | يحتوي على سلاسل أحرف DropDownStyle=DropDownList |
مكون ComboBox عبارة عن قائمة منسدلة مصحوبة بمنطقة إدخال: يمكن للمستخدم إما اختيار عنصر من (2) أو كتابة نص في (1). هناك ثلاثة أنواع من ComboBox يتم تحديدها بواسطة الخاصية DropDownStyle:
قائمة غير منسدلة مع منطقة تحرير | |
قائمة منسدلة مع منطقة تحرير | |
قائمة منسدلة بدون منطقة تحرير |
بشكل افتراضي، نوع ComboBox هو DropDown.
تحتوي الفئة ComboBox على منشئ واحد:
ينشئ قائمة منسدلة فارغة |
عناصر ComboBox متوفرة في الخاصية Items:
وهي خاصية مفهرسة، حيث يشير Items[i] إلى العنصر i في القائمة المنسدلة. وهي خاصية للقراءة فقط.
لنفترض أن C عبارة عن قائمة منسدلة و C.Items هي قائمة عناصرها. لدينا الخصائص التالية:
عدد عناصر القائمة المنسدلة | |
العنصر i من القائمة المنسدلة | |
يضيف الكائن o كآخر عنصر في القائمة المنسدلة | |
يضيف مصفوفة من الكائنات في نهاية القائمة المنسدلة | |
يضيف الكائن o في الموضع i من القائمة المنسدلة | |
يزيل العنصر i من القائمة المنسدلة | |
إزالة الكائن o من القائمة المنسدلة | |
حذف جميع عناصر القائمة المنسدلة | |
يعرض الموضع i للعنصر o في القائمة المنسدلة | |
مؤشر العنصر المحدد | |
العنصر المحدد | |
النص المعروض للعنصر المحدد | |
النص المعروض للعنصر المحدد |
قد يكون من المستغرب أن يحتوي عنصر قائمة منسدلة على كائنات في حين أنه يعرض سلاسل أحرف بصريًا. إذا كان ComboBox يحتوي على كائن obj، فإنه يعرض السلسلة obj.ToString(). نتذكر أن كل كائن له طريقة ToString موروثة من الفئة object والتي تُرجع سلسلة أحرف "تمثل" الكائن.
العنصر Item المحدد في القائمة المنسدلة C هو C.SelectedItem أو C.Items[C.SelectedIndex] حيث C.SelectedIndex هو رقم للعنصر المحدد، ويبدأ هذا الرقم من الصفر للعنصر الأول. يمكن الحصول على النص المحدد بطرق مختلفة: C.SelectedItem.Text، C.Text
عند اختيار عنصر من القائمة المنسدلة، يحدث الحدث SelectedIndexChanged الذي يمكن استخدامه للتنبيه بتغيير الاختيار في القائمة المنسدلة. في التطبيق التالي، نستخدم هذا الحدث لعرض العنصر الذي تم تحديده في القائمة.
![]() |
فيما يلي كود التطبيق:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
private int previousSelectedIndex=0;
public Form1() {
InitializeComponent();
// ملء القائمة المنسدلة
comboBoxNombres.Items.AddRange(new string[] { "zéro", "un", "deux", "trois", "quatre" });
// تحديد العنصر رقم 0
comboBoxNombres.SelectedIndex = 0;
}
private void comboBoxNombres_SelectedIndexChanged(object sender, System.EventArgs e) {
int newSelectedIndex = comboBoxNombres.SelectedIndex;
if (newSelectedIndex != previousSelectedIndex) {
// تغير العنصر المحدد - يتم عرضه
MessageBox.Show(string.Format("Elément sélectionné : ({0},{1})", comboBoxNombres.Text, newSelectedIndex), "Combo", MessageBoxButtons.OK, MessageBoxIcon.Information);
// تسجيل الفهرس الجديد
previousSelectedIndex = newSelectedIndex;
}
}
}
}
- السطر 5: previousSelectedIndex يحفظ آخر مؤشر تم تحديده في القائمة المنسدلة
- السطر 10: ملء القائمة المنسدلة بجدول من سلاسل الأحرف
- السطر 12: يتم تحديد العنصر الأول
- السطر 15: الطريقة التي يتم تنفيذها في كل مرة يختار فيها المستخدم عنصرًا من القائمة المنسدلة. على عكس ما قد يوحي به اسم الحدث، فإنه يحدث حتى لو كان العنصر المحدد هو نفسه العنصر السابق.
- السطر 16: يتم تسجيل مؤشر العنصر المحدد
- السطر 17: إذا كان مختلفًا عن السابق
- السطر 19: يتم عرض رقم العنصر المحدد ونصه
- السطر 21: يتم تسجيل الفهرس الجديد
7.2.4. المكون ListBox
نقترح إنشاء الواجهة التالية:
![]() |
مكونات هذه النافذة هي التالية:
رقم | النوع | الاسم | الدور/الخصائص |
0 | نموذج | Form1 | نموذج FormBorderStyle=FixedSingle (إطار غير قابل لتغيير الحجم) |
1 | TextBox | textBoxSaisie | حقل الإدخال |
2 | زر | buttonAjouter | زر يسمح بإضافة محتوى حقل الإدخال [1] إلى القائمة [3] |
3 | ListBox | listBox1 | القائمة 1 SelectionMode=MultiExtended : |
4 | ListBox | listBox2 | القائمة 2 SelectionMode=MultiSimple: |
5 | زر | button1vers2 | ينقل العناصر المحددة من القائمة 1 إلى القائمة 2 |
6 | زر | button2vers1 | يقوم بالعكس |
7 | زر | buttonEffacer1 | يفرغ القائمة 1 |
8 | زر | buttonEffacer2 | إفراغ القائمة 2 |
تتميز مكونات ListBox بطريقة لاختيار عناصرها يتم تحديدها بواسطة خاصيتها SelectionMode:
يمكن تحديد عنصر واحد فقط | |
يمكن التحديد المتعدد: الضغط باستمرار على المفتاح SHIFT والنقر على عنصر ما يوسع نطاق تحديد العنصر المحدد سابقًا ليشمل العنصر الحالي. | |
يمكن التحديد المتعدد: يتم تحديد/إلغاء تحديد عنصر بنقرة بالماوس أو بالضغط على مفتاح المسافة. |
- يكتب المستخدم نصًا في الحقل 1. ويضيفه إلى القائمة 1 باستخدام الزر Ajouter (2). ثم يُفرغ حقل الإدخال (1) ويمكن للمستخدم إضافة عنصر جديد.
- يمكنه نقل العناصر من قائمة إلى أخرى عن طريق تحديد العنصر المراد نقله في إحدى القوائم واختيار زر النقل المناسب 5 أو 6. يتم إضافة العنصر المنقول إلى نهاية قائمة الوجهة وإزالته من قائمة المصدر.
- يمكنه النقر مرتين على عنصر في القائمة 1. عندئذٍ يتم نقل هذا العنصر إلى مربع الإدخال لتعديله وإزالته من القائمة 1.
يتم تشغيل الأزرار أو إيقاف تشغيلها وفقًا للقواعد التالية:
- لا يتم تشغيل الزر Ajouter إلا إذا كان هناك نص غير فارغ في حقل الإدخال
- لا يضيء الزر [5] الخاص بالنقل من القائمة 1 إلى القائمة 2 إلا إذا كان هناك عنصر محدد في القائمة 1
- لا يضيء زر [6] لنقل القائمة 2 إلى القائمة 1 إلا إذا كان هناك عنصر محدد في القائمة 2
- لا تضيء أزرار [7] و [8] الخاصة بمسح القائمتين 1 و 2 إلا إذا كانت القائمة المراد مسحها تحتوي على عناصر.
في الحالات السابقة، يجب أن تكون جميع الأزرار مطفأة عند بدء تشغيل التطبيق. لذا، يجب ضبط خاصية Enabled للأزرار على false. يمكن القيام بذلك عند التصميم، مما سيؤدي إلى إنشاء الكود المقابل في الطريقة InitializeComponent، أو القيام بذلك بأنفسنا في المنشئ كما يلي:
public Form1() {
InitializeComponent();
// --- عمليات تهيئة إضافية ---
// يتم تعطيل عدد معين من الأزرار
buttonAjouter.Enabled = false;
button1vers2.Enabled = false;
button2vers1.Enabled = false;
buttonEffacer1.Enabled = false;
buttonEffacer2.Enabled = false;
}
يتم التحكم في حالة الزر Ajouter من خلال محتوى حقل الإدخال. ويتيح لنا الحدث TextChanged تتبع التغييرات في هذا المحتوى:
private void textBoxSaisie_TextChanged(object sender, System.EventArgs e) {
// تغير محتوى textBoxSaisie
// لا يضيء زر "إضافة" إلا إذا كان الحقل غير فارغ
buttonAjouter.Enabled = textBoxSaisie.Text.Trim() != "";
}
يعتمد حالة أزرار النقل على ما إذا كان قد تم تحديد عنصر ما أم لا في القائمة التي تتحكم فيها:
private void listBox1_SelectedIndexChanged(object sender, System.EventArgs e) {
// تم تحديد عنصر
// يتم تفعيل زر النقل من 1 إلى 2
button1vers2.Enabled = true;
}
private void listBox2_SelectedIndexChanged(object sender, System.EventArgs e) {
// تم تحديد عنصر
// يتم تشغيل زر النقل من 2 إلى 1
button2vers1.Enabled = true;
}
الرمز المرتبط بالنقر على الزر Ajouter هو التالي:
private void buttonAjouter_Click(object sender, System.EventArgs e) {
// إضافة عنصر جديد إلى القائمة 1
listBox1.Items.Add(textBoxSaisie.Text.Trim());
// مسح الإدخال
textBoxSaisie.Text = "";
// القائمة 1 ليست فارغة
buttonEffacer1.Enabled = true;
// إعادة التركيز إلى مربع الإدخال
textBoxSaisie.Focus();
}
تجدر الإشارة إلى الطريقة Focus التي تسمح بوضع "التركيز" على عنصر تحكم في النموذج. الرمز المرتبط بالنقر على الأزرار Effacer:
private void buttonEffacer1_Click(object sender, System.EventArgs e) {
// مسح القائمة 1
listBox1.Items.Clear();
// زر مسح
buttonEffacer1.Enabled = false;
}
private void buttonEffacer2_Click(object sender, System.EventArgs e) {
// مسح القائمة 2
listBox2.Items.Clear();
// زر "مسح"
buttonEffacer2.Enabled = false;
}
رمز نقل العناصر المحددة من قائمة إلى أخرى:
private void button1vers2_Click(object sender, System.EventArgs e) {
// نقل العنصر المحدد في القائمة 1 إلى القائمة 2
transfert(listBox1, button1vers2, buttonEffacer1, listBox2, button2vers1, buttonEffacer2);
}
private void button2vers1_Click(object sender, System.EventArgs e) {
// نقل العنصر المحدد من القائمة 2 إلى القائمة 1
transfert(listBox2, button2vers1, buttonEffacer2, listBox1, button1vers2, buttonEffacer1);
}
تقوم الطريقتان المذكورتان أعلاه بتفويض نقل العناصر المحددة من قائمة إلى أخرى إلى طريقة خاصة واحدة تسمى transfert:
// نقل
private void transfert(ListBox l1, Button button1vers2, Button buttonEffacer1, ListBox l2, Button button2vers1, Button buttonEffacer2) {
// نقل العناصر المحددة من القائمة l1 إلى القائمة l2
for (int i = l1.SelectedIndices.Count - 1; i >= 0; i--) {
// فهرس العنصر المحدد
int index = l1.SelectedIndices[i];
// إضافة إلى l2
l2.Items.Add(l1.Items[index]);
// حذف في l1
l1.Items.RemoveAt(index);
}
// أزرار "مسح"
buttonEffacer2.Enabled = l2.Items.Count != 0;
buttonEffacer1.Enabled = l1.Items.Count != 0;
// أزرار النقل
button1vers2.Enabled = false;
}
- السطر ب: تتلقى طريقة "transfert" ستة معلمات:
- مرجع إلى القائمة التي تحتوي على العناصر المحددة والتي تسمى هنا l1. عند تشغيل التطبيق، تكون l1 إما listBox1 أو listBox2. نرى أمثلة على الاستدعاء، في السطرين 3 و8 من إجراءات النقل buttonXversY_Click.
- مرجع إلى زر النقل المرتبط بالقائمة l1. على سبيل المثال، إذا كانت l1 هي listBox2، فسيكون button2vers1 (انظر الاستدعاء في السطر 8)
- مرجع على زر مسح القائمة l1. على سبيل المثال، إذا كانت l1 هي listBox1، فسيكون المرجع هو buttonEffacer1 (انظر الاستدعاء في السطر 3)
- المراجع الثلاثة الأخرى مماثلة ولكنها تشير إلى القائمة l2.
- السطر د: تمثل المجموعة [ListBox].SelectedIndices مؤشرات العناصر المحددة في المكون [ListBox]. وهي مجموعة:
- [ListBox].SelectedIndices.Count هو عدد العناصر في هذه المجموعة
- [ListBox].SelectedIndices[i] هو العنصر رقم i في هذه المجموعة
نقوم بتصفح المجموعة في الاتجاه المعاكس: نبدأ من نهاية المجموعة وننتهي عند بدايتها. وسنشرح السبب.
- السطر f: مؤشر لعنصر محدد من القائمة l1
- السطر h: تمت إضافة هذا العنصر إلى القائمة l2
- السطر j: ويتم حذفه من القائمة l1. ولأنه تم حذفه، فإنه لم يعد محددًا. سيتم إعادة حساب المجموعة l1.SelectedIndices في السطر d. وستفقد العنصر الذي تم حذفه للتو. وستتغير أرقام جميع العناصر التي تلي هذا العنصر من n إلى n-1.
- إذا كانت حلقة السطر (d) تصاعدية وكانت قد عالجت للتو العنصر رقم 0، فستعالج بعد ذلك العنصر رقم 1. لكن العنصر الذي كان يحمل الرقم 1 قبل حذف العنصر رقم 0، سيحمل بعد ذلك الرقم 0. وبالتالي سيتم تجاهله من قبل الحلقة.
- إذا كانت حلقة السطر (d) تنازلية وكانت قد عالجت للتو العنصر رقم n، فستعالج بعد ذلك العنصر رقم n-1. بعد حذف العنصر رقم n، لا يتغير رقم العنصر رقم n-1. لذلك يتم معالجته في الدورة التالية من الحلقة.
- السطور m-n: يعتمد حالة الأزرار [Effacer] على وجود أو عدم وجود عناصر في القوائم المرتبطة
- السطر p: لم تعد القائمة l2 تحتوي على عناصر محددة: يتم إيقاف تشغيل زر النقل الخاص بها.
7.2.5. مربعات الاختيار CheckBox، أزرار الاختيار ButtonRadio
نقترح كتابة التطبيق التالي:
![]() |
مكونات النافذة هي التالية:
رقم | النوع | الاسم | الدور |
1 | GroupBox انظر [6] | groupBox1 | حاوية مكونات. يمكن وضع مكونات أخرى فيها. Text=أزرار الاختيار |
2 | RadioButton | radioButton1 radioButton2 radioButton3 | 3 أزرار اختيار - radioButton1 له الخاصية Checked=True والخاصية Text=1 - radioButton2 له الخاصية Text=2 - radioButton3 له الخاصية Text=3 أزرار الاختيار الموجودة في نفس الحاوية، هنا GroupBox، متنافية فيما بينها: يتم تمييز واحد منها فقط. |
3 | GroupBox | groupBox2 | |
4 | CheckBox | checkBox1 checkBox2 checkBox3 | 3 خانات اختيار. chechBox1 له الخاصية Checked=True والخاصية Text=A - chechBox2 له الخاصية Text=B - chechBox3 له الخاصية Text=C |
5 | ListBox | listBoxValeurs | قائمة تعرض قيم أزرار الاختيار ومربعات الاختيار فور حدوث أي تغيير. |
6 | يوضح مكان الحاوية GroupBox |
الحدث الذي يهمنا بالنسبة لهذه العناصر الستة هو الحدث CheckChanged الذي يشير إلى تغير حالة مربع الاختيار أو زر الاختيار. ويتم تمثيل هذه الحالة في كلتا الحالتين بالخاصية المنطقية Checked التي تعني، عند قيمتها "صحيح"، أن عنصر التحكم محدد. لن نستخدم هنا سوى طريقة واحدة لمعالجة الأحداث الستة CheckChanged، وهي الطريقة affiche. لضمان معالجة الأحداث الستة CheckChanged بواسطة نفس الطريقة affiche، يمكننا القيام بما يلي:
نختار المكون radioButton1 ونضغط بزر الفأرة الأيمن عليه للوصول إلى خصائصه:
![]() |
في علامة التبويب événements [1]، نربط الطريقة affiche [2] بالحدث CheckChanged. وهذا يعني أننا نريد أن يتم معالجة النقر على الخيار A1 بواسطة طريقة تسمى affiche. يقوم Visual Studio تلقائيًا بإنشاء الطريقة affiche في نافذة الكود:
private void affiche(object sender, EventArgs e) {
}
الطريقة affiche هي طريقة من النوع EventHandler.
بالنسبة للمكونات الخمسة الأخرى، يتم اتباع نفس الإجراء. لنختر على سبيل المثال الخيار CheckBox1 وأحداثه [3]. أمام الحدث Click، توجد قائمة منسدلة [4] تحتوي على الطرق الموجودة التي يمكنها معالجة هذا الحدث. هنا لا يوجد سوى الطريقة affiche.. نختارها. نكرر هذه العملية لجميع المكونات الأخرى.
في الطريقة InitializeComponent، تم إنشاء الكود. تم تعريف الطريقة affiche كمدير للأحداث الستة CheckedChanged بالطريقة التالية:
this.radioButton1.CheckedChanged += new System.EventHandler(this.affiche);
this.radioButton2.CheckedChanged += new System.EventHandler(this.affiche);
this.radioButton3.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox1.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox2.CheckedChanged += new System.EventHandler(this.affiche);
this.checkBox3.CheckedChanged += new System.EventHandler(this.affiche);
تم استكمال الطريقة affiche على النحو التالي:
private void affiche(object sender, System.EventArgs e) {
// يعرض حالة زر الاختيار أو مربع الاختيار
// هل هذا مربع اختيار؟
if (sender is CheckBox) {
CheckBox chk = (CheckBox)sender;
listBoxvaleurs.Items.Add(chk.Name + "=" + chk.Checked);
}
// هل هو زر اختيار؟
if (sender is RadioButton) {
RadioButton rdb = (RadioButton)sender;
listBoxvaleurs.Items.Add(rdb.Name + "=" + rdb.Checked);
}
}
الصيغة
if (sender is CheckBox) {
التحقق من أن الكائن sender هو من النوع CheckBox. وهذا يسمح لنا بعد ذلك بإجراء تحويل إلى النوع الدقيق sender. تقوم الطريقة affiche بكتابة اسم المكون الذي تسبب في الحدث وقيمة خاصيته Checked في القائمة listBoxValeurs. عند تنفيذ [7]، نرى أن النقر على زر الاختيار يؤدي إلى حدوث حدثين CheckChanged: أحدهما على الزر القديم المحدد الذي يتحول إلى "غير محدد" والآخر على الزر الجديد الذي يتحول إلى "محدد".
7.2.6. المتغيرات ScrollBar
هناك عدة أنواع من المتغيرات: المتغير الأفقي (HscrollBar)، المغير الرأسي (VscrollBar)، المُزيد (NumericUpDown). |
لنقم بتنفيذ التطبيق التالي:
![]() |
رقم | النوع | الاسم | الدور |
1 | hScrollBar | hScrollBar1 | محول أفقي |
2 | hScrollBar | hScrollBar2 | محول أفقي يتبع تغيرات المحول 1 |
3 | العلامة | labelValeurHS1 | يعرض قيمة المتغير الأفقي |
4 | NumericUpDown | numericUpDown2 | يسمح بتحديد قيمة المتغير 2 |
يتيح المنظم ScrollBar للمستخدم اختيار قيمة من نطاق من القيم الصحيحة التي يرمز لها "شريط" المنظم الذي يتحرك عليه المؤشر. تتوفر قيمة المنظم في خاصية Value.
- بالنسبة لمزلاج أفقي، يمثل الطرف الأيسر القيمة الدنيا للنطاق، والطرف الأيمن القيمة القصوى، والمؤشر القيمة الحالية المختارة. بالنسبة لمزلاج عمودي، يمثل الطرف العلوي القيمة الدنيا، والطرف السفلي القيمة القصوى. يتم تمثيل هذه القيم بواسطة الخصائص Minimum و Maximum وتبلغ قيمتها الافتراضية 0 و 100.
- يؤدي النقر على طرفي المنزلق إلى تغيير القيمة بمقدار زيادة (إيجابية أو سلبية) وفقًا للطرف الذي تم النقر عليه المسمى SmallChange والذي تبلغ قيمته الافتراضية 1.
- يؤدي النقر على جانبي المؤشر إلى تغيير القيمة بمقدار زيادة (إيجابية أو سلبية) وفقًا للطرف الذي تم النقر عليه المسمى LargeChange والذي تبلغ قيمته الافتراضية 10.
- عند النقر على الطرف العلوي لمزلاج عمودي، تنخفض قيمته. قد يفاجئ هذا المستخدم العادي الذي يتوقع عادةً أن ترتفع القيمة. يتم حل هذه المشكلة بإعطاء قيمة سالبة للخصائص SmallChange و LargeChange
- هذه الخصائص الخمس (Value، Minimum، Maximum، SmallChange، LargeChange) قابلة للقراءة والكتابة.
- الحدث الرئيسي للمحرك هو الذي يشير إلى تغيير في القيمة: حدث Scroll.
يوجد مكون NumericUpDown بالقرب من المحرك: وهو أيضًا يحتوي على الخصائص Minimum و Maximum و Value، بقيم افتراضية 0 و 100 و 0. ولكن هنا، يتم عرض الخاصية Value في مربع إدخال يمثل جزءًا لا يتجزأ من عنصر التحكم. يمكن للمستخدم تعديل هذه القيمة بنفسه ما لم يتم تعيين الخاصية ReadOnly لعنصر التحكم على "صحيح". يتم تحديد قيمة الزيادة بواسطة الخاصية Increment، والقيمة الافتراضية هي 1. الحدث الرئيسي للمكون NumericUpDown هو الذي يشير إلى تغيير في القيمة: الحدث ValueChanged
رمز التطبيق هو التالي:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
// يتم تحديد خصائص المحول 1
hScrollBar1.Value = 7;
hScrollBar1.Minimum = 1;
hScrollBar1.Maximum = 130;
hScrollBar1.LargeChange = 11;
hScrollBar1.SmallChange = 1;
// نمنح المحول 2 نفس خصائص المحول 1
hScrollBar2.Value = hScrollBar1.Value;
hScrollBar2.Minimum = hScrollBar1.Minimum;
hScrollBar2.Maximum = hScrollBar1.Maximum;
hScrollBar2.LargeChange = hScrollBar1.LargeChange;
hScrollBar2.SmallChange = hScrollBar1.SmallChange;
// نفس الشيء بالنسبة للمُزيد
numericUpDown2.Value = hScrollBar1.Value;
numericUpDown2.Minimum = hScrollBar1.Minimum;
numericUpDown2.Maximum = hScrollBar1.Maximum;
numericUpDown2.Increment = hScrollBar1.SmallChange;
// نمنح التسمية قيمة المحول 1
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e) {
// تغيير قيمة المحول 1
// يتم نقل قيمته إلى المحرك 2 وإلى التسمية
hScrollBar2.Value = hScrollBar1.Value;
labelValeurHS1.Text = hScrollBar1.Value.ToString();
}
private void numericUpDown2_ValueChanged(object sender, System.EventArgs e) {
// تغيرت قيمة عداد التزايد
// يتم تحديد قيمة المحرك 2
hScrollBar2.Value = (int)numericUpDown2.Value;
}
}
}
7.3. أحداث الماوس
عند الرسم في حاوية، من المهم معرفة موضع الماوس، على سبيل المثال، لعرض نقطة عند النقر. تؤدي تحركات الماوس إلى حدوث أحداث في الحاوية التي يتحرك فيها.
![]() |
- [1]: الأحداث التي تحدث عند تحريك الماوس على النموذج أو على عنصر تحكم
- [2]: الأحداث التي تحدث عند السحب والإفلات (Drag'nDrop)
دخلت الماوس للتو في نطاق عنصر التحكم | |
الماوس قد غادر للتو نطاق عنصر التحكم | |
الماوس يتحرك داخل منطقة التحكم | |
الضغط على الزر الأيسر للماوس | |
تحرير الزر الأيسر للماوس | |
يقوم المستخدم بإفلات عنصر على عنصر التحكم | |
يقوم المستخدم بالدخول إلى نطاق عنصر التحكم عن طريق سحب كائن | |
يخرج المستخدم من نطاق عنصر التحكم عن طريق سحب كائن | |
يمر المستخدم فوق منطقة التحكم عن طريق سحب كائن |
فيما يلي تطبيق يتيح فهم أفضل للأوقات التي تحدث فيها أحداث الماوس المختلفة:
![]() |
رقم | النوع | الاسم | الدور |
1 | العلامة | lblPositionSouris | لعرض موضع الماوس في النموذج 1 أو القائمة 2 أو الزر 3 |
2 | ListBox | listBoxEvts | لعرض أحداث الماوس بخلاف MouseMove |
3 | زر | buttonEffacer | لمسح محتوى 2 |
لتتبع تحركات الماوس على عناصر التحكم الثلاثة، نكتب معالجًا واحدًا فقط، وهو المعالج affiche:
![]() |
رمز الإجراء affiche هو التالي:
private void affiche(object sender, MouseEventArgs e) {
// حركة الماوس - يتم عرض إحداثياته (X، Y)
labelPositionSouris.Text = "(" + e.X + "," + e.Y + ")";
}
في كل مرة تدخل فيها الماوس إلى نطاق عنصر تحكم، يتغير نظام إحداثياتها. نقطة أصلها (0,0) هي الزاوية العلوية اليسرى لعنصر التحكم الذي توجد عليه. وهكذا عند التنفيذ، عندما نمرر الماوس من النموذج إلى الزر، نرى بوضوح تغير الإحداثيات. من أجل رؤية هذه التغييرات في نطاق الماوس بشكل أفضل، يمكن استخدام خاصية Cursor [1] لعناصر التحكم:
![]() |
تسمح هذه الخاصية بتحديد شكل مؤشر الماوس عندما يدخل في نطاق عنصر التحكم. وهكذا، في مثالنا، قمنا بتعيين المؤشر على Default للنموذج نفسه [2]، وHand للقائمة 2 [3]، وCross للزر 3 [4].
بالإضافة إلى ذلك، لاكتشاف دخول وخروج الماوس من القائمة 2، نقوم بمعالجة الأحداث MouseEnter و MouseLeave من هذه القائمة نفسها:
private void listBoxEvts_MouseEnter(object sender, System.EventArgs e) {
// يتم الإبلاغ عن الحدث
listBoxEvts.Items.Insert(0, string.Format("MouseEnter à {0:hh:mm:ss}",DateTime.Now));
}
private void listBoxEvts_MouseLeave(object sender, EventArgs e) {
// يتم الإبلاغ عن الحدث
listBoxEvts.Items.Insert(0, string.Format("MouseLeave à {0:hh:mm:ss}", DateTime.Now));
}
لمعالجة النقرات على النموذج، نقوم بمعالجة الأحداث MouseDown و MouseUp:
private void listBoxEvts_MouseDown(object sender, MouseEventArgs e) {
// يتم الإبلاغ عن الحدث
listBoxEvts.Items.Insert(0, string.Format("MouseDown à {0:hh:mm:ss}", DateTime.Now));
}
private void listBoxEvts_MouseUp(object sender, MouseEventArgs e) {
// يتم الإبلاغ عن الحدث
listBoxEvts.Items.Insert(0, string.Format("MouseUp à {0:hh:mm:ss}", DateTime.Now));
}
- السطران 3 و 8: يتم وضع الرسائل في المرتبة الأولى في ListBox بحيث تكون الأحداث الأحدث هي الأولى في القائمة.
![]() |
أخيرًا، رمز معالج النقر على الزر Effacer:
private void buttonEffacer_Click(object sender, EventArgs e) {
listBoxEvts.Items.Clear();
}
7.4. إنشاء نافذة مع قائمة
لنرى الآن كيفية إنشاء نافذة مع قائمة. سنقوم بإنشاء النافذة التالية:
![]() |
لإنشاء قائمة، نختار المكون "MenuStrip" في شريط "Menus & Tollbars":
![]() |
- [1]: اختيار المكون [MenuStrip]
- [2]: نحصل عندئذٍ على قائمة يتم تثبيتها على النموذج مع مربعات فارغة بعنوان "Type Here". يكفي إدخال الخيارات المختلفة للقائمة فيها.
- [3]: تمت كتابة العنوان "خيارات أ". ننتقل إلى العنوان [4].
- [5]: تم إدخال عناوين خيارات A. ننتقل إلى العنوان [6]
![]() |
- [6]: الخيارات الأولى من النوع B
- [7]: تحت B1، نضع فاصل. هذا الفاصل متاح في قائمة منسدلة مرتبطة بالنص "Type Here"
- [8]: لإنشاء قائمة فرعية، استخدم السهم [8] واكتب القائمة الفرعية في [9]
يبقى تسمية المكونات المختلفة للنموذج:
![]() |
رقم | النوع | الاسم (الأسماء) | الدور |
1 | العلامة | labelStatut | لعرض نص خيار القائمة الذي تم النقر عليه |
2 | toolStripMenuItem | toolStripMenuItemOptionsA toolStripMenuItemA1 toolStripMenuItemA2 toolStripMenuItemA3 | خيارات القائمة تحت الخيار الرئيسي "خيارات أ" |
3 | toolStripMenuItem | toolStripMenuItemOptionsB toolStripMenuItemB1 toolStripMenuItemB2 toolStripMenuItemB3 | خيارات القائمة تحت الخيار الرئيسي "خيارات ب" |
4 | toolStripMenuItem | toolStripMenuItemB31 toolStripMenuItemB32 | خيارات القائمة تحت الخيار الرئيسي "B3" |
خيارات القائمة هي عناصر تحكم مثل المكونات المرئية الأخرى ولها خصائص وأحداث. على سبيل المثال، خصائص خيار القائمة A1 هي كما يلي:
![]() |
يتم استخدام خاصيتين في مثالنا:
اسم عنصر التحكم في القائمة | |
نص خيار القائمة |
في بنية القائمة، نختار الخيار A1 ونضغط بزر الفأرة الأيمن للوصول إلى خصائص عنصر التحكم:
![]() |
في علامة التبويب événements [1]، نربط الطريقة affiche [2] بالحدث Click. وهذا يعني أننا نريد أن يتم معالجة النقر على الخيار A1 بواسطة طريقة تسمى affiche. يقوم Visual Studio تلقائيًا بإنشاء الطريقة affiche في نافذة الكود:
private void affiche(object sender, EventArgs e) {
}
في هذه الطريقة، سنكتفي بعرض الخاصية Text لخيار القائمة الذي تم النقر عليه في التسمية labelStatut:
private void affiche(object sender, EventArgs e) {
// يعرض في TextBox اسم القائمة الفرعية المختارة
labelStatut.Text = ((ToolStripMenuItem)sender).Text;
}
مصدر الحدث sender هو من النوع object. خيارات القائمة هي من النوع ToolStripMenuItem، لذا يتعين إجراء تحويل من object إلى ToolStripMenuItem.
بالنسبة لجميع خيارات القائمة، نحدد معالج النقر بالطريقة affiche [3,4].
دعونا نقوم بتشغيل التطبيق واختيار عنصر من القائمة:
![]() | ![]() |
7.5. المكونات غير المرئية
ننتقل الآن إلى عدد من المكونات غير المرئية: يتم استخدامها أثناء التصميم ولكنها لا تظهر أثناء التشغيل.
7.5.1. مربعات الحوار OpenFileDialog و SaveFileDialog
سنقوم بإنشاء التطبيق التالي:
![]() |
العناصر التالية:
رقم | النوع | الاسم | الدور |
1 | TextBox | TextBoxLignes | نص مكتوب من قبل المستخدم أو تم تحميله من ملف MultiLine=True، ScrollBars=Both، AccepReturn=True، AcceptTab=True |
2 | زر | buttonSauvegarder | يسمح بحفظ نص [1] في ملف نصي |
3 | زر | buttonCharger | يسمح بتحميل محتوى ملف نصي في [1] |
4 | زر | buttonEffacer | يُزيل محتوى [1] |
5 | SaveFileDialog | saveFileDialog1 | مكون يسمح باختيار اسم وموقع ملف النسخ الاحتياطي لـ [1]. يتم أخذ هذا المكون من شريط الأدوات [7] ووضعه ببساطة على النموذج. يتم تسجيله عندئذٍ ولكنه لا يشغل أي مساحة على النموذج. إنه مكون غير مرئي. |
6 | OpenFileDialog | openFileDialog1 | مكون يسمح باختيار الملف المراد تحميله في [1]. |
الرمز المرتبط بالزر Effacer بسيط:
private void buttonEffacer_Click(object sender, EventArgs e) {
// يتم وضع السلسلة الفارغة في TexBox
textBoxLignes.Text = "";
}
سنستخدم الخصائص والأساليب التالية للفئة SaveFileDialog:
الحقل | النوع | الدور |
Propriété | أنواع الملفات المتوفرة في القائمة المنسدلة لأنواع الملفات في مربع الحوار | |
Propriété | رقم نوع الملف المقترح افتراضيًا في القائمة أعلاه. يبدأ من 0. | |
Propriété | المجلد المقدم مبدئيًا لحفظ الملف | |
Propriété | اسم ملف النسخ الاحتياطي المحدد من قبل المستخدم | |
Méthode | طريقة تعرض مربع حوار النسخ الاحتياطي. تُرجع نتيجة من النوع DialogResult. |
تعرض الطريقة ShowDialog مربع حوار مشابه لما يلي:
![]() |
قائمة منسدلة مبنية على أساس الخاصية Filter. يتم تحديد نوع الملف المقترح افتراضيًا بواسطة FilterIndex | |
المجلد الحالي، الذي يتم تحديده بواسطة InitialDirectory إذا تم ملء هذه الخاصية | |
اسم الملف الذي تم اختياره أو كتابته مباشرةً من قبل المستخدم. سيكون متاحًا في الخاصية FileName | |
أزرار "حفظ"/"إلغاء". إذا تم استخدام الزر Enregistrer، فإن الدالة ShowDialog تُرجع النتيجة DialogResult.OK |
يمكن كتابة إجراء الحفظ على النحو التالي:
private void buttonSauvegarder_Click(object sender, System.EventArgs e) {
// يتم حفظ مربع الإدخال في ملف نصي
// يتم تهيئة مربع الحوار savefileDialog1
saveFileDialog1.InitialDirectory = Application.ExecutablePath;
saveFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
saveFileDialog1.FilterIndex = 0;
// يتم عرض مربع الحوار واسترداد نتيجته
if (saveFileDialog1.ShowDialog() == DialogResult.OK) {
// يتم استرداد اسم الملف
string nomFichier = saveFileDialog1.FileName;
StreamWriter fichier = null;
try {
// يتم فتح الملف للكتابة
fichier = new StreamWriter(nomFichier);
// نكتب النص فيه
fichier.Write(textBoxLignes.Text);
} catch (Exception ex) {
// مشكلة
MessageBox.Show("Problème à l'écriture du fichier (" +
ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
} finally {
// يتم إغلاق الملف
if (fichier != null) {
fichier.Dispose();
}
}
}
}
- السطر 4: يتم تعيين المجلد الأولي (InitialDirectory) إلى المجلد (Application.ExecutablePath) الذي يحتوي على الملف القابل للتنفيذ للتطبيق.
- السطر 5: يتم تحديد أنواع الملفات المراد عرضها. تجدر الإشارة إلى صيغة المرشحات: filtre1|filtre2|..|filtren مع filteri= نص|نموذج ملف. هنا سيكون للمستخدم الخيار بين الملفين *.txt و *.*.
- السطر 6: يتم تحديد نوع الملف الذي سيتم عرضه أولاً للمستخدم. هنا يشير الرقم 0 إلى الملفات *.txt.
- السطر 8: يتم عرض مربع الحوار واسترداد نتيجته. أثناء عرض مربع الحوار، لا يمكن للمستخدم الوصول إلى النموذج الرئيسي (مربع الحوار المسمى "نموذجي"). يحدد المستخدم اسم الملف المراد حفظه ويغادر مربع الحوار إما عن طريق الزر Enregistrer، أو عن طريق الزر Annuler,، أو عن طريق إغلاق مربع الحوار. تكون نتيجة الطريقة ShowDialog هي DialogResult.OK فقط إذا استخدم المستخدم الزر Enregistrer لمغادرة مربع الحوار.
- وبذلك، يصبح اسم الملف المراد إنشاؤه موجودًا الآن في الخاصية FileName للكائن saveFileDialog1. نعود بعد ذلك إلى إنشاء ملف نصي بشكل تقليدي. نكتب فيه محتوى TextBox: textBoxLignes.Text مع معالجة الاستثناءات التي قد تحدث.
تشبه الفئة OpenFileDialog إلى حد كبير الفئة SaveFileDialog. سنستخدم نفس الأساليب والخصائص التي استخدمناها سابقًا. تعرض الطريقة ShowDialog مربع حوار مشابه لما يلي:
![]() |
قائمة منسدلة مبنية على أساس الخاصية Filter. يتم تحديد نوع الملف المقترح افتراضيًا بواسطة FilterIndex | |
المجلد الحالي، الذي يتم تحديده بواسطة InitialDirectory إذا تم ملء هذه الخاصية | |
اسم الملف الذي تم اختياره أو كتابته مباشرةً من قبل المستخدم. سيكون متاحًا في الخاصية FileName | |
أزرار «فتح»/«إلغاء». إذا تم استخدام الزر Ouvrir، فإن الدالة ShowDialog تُنتج النتيجة DialogResult.OK |
يمكن كتابة إجراء تحميل الملف النصي على النحو التالي:
private void buttonCharger_Click(object sender, EventArgs e) {
// نقوم بتحميل ملف نصي في مربع الإدخال
// يتم تكوين مربع الحوار openfileDialog1
openFileDialog1.InitialDirectory = Application.ExecutablePath;
openFileDialog1.Filter = "Fichiers texte (*.txt)|*.txt|Tous les fichiers (*.*)|*.*";
openFileDialog1.FilterIndex = 0;
// يتم عرض مربع الحوار واسترداد نتيجته
if (openFileDialog1.ShowDialog() == DialogResult.OK) {
// يتم استرداد اسم الملف
string nomFichier = openFileDialog1.FileName;
StreamReader fichier = null;
try {
// يتم فتح الملف للقراءة
fichier = new StreamReader(nomFichier);
// يتم قراءة الملف بالكامل ووضعه في TextBox
textBoxLignes.Text = fichier.ReadToEnd();
} catch (Exception ex) {
// مشكلة
MessageBox.Show("Problème à la lecture du fichier (" +
ex.Message + ")", "Erreur", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
} finally {
// يتم إغلاق الملف
if (fichier != null) {
fichier.Dispose();
}
}//أخيرًا
}//إذا
}
- السطر 4: يتم تعيين المجلد الأولي (InitialDirectory) إلى المجلد (Application.ExecutablePath) الذي يحتوي على الملف القابل للتنفيذ للتطبيق.
- السطر 5: يتم تحديد أنواع الملفات المراد عرضها. تجدر الإشارة إلى صيغة المرشحات: filtre1|filtre2|..|filtren مع filteri= نص|نموذج الملف. هنا سيكون للمستخدم الخيار بين الملفين *.txt و *.*.
- السطر 6: يتم تحديد نوع الملف الذي سيتم عرضه أولاً للمستخدم. هنا يشير الرقم 0 إلى الملفات *.txt.
- السطر 8: يتم عرض مربع الحوار واسترداد نتيجته. أثناء عرض مربع الحوار، لا يمكن للمستخدم الوصول إلى النموذج الرئيسي (مربع الحوار المسمى "نموذجي"). يحدد المستخدم اسم الملف المراد حفظه ويغادر مربع الحوار إما عن طريق الزر Ouvrir، أو عن طريق الزر Annuler,، أو عن طريق إغلاق مربع الحوار. تكون نتيجة الطريقة ShowDialog هي DialogResult.OK فقط إذا استخدم المستخدم الزر Enregistrer لمغادرة مربع الحوار.
- وبذلك، يصبح اسم الملف المراد إنشاؤه موجودًا الآن في الخاصية FileName للكائن openFileDialog1. ثم نعود إلى القراءة التقليدية لملف نصي. لاحظ، في السطر 16، الطريقة التي تسمح بقراءة الملف بالكامل.
7.5.2. مربعات الحوار FontColor و ColorDialog
نواصل المثال السابق بإضافة زرين جديدين وعنصرين تحكم غير مرئيين جديدين:
![]() |
6
7
رقم | النوع | الاسم | الدور |
1 | زر | buttonCouleur | لتحديد لون أحرف TextBox |
2 | زر | buttonPolice | لتحديد خط الحروف في TextBox |
3 | ColorDialog | colorDialog1 | المكون الذي يسمح باختيار لون - مأخوذ من مجموعة الأدوات [5]. |
4 | FontDialog | colorDialog1 | المكون الذي يسمح باختيار خط - مأخوذ من مجموعة الأدوات [5]. |
تحتوي الفئتان FontDialog و ColorDialog على طريقة ShowDialog مشابهة لطريقة ShowDialog الموجودة في الفئتين OpenFileDialog و SaveFileDialog.
![]() |
تسمح الطريقة ShowDialog للفئة ColorDialog باختيار لون [1]. تسمح طريقة الفئة FontDialog باختيار خط [2]:
- [1]: إذا غادر المستخدم مربع الحوار باستخدام الزر OK، فإن نتيجة الطريقة ShowDialog هي DialogResult.OK واللون المختار موجود في الخاصية Color للكائن ColorDialog المستخدم.
- [2]: إذا غادر المستخدم مربع الحوار باستخدام الزر OK، فإن نتيجة الطريقة ShowDialog هي DialogResult.OK وتكون الخط المختار موجودًا في الخاصية Font للكائن FontDialog المستخدم.
لدينا الآن العناصر اللازمة لمعالجة النقرات على الأزرار Couleur و Police:
private void buttonCouleur_Click(object sender, EventArgs e) {// اختيار لون النص
if (colorDialog1.ShowDialog() == DialogResult.OK) {
// تغيير خاصية Forecolor لـ TextBox
textBoxLignes.ForeColor = colorDialog1.Color;
}//if
}
private void buttonPolice_Click(object sender, EventArgs e) {
// اختيار خط
if (fontDialog1.ShowDialog() == DialogResult.OK) {
// تغيير خاصية Font في TextBox
textBoxLignes.Font = fontDialog1.Font;
}
- السطر [4]: تحدد الخاصية [ForeColor] لمكون TextBox لون نوع [Color] لأحرف TextBox. وهنا، هذا اللون هو الذي اختاره المستخدم في مربع الحوار من النوع [ColorDialog].
- السطر [12]: تشير الخاصية [Font] لمكون TextBox إلى خط الأحرف من النوع [Font] لأحرف TextBox. وهنا، هذه الخطوط هي تلك التي اختارها المستخدم في مربع الحوار من النوع [FontDialog].
7.5.3. المؤقت
نقترح هنا كتابة التطبيق التالي:
![]() |
رقم | النوع | الاسم | الدور |
1 | العلامة | labelChrono | يعرض ساعة توقيت |
2 | زر | buttonArretMarche | زر إيقاف/تشغيل ساعة التوقيت |
3 | المؤقت | timer1 | مكون يصدر هنا حدثًا كل ثانية |
في [4]، نرى الكرونومتر قيد التشغيل، وفي [5] نرى الكرونومتر متوقفًا.
لتغيير محتوى التسمية LabelChrono كل ثانية، نحتاج إلى مكون يولد حدثًا كل ثانية، وهو حدث يمكننا اعتراضه لتحديث عرض المؤقت. هذا المكون هو Timer [1] المتوفر في مجموعة أدوات Components [2]:
![]() |
ستكون خصائص مكون Timer المستخدمة هنا كما يلي:
عدد المللي ثانية التي يتم بعدها إصدار حدث Tick. | |
الحدث الذي يحدث في نهاية Interval ميلي ثانية | |
يجعل المؤقت نشطًا (true) أو غير نشط (false) |
في مثالنا، يُسمى المؤقت timer1 ويتم تعيين timer1.Interval على 1000 مللي ثانية (1 ثانية). وبالتالي، سيحدث الحدث Tick كل ثانية. يتم معالجة النقر على زر الإيقاف/التشغيل بواسطة الإجراء buttonArretMarche_Click التالي:
using System;
using System.Windows.Forms;
namespace Chap5 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
// متغير المثيل
private DateTime début = DateTime.Now;
...
private void buttonArretMarche_Click(object sender, EventArgs e) {
// إيقاف أم تشغيل؟
if (buttonArretMarche.Text == "Marche") {
// تدوين وقت البدء
début = DateTime.Now;
// نقوم بعرضها
labelChrono.Text = "00:00:00";
// تشغيل المؤقت
timer1.Enabled = true;
// يتم تغيير نص الزر
buttonArretMarche.Text = "Arrêt";
// النهاية
return;
}//
if (buttonArretMarche.Text == "Arrêt") {
// إيقاف المؤقت
timer1.Enabled = false;
// تغيير نص الزر
buttonArretMarche.Text = "Marche";
// النهاية
return;
}
}
}
}
- السطر 13: الإجراء الذي يعالج النقر على زر التشغيل/الإيقاف.
- السطر 15: نص زر "إيقاف/تشغيل" هو إما "إيقاف" أو "تشغيل". لذلك، يتعين إجراء اختبار على هذا النص لمعرفة الإجراء المطلوب.
- السطر 17: في حالة "تشغيل"، يتم تسجيل وقت البدء في متغير début وهو متغير عام (السطر 11) لكائن النموذج
- السطر 19: تهيئة محتوى التسمية LabelChrono
- السطر 21: يتم تشغيل المؤقت (Enabled=true)
- السطر 23: يتغير نص الزر إلى "إيقاف".
- السطر 27: في حالة "إيقاف"
- السطر 29: يتم إيقاف المؤقت (Enabled=false)
- السطر 31: يتم تغيير تسمية الزر إلى "تشغيل".
يبقى لنا معالجة الحدث Tick على الكائن timer1، وهو حدث يحدث كل ثانية:
private void timer1_Tick(object sender, EventArgs e) {
// مرت ثانية واحدة
DateTime maintenant = DateTime.Now;
TimeSpan durée = maintenant - début;
// تحديث المؤقت
labelChrono.Text = durée.Hours.ToString("d2") + ":" + durée.Minutes.ToString("d2") + ":" + durée.Seconds.ToString("d2");
}
- السطر 3: نسجل الوقت الحالي
- السطر 4: يتم حساب الوقت المنقضي منذ بدء تشغيل المؤقت. ونحصل على كائن من النوع TimeSpan يمثل مدة زمنية.
- السطر 6: يجب عرض هذا في الكرونومتر بالشكل hh:mm:ss. وللقيام بذلك، نستخدم الخصائص Hours و Minutes و Seconds الخاصة بالكائن TimeSPan والتي تمثل على التوالي الساعات والدقائق والثواني للمدة التي نعرضها بتنسيق ToString("d2") للحصول على عرض مكون من رقمين.
7.6. تطبيق مثال - الإصدار 6
نعود إلى تطبيق المثال IMPOTS. تمت دراسة الإصدار الأخير في الفقرة 6.4. كان هذا هو التطبيق ثلاثي الطبقات التالي:
![]() |
- تم تغليف الطبقتين [metier] و [dao] في DLL
- كانت الطبقة [ui] عبارة عن طبقة [console]
- تم توفير إنشاء مثيلات الطبقات ودمجها في التطبيق بواسطة Spring.
في هذا الإصدار الجديد، سيتم توفير الطبقة [ui] بواسطة واجهة المستخدم الرسومية التالية:
![]() |
7.6.1. حل Visual Studio
يتكون حل Visual Studio من العناصر التالية:
![]() |
- [1]: يتكون المشروع من العناصر التالية:
- [Program.cs]: الفئة التي تطلق التطبيق
- [Form1.cs]: فئة النموذج الأول
- [Form2]: فئة النموذج الثاني
- [lib] المفصل في [2]: تم وضع جميع DLL اللازمة للمشروع فيه:
- [ImpotsV5-dao.dll]: DLL للطبقة [dao] التي تم إنشاؤها في الفقرة 6.4.3؛
- [ImpotsV5-metier.dll]: DLL للطبقة [dao] التي تم إنشاؤها في الفقرة 6.4.4؛
- [Spring.Core.dll]، [Common.Logging.dll]، [antlr.runtime.dll]: DLL من Spring المستخدمة بالفعل في الإصدار السابق (انظر الفقرة 6.4.6).
- [references] المفصل في [3]: مراجع المشروع. تمت إضافة مرجع لكل ملف من ملفات DLL الموجودة في المجلد [lib]
- [App.config]: ملف تكوين المشروع. وهو مطابق لملف الإصدار السابق الموصوف في الفقرة 6.4.6؛
- [DataImpot.txt]: ملف شرائح الضريبة المُعد لنسخه تلقائيًا إلى مجلد تشغيل المشروع [4]
النموذج [Form1] هو نموذج إدخال معلمات حساب الضريبة [A] الذي سبق عرضه أعلاه. يستخدم النموذج [Form2] [B] لعرض رسالة خطأ:
![]() |
7.6.2. الفئة [Program.cs]
تقوم الفئة [Program.cs] بتشغيل التطبيق. ورمزها هو كما يلي:
using System;
using System.Windows.Forms;
using Spring.Context;
using Spring.Context.Support;
using Metier;
using System.Text;
namespace Chap5 {
static class Program {
/// <summary>
/// نقطة الدخول الرئيسية للتطبيق.
/// </summary>
[STAThread]
static void Main() {
// رمز تم إنشاؤه بواسطة Vs
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// --------------- كود المطور
// مثيلات الطبقات [metier] و [dao]
IApplicationContext ctx = null;
Exception ex = null;
IImpotMetier metier = null;
try {
// سياق Spring
ctx = ContextRegistry.GetContext();
// يُطلب مرجع على الطبقة [metier]
metier = (IImpotMetier)ctx.GetObject("metier");
} catch (Exception e1) {
// تخزين الاستثناء
ex = e1;
}
// نموذج للعرض
Form form = null;
// هل حدث استثناء؟
if (ex != null) {
// نعم - يتم إنشاء رسالة الخطأ المراد عرضها
StringBuilder msgErreur = new StringBuilder(String.Format("Chaîne des exceptions : {0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
Exception e = ex;
while (e != null) {
msgErreur.Append(String.Format("{0}: {1}{2}", e.GetType().FullName, e.Message, Environment.NewLine));
msgErreur.Append(String.Format("{0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
e = e.InnerException;
}
// إنشاء نافذة خطأ يتم تمرير رسالة الخطأ المراد عرضها إليها
Form2 form2 = new Form2();
form2.MsgErreur = msgErreur.ToString();
// ستكون هذه هي النافذة التي سيتم عرضها
form = form2;
} else {
// كل شيء سار على ما يرام
// إنشاء واجهة رسومية [Form1] يتم تمرير المرجع إليها على الطبقة [metier]
Form1 form1 = new Form1();
form1.Metier = metier;
// ستكون هذه هي النافذة التي سيتم عرضها
form = form1;
}
// عرض النافذة
Application.Run(form);
}
}
}
تم استكمال الكود الذي تم إنشاؤه بواسطة Visual Studio بدءًا من السطر 19. يستخدم التطبيق الملف [App.config] التالي:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<sectionGroup name="spring">
<section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
<section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
</sectionGroup>
</configSections>
<spring>
<context>
<resource uri="config://spring/objects" />
</context>
<objects xmlns="http://www.springframework.net">
<object name="dao" type="Dao.FileImpot, ImpotsV5-dao">
<constructor-arg index="0" value="DataImpot.txt"/>
</object>
<object name="metier" type="Metier.ImpotMetier, ImpotsV5-metier">
<constructor-arg index="0" ref="dao"/>
</object>
</objects>
</spring>
</configuration>
- الأسطر 24-32: استخدام الملف [App.config] السابق لإنشاء مثيلات الطبقات [metier] و [dao]
- السطر 26: استخدام الملف [App.config]
- السطر 28: استرداد مرجع على الطبقة [metier]
- السطر 31: تخزين الاستثناء المحتمل
- السطر 34: سيحدد المرجع form النموذج المراد عرضه (form1 أو form2)
- الأسطر 36-50: في حالة وجود استثناء، يتم التحضير لعرض نموذج من النوع [Form2]
- الأسطر 38-44: يتم إنشاء رسالة الخطأ المراد عرضها. وتتكون من تسلسل رسائل الأخطاء الخاصة بالاستثناءات المختلفة الموجودة في سلسلة الاستثناءات.
- السطر 46: يتم إنشاء نموذج من النوع [Form2].
- السطر 47: سنرى لاحقًا أن هذا النموذج له خاصية عامة MsgErreur وهي رسالة الخطأ المراد عرضها:
public string MsgErreur { private get; set; }
نقوم بتعبئة هذه الخاصية.
- السطر 49: يتم تهيئة المرجع form الذي يشير إلى النافذة المراد عرضها. يجب ملاحظة تعدد الأشكال في العمل. form2 ليس من النوع [Form] بل من النوع [Form2]، وهو نوع مشتق من [Form].
- الأسطر 50-57: لم تحدث أي استثناءات. يتم التحضير لعرض نموذج من النوع [Form1].
- السطر 53: يتم إنشاء نموذج من النوع [Form1].
- السطر 54: سنرى لاحقًا أن هذا النموذج يحتوي على خاصية عامة Metier، وهي مرجع إلى الطبقة [metier]:
public IImpotMetier Metier { private get; set; }
نقوم بتعبئة هذه الخاصية.
- السطر 56: يتم تهيئة المرجع form الذي يشير إلى النافذة المراد عرضها. نلاحظ مرة أخرى تعدد الأشكال في العمل. form1 ليس من النوع [Form] بل من النوع [Form1]، وهو نوع مشتق من [Form].
- السطر 59: يتم عرض النافذة المشار إليها بواسطة form.
7.6.3. النموذج [Form1]
في الوضع [conception]، يكون النموذج [Form1] كما يلي:
![]() |
العناصر هي كما يلي
رقم | النوع | الاسم | الدور |
0 | GroupBox | groupBox1 | Text=هل أنت متزوج/متزوجة |
1 | RadioButton | radioButtonOui | ضع علامة إذا كنت متزوجًا |
2 | RadioButton | radioButtonNon | ضع علامة إذا لم تكن متزوجًا Checked=True |
3 | NumericUpDown | numericUpDownEnfants | عدد أطفال المكلف الحد الأدنى=0، الحد الأقصى=20، الزيادة=1 |
4 | TextBox | textSalaire | الراتب السنوي للمكلف بالضريبة باليورو |
5 | العلامة | labelImpot | مبلغ الضريبة المستحقة BorderStyle=Fixed3D |
6 | زر | buttonCalculer | يبدأ حساب الضريبة |
7 | زر | buttonEffacer | إعادة النموذج إلى الحالة التي كان عليها عند التحميل |
8 | زر | buttonQuitter | للخروج من التطبيق |
قواعد تشغيل النموذج
- يظل الزر Calculer معطلاً طالما لم يتم إدخال أي قيمة في حقل الراتب
- إذا تبين عند بدء الحساب أن الراتب غير صحيح، يتم الإبلاغ عن الخطأ [9]
رمز الفئة هو التالي:
using System.Windows.Forms;
using Metier;
using System;
namespace Chap5 {
public partial class Form1 : Form {
// الطبقة [métier]
public IImpotMetier Metier { private get; set; }
public Form1() {
InitializeComponent();
}
private void buttonCalculer_Click(object sender, System.EventArgs e) {
// هل الراتب صحيح
int salaire;
bool ok=int.TryParse(textSalaire.Text.Trim(), out salaire);
if (! ok || salaire < 0) {
// رسالة خطأ
MessageBox.Show("Salaire incorrect", "Erreur de saisie", MessageBoxButtons.OK, MessageBoxIcon.Error);
// العودة إلى الحقل الخاطئ
textSalaire.Focus();
// تحديد نص حقل الإدخال
textSalaire.SelectAll();
// العودة إلى واجهة الإدخال
return;
}
// الراتب صحيح - يمكن حساب الضريبة
labelImpot.Text = Metier.CalculerImpot(radioButtonOui.Checked, (int)numericUpDownEnfants.Value, salaire).ToString();
}
private void buttonQuitter_Click(object sender, System.EventArgs e) {
Environment.Exit(0);
}
private void buttonEffacer_Click(object sender, System.EventArgs e) {
// مسح النموذج
labelImpot.Text = "";
numericUpDownEnfants.Value = 0;
textSalaire.Text = "";
radioButtonNon.Checked = true;
}
private void textSalaire_TextChanged(object sender, EventArgs e) {
// حالة الزر [Calculer]
buttonCalculer.Enabled=textSalaire.Text.Trim()!="";
}
}
}
نكتفي بالتعليق على الأجزاء المهمة:
- السطر [8]: الخاصية العامة Metier التي تسمح لفئة التشغيل [Program.cs] بإدخال مرجع في [Form1] على الطبقة [metier].
- السطر [14]: إجراء حساب الضريبة
- الأسطر 15-27: التحقق من صحة الراتب (عدد صحيح >=0).
- السطر 29: حساب الضريبة باستخدام الطريقة [CalculerImpot] للطبقة [metier]. تجدر الإشارة إلى بساطة هذه العملية التي تم تحقيقها بفضل تغليف الطبقة [metier] في DLL.
7.6.4. النموذج [Form2]
في الوضع [conception]، يكون النموذج [Form2] كما يلي:
![]() |
العناصر التالية
رقم | النوع | الاسم | الدور |
1 | TextBox | textBoxErreur | Multiline=True، Scrollbars=Both |
رمز الفصل هو التالي:
using System.Windows.Forms;
namespace Chap5 {
public partial class Form2 : Form {
// رسالة خطأ
public string MsgErreur { private get; set; }
public Form2() {
InitializeComponent();
}
private void Form2_Load(object sender, System.EventArgs e) {
// عرض رسالة الخطأ
textBoxErreur.Text = MsgErreur;
// إلغاء تحديد كل النص
textBoxErreur.Select(0, 0);
}
}
}
- السطر 6: الخاصية العامة MsgErreur التي تسمح لفئة التشغيل [Program.cs] بإدراج رسالة الخطأ المراد عرضها في [Form2]. يتم عرض هذه الرسالة أثناء معالجة الحدث Load، الأسطر 12-16.
- السطر 14: يتم وضع رسالة الخطأ في TextBox
- السطر 16: يتم إزالة التحديد الذي تم في العملية السابقة. [TextBox].Select(البداية، الطول) يحدد (يبرز) longueur حرفًا بدءًا من الحرف رقم début. [TextBox].Select(0,0) تعني إلغاء تحديد أي نص.
7.6.5. الخلاصة
لنعد إلى بنية الطبقات الثلاث المستخدمة:
![]() |
سمحت لنا هذه البنية باستبدال التنفيذ الرسومي بالتنفيذ الخاص بوحدة التحكم للطبقة [ui] الموجودة، دون تغيير أي شيء في الطبقتين [metier] و [dao]. تمكنا من التركيز على الطبقة [ui] دون القلق بشأن التأثيرات المحتملة على الطبقات الأخرى. وهذا هو الميزة الرئيسية للبنى ثلاثية الطبقات. سنرى مثالاً آخر على ذلك لاحقاً، عندما يتم استبدال الطبقة [dao] التي تستخدم حالياً بيانات ملف نصي، بطبقة [dao] تستخدم بيانات قاعدة بيانات. وسنرى أن ذلك سيتم دون أي تأثير على الطبقتين [ui] و [metier].


































































