مقدمة Windows Forms بلغة C# - الجزء الأول
في نهاية هذا الدرس ستكون قادرا على بناء تطبيق "User Details" كامل باستخدام Windows Forms، يحتوي على controls بعناوين واضحة، event handlers، ترتيب tab منطقي، menu، وform ثانية تستقبل البيانات من الأولى.
الأهداف
في نهاية هذا الدرس ستكون قادرا على:
- شرح ما هو تطبيق Windows Forms، وما هو الـ Form والـ control بلغة classes وobjects في C#، وتحديد الحالات التي يكون فيها Windows Forms نوع المشروع المكتبي (desktop) الصحيح بالمقارنة مع WPF وUWP و.NET MAUI.
- إنشاء مشروع Windows Forms جديد في Visual Studio، وتحديد دور ملف كود الـ form، وملف بدء تشغيل البرنامج، والـ Form Designer، والـ Toolbox، ونافذة الـ Properties.
- بناء form بسيطة من نوع "Hello World" بوضع Label وتغيير خاصية Text الخاصة بها.
- وضع الـ controls الأساسية (Label، TextBox، GroupBox، ListBox، RadioButton، CheckBox، Button) على form وتحديد خاصيتي Name وText لكل منها، مع نسخ زوج label وtext box بدلا من بنائه مرتين.
- تعبئة ListBox بعناصر عن طريق String Collection Editor، وTreeView بـ nodes عن طريق TreeNode Editor، وPictureBox بصورة.
- شرح ما هو الـ event وما هو الـ event handler، إنشاء handlers بعمل double click على control في الـ designer، وتصليح الـ designer عندما يتم حذف handler بطريقة خاطئة.
- كتابة handler لـ SelectedIndexChanged الخاصة بـ ListBox يعرض العنصر المحدد في MessageBox، وhandler لـ Click الخاصة بـ Button يقرأ قيم TextBox.
- شرح الـ focus وترتيب الـ tab، وتحديد خاصية TabIndex بحيث ينتقل مفتاح Tab عبر الـ form بترتيب منطقي.
- إضافة MenuStrip بمفاتيح اختصار (keyboard accelerators)، إغلاق الـ form من عنصر في المنيو، وفتح form ثانية تستقبل بيانات عبر constructor الخاص بها.
- بناء وتشغيل form "User Details" الكاملة، و(اختياري) التحكم في progress bar وstatus label الخاصين بـ StatusStrip من الكود.
المتطلبات السابقة
- Visual Studio مثبت على نظام Windows مع توفر قوالب مشاريع Windows Forms (فلتر Desktop في نافذة new project يعرض "Windows Forms App").
- معرفة أساسية بلغة C#: classes، objects، inheritance، constructors، methods، properties، متغيرات من نوع
string، وعملية concatenation للنصوص. Windows Forms مبني بالكامل على هذه الأفكار، فيجب أن تكون مألوفة قبل البدء. - الإلمام بإنشاء وتشغيل مشروع console في Visual Studio (زر Start، وSolution Explorer).
المفهوم 1: ما هو تطبيق Windows Forms
التعريف. تطبيق Windows Forms (WinForms) هو برنامج يعمل على سطح المكتب (desktop) ويعرض نافذة واحدة أو أكثر، تسمى forms. تحتوي الـ form عادة على مجموعة من الـ controls مثل labels، text boxes، list boxes وbuttons. يعتبر Windows Forms أحد أنواع المشاريع الأصلية في .NET، وهو مصمم كبيئة تطوير سريع للتطبيقات (rapid application development): تبني واجهة المستخدم بسحب controls على سطح تصميم (design surface) بدلا من كتابة كل سطر من كود الواجهة يدويا.
الغرض. يتيح لك Windows Forms الحصول على برنامج desktop يعمل بسرعة. مشروع جديد تماما يعطيك مباشرة نافذة بها title bar، أيقونة، أزرار minimize وmaximize وclose، وإمكانية تغيير الحجم، من دون كتابة أي كود. يناسب هذا proof of concept سريع، أداة صغيرة تكتبها لنفسك، وforms بسيطة لأغراض العمل، وكثير من تطبيقات الأعمال ما زالت تعمل عليه.
متى تستخدم Windows Forms. Windows Forms ليس نوع المشروع المكتبي الوحيد في .NET. WPF وUWP و.NET MAUI ظهرت بعده. الثلاثة تصف واجهاتها بلغة markup تسمى XAML، وهي أعقد في التعامل من سحب controls على designer، فهي ليست أدوات rapid application development بنفس الطريقة. مع ذلك يبقى Windows Forms أحد أكثر أنواع المشاريع شيوعا في بيئة الأعمال: جاء مع أول إصدار من .NET، منذ حوالي عشرين سنة، وكان يعمل بنفس طريقة designer السحب والإفراغ (drag-and-drop) الخاص بـ Visual Basic 6 الذي كانت الشركات تعرفه بالفعل، فبنوا تطبيقاتهم المكتبية به واستمروا في البناء عليه منذ ذلك الوقت. تم إيقاف دعم UWP منذ ذلك الحين. لتطبيق مكتبي جديد اليوم فإن WPF هو التوصية المعتادة؛ أما Windows Forms فهو الخيار الصحيح لـ proof of concept سريع، أداة أو utility صغيرة، وشركة من شخص واحد تحتاج أبسط تطبيق ممكن للحفاظ عليه. أكبر قيد فيه هو تغيير الحجم: الـ form تبقى بالحجم الذي صممتها عليه، فتكبير النافذة (maximize) يترك مساحة فاضية وتصغيرها يقطع الـ controls، دون إعادة ترتيب تلقائي ودون scroll bars تلقائية. ما تراه في الـ designer هو ما تحصل عليه وقت التشغيل (run time).
طريقة العمل. كل ما تراه على الـ form هو object بلغة C#. الـ form هي class ترث من class اسمها Form الموجودة في namespace اسمها System.Windows.Forms. الـ base class Form تعرف بالفعل كيف ترسم إطار النافذة، والـ title bar، والأزرار، فتكتسب class الخاصة بك كل هذا العمل عبر inheritance. الـ Form نفسها هي آخر حلقة في سلسلة من classes: Form ترث من ContainerControl، التي ترث من ScrollableControl، التي ترث من Control، التي ترث من Component. كل class في هذه السلسلة تضيف مئات الـ members، وتكتسبها form الخاصة بك جميعا. يمكنك تتبع هذه السلسلة بنفسك: ضع الـ cursor على اسم class في محرر الكود واضغط F12 (Go to Definition)؛ يفتح Visual Studio تعريف تلك class، وسطرها الأول يذكر اسم class ترث منها، فالضغط على F12 على هذا الاسم يأخذك خطوة أخرى للأعلى في السلسلة.
classDiagram
Component <|-- Control
Control <|-- ScrollableControl
ScrollableControl <|-- ContainerControl
ContainerControl <|-- Form
Form <|-- Form1
class Form1 {
Your form: controls you add, handlers you write
}
class Form {
Window frame, title bar, minimize, maximize, close, resizing
}كل control على الـ form (Button، TextBox، Label) هي أيضا class من System.Windows.Forms؛ عندما تسحب control إلى الـ form، يضيف الـ designer متغيرا خاصا (private variable) جديدا من هذا النوع إلى class الـ form وينشئ instance منها. تحديد قيمة في نافذة الـ Properties يحدد خاصية لهذا الـ object؛ وعمل double click على control ينشئ method تعالج event من events هذا الـ object.
مثال. شاشة login بسيطة هي تطبيق Windows Forms نموذجي: يوجد label لاسم المستخدم ("User name") وlabel لكلمة المرور ("Password")، يصف كل منهما text box بجانبه يحمل ما يكتبه المستخدم، ويحمل زر Login الكود الذي يتحقق من صحة بيانات الدخول عند النقر عليه. الـ labels تصف، والـ text boxes تجمع المدخلات، والـ buttons تشغل المعالجة (processing)؛ ستستخدم هذه الأدوار الثلاثة طوال هذا الدرس.
المفهوم 2: ملفات المشروع وتسلسل بدء التشغيل
التعريف. يحتوي مشروع Windows Forms جديد على ملفين من الكود ستعمل عليهما:
- ملف class الـ form، ويحمل الكود الذي تكتبه للـ form، مثل event handlers.
- ملف البرنامج الرئيسي، ويحتوي على كود بدء تشغيل التطبيق كله: method اسمها
Mainتعمل أولا وتحدد أي form سيتم فتحها.
الغرض. معرفة أي ملف يقوم بأي عمل توضح لك أين تبحث. كود الواجهة وevent handlers يذهبان في ملف class الـ form؛ نقطة دخول التطبيق واختيار الـ form التي تبدأ منها موجودان في ملف البرنامج.
طريقة العمل. عندما تضغط Start، تعمل Main الموجودة في ملف البرنامج، وتقوم بتهيئة إعدادات التطبيق، وتستدعي Application.Run مع instance جديد من Form1. هذه الـ form هي الـ main form: يبقى التطبيق موجودا طالما بقيت موجودة، وإغلاقها يغلق التطبيق. class الـ Form1 نفسها مقسمة إلى ملفين باستخدام الكلمة المفتاحية partial. النصف الأول هو ملف الكود الذي تعدله. النصف الثاني يولده الـ designer؛ ويحتوي على method اسمها InitializeComponent تنشئ كل control، وتحدد خصائصه، وتربط الـ events الخاصة به. يدمج الـ compiler النصفين في class واحدة. لا تقم بتعديل النصف المولد يدويا إلا في أحوال نادرة جدا: الـ form التي تراها في الـ designer ليست أكثر من هذا الكود مرسوما على الشاشة، فملف مولد معطوب يعني designer لا يستطيع الفتح. أحد هذه الحالات النادرة التي يكون فيها التعديل اليدوي هو الخيار الصحيح، وهي إزالة سطر ربط event قديم، موصوف في المفهوم 5 ومطبق في المهمة 3.
flowchart TD
A["Press Start in Visual Studio"] --> B["Program.cs: static void Main()"]
B --> C["ApplicationConfiguration.Initialize()"]
C --> D["Application.Run(new Form1())"]
D --> E["Form1 constructor runs"]
E --> F["InitializeComponent() in the generated designer file creates the controls and wires events"]
F --> G["Form1 window is shown"]
G --> H["User acts on controls: events fire and handlers in the form code run"]
H --> I["User closes the main form: application exits"]مثال. الجزء من ملف البرنامج المولد الذي يهمنا في هذا الدرس هو method اسمها Main:
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}سطر بسطر:
static void Main()هي نقطة الدخول: أول method تعمل عندما تضغط Start.ApplicationConfiguration.Initialize();تهيئ إعدادات التطبيق قبل عرض أي form. القالب (template) يولد هذا السطر ويعتمد عليه؛ اتركه دون تغيير.Application.Run(new Form1());تنشئ instance واحد منForm1وتعرضه كـ main form. تغييرForm1إلى class form أخرى يجعل تلك الـ form هي نقطة بداية التطبيق.
يضع القالب Main داخل class اسمها Program داخل namespace المشروع. اترك كل شيء آخر في هذا الملف كما تم توليده بالضبط.
ملف كود الـ form المولد يبدأ كالتالي:
namespace DemoApplication
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}namespace DemoApplicationتجمع كل كود المشروع تحت اسم المشروع.public partial class Form1 : Formتعرف class الـ form. الكلمةpartialتعني أن بقية class موجودة في ملف الـ designer المولد؛ و: Formتعني أنها ترث من الـ base classForm، التي توفر النافذة نفسها.public Form1()هي constructor، تعمل مرة واحدة عندما يتم تنفيذnew Form1()عند بدء التشغيل.InitializeComponent();تستدعي method المولدة التي تبني الـ controls. الكود الذي تضيفه بعد هذا السطر يعمل بعد وجود كل control، وهذا ما يجعله مكانا آمنا لتغيير خصائص control عند بدء التشغيل.
المفهوم 3: الـ Designer والـ Toolbox ونافذة الـ Properties
التعريف. الـ Form Designer هو السطح المرئي الذي تبني عليه الـ form. الـ Toolbox (على الجانب الأيسر من Visual Studio) يسرد كل control يمكن إضافتها إلى form، مقسمة إلى أقسام مثل Common Controls وContainers وMenus & Toolbars. نافذة الـ Properties تعرض خصائص control المحددة حاليا وتتيح لك تغيير قيمها.
الغرض. تحل هذه الثلاث نوافذ معا محل كود واجهة مكتوب يدويا: تسحب control من الـ Toolbox إلى الـ designer، ثم تصفها في نافذة الـ Properties.
طريقة العمل. لإضافة control، اخترها من الـ Toolbox واسحبها إلى الـ form. لفتح نافذة الـ Properties، انقر بزر الفأرة الأيمن على control واختر Properties، أو حدد control واضغط F4 (متوفرة أيضا من منيو View). كل control لها مجموعة من الخصائص التي تصفها. أهم ثلاث خصائص ستستخدمها هي:
Name: اسم متغير control object داخل class الـ form. تستخدم هذا الاسم في الكود (lstCity,txtUser).Text: النص المعروض على control (عنوان label أو button، محتوى text box، عنوان group box، عنوان title bar الخاص بـ form).Size: عرض وارتفاع control بالبكسل. وسع صفSizeفي نافذة الـ Properties لكتابةWidthوHeightمباشرة، أو اسحب المقبض الصغير في الزاوية اليمنى السفلية لـ control على سطح التصميم. الطريقتان تؤديان لنفس النتيجة: نافذة الـ Properties تعرض القيمة التي يرسمها الـ designer.
القيمة المكتوبة في نافذة الـ Properties هي القيمة الأولية لهذه الخاصية عند تشغيل التطبيق. يستطيع الكود قراءة أو تغيير نفس الخاصية وقت عمل التطبيق، مثلا label1.Text = "First name"; داخل الـ constructor أو txtUser.Text داخل handler لزر. قائمة منسدلة في أعلى نافذة الـ Properties تسرد كل control على الـ form باسمها، وهي طريقة سريعة لإيجاد controls لا تزال تحمل أسماء افتراضية مثل button1.
تحديد ونسخ الـ controls. انقر على control لتحديدها؛ اضغط مع الاستمرار على Ctrl وانقر على controls أخرى لإضافتها للتحديد. Ctrl+C ثم Ctrl+V ينسخان الـ controls المحددة ويلصقان نسخا جديدة على الـ form، وهذه أسرع طريقة لبناء الزوج الثاني من label وtext box في form: النسخ الملصوقة تحتفظ بـ Text الأصلي لكنها تحصل على أسماء افتراضية جديدة، فأعد تسميتها فورا. انقر على جزء فاضي من الـ form واسحب لرسم مستطيل تحديد حول عدة controls؛ يمكن بعد ذلك تحريكها معا كوحدة واحدة. عندما تشترك عدة controls محددة في خاصية واحدة، تعرض نافذة الـ Properties هذه الخاصية مرة واحدة، والقيمة المكتوبة هناك تطبق عليها جميعا. أثناء سحب control يعرض الـ designer خطوط دليلية للمحاذاة (alignment guide lines)؛ بالنسبة لـ text box موضوعة بجانب label، الخط الذي يمر بمستوى نص text box محاذيا لنص label يعني أن النص المكتوب سيتطابق مع نص label.
اتفاقيات التسمية. يوجد اتفاقيتان شائعتا الاستخدام لتسمية الـ controls، ويستخدم هذا الدرس الأولى منهما:
- أسماء ببداية تدل على نوع control (type-prefixed): بداية قصيرة تدل على نوع control متبوعة بالغرض منها، مثل
txtUser,lstCity,btnSubmit. كانت هذه الطريقة موصى بها في الإصدارات الأولى من .NET وهي ما يستخدمه الشرح أدناه. - أسماء وصفية بأسلوب camelCase: الغرض من control متبوعا بنوعها، مثل
userTextBox,cityListBox,submitButton. خاصيةNameهي اسم متغير خاص (private variable) في class الـ form، فهذا الأسلوب يسميها بالطريقة التي يسمى بها أي متغير آخر، ويقرأ بشكل طبيعي ("زر الإرسال"). إرشادات تسمية .NET الحالية تفضل هذا الأسلوب وتنصح ضد بدايات النوع لأنها تقرأ بشكل غير مريح.
كل الأسلوبين مقبول ما دمت تطبقه بشكل متناسق على الـ form كاملة، وتحدد الاسم قبل إنشاء أي event handler (المفهوم 5 يوضح لماذا يهم الترتيب).
عادات التجهيز. ثلاث عادات من مطورين ذوي خبرة في Windows Forms توفر عليك إعادة العمل بعد ذلك. يحتفظ هذا الدرس باسم class Form1 لكي يتطابق ملفك مع القائمة الكاملة في نهاية الدرس، لكن طبق هذه العادات في مشاريعك الخاصة:
- أعد تسمية
Form1قبل إضافة أي control. حدد ملف الـ form في Solution Explorer، اضغط F2، اكتب اسما ذا معنى مثلMainForm، واقبل الرسالة التي تعرض إعادة تسمية كل مرجع لهذه class. عنوان title bar للنافذة عند التشغيل يبقى "Form1" بعد ذلك، لأن العنوان يأتي من خاصيةTextللـ form، وليس من اسم class؛ غيرTextبشكل منفصل. - حدد خاصية
Fontللـ form قبل إضافة أي control. كل control تسحبها بعد ذلك تأخذ خط الـ form الحالي كقيمة افتراضية لها. إذا غيرت خط الـ form بعد وجود controls، يعيد الـ designer تحجيم وترتيب كل شيء، وأي control حددت خطها بشكل منفرد تحتفظ بقيمتها الخاصة بدلا من اتباع الـ form. حدد الخط أولا، أعد حجم الـ form إلى الحجم الذي تريده (خاصيةSizeأو مقبض الزاوية)، ثم أضف الـ controls. - أبعد منطق المعالجة (processing logic) عن ملف الـ form. يجب أن يحتوي كود الـ form فقط على ما يتعامل مع واجهة المستخدم: قراءة وكتابة خصائص control ومعالجة events. الحساب، والتحقق (validation)، والوصول للبيانات (data access) ينتمون إلى class library: مشروع إضافي في نفس الـ solution يحتوي فقط على classes بدون نافذة، يرجع له مشروع الـ form ويستدعيه. يمكن بعد ذلك استبدال الواجهة دون إعادة كتابة المنطق. لا يطبق هذا الدرس class libraries؛ العادة مذكورة هنا كإشارة لمشاريعك الخاصة.
مثال. سير عمل الـ designer الذي ستكرره لكل control في هذا الدرس:
flowchart LR
T["Toolbox: pick a control"] --> D["Drag it onto the form"]
D --> P["Properties window: set Name, then Text"]
P --> E["Double-click the control to create its default event handler"]
E --> C["Write code in the form's code file"]
C --> R["Run and test"]المفهوم 4: الـ controls الأساسية
التعريف. control هي class من System.Windows.Forms توضع على form كـ object. كل نوع من الـ controls له مهمة واحدة: الوصف (Label)، جمع النص (TextBox)، تقديم اختيار واحد من قائمة (ListBox، RadioButton)، تقديم أي عدد من الاختيارات (CheckBox)، تشغيل معالجة (Button)، تجميع controls أخرى (GroupBox)، عرض شجرة من العناصر (TreeView)، أو عرض صورة (PictureBox).
الغرض. تجمع form عدة أنواع مختلفة من البيانات من المستخدم، ولكل نوع control مصممة له. اختيار control الصحيحة يعني أن المستخدم لا يستطيع إدخال نوع قيمة خاطئ: radio button واحدة لا يمكن أن تحمل جنسين، وlist box لا يمكن أن تحمل مدينة غير موجودة في القائمة. يبني هذا الدرس form واحدة، "User Details"، من كل هذه الـ controls.
طريقة العمل. تضاف كل control بنفس الطريقة (المفهوم 3): اسحبها من الـ Toolbox، حدد Name إذا كان الكود سيشير إليها، ثم حدد Text. ما يختلف هو طريقة تعبئة كل control:
- GroupBox (قسم Containers في الـ Toolbox): تجمع controls متعلقة ببعضها ضمن قسم بعنوان. حدد
Textبعنوان القسم ("User Details"). - Label: تعرض نصا للمستخدم وترافق تقريبا دائما control أخرى لتوضيح ما تتوقعه. حدد
Text("Name", "Address"). - TextBox: تتيح للمستخدم كتابة نص. حدد
Nameباسم ذا معنى (txtUser,txtAddress). خاصيةTextالخاصة بها تحمل ما كتبه المستخدم. - ListBox: تعرض قائمة عناصر يختار المستخدم منها واحدا. حدد
Name(lstCity) وعبئ خاصيةItemsعن طريق String Collection Editor: انقر على صفItems، ثم انقر على الزر الصغير بثلاث نقاط (...) الذي يظهر في نهاية الصف، اكتب عنصرا واحدا في كل سطر داخل المحرر، واضغط OK. - RadioButton: تعرض اختيارات يمكن للمستخدم أن يختار منها واحدا فقط بالضبط. حدد
Text("Male", "Female") وName(rdMale,rdFemale). - CheckBox: تعرض اختيارات يمكن للمستخدم أن يختار منها أي عدد. حدد
Text("C#", "ASP.Net") وName(chkC,chkASP). - Button: ينقر المستخدم عليها لبدء معالجة الـ form. حدد
Text("Submit") وName(btnSubmit). - TreeView: تسرد عناصر في شجرة من nodes، بالطريقة التي يعرض بها Windows Explorer فولدرات داخل فولدرات. خاصية
Nodesالخاصة بها تفتح TreeNode Editor: انقر على صفNodes، انقر على زر ثلاث النقاط (...)، اضغط Add Root لإضافة node على المستوى الأعلى وحددTextالخاص بها، ثم مع تحديد هذه node اضغط Add Child لكل node تنتمي تحتها وحددTextكل واحدة منها. عند تشغيل البرنامج، يوسع المستخدم node الجذر ليرى أبناءها. - PictureBox: تعرض صورة على الـ form. خاصية
Imageالخاصة بها تفتح نافذة بها زر Import: انقر على صفImage، انقر على زر ثلاث النقاط (...)، اضغط Import، اختر ملف صورة من القرص في النافذة التي تفتح، واضغط OK.
مثال. form "User Details" النهائية لهذا الدرس مرسومة في بداية المهمة 2: تحمل group box بعنوان "User Details" اثنين من الـ labels مع text box بجانب كل منهما، وlist box بثلاث مدن، وزوج radio buttons لـ Male/Female، وزوج check boxes لـ C#/ASP.Net، ويوجد زر Submit أسفل group box. تضيف المهمة 5 tree view بـ node جذر وثلاثة أبناء وpicture box على يمين group box.
المفهوم 5: الـ Events وEvent Handlers
التعريف. الـ event هو شيء يحدث عند تنفيذ إجراء معين: النقر على زر، اختيار عنصر في list box، تحميل form. الـ event handler هي method تعمل استجابة لـ event واحدة.
الغرض. الـ events هي الطريقة التي تنفذ بها form العمل. زر بمفرده لا يفعل شيئا؛ الكود الموجود في handler الخاصة بـ Click هو ما يتحقق من صحة تسجيل الدخول، أو يحفظ البيانات، أو يعرض رسالة.
طريقة العمل. كل control لها event افتراضية، وهي الأكثر استخداما لهذا النوع من control: Click لـ Button، وSelectedIndexChanged لـ ListBox، وLoad للـ form نفسها. تحدث Click بنقرة فأرة، بلمسة إصبع على شاشة لمس، وبمفتاح Space أثناء وجود focus على الزر. تحدث SelectedIndexChanged، كما يقول اسمها، عندما يتغير index العنصر المحدد في القائمة: النقر على العنصر المحدد أصلا لا يغير الـ index، فلا تحدث الـ event مرة أخرى. تحدث Load عندما يتم تحميل الـ form؛ الاستخدامات الشائعة لها هي عرض splash screen، وضع الـ cursor في control الصحيحة، أو بدء تشغيل timer. عمل double click على control في الـ designer ينشئ handler لـ event الافتراضية الخاصة بها: يفتح Visual Studio ملف كود الـ form، ويضيف method فاضية اسمها <اسم control>_<اسم event>، ويربط الـ event بهذه method في ملف الـ designer المولد. الربط هو سطر واحد من الشكل control.Event += method، مثلا:
this.lstCity.SelectedIndexChanged += new System.EventHandler(this.lstCity_SelectedIndexChanged);+= تربط method الموجودة على اليمين بـ event الموجودة على اليسار، بحيث تعمل method lstCity_SelectedIndexChanged كل مرة تحدث فيها list box الـ event SelectedIndexChanged. System.EventHandler هو النوع (type) الذي يستخدمه Visual Studio لوصف method بتوقيع (object sender, EventArgs e) كما يظهر في الأمثلة أدناه. إذا كان السطر المولد في ملفك يبدو مختلفا قليلا، فإن += واسم الـ handler هما ما يهم. هذا سطر الربط هو ما تشير إليه رسالة خطأ الـ designer في القاعدة 2 أدناه. تحتوي نافذة الـ Properties أيضا على زر بشكل برق (lightning bolt) يسرد كل event خاصة بـ control المحددة، للحالات التي تحتاج فيها event غير الافتراضية: مثل MouseClick (نقرة فأرة فقط، ليس لمسة أو مفتاح Space)، MouseHover، VisibleChanged، وevents السحب والإفراغ (drag-and-drop). عمل double click على event في هذه القائمة ينشئ handler الخاصة بها بنفس الطريقة.
ينتج عن طريقة عمل هذا الربط قاعدتان عمليتان:
- سم control قبل عمل double click عليها. يتم توليد اسم الـ handler من اسم control في تلك اللحظة. إذا عملت double click على
button1وأعدت تسميتها إلىbtnSubmitبعد ذلك، يبقى اسم الـ handlerbutton1_Click، وتصبح form بها عدة أزرار صعبة القراءة. - أزل handler من الـ designer، وليس بحذف الـ method. إذا عملت double click بالخطأ (مثلا على الـ form نفسها، وهذا ينشئ
Form1_Load) ثم حذفت فقط الـ method من ملف كود الـ form، فإن سطر الربط في ملف الـ designer المولد يظل يشير إلى method لم تعد موجودة، وفي المرة التالية التي يحمل فيها الـ designer الملف يرفض الفتح ويعرض صفحة خطأ تقول "The designer cannot process unknown name 'Form1_Load' at line N. The code within the method 'InitializeComponent' is generated by the designer and should not be manually modified. Please remove any changes and try opening the designer again." اقرأ الرسالة: تذكر اسم الـ method الناقصة، ورابط Go to code الخاص بها ينقلك إلى سطر+=المسبب للمشكلة في الملف المولد. حذف هذا السطر الواحد وحفظ الملف هو أحد الحالات النادرة التي يكون فيها التعديل اليدوي للملف المولد هو الخيار الصحيح، وهي الحالة التي ستقابلها في هذا الدرس؛ يفتح الـ designer بعد ذلك من جديد. الأفضل هو تجنب الوقوع في هذا الوضع من الأساس: حدد control، افتح قائمة الـ events (زر البرق) في نافذة الـ Properties، حدد اسم الـ handler في خلية قيمة الـ event واحذفه. يزيل Visual Studio الربط عنك، والـ method غير المستخدمة الآن في ملف كود الـ form، إذا كانت لا تزال موجودة، يمكن حذفها بأمان. المهمة 3 تجعلك تنفذ الأمرين.
flowchart TD
A["Accidental double-click on the form creates Form1_Load"] --> B{"How do you remove it?"}
B -- "Wrong: delete only the method in the form code file" --> C["Wiring line in the generated designer file still names Form1_Load"]
C --> D["Designer refuses to open: cannot process unknown name 'Form1_Load'"]
D --> E["Click Go to code"]
E --> F["Delete the this.Load += ... line and save"]
F --> G["Designer opens again"]
B -- "Right: Properties window, events list" --> H["Select the handler name in the Load cell and delete it"]
H --> I["Visual Studio removes the wiring line"]
I --> J["Delete the unused method in the form code file if it is still there"]
J --> GsequenceDiagram
participant U as User
participant L as lstCity (ListBox object)
participant H as lstCity_SelectedIndexChanged (handler)
participant M as MessageBox
U->>L: clicks an item
L->>H: raises SelectedIndexChanged
H->>L: reads SelectedItem, calls GetItemText
H->>M: MessageBox.Show(text)
M->>U: pop-up shows the selected cityمثال 1: handler لـ ListBox. بعد عمل double click على lstCity، أكمل الـ method المولدة كالتالي:
private void lstCity_SelectedIndexChanged(object sender, EventArgs e)
{
string text = lstCity.GetItemText(lstCity.SelectedItem);
MessageBox.Show(text);
}private void lstCity_SelectedIndexChanged(object sender, EventArgs e)هي الـ handler التي ولدها Visual Studio.senderهو الـ object الذي أحدث الـ event (هنا list box).EventArgsهي class تحمل تفاصيل الـ event؛ في events هذا الدرس لا تحمل شيئا تحتاجه، فلا تتم قراءةeأبدا. لا تحتاج لتغيير اسم الـ method أو الـ parameters.string text = lstCity.GetItemText(lstCity.SelectedItem);تقرأ العنصر المحدد حاليا عن طريق خاصيةSelectedItemالخاصة بـlstCity. تعطيSelectedItemالعنصر كـobjectعام، وليس كـstring، فلا يمكن تخصيصه لمتغير من نوع string مباشرة؛ ترجعGetItemTextالنص الذي تعرضه list box لهذا العنصر، ويتم تخزين النتيجة في المتغيرtext.MessageBox.Show(text);تعرض نافذة صغيرة منبثقة (pop-up) تحتوي على النص.MessageBoxهي أبسط طريقة لعرض بيانات في تطبيق Windows Forms.
مثال 2: handler لـ Button. بعد عمل double click على btnSubmit:
private void btnSubmit_Click(object sender, EventArgs e)
{
string name = txtUser.Text;
string address = txtAddress.Text;
MessageBox.Show("Name: " + name + ", Address: " + address);
}private void btnSubmit_Click(object sender, EventArgs e)هي handler الـ Click المولدة للزر المسمىbtnSubmit.string name = txtUser.Text;تقرأ ما كتبه المستخدم في text box الخاصة بـ Name. قيمة text box هي خاصيةTextالخاصة بها.string address = txtAddress.Text;تفعل نفس الشيء لـ text box الخاصة بـ Address.MessageBox.Show("Name: " + name + ", Address: " + address);تجمع (concatenate) القيمتين في رسالة واحدة وتعرضها.
يمكن كتابة نفس الرسالة باستخدام string interpolation: MessageBox.Show($"Name: {txtUser.Text}, Address: {txtAddress.Text}");. علامة $ قبل النص تتيح لك وضع تعبيرات (expressions) داخل { }. خطأ شائع: {txtUser} بدلا من {txtUser.Text} تعمل compile وتشتغل، لكن الرسالة تعرض اسم نوع الـ object متبوعا بنصه، مثلا "System.Windows.Forms.TextBox, Text: Ahmed" بدلا من "Ahmed"، لأنك مررت الـ object كاملا بدلا من خاصية Text الخاصة به، واستخدمت method الـ ToString الخاصة بالـ object لتحويله إلى نص.
المفهوم 6: الـ Focus وترتيب الـ Tab
التعريف. في أي لحظة، توجد control واحدة بالضبط على الـ form النشطة تحمل الـ focus: وهي control التي تستقبل ما تكتبه. text box تحمل الـ focus تعرض cursor يومض؛ زر يحمل الـ focus يعرض حدا مميزا حوله. ترتيب الـ tab هو التسلسل الذي ينقل به مفتاح Tab الـ focus من control إلى التالية.
الغرض. المستخدم الذي لا يستطيع أو لا يريد استخدام الفأرة يعبئ الـ form بالـ keyboard فقط: يكتب، يضغط Tab، يكتب، يضغط Tab، ثم يضغط Space على الزر. هذا لا يعمل إلا إذا زار Tab الـ controls بالترتيب الذي وضعت به، وإلا إذا تم تخطي الـ controls التي لا يمكن تعبئتها.
طريقة العمل. خاصيتان في نافذة الـ Properties تتحكمان في ذلك:
TabIndex: رقم يحدد موضع control في ترتيب الـ tab. يحدد الـ designer هذه الأرقام بترتيب إضافة الـ controls، وهذا سبب أن يكون الترتيب خاطئا في الغالب بعد إعادة ترتيب form. الترتيب فقط هو المهم، وليست القيم نفسها: عند الضغط على Tab، تبحث الـ form عن control التي تستطيع أخذ الـ focus والتالية في الترتيب حسبTabIndex. لا يلزم أن تكون القيم متتالية، فـ 10، 20، 30 تعمل تماما مثل 1، 2، 3 وتترك مساحة لإضافة control بعد ذلك دون إعادة ترقيم الباقي.TabStop: تكونtrueعندما تستطيع control استقبال الـ focus عن طريق Tab. لا يمكن لـ Label أن تستقبل الـ focus مطلقا، فـTabIndexالخاصة بها ليس لها تأثير ظاهر ويتخطاها Tab.
مجموعة radio buttons هي اختيار واحد (المفهوم 4)، ويعاملها Tab كتوقف واحد: لا يزور كل زر في المجموعة بالتناوب. مفاتيح الأسهم تنقل الـ focus بين أزرار المجموعة. بعد آخر control في الترتيب، يعود Tab إلى الأولى. أثناء وجود الـ focus على زر، الضغط على Space ينقر عليه، فتظهر نفس رسالة message box التي تظهر بنقرة الفأرة.
مثال. form بها label واحدة، واثنين من TextBox، وزر. يبدأ المستخدم من أول text box:
flowchart LR
A["txtFirst (TabIndex 1)"] -- Tab --> B["txtLast (TabIndex 2)"]
B -- Tab --> C["btnSay (TabIndex 3)"]
C -- Tab --> A
C -- Space --> M["btnSay_Click runs"]لا تظهر label (TabIndex 0) في هذه الدورة أبدا لأنها لا تستطيع استقبال الـ focus.
المفهوم 7: الـ Forms المتعددة
التعريف. الـ form هي class، فيمكن أن يحتوي التطبيق على أي عدد يحتاجه من form classes ويمكنه إنشاء أي عدد من instances من كل واحدة. تضاف form ثانية إلى المشروع كـ class جديدة (Form2) لها designer خاص بها.
الغرض. التطبيقات الحقيقية تفتح نافذة تفاصيل، أو نافذة إعدادات، أو نافذة رسالة من النافذة الرئيسية. فهم أن كل نافذة هي object تنشئه بـ new هو ما يجعل ذلك ممكنا.
طريقة العمل. ثلاث حقائق تنتج من كون "الـ form هي class":
Form2 frm = new Form2(); frm.Show();تنشئ instance منForm2وتعرضه.Showهي method موروثة منForm. تنفيذ نفس السطرين مرة أخرى ينشئ نافذة ثانية مستقلة؛ الكتابة في instance واحد لا تغير الآخر، لأن كل instance له control objects خاصة به.- الـ form التي تمر إلى
Application.Runفي ملف البرنامج هي الـ main form وعمرها هو عمر التطبيق. إغلاق الـ main form يغلق التطبيق كله، حتى لو كانت forms أخرى لا تزال مفتوحة. إغلاق form ثانوية يغلق فقط هذه النافذة. - يمكن لـ constructor الخاص بـ form أن يستقبل parameters كأي constructor آخر. غير
public Form2()إلىpublic Form2(string message)واستخدمmessageداخل الـ constructor (مثلا لتحديدTextالخاصة بـ label). بعد ذلك، لا تعملnew Form2()compile: يجب على المستدعي تمرير قيمة،new Form2("Hello from Form1")، وهذه هي الطريقة التي تنتقل بها البيانات من form إلى form أخرى.
sequenceDiagram
participant F1 as Form1 (main form)
participant F2 as Form2 instance
F1->>F2: new Form2("Name: Ahmed, Address: Mansoura")
F2->>F2: constructor stores the message in lblMessage.Text
F1->>F2: frm.Show()
F2-->>F1: window appears next to Form1
F1->>F1: user closes Form1 with X
F1-->>F2: application exits, Form2 closes tooمثال. Form2 تعرض النص الذي تستقبله:
namespace DemoApplication
{
public partial class Form2 : Form
{
public Form2(string message)
{
InitializeComponent();
lblMessage.Text = message;
}
}
}public Form2(string message)تحل محل الـ constructor الفاضي المولد من القالب. على من يستدعيها الآن أن يمرر string.InitializeComponent();لا تزال تعمل أولا، فتكون label موجودة قبل أن يلمسها السطر التالي.lblMessage.Text = message;تنسخ الـ string المستقبل إلى الـ label المسماةlblMessageفيForm2، وهذه هي الطريقة التي يصبح بها النص مرئيا.
المفهوم 8: المنيوهات (Menus)، مفاتيح الاختصار، وStatus Strip
التعريف. الـ MenuStrip هو شريط المنيو (menu bar) الخاص بنافذة (File، Help). كل مدخل فيه هو menu item يتصرف كزر: له Text، وName، وevent اسمها Click. الـ StatusStrip هو الشريط الموجود في أسفل نافذة يعرض تقدم العمل (progress)، بالطريقة التي يعرض بها الشريط السفلي في Visual Studio أن عملية build قيد التشغيل.
الغرض. تقدم المنيوهات للمستخدم أوامر لا تستحق زرا على الـ form، ويمكن تشغيلها بالكامل من الـ keyboard، وهذا مهم لسهولة الاستخدام (accessibility) وللمستخدمين الذين يفضلون عدم مد يدهم للفأرة. الـ status strip يخبر المستخدم أن العمل قيد التقدم دون مقاطعته برسالة message box.
طريقة العمل. اسحب MenuStrip من قسم Menus & Toolbars في الـ Toolbox إلى الـ form. يظهر شريط منيو على الـ form، وتظهر أيقونة component الخاص بالشريط في المنطقة (tray) أسفل سطح التصميم، وهذا هو المكان الذي يمكن منه تحديد الشريط للتعديل. انقر على مدخل المنيو الفاضي على شريط المنيو واكتب نص المنيو؛ تظهر مدخلات فاضية أخرى على يمينه (لمزيد من المنيوهات) وأسفله (لعناصر المنيو الحالي).
- وضع
&قبل حرف فيTextالخاص بـ menu item يجعل هذا الحرف هو مفتاح الاختصار (accelerator): لا تظهر&نفسها، ويتم تحديد الحرف بخط تحته، والضغط على Alt متبوعا بالحرف ينشط العنصر.&Fileيفتح بـ Alt، F؛ وE&xitتحته يعمل بـ X. لا يمكن أن يتشارك عنصران في نفس المنيو بنفس الحرف، لكن submenu يمكنها إعادة استخدام حرف من الأب (parent) الخاص بها لأن السياق النشط في تلك اللحظة هو submenu فقط. - اتبع معيار Windows حيث يوجد معيار: File بـ Exit في الأسفل، وHelp بـ About. المستخدمون يعرفون بالفعل أن Alt، F، X تغلق برنامجا.
- عمل double click على menu item ينشئ handler الـ
Clickالخاصة بها بنفس طريقة الزر، فسم العنصر أولا (المفهوم 5، القاعدة 1). داخل الـ handler، تغلقthis.Close();instance الـ form الحالية؛ وعلى الـ main form ينهي هذا التطبيق. - يتم سحب StatusStrip إلى الـ form بنفس الطريقة، ويحمل عناصر مثل progress bar (
ToolStripProgressBar) وstatus label (ToolStripStatusLabel)، تضاف من خلال القائمة المنسدلة الصغيرة لإضافة عنصر (add-item) الموجودة على الشريط. يحدث الكود هذه العناصر كأي control أخرى، من خلال خاصيتيValueوText. تمتلك progress bar خاصيتيMinimumوMaximum، 0 و100 افتراضيا؛ تحديدValueخارج هذا المدى يوقف البرنامج بـ exception (مثال 2).
مثال 1: handlers للمنيو. handlers لعنصر Exit المسمى mnuExit وعنصر About المسمى mnuAbout:
private void mnuExit_Click(object sender, EventArgs e)
{
this.Close();
}
private void mnuAbout_Click(object sender, EventArgs e)
{
MessageBox.Show("User Details Form, Lab 1");
}private void mnuExit_Click(object sender, EventArgs e)هي الـ handler المولدة بعمل double click على عنصر Exit بعد تسميتهmnuExit.this.Close();تغلق الـ form التي تنتمي إليها الـ handler. لأنForm1هي الـ main form، يخرج التطبيق.private void mnuAbout_Click(object sender, EventArgs e)هي الـ handler لعنصر About.MessageBox.Show("User Details Form, Lab 1");تعرض نص About في نافذة منبثقة، أبسط About box ممكنة.
مثال 2: تحديث Status Strip. سطران يضافان إلى handler الـ Click الخاصة بزر، لـ progress bar مسماة prgStatus وstatus label مسماة lblStatus:
prgStatus.Value += 5;
lblStatus.Text = "Working...";prgStatus.Value += 5;تضيف 5 إلىValueالحالية لـ progress bar، فيكبر الجزء المملوء من الشريط بمقدار 5 من كل 100 مع كل نقرة. عندما تكونValueبالفعل 100، يحاول هذا السطر تحديد 105، وهذا أعلى منMaximum، فيتوقف البرنامج بـ exception تقول أن 105 ليست قيمة صحيحة لـValue.lblStatus.Text = "Working...";تستبدل نص status label، بالضبط كتحديدTextعلى أي label أخرى.
المهام
البرنامج الكامل لهذا الدرس بأكمله موجود في قسم "القائمة الكاملة للبرنامج" في النهاية. هو موضح برقم المهمة التي تضيف كل جزء، لكي تستطيع مقارنة ملفك في أي لحظة: يجب أن تكون فقط الأجزاء المحددة برقم المهمة الحالية أو أقل موجودة في ملفك.
المهمة 1: إنشاء المشروع، استكشافه، وعرض Hello World
- شغل Visual Studio واختر Create a new project (في الإصدارات الأقدم: File > New > Project).
- في نافذة المشروع، فلتر أو حدد أنواع مشاريع Windows / Desktop، ثم اختر Windows Forms App (في الإصدارات الأقدم تظهر باسم Windows Forms Application). إذا ظهر مدخلان، واحد لـ .NET وواحد لـ .NET Framework، اختر مدخل .NET. الـ .NET Framework هو المنصة الأقدم؛ ومدخل .NET هو الحالي، ويأتي بـ Form Designer أحدث تمت إعادة كتابته. خطوات هذا الدرس كتبت لمدخل .NET. بسبب إعادة كتابة الـ designer، حدوث خلل عرضي أمر طبيعي: إذا فشل الـ designer في رسم الـ form في لحظة ما، أغلق تبويب الـ designer وأعد فتحه بعمل double click على ملف الـ form.
- سم المشروع
DemoApplication، اختر موقعا على القرص، وانقر Create (أو OK). إذا طلب منك إصدار framework، اقبل الإصدار الافتراضي. - في Solution Explorer، تأكد من أن الـ solution تحتوي على ملف كود الـ form وملف البرنامج. وسع ملف الـ form ولاحظ ملف الـ designer المولد أسفله.
- اعمل double click على ملف الـ form لفتح الـ Form Designer. افتح الـ Toolbox (الجانب الأيسر، أو View > Toolbox) ونافذة الـ Properties (انقر بزر الفأرة الأيمن على الـ form واختر Properties، أو اضغط F4).
- افتح ملف البرنامج وابحث عن السطر
Application.Run(new Form1());. - اضغط Start دون تغيير أي شيء.
المتوقع: تظهر نافذة فاضية بعنوان "Form1". يمكن تحريكها، تغيير حجمها، تكبيرها، تصغيرها، وإغلاقها بزر X. إغلاقها ينهي البرنامج ويعيد Visual Studio إلى وضع التصميم (design mode).
- استكشاف اختياري: انقر بزر الفأرة الأيمن على ملف الـ form في Solution Explorer واختر View Code. ضع الـ cursor على الكلمة
Formفيpublic partial class Form1 : Formواضغط F12.
المتوقع: يفتح Visual Studio تعريف class Form في namespace اسمها System.Windows.Forms، وهي قائمة طويلة من الـ members التي ترثها form الخاصة بك. السطر الأول لهذه class يقول public class Form : ContainerControl؛ اضغط F12 على ContainerControl للتحرك خطوة أخرى للأعلى في السلسلة الموضحة في المفهوم 1، وأغلق هذه التبويبات عندما ترى ما يكفيك.
- ارجع إلى Form Designer الخاص بـ
Form1. في الـ Toolbox، ابحث عن Label تحت Common Controls واسحبها إلى الـ form. - مع تحديد label، افتح نافذة الـ Properties وحدد موضع خاصية Text. استبدل
label1بـHello Worldواضغط Enter. - لاحظ أن label على سطح التصميم تقرأ الآن "Hello World".
- اضغط Start.
المتوقع: تظهر الـ form بنص "Hello World" معروضا في الموضع الذي أسقطت فيه label. أوقف البرنامج واحذف label (حددها في الـ designer واضغط Delete) قبل المهمة التالية، لتصبح الـ form فاضية مرة أخرى.
المهمة 2: بناء تصميم form "User Details"
ابن الـ form بهذا الترتيب. لكل control سيشير الكود إليها (text boxes، list box، radio buttons، check boxes، button) حدد Name أولا وText ثانيا؛ الـ labels وgroup box تحتاج فقط Text، لأن الكود لا يشير إليها. التصميم المستهدف هو:
+-- Form1 -----------------------------------------------+
| |
| +-- User Details ---------------------------------+ |
| | | |
| | Name [_________________] +-----------+ | |
| | | Mumbai | | |
| | Address [_________________] | Bangalore | | |
| | | Hyderabad | | |
| | +-----------+ | |
| | ( ) Male ( ) Female | |
| | | |
| | [ ] C# [ ] ASP.Net | |
| | | |
| +-------------------------------------------------+ |
| |
| [ Submit ] |
| |
+--------------------------------------------------------+يوضح هذا الرسم فقط النص المرئي على الـ form. أسماء الـ controls (txtUser، lstCity والباقي) معطاة في الخطوات أدناه ولا تظهر أبدا على الـ form.
- اسحب GroupBox (قسم Containers في الـ Toolbox) إلى الـ form. في نافذة الـ Properties وسع Size وحدد Width بـ
360وHeight بـ320؛ يمكنك أيضا سحب مقبض الزاوية لتغيير حجمها بعد ذلك. إذا لم تتسع لها الـ form، حدد الـ form وكبرها بنفس الطريقة عن طريق خاصية Size الخاصة بها. حدد Text الخاصة بـ group box بـUser Details. - اسحب Label واحدة إلى داخل group box، قريبا من الزاوية العلوية اليسرى. حدد Text الخاصة بها بـ
Name. - اسحب TextBox واحدة إلى داخل group box، إلى يمين label. حدد Name الخاصة بها بـ
txtUser. استخدم خطوط المحاذاة الدليلية التي يعرضها الـ designer أثناء السحب: خط يمر بمستوى نص text box محاذيا لنص label يعني أن النص المكتوب سيتطابق مع نص label. - انسخ الزوج (المفهوم 3، "تحديد ونسخ الـ controls"): انقر على label، اضغط مع الاستمرار على Ctrl وانقر على text box بحيث يتم تحديد الاثنين، اضغط Ctrl+C، ثم Ctrl+V. اسحب الزوج الملصوق أسفل الزوج الأول. تحصل النسخ الملصوقة على أسماء افتراضية، فأعد تسميتها الآن: حدد Text الخاصة بـ label المنسوخة بـ
AddressوName الخاصة بـ text box المنسوخة بـtxtAddress. - اسحب ListBox إلى داخل group box، إلى يمين الـ text boxes. حدد Name الخاصة بها بـ
lstCity. انقر على صف Items، انقر على زر ثلاث النقاط الصغير (...) في نهاية الصف لفتح String Collection Editor، اكتبMumbai،BangaloreوHyderabadفي ثلاثة أسطر منفصلة، وانقر OK. - اسحب اثنين من RadioButton إلى داخل group box أسفل الـ text boxes، جنبا إلى جنب. حدد Name الخاصة بالأولى بـ
rdMaleوText بـMale؛ حدد Name الخاصة بالثانية بـrdFemaleوText بـFemale. - اسحب اثنين من CheckBox إلى داخل group box أسفل الـ radio buttons، جنبا إلى جنب. حدد Name الخاصة بالأولى بـ
chkCوText بـC#؛ حدد Name الخاصة بالثانية بـchkASPوText بـASP.Net. - اسحب Button إلى الـ form أسفل group box (خارجها). حدد Name الخاصة بها بـ
btnSubmitوText بـSubmit. - افتح القائمة المنسدلة في أعلى نافذة الـ Properties وتأكد من عدم وجود text box، list box، radio button، check box أو button ما زالت تحمل اسما افتراضيا مثل
textBox1. يمكن للـ labels وgroup box الاحتفاظ بأسمائها الافتراضية (label1،groupBox1وما حصلت عليه label المنسوخة). - قارن designer الخاص بك بالرسم أعلاه، ثم اضغط Start.
المتوقع: نافذة بها قسم "User Details" يحتوي على labels وtext boxes لـ Name وAddress، وقائمة تعرض Mumbai، Bangalore و Hyderabad، وradio buttons لـ Male وFemale، وcheck boxes لـ C# وASP.Net، وزر Submit أسفل القسم. يمكنك الكتابة في الـ text boxes، اختيار مدينة، اختيار واحد فقط من Male أو Female، وتحديد عنصري CheckBox معا. زر Submit لا يفعل شيئا بعد.
المهمة 3: معالجة events الـ ListBox وSubmit، ثم كسر الـ designer وتصليحه
- في الـ Form Designer، اعمل double click على list box الخاصة بـ
lstCity. يفتح Visual Studio ملف كود الـ form ويضيف method فاضية اسمهاlstCity_SelectedIndexChanged. - داخل الـ method، أضف السطرين من المفهوم 5، مثال 1:
string text = lstCity.GetItemText(lstCity.SelectedItem);
MessageBox.Show(text);- وسع ملف الـ form في Solution Explorer، افتح ملف الـ designer المولد، وابحث عن
lstCity_SelectedIndexChanged. اقرأ سطر الربط وأغلق الملف دون تعديله.
المتوقع: سطر واحد داخل InitializeComponent يحتوي على lstCity.SelectedIndexChanged += وينتهي بـ lstCity_SelectedIndexChanged، بالشكل this.lstCity.SelectedIndexChanged += new System.EventHandler(this.lstCity_SelectedIndexChanged);. هذا هو ربط += الموصوف في المفهوم 5.
- اضغط Start وانقر على كل مدينة بالتناوب. ثم انقر على المدينة المحددة أصلا مرة ثانية.
المتوقع: كل نقرة على مدينة مختلفة تعرض message box تحتوي بالضبط على اسم هذه المدينة، مثلا "Bangalore". النقر على زر OK في message box يغلقها ويرجع إلى الـ form. النقر على المدينة المحددة أصلا لا يعرض أي message box: لم يتغير الـ index المحدد، فلم تحدث SelectedIndexChanged (المفهوم 5).
- أوقف البرنامج. في الـ Form Designer، اعمل double click على
btnSubmit. يضيف Visual Studio method فاضية اسمهاbtnSubmit_Clickإلى ملف كود الـ form. - داخل الـ method، أضف الكود من المفهوم 5، مثال 2:
string name = txtUser.Text;
string address = txtAddress.Text;
MessageBox.Show("Name: " + name + ", Address: " + address);- قارن كودك بقسم "القائمة الكاملة للبرنامج" في نهاية هذا الدرس. يجب أن يتطابق handler الـ ListBox وhandler الـ Button، المحددان بالمهمة 3، مع كودك سطرا بسطر. الـ constructor في هذه القائمة به سطر إضافي واحد محدد بالمهمة 4 لم تكتبه بعد، وتحتوي القائمة أيضا على ثلاث handlers محددة بالمهمة 6؛ تجاهل هذه الأجزاء وتعليقاتها الآن.
- اضغط Start. اكتب
Ahmedفي صندوق Name وMansouraفي صندوق Address، ثم انقر Submit. - انقر OK، أفرغ الصندوقين، وانقر Submit مرة أخرى.
المتوقع: مع كتابة "Ahmed" و"Mansoura"، تقرأ message box "Name: Ahmed, Address: Mansoura". مع كون الصندوقين فاضيين، تقرأ message box "Name: , Address: " لأن خاصية Text الخاصة بـ text box فاضية هي string فاضية.
كسر وتصليح الـ designer (المفهوم 5، القاعدة 2). الخطوات التالية تكسر الـ designer عمدا، لكي تقابل هذا الخطأ مرة هنا بدلا من مواجهته وحيدا في مشروع خاص بك.
- أوقف البرنامج. في الـ Form Designer، اعمل double click على جزء فاضي من الـ form نفسها (ليس على control). يفتح Visual Studio ملف كود الـ form ويضيف method فاضية
private void Form1_Load(object sender, EventArgs e). - احذف هذه الـ method كاملة من ملف الكود (فقط الـ method، وليس أي شيء آخر) واحفظ الملف.
- أغلق تبويب الـ designer، ثم اعمل double click على ملف الـ form في Solution Explorer لإعادة فتح الـ designer. إذا كانت الـ form لا تزال تظهر، أغلق التبويب وأعد فتحه مرة أخرى؛ يظهر الخطأ عندما يعيد الـ designer تحميل الملف.
المتوقع: لا يعرض الـ designer الـ form. في مكانها صفحة خطأ تقول "The designer cannot process unknown name 'Form1_Load' at line N. The code within the method 'InitializeComponent' is generated by the designer and should not be manually modified. Please remove any changes and try opening the designer again." (N هو رقم سطر في ملف الـ designer المولد)، ومعها رابط Go to code أسفلها.
- انقر Go to code. يفتح Visual Studio ملف الـ designer المولد عند السطر
this.Load += new System.EventHandler(this.Form1_Load);داخلInitializeComponent، محددا بخط أحمر متعرج لأنForm1_Loadلم تعد موجودة. - احذف هذا السطر الواحد، احفظ الملف، واعمل double click على ملف الـ form في Solution Explorer مرة أخرى.
المتوقع: يفتح الـ designer ويعرض الـ form بالضبط كما كانت. يتطابق كود الـ form مرة أخرى مع أجزاء المهمة 3 في القائمة الكاملة.
- الآن أزل handler بالطريقة الصحيحة. اعمل double click على جزء فاضي من الـ form مرة أخرى، لكي يتم إنشاء
Form1_Loadمن جديد. ارجع إلى الـ designer، حدد الـ form، انقر على زر البرق في أعلى نافذة الـ Properties، وابحث عن صف Load: خلية قيمته تقرأForm1_Load. - انقر على خلية القيمة، حدد النص
Form1_Loadواحذفه، ثم انقر على صف آخر في نافذة الـ Properties. افتح ملف كود الـ form: إذا كانت method الفاضيةForm1_Loadلا تزال موجودة، احذفها الآن؛ لم تعد مربوطة بأي شيء. - احفظ كل الملفات، أغلق تبويب الـ designer وأعد فتحه.
المتوقع: يفتح الـ designer بشكل طبيعي. البحث عن Form1_Load في ملف الـ designer المولد لا يجد شيئا، ويتطابق كود الـ form مع أجزاء المهمة 3 في القائمة الكاملة.
المهمة 4: تغيير العنوان من الكود وتصليح ترتيب الـ tab
- افتح ملف كود الـ form. في الـ constructor، بعد
InitializeComponent();، أضف:
this.Text = "User Details Form";this هو object الـ form الحالية وText هي نفس الخاصية التي رأيتها في نافذة الـ Properties؛ تحديدها هنا يغير عنوان النافذة عند بدء التشغيل، متجاوزا القيمة الموجودة في الـ designer.
- اضغط Start وتأكد من عنوان النافذة. أثناء عمل البرنامج، انقر داخل صندوق Name واضغط Tab بشكل متكرر. اكتب الترتيب الذي يتحرك به الـ focus (cursor يومض في text box، عنصر محدد بالتظليل في القائمة، حد مميز حول زر أو اختيار).
- أوقف البرنامج. حدد خاصية TabIndex لكل control في الـ designer بالقيم الموجودة في هذا الجدول، الذي يرقم الـ controls بالترتيب الذي تريد أن يزورها Tab به، بدءا بـ group box التي تحمل معظمها:
| Control | TabIndex |
|---|---|
| groupBox1 | 0 |
| txtUser | 1 |
| txtAddress | 2 |
| lstCity | 3 |
| rdMale | 4 |
| rdFemale | 5 |
| chkC | 6 |
| chkASP | 7 |
| btnSubmit | 8 |
يمكن للـ labels الاحتفاظ بأي قيمة موجودة عندها: لا يتوقف Tab أبدا على label. القيم فقط تحتاج أن تتصاعد بالترتيب المقصود؛ 10، 20، 30 تعمل بنفس الكفاءة وتترك فراغات لـ controls تضاف بعد ذلك.
- اضغط Start، انقر في صندوق Name، اضغط Tab عبر الـ form كاملة دون كتابة أي شيء، واضغط Space أثناء وجود الـ focus على Submit. انقر OK، ثم اضغط Tab مرة أخرى.
المتوقع: يقرأ عنوان النافذة "User Details Form" مع أن نافذة الـ Properties لا تزال تعرض القيمة القديمة، لأن سطر الـ constructor يعمل بعد تطبيق قيم الـ designer. يزور الـ focus، بهذا الترتيب: Name، Address، قائمة المدن، زوج Male/Female (توقف واحد، لأن مجموعة radio buttons هي اختيار واحد؛ انظر المفهوم 6)، C#، ASP.Net، Submit. الضغط على Space أثناء وجود الـ focus على Submit يعرض message box "Name: , Address: " (الصندوقان فاضيان في هذا التشغيل)، وهي نفس النافذة التي تنتج عن نقرة فأرة. بعد OK، الضغطة الإضافية على Tab ترجع الـ focus إلى صندوق Name. إذا زار تشغيلك الـ controls بترتيب مختلف، راجع كل قيمة مقابل الجدول.
المهمة 5: إضافة TreeView وPictureBox
- وسع الـ form إذا لزم (حدد الـ form، وسع Size، زد Width)، حتى تكون هناك مساحة فاضية إلى يمين group box.
- اسحب TreeView (Common Controls) إلى الـ form إلى يمين group box. حدد TabIndex الخاصة بها بـ
9، حتى تأتي بعد Submit في ترتيب الـ tab. - في نافذة الـ Properties انقر على صف Nodes، ثم زر ثلاث النقاط (...)، لفتح TreeNode Editor. انقر Add Root، حدد Text الخاصة بـ node الجديدة بـ
Root. - مع تحديد
Root، انقر Add Child وحدد Text الخاصة بابنها بـLabel. كرر Add Child مرتين أخريين لـButtonوCheckbox، حتى يصبح لـRootثلاثة أبناء. انقر OK. - اسحب PictureBox (Common Controls) إلى الـ form أسفل tree view.
- انقر على صف Image، ثم زر ثلاث النقاط (...). في النافذة التي تفتح انقر Import، اختر أي ملف صورة على جهازك (مثل ملف .png أو .jpg)، وانقر OK.
- اضغط Start. وسع الشجرة، ثم انقر في صندوق Name واضغط Tab عبر الـ form مرة واحدة.
المتوقع: تعرض tree view node مطوية "Root" مع سهم توسيع صغير بجانبها؛ النقر على سهم التوسيع يظهر ثلاث nodes الأبناء "Label"، "Button" و"Checkbox". يعرض picture box الصورة المختارة. قسم User Details وزر Submit ما زالا يتصرفان كما في المهمة 4، وتكسب دورة الـ tab توقفا واحدا في النهاية: Name، Address، قائمة المدن، زوج Male/Female، C#، ASP.Net، Submit، tree view، ثم رجوعا إلى Name. يتخطى Tab picture box، التي تعرض فقط صورة، بالضبط كما يتخطى الـ labels.
المهمة 6: إضافة menu وفتح form ثانية
- في Solution Explorer، انقر بزر الفأرة الأيمن على مشروع
DemoApplication، اختر Add > New Form (النص الدقيق يعتمد على الإصدار: Form (Windows Forms) أو Windows Form)، احتفظ بالاسمForm2.cs، وانقر Add. يفتح designer الـ form الجديدة. - اسحب Label إلى
Form2. حدد Name الخاصة بها بـlblMessageوText الخاصة بها بـMessage. - انقر بزر الفأرة الأيمن على ملف الـ form الجديدة في Solution Explorer واختر View Code. غير الـ constructor حتى يتطابق الملف مع مثال المفهوم 7: يصبح الـ constructor
public Form2(string message)ويأتي السطرlblMessage.Text = message;بعدInitializeComponent();. - ارجع إلى designer الخاص بـ
Form1. في الـ Toolbox افتح Menus & Toolbars واسحب MenuStrip إلى الـ form. يظهر شريط منيو على الـ form. انقر على المدخل الفاضي على شريط المنيو، اكتب&Fileواضغط Enter. في المدخل الفاضي الذي يظهر أسفل File اكتبShow &Details، اضغط Enter، ثم اكتبE&xitواضغط Enter. انقر على المدخل الفاضي إلى يمين File، اكتب&Help، اضغط Enter، وفي المدخل أسفله اكتب&About. - سم العناصر الثلاثة التي ستكتب لها كودا: حدد Show Details وحدد Name بـ
mnuShowDetails؛ حدد Exit وحدد Name بـmnuExit؛ حدد About وحدد Name بـmnuAbout. يمكن لـ File وHelp الاحتفاظ بأسمائهما الافتراضية. - اعمل double click على Show Details وأكمل الـ handler:
private void mnuShowDetails_Click(object sender, EventArgs e)
{
Form2 frm = new Form2("Name: " + txtUser.Text + ", Address: " + txtAddress.Text);
frm.Show();
}Form2 frm = new Form2(...)تنشئ instance جديد منForm2، وتمرر نفس string "Name:, Address: " التي يبنيها زر Submit، والتي ينسخها constructor الخاص بـ Form2إلى label الخاصة به.frm.Show();تعرض هذا الـ instance كنافذة مستقلة.
- اعمل double click على Exit وأضف
this.Close();داخل الـ handler؛ اعمل double click على About وأضفMessageBox.Show("User Details Form, Lab 1");داخل الـ handler (المفهوم 8، مثال 1). - قارن كودك بقسم "القائمة الكاملة للبرنامج" في نهاية هذا الدرس. يجب أن يتطابق معها بالكامل الآن، بصرف النظر عن التعليقات (comments).
- اضغط Start. اكتب
Ahmedفي صندوق Name وMansouraفي صندوق Address. اضغط Alt وحرره، ثم F، ثم D. - غير صندوق Name إلى
Saraواضغط Alt، F، D مرة أخرى. - اضغط Alt، H، A.
- أغلق
Form1بزر X الخاص بها أثناء بقاء نوافذForm2مفتوحة. - اضغط Start مرة أخرى واضغط Alt، F، X.
المتوقع: في الـ designer يعرض المنيو File وHelp مع تحديد الحروف F، D، x، H وA بخط تحتها ودون ظهور &. عند التشغيل، Alt، F، D تفتح نافذة Form2 تقرأ label الخاصة بها "Name: Ahmed, Address: Mansoura". الضغطة الثانية على Alt، F، D تفتح نافذة أخرى تقرأ "Name: Sara, Address: Mansoura" بينما النافذة الأولى لا تزال تقرأ "Ahmed": نافذتان مستقلتان من نوع instance، ومجموعتا قيم مختلفتان. Alt، H، A تعرض message box تقرأ "User Details Form, Lab 1". إغلاق Form1 يغلق كل نافذة Form2 أيضا ويعيد Visual Studio إلى وضع التصميم، لأن Form1 هي الـ main form. في التشغيل الثاني، Alt، F، X تنهي البرنامج دون لمس الفأرة.
إضافة اختيارية: status strip. القائمة الكاملة للبرنامج في نهاية هذا الدرس تتوقف قبل هذا الجزء؛ نفذه بعد الخطوة 13 إذا سمح الوقت.
- في قسم Menus & Toolbars بالـ Toolbox، اسحب StatusStrip إلى الـ form. يظهر شريط أسفل الـ form.
- انقر على القائمة المنسدلة الصغيرة لإضافة عنصر على الشريط وأضف ProgressBar؛ انقر عليها مرة أخرى وأضف StatusLabel. حدد progress bar وحدد Name الخاصة بها بـ
prgStatus؛ حدد label، حدد Name الخاصة بها بـlblStatusوText الخاصة بها بـReady. - افتح ملف كود الـ form وأضف السطرين من المفهوم 8، مثال 2، في أعلى
btnSubmit_Click، قبل السطور الثلاثة الموجودة:
prgStatus.Value += 5;
lblStatus.Text = "Working...";- اضغط Start وانقر Submit بشكل متكرر، منقرا OK على كل message box.
المتوقع: عند النقرة الأولى تتغير الـ label في الأسفل من "Ready" إلى "Working..." ويمتلئ الشريط بمقدار 5؛ كل نقرة أخرى تضيف 5، وبعد 20 نقرة يكون الشريط ممتلئا. عند النقرة الحادية والعشرين يتوقف البرنامج في Visual Studio بـ exception تقول أن القيمة 105 غير صحيحة لـ Value، التي يجب أن تقع بين الـ minimum والـ maximum: مدى الشريط من 0 إلى 100 و105 خارجه. أوقف البرنامج.
الخلاصة
- تطبيق Windows Forms هو برنامج desktop مكون من forms؛ الـ form هي class ترث من
Form(ومن خلالها منContainerControl،ScrollableControl،ControlوComponent)، وكل control عليها هي object من class منSystem.Windows.Forms. F12 على اسم class يعرض ما ترثه. - Windows Forms هو نوع المشروع المكتبي السريع بأسلوب السحب والإفراغ؛ تستخدم WPF وUWP و.NET MAUI لغة XAML وهي أقل سرعة. يناسب Windows Forms عمليات proof of concept، وأدوات صغيرة، وforms بسيطة لأعمال التجارة، ولا يعيد ترتيب الـ controls عند تغيير حجم النافذة.
- يحمل ملف البرنامج
Main، التي تستدعيApplication.Run(new Form1())؛ الـ form الممررة هناك هي الـ main form، وإغلاقها ينهي التطبيق حتى لو كانت forms أخرى مفتوحة. - ملف كود الـ form هو النصف القابل للتعديل من partial class؛ ملف الـ designer المولد هو النصف الآخر، ويحتوي على
InitializeComponent. تعديله يدويا صحيح فقط في حالات نادرة، مثل إزالة سطر ربط+=معلق بعد حذف handler بطريقة خاطئة. - الـ Toolbox يوفر الـ controls، والـ designer يضعها، ونافذة الـ Properties تحدد خصائصها.
Nameهو اسم المتغير المستخدم في الكود؛Textهو ما يراه المستخدم؛Sizeهو العرض والارتفاع. Ctrl+click يحدد عدة controls، وCtrl+C وCtrl+V ينسخانها. استخدم اتفاقية تسمية واحدة بشكل متناسق، سم الـ controls قبل إنشاء handlers، وحدد خط الـ form قبل إضافة controls. - القيم المحددة في نافذة الـ Properties هي قيم أولية؛ يمكن للكود قراءة أو تغيير نفس الخصائص وقت التشغيل (
txtUser.Text،this.Text = ...). - GroupBox تجمع، Label تصف، TextBox تجمع نصا، ListBox تقدم قائمة (تعبأ بـ String Collection Editor)، RadioButton تختار خيارا واحدا، CheckBox تختار عدة خيارات، Button تشغل معالجة، TreeView تعرض nodes (تعبأ بـ TreeNode Editor)، PictureBox تعرض صورة (Image > Import).
- الـ event هو إجراء يحدث على control؛ عمل double click على control ينشئ handler لـ event الافتراضية الخاصة بها (
Clickللأزرار،SelectedIndexChangedلـ list boxes،Loadللـ form)، مربوطة بـcontrol.Event += handlerفي ملف الـ designer. أزل handlers غير المرغوبة من قائمة الـ events بدلا من حذف الـ method؛ إذا تعطل الـ designer، اقرأ الخطأ واستخدم Go to code. lstCity.GetItemText(lstCity.SelectedItem)تعطي عنصر القائمة المحدد كنص؛txtUser.Textتعطي ما كتبه المستخدم؛MessageBox.Show(...)تعرض نتيجة.TabIndexيرتب توقفات مفتاح Tab (فقط الترتيب هو المهم، والفراغات مقبولة)؛ الـ labels ليست توقفات أبدا؛ مجموعة radio buttons هي توقف واحد؛ Space ينقر على زر يحمل الـ focus.new Form2(message)معShow()تفتح نافذة مستقلة وتمرر بيانات عبر الـ constructor؛this.Close()تغلق الـ form الحالية.- في
Textالخاص بـ menu item،&قبل حرف تجعله مفتاح اختصار بـ Alt؛ حرف واحد لكل عنصر داخل منيو؛ اتبع اتفاقيتي File > Exit وHelp > About. تحدث progress bar وlabel الخاصان بـ StatusStrip من خلالValueوText، ويجب أن تبقىValueبينMinimumوMaximum.
القائمة الكاملة للبرنامج
هذا هو ملف code-behind النهائي لـ form "User Details" المبنية عبر المهام 1 إلى 6. كل جزء محدد بالمهمة التي تضيفه، لكي تكون فقط الأجزاء حتى مهمتك الحالية موجودة في ملفك الخاص، في أي لحظة من الدرس.
namespace DemoApplication
{
// Editable half of the Form1 partial class.
// The generated half (control creation and event wiring) lives in the designer file.
// Each block is marked with the task that adds it; compare only up to your current task.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// Task 4: change a property from code after the designer values are applied.
this.Text = "User Details Form";
}
// Task 3: default event of the ListBox named lstCity.
private void lstCity_SelectedIndexChanged(object sender, EventArgs e)
{
string text = lstCity.GetItemText(lstCity.SelectedItem);
MessageBox.Show(text);
}
// Task 3: default event of the Button named btnSubmit.
private void btnSubmit_Click(object sender, EventArgs e)
{
string name = txtUser.Text;
string address = txtAddress.Text;
MessageBox.Show("Name: " + name + ", Address: " + address);
}
// Task 6: File > Show Details opens a new Form2 instance and passes it the details.
private void mnuShowDetails_Click(object sender, EventArgs e)
{
Form2 frm = new Form2("Name: " + txtUser.Text + ", Address: " + txtAddress.Text);
frm.Show();
}
// Task 6: File > Exit closes the main form, which ends the application.
private void mnuExit_Click(object sender, EventArgs e)
{
this.Close();
}
// Task 6: Help > About shows a simple message box.
private void mnuAbout_Click(object sender, EventArgs e)
{
MessageBox.Show("User Details Form, Lab 1");
}
}
}