الـ form كـ class مقسوم على ملفين، والأحداث والخصائص في وقت التشغيل، والقوائم والتبويبات، والانتقال بين أكثر من نافذة.
في نهاية هذا الدرس، ستكون قادرا على قراءة وتصحيح الكود الذي يتولد خلف الـ form، والتحكم في الـ properties الخاصة به وقت التشغيل (run time)، وبناء تطبيق login بنافذتين من نوع Form مع menu وstatus strip وtab control.
الأهداف
في نهاية هذا الدرس، ستكون قادرا على:
قراءة الكود المتولد داخل method اسمها InitializeComponent() وفهم ما يفعله كل سطر فيها: إنشاء objects الخاصة بالـ controls، وتعيين قيم الـ properties، وربط الـ events.
تتبع سلسلة الـ inheritance الخاصة بالـ form (Form, ContainerControl, ScrollableControl, Control, Component) وتوضيح ما يضيفه كل مستوى منها.
تصحيح ملف الـ designer الخاص بالـ form عندما يرفض الـ designer الفتح، عن طريق قراءة رسالة الخطأ، وإزالة handler بالطريقة الصحيحة من تبويب Events.
تغيير قيم الـ properties وقت التشغيل، وتوضيح لماذا تجعل قيمة خارج قواعد الـ property (مثل Value في ProgressBar عند تجاوز Maximum) الـ build ينجح بشكل عادي بينما يفشل التنفيذ وقت الـ run time.
تجهيز form جديدة بالترتيب الصحيح: تسميتها، وتحديد Text وFont وSize، ثم إضافة الـ controls، وتسمية كل control قبل عمل double click عليه، والتأكد من ترتيب الـ tab order.
توضيح لماذا لا يعيد الـ Windows Form ترتيب الـ controls الخاصة به عند تغيير حجم الـ window، وما هي WPF.
استخدام TabControl، وMenuStrip بعناصر مسماة ومفاتيح اختصار (keyboard accelerators)، وStatusStrip يحتوي على status label وprogress bar.
اختيار startup form الخاص بالتطبيق، وفتح form ثانية من زر، وتمرير data إليها عبر الـ constructor الخاص بها.
بناء تطبيق login كامل من البداية إلى النهاية: validation، وmessage boxes، وform ترحيب يستقبل اسم المستخدم.
المتطلبات السابقة
إنهاء الدرس السابق، الذي غطى: إنشاء مشروع Windows Forms App (.NET)؛ دور كل من ملف كود الـ form، وملف الـ designer الخاص به، ونقطة دخول البرنامج؛ الـ Toolbox ونافذة الـ Properties؛ الـ controls الأساسية (Label، TextBox، GroupBox، ListBox، RadioButton، CheckBox، Button، TreeView، PictureBox)؛ إنشاء handler بعمل double click على control، وتصحيح الـ designer عندما تزال handler بطريقة خاطئة؛ الـ focus وTabIndex؛ MenuStrip بمفاتيح اختصار عن طريق &؛ form ثانية تستقبل string عبر الـ constructor الخاص بها؛ وثلاث عادات في التجهيز: تسمية الـ form، وتحديد الـ font أولا، وإبعاد المنطق (logic) عن الـ form.
Visual Studio مع workload اسمها ".NET desktop development"، على نظام Windows.
بنى الجزء الأول form واحدة اسمها "User Details" عن طريق سحب controls، وتسميتها، ومعالجة event لـ ListBox وevent لـ Button، وتصحيح ترتيب الـ tab، وإضافة menu، وفتح form ثانية استقبلت رسالة عبر الـ constructor الخاص بها. وضح الجزء الأول أيضا رسالة الخطأ التي تظهر عندما تزال handler من ملف كود الـ form فقط، وطريقة إزالة سطر += القديم.
هذا الدرس لن يكرر نفس الـ walkthrough. سيتناول مستوى أعمق: ماذا يقول الكود المتولد فعليا، ومن أين تأتي الـ window نفسها (الـ inheritance)، وماذا يحدث عندما يغير الكود property وقت التشغيل، وكيف يتفاعل الـ font والـ size مع بعضهما، وكيف تحدد وتنسق أكثر من control في وقت واحد، وTabControl. وعندما يظهر موضوع من الجزء الأول مرة أخرى (المنيوهات، status strip، TreeView، PictureBox)، فإنه يظهر كتذكير مختصر، وتستخدمه المهام داخل مشروع مجهز "بالطريقة الصحيحة". يختتم الدرس بتطبيق login كامل على نافذتين من نوع Form.
المفهوم 1: الـ form عبارة عن class مقسمة على ملفين
التعريف. الـ form هي class بلغة C# ترث من System.Windows.Forms.Form. كل control تسحبها على الـ form تتحول إلى private field داخل هذه الـ class، تحمل instance من control class (Button, TextBox, Label, ProgressBar).
الغرض. يزيل هذا "السحر": الـ designer هو code generator بالإضافة إلى كونه renderer لهذا الكود، وعندما تعرف مكان الكود تستطيع تصحيحه عند تعطله بدل أن تبدأ المشروع من جديد.
طريقة العمل. الـ class معرفة بكلمة partial في ملفين، ويدمج الـ compiler النصفين في class واحدة:
الملف
من يعدله
ماذا يحتوي
ملف كود الـ form
أنت
الـ event handlers الخاصة بك وأي كود تكتبه
ملف الـ designer الخاص بالـ form
الـ designer (غالبا وليس أنت أبدا)
InitializeComponent(): إنشاء كل control، والـ properties الخاصة بها، وربط الـ events الخاصة بها
ملف الـ designer يكون عادة مطويا تحت ملف كود الـ form في شجرة المشروع. يذكر الـ header الخاص به أن الـ method متولدة ولا ينبغي تعديلها بمحرر الكود، وهذا صحيح في 99 من كل 100 مرة؛ والاستثناء موجود في المفهوم 2.
flowchart LR A["Form1.cs<br/>public partial class Form1 : Form<br/>(your handlers)"] --> C["Compiler joins both halves"] B["Form1.Designer.cs<br/>partial class Form1<br/>InitializeComponent()"] --> C C --> D["One Form1 class"] D --> E["Form Designer renders it as a window"] D --> F["Application.Run(new Form1()) shows it at run time"]
مثال. جزء مختصر من InitializeComponent() متولد لـ form فيها زر وtext box واحدة:
this.button1 = new System.Windows.Forms.Button();this.textBox1 = new System.Windows.Forms.TextBox();this.button1.Location = new System.Drawing.Point(30, 120);this.button1.Name = "button1";this.button1.Text = "button1";this.button1.Click += new System.EventHandler(this.button1_Click);this.Controls.Add(this.button1);this.Load += new System.EventHandler(this.Form1_Load);
سطر بسطر:
this.button1 = new ...Button(); تنشئ object الخاص بالـ Button وتحفظه في field اسمها button1؛ سحب زر على الـ form هو الذي أنتج هذا السطر.
this.textBox1 = new ...TextBox(); تقوم بنفس الشيء لـ TextBox.
Location = new Point(30, 120) هو المكان الذي وضعت فيه الزر، كقيمة property.
Name = "button1" هي قيمة property اسمها Name التي تظهر في أعلى نافذة الـ Properties.
Text = "button1" هو النص المكتوب على الزر.
Click += new EventHandler(this.button1_Click) تربط event اسمها Click بـ method اسمها button1_Click داخل ملف كود الـ form؛ += تضيف handler إلى event.
Controls.Add(this.button1) تضيف الزر إلى سطح الـ form؛ يمكن إنشاء control دون إضافتها إلى Controls، فتبقى غير ظاهرة.
this.Load += ... تربط event اسمها Load الخاصة بالـ form نفسها بـ Form1_Load.
بحسب إصدار Visual Studio، يمكن أن تتولد سطور الربط بشكل أقصر، button1.Click += button1_Click; وLoad += Form1_Load;، دون this. ودون new System.EventHandler(...). الشكلان يعنيان نفس الشيء. عندما تحتاج إلى إيجاد سطر ربط، ابحث باسم الـ handler (Form1_Load)، لا بالسطر كاملا.
سلسلة الـ inheritance. ضع الـ cursor على Form في ملف كود الـ form واقفز إلى تعريفها: موجودة داخل System.Windows.Forms. ترث Form من ContainerControl، ثم ScrollableControl، وControl، وComponent، ويضيف كل مستوى properties وmethods وevents. الـ title bar، وأزرار minimize وmaximize وclose، والخلفية الرمادية، وإمكانية تغيير الحجم، كل ذلك موروث، لا مكتوب. كتابة window من الصفر تعني كتابة كل جزء من هذه الأجزاء بنفسك؛ الـ inheritance من Form هو الطريقة التي لا يتكرر بها العمل.
classDiagram Component <|-- Control Control <|-- ScrollableControl ScrollableControl <|-- ContainerControl ContainerControl <|-- Form Form <|-- Form1 class Form1 { -Button button1 -TextBox textBox1 +Form1() -button1_Click() }
المفهوم 2: الـ events، الـ event الافتراضية، والـ designer المعطلة
التعريف. تحدث الـ event من control عندما تحصل action معينة (نقرة، تمرير الماوس (hover)، تحمل الـ form)؛ الـ event handler هي الـ method التي تعمل ردا على هذا الحدث.
الغرض. عمل double click على control ينشئ handler لـ event واحدة فقط، هي الـ event الافتراضية (default). فهم ذلك يوضح كيفية الوصول إلى باقي الـ events ولماذا يرفض الـ designer الفتح أحيانا.
طريقة العمل.
لكل control events كثيرة. حدد الـ control، افتح نافذة الـ Properties، وانقر أيقونة البرق (بجانب أيقونة المفتاح Wrench التي تعرض الـ properties) لرؤية القائمة الأبجدية الكاملة: Click, DragDrop, MouseClick, MouseHover, VisibleChanged من بينها. عمل double click على اسم event هناك ينشئ handler لهذه الـ event.
عمل double click على الـ control نفسها يستخدم الـ event الافتراضية: Click للـ Button، وLoad للـ Form (تحدث عند تحمل الـ form؛ استخداماتها المعتادة هي وضع الـ cursor في الحقل الصحيح أو عرض splash screen).
لإزالة handler بالطريقة الصحيحة: تبويب Events، انقر خلية قيمة الـ event (ستجدها تعرض اسم الـ method)، اضغط Delete، ثم اضغط Enter. يزيل ذلك سطر الربط من ملف الـ designer؛ ثم أزل الـ method الفارغة من ملف الكود. هذا نفس الترتيب الذي تعلمناه في الجزء الأول.
الـ designer المعطلة. إذا أزلت الـ method من ملف الكود وتركت الربط في ملف الـ designer، فعند فتح الـ designer مرة أخرى ستحصل على:
The designer cannot process unknown name 'Form1_Load' at line 153.The code within the method 'InitializeComponent' is generated by the designerand should not be manually modified. Please remove any changes and tryopening the designer again.
اقرأ الرسالة: تذكر اسم الـ method الناقصة وتحدد رقم السطر وتعرض زر "Go to code". السطر الذي ستصل إليه هو الذي يحتوي على Form1_Load، سواء this.Load += new System.EventHandler(this.Form1_Load); أو النسخة الأقصر Load += Form1_Load;. فهو يقوم بعمل subscribe لـ Form1_Load التي لم تعد موجودة، فيظهر تحتها خط أحمر متعرج. أزل هذا السطر فقط، احفظ، وافتح الـ designer مرة أخرى. هذه الحالة الشرعية الوحيدة لتعديل ملف الـ designer.
flowchart TD A[Double-click the form by accident] --> B[Form1_Load method created in Form1.cs<br/>Load += Form1_Load added in Designer.cs] B --> C{How do you remove it?} C -->|Delete the method only| D[Designer cannot open:<br/>unknown name Form1_Load] D --> E[Go to code, delete the line containing Form1_Load, save] C -->|Events tab, select Load, press Delete, press Enter| F[Wiring removed, designer fine] E --> F
المفهوم 3: الـ properties هي properties عادية خاصة بالـ class، ويمكن تغييرها وقت التشغيل
التعريف. العناصر الموجودة في نافذة الـ Properties هي الـ properties بلغة C# الخاصة بـ control object. القيمة التي تكتبها هناك هي القيمة الأولية فقط؛ ويمكن للكود قراءتها أو الكتابة فوقها بعد ذلك.
الغرض. عندما تنظر إلى نافذة الـ Properties كواجهة لعرض properties عادية بلغة C#، تتحول كل control إلى object أنت تعرف أصلا كيف تستخدمه: تقرأ property لتحصل على ما كتبه المستخدم، وتكتب property لتغير ما يراه المستخدم.
طريقة العمل. ضع الماوس فوق .Text في الكود: هي property من نوع string لها getter وsetter. قراءتها (textBox1.Text) تعيد القيمة الحالية؛ والكتابة فيها (label2.Text = "Last name") تغير الـ control على الشاشة فورا. بعض الـ properties لها قواعد؛ يجب أن تبقى ProgressBar.Value بين Minimum (0 افتراضيا) وMaximum (100 افتراضيا)، وكسر هذه القاعدة ليس خطأ compile، بل هو exception وقت الـ run time.
مثال. لنفترض form تجريبية فيها ثلاثة أزواج من Label/TextBox (first name, last name, full name)، وButton، وProgressBar محددة قيمة Value الخاصة بها بـ 45 من الـ designer، وكل الـ controls متروكة بأسمائها الافتراضية (label2, textBox3, progressBar1). هذه الـ form للمثال فقط؛ تستخدم المهام controls مسماة بشكل صحيح.
يجب أن يبقى InitializeComponent(); أول سطر: فهي التي تنشئ كل الـ controls؛ أي كود قبله سيتعامل مع fields لا تزال قيمتها null.
label2.Text = "Last name"; تكتب فوق النص وقت التشغيل؛ تحديدها من الـ designer أفضل لأنه لا يوجد كود يحتاج إلى تتبعه.
MessageBox.Show(...) تعرض dialog قياسي بالـ string المكون بـ interpolation. textBox1.Text تقرأ property الـ Text.
textBox3.Text = ... تكتب property، فتعرض الـ text box الثالثة الاسم الكامل.
progressBar1.Value += 10; ترفع الـ bar بمقدار 10 مع كل click. بدءا من 45، تصل النقرات إلى 55، 65، 75، 85، و95؛ وتحاول النقرة السادسة تحديد 105، وهذا أكبر من Maximum، فتظهر exception تقول إن 105 ليست قيمة صحيحة. للـ properties قواعد، وكسرها يفشل وقت الـ run time. (تستخدم المهمة 3 لاحقا في هذا الدرس bar تبدأ من 0 وتزيد 20 مع كل نقرة، وهناك تحاول النقرة السادسة تحديد 120.)
خطأ شائع.$"Hello {textBox1} {textBox2}" (نسيان .Text) تنجح في الـ compile وتعمل، لكنها تطبع Hello System.Windows.Forms.TextBox, Text: Ahmed ... لأن الـ object كاملا هو ما تم تمريره واستخدمت ToString() الافتراضية الخاصة به. سم دائما الـ property التي تريدها.
المفهوم 4: حجم الـ form، والـ fonts، وماذا يحدث عند تغيير حجم الـ window
التعريف.Size (Width وHeight) هو حجم الـ window الخاص بالـ form وقت التشغيل. Font هو الـ font الافتراضي الخاص بالـ form، وكل control تسحب على الـ form بعد ذلك تأخذه كـ default خاص بها.
الغرض. هذان الـ property هما اللذان يحددان شكل الـ form كله، والترتيب الذي تحددهما به هو ما يحدد إن كنت ستقضي خمس ثوان أو ساعة كاملة في الـ layout.
طريقة العمل.
لتحديد Size، اسحب مقبض الزاوية في الـ designer، أو حدد الـ form ووسع Size في نافذة الـ Properties واكتب Width وHeight، مثلا 500 و400. يعيد الـ designer رسم الـ form بالقيمة المكتوبة.
لتحديد Font، حدد الـ form (انقر منطقة فارغة منها)، ابحث عن Font في نافذة الـ Properties، وسعها بالسهم الصغير وحدد Size بـ 14 أو 16 أو 18 (الزر الموجود على يمين سطر Font يفتح font dialog بدلا من ذلك). نفذ هذا قبل إضافة الـ controls: كل control تسحب بعد ذلك ستستخدم هذا الـ font. تغييره بعد وجود الـ controls يكبر الـ form كله (تتضاعف الأحجام تقريبا من 9 إلى 18 نقطة) ويشوه الشكل، وأي control غير الـ font الخاص بها بشكل منفرد تبقى محتفظة بقيمتها الخاصة بدلا من أن تتبع الـ form. القاعدة: حدد الـ defaults الخاصة بالـ form أولا، ثم أضف الـ controls، ثم اترك الـ defaults كما هي.
الـ Windows Forms هو "ما تراه هو ما تحصل عليه" (what you see is what you get): تبقى كل control بالضبط في المكان الذي وضعتها فيه. قلص الـ window فتقطع الـ controls؛ وكبرها فتبقى المساحة الزائدة فارغة؛ ولا تظهر scroll bars. يمكن للكود تغيير ذلك، لكنه يحتاج إلى عمل إضافي. WPF (Windows Presentation Foundation) تقنية أحدث في .NET لواجهات الـ desktop، وتعيد الـ layouts الخاصة بها ترتيب الـ controls عند تغيير حجم الـ window، وهذا أحد الأسباب التي تجعلها مفضلة للتطبيقات الجديدة الكبيرة. في Windows Forms، صمم الـ form على الحجم الذي ستستخدمه به.
مثال. تحدد المهمة 1 لاحقا في هذا الدرس Font بحجم 14 أولا، وSize بـ 500 على 400 بعد ذلك؛ فتظهر أي Label تسحب بعد ذلك بالـ font 14 نقطة دون خطوة إضافية.
المفهوم 5: التسمية، الترتيب والمحاذاة، أدوات التحديد، وترتيب الـ tab
التعريف. الـ property اسمها Name هي اسم الـ variable الخاص بـ field الـ control داخل الـ class الخاصة بك. خطوط المحاذاة هي الخطوط الدليلية التي يرسمها الـ designer وقت سحب control. TabIndex وTabStop هما ما يحددان إلى أين يذهب مفتاح Tab.
الغرض. الأسماء هي ما تكتبه في الكود وما تتكون منه أسماء الـ handlers؛ المحاذاة والتحديد المتعدد يجعلان الـ form منظمة بسرعة؛ وترتيب الـ tab هو ما يعتمد عليه مستخدمو الـ keyboard.
التسمية. لأن Name هي variable خاصة، سمها على هذا الأساس: camelCase وواضحة بالإنجليزية، مثلا firstNameLabel, firstNameText, sayHelloButton. اربط الأسماء بشكل متناسق: firstNameLabel مع firstNameText، وlastNameLabel مع lastNameText. الـ Hungarian notation تعني وضع نوع الـ control كـ prefix للاسم (lblFirstName, txtUser)؛ كانت الشركة المطورة للمنصة تنصح بها في الماضي وهي الآن تنصح ضدها، لأن firstNameLabel تقرأ كما يمكن أن تقال بصوت مسموع. استخدم الجزء الأول الـ prefixes القديمة txt/lst/btn، ويقبل هذا المقرر أي طريقة من الاثنتين ما دامت مطبقة بشكل متناسق على الـ form، ومن هذا الدرس فصاعدا سنستخدم أسلوب camelCase، وهو الأسلوب المفضل حاليا.
سم قبل الـ double click. يأتي اسم الـ handler من اسم الـ control وقت عمل double click عليه: button1 تعطي button1_Click، وتسمية الزر بعد ذلك لا تغير اسم الـ method، فلن تعرف بعد ذلك إن كانت buttonN_Click تخص أي زر. للعثور على controls لا تزال بلا أسماء، افتح القائمة المنسدلة في أعلى نافذة الـ Properties: تسرد كل control على الـ form، وأي واحدة لا يزال اسمها منتهيا بـ 1 يعني أنها لم تسم.
المحاذاة وأدوات التحديد. وقت السحب، تظهر خطوط المحاذاة عندما تتطابق حافة أعلى أو أسفل control مع جارتها؛ تظهر لـ TextBox المجاورة لـ Label خط إضافي حتى يتطابق النص المكتوب مع نص الـ label. يحدد Ctrl+click أكثر من control، ويحدد مربع تحديد بالسحب مجموعة، وينسخ Ctrl+C / Ctrl+V ويلصق مع الاحتفاظ بالمحاذاة؛ تأتي النسخ الملصقة بأسماء افتراضية (label1, textBox1)، فسمها فورا.
ترتيب الـ tab.TabIndex هو موضع الـ control عند ضغط المستخدم على Tab؛ ويحدد TabStop إن كان Tab يمكنه الوصول إليها أصلا. لا تستطيع الـ Labels استقبال focus. يجب أن تكون الأرقام بترتيب متصاعد فقط، لا متتالية بالضرورة: 10، 50، 110 قيمة صحيحة وتترك مساحة لإضافة control فيما بعد. بعد آخر توقف، يرجع Tab إلى الأول مرة أخرى، ويمكن الضغط على زر عليه focus (يرسم بخط تمييز حوله) بمفتاح المسافة (Space). form مبنية بترتيب "first name, button, last name" ستنتقل بالـ tab بهذا الترتيب بالضبط، فراجع الترتيب دائما عند إضافة controls بترتيب مختلف.
الترتيب الذي يجب تجهيز form به. هذا الـ workflow الذي تتبعه كل مهمة في هذا الدرس:
flowchart LR A["Rename the form (F2)"] --> B["Set Text"] B --> C["Set Font"] C --> D["Set Size"] D --> E["Add controls"] E --> F["Name each control"] F --> G["Double-click for handlers"] G --> H["Set TabIndex"] H --> I["Run"]
مثال. Label اسمها firstNameLabel وTextBox اسمها firstNameText، تم نسخهما ولصقهما كتحديد واحد، تتحولان إلى صف آخر محاذ يحتاج فقط إلى تسمية lastNameLabel وlastNameText؛ والزر الموجود تحتهما، الذي سمي sayHelloButton قبل أول double click عليه، ينتج عنه handler اسمها sayHelloButton_Click.
المفهوم 6: الـ containers، المنيوهات، وstatus strip
التعريف. يقسم الـ Toolbox الـ controls إلى مجموعات. بجانب Common Controls (Button, CheckBox, ComboBox, DateTimePicker, NumericUpDown, TextBox, ProgressBar) توجد Containers، وهي controls تحمل controls أخرى بداخلها (GroupBox من الجزء الأول، وTabControl)، وMenus and Toolbars (MenuStrip وStatusStrip).
الغرض. تنظم الـ containers form كانت ستكون سطحا مسطحا واحدا لولاها؛ يمنح الـ menu وصولا بالـ keyboard لأوامر كانت ستحتاج زرا لكل واحد منها؛ ويعرض status strip الـ progress دون الحاجة إلى dialog.
TabControl. يعرض الـ TabControl عدة tabs في form واحدة. الضغط على tab يعرض شكلا يبدو كـ form مختلفة، لكنه في الحقيقة مجرد صفحة مختلفة من نفس الـ form. كل صفحة هي container: أضف controls بداخلها فتصبح تابعة لهذه الصفحة. Text الخاص بالصفحة هو النص المكتوب على الـ tab الخاص بها.
MenuStrip. اسحبها إلى أي مكان على الـ form؛ ستلتصق (dock) في الأعلى. انقر "Type Here" واكتب أسماء المنيو، متبعا اتفاقيات Windows: menu اسمها File تنتهي بـ Exit، ومنيو Help فيها About. ضع علامة & قبل حرف الاختصار: &File, E&xit, &Help, &About. لا تظهر علامة &؛ ويحدد تحت الحرف الذي يليها خط عند ضغط المستخدم على Alt، فيشغل Alt, F, X أمر Exit دون الماوس. لا يمكن لعنصرين في نفس المنيو أن يتشاركا في نفس الحرف؛ ويمكن تكرار الحروف في submenu لأن عناصرها هي فقط ما يكون في السياق. كل عنصر هو زر له event اسمها Click، ويسمي الـ designer كل عنصر بالنص الخاص به مضافا إليه ToolStripMenuItem (aboutToolStripMenuItem)، وهناك اسم أفضل من تركه كذلك: حدد Name أولا (aboutMenuItem, exitMenuItem)، ثم نفذ double click، لتصبح الـ handler المتولدة aboutMenuItem_Click.
تعرض الـ handler الأولى dialog عند الضغط على About أو الضغط على Alt, H, A.
تغلق this.Close(); الـ form الحالية: this هو object الـ form، وClose موروثة من Form. على الـ form الرئيسية، تغلق التطبيق كله.
StatusStrip. اسحبها على الـ form؛ ستلتصق في الأسفل، شبيهة بشريط الحالة الموجود في أسفل Visual Studio الذي يعرض progress البناء. في الـ designer، انقر الـ StatusStrip: يظهر عليها زر صغير عليه سهم لأسفل. انقره واختر StatusLabel (الـ class الخاصة بها ToolStripStatusLabel)، ثم انقر مرة أخرى واختر ProgressBar (الـ class الخاصة بها ToolStripProgressBar). يتم تسميتهما toolStripStatusLabel1 وtoolStripProgressBar1 بشكل افتراضي؛ انقر كل عنصر داخل الشريط لتحديده وحدد Name الخاص به من نافذة الـ Properties.
كل click يحرك الـ progress bar الموجود في الأسفل بمقدار 5 (statusProgress هو الاسم بعد التغيير الخاص بـ ToolStripProgressBar).
يصبح status label "Working...". عند 100 يمكنك تحديدها بـ "Ready" وإخفاء الـ bar؛ وتنطبق نفس قاعدة Minimum/Maximum من المفهوم 3 على هذا الـ bar.
المفهوم 7: أكثر من form
التعريف. الـ form هي class، فيمكن للمشروع أن يحتوي على عدة form classes وأن ينشئ أي عدد من instances من كل واحدة. عرف الجزء الأول بهذا؛ والتركيز هنا على أي form هي التي تبدأ التطبيق وعلى الـ constructor.
الغرض. تفتح التطبيقات الحقيقية window تفاصيل أو window ترحيب من window رئيسية، ويجب أن تنتقل الـ data المعروضة هناك من object form إلى object form آخر.
Startup form والعمر (lifetime). تبدو نقطة دخول التطبيق كما يلي:
تطبق ApplicationConfiguration.Initialize(); الإعدادات العامة للتطبيق.
تنشئ Application.Run(new Form1()); instance واحدة من Form1 وتشغل التطبيق بها كـ main form: إغلاقها يغلق كل شيء، حتى لو كانت هناك forms أخرى مفتوحة. لتبدأ بـ form مختلفة، بدل Form1 هنا باسم الـ class الأخرى؛ وهذا ما تنفذه المهمة 5 لاحقا في هذا الدرس.
إضافة وفتح form ثانية. أضف form جديدة إلى المشروع وسمها Form2. افتحها من زر على Form1:
تنشئ new Form2() instance جديدة من الـ form class، كأي class أخرى.
تعرضها frm.Show()؛ Show موروثة من Form. كل click ينشئ window مستقلة أخرى: الكتابة في واحدة لا تغير الباقي، لأن كل واحدة object مستقل بذاته.
تمرير data عبر الـ constructor. الـ form هي class، فيمكن لـ constructor الخاص بها استقبال parameters. Form2 هنا لها Label واحدة مسحوبة عليها ومتروكة باسمها الافتراضي label2 (تستخدم المهمة 5 الاسم الصحيح welcomeLabel). في ملف كود Form2:
formMessage هي field خاصة تحتفظ بالقيمة للاستخدام لاحقا.
أصبح الـ constructor الآن يتطلب string؛ new Form2() بدون argument لم تعد تنجح في الـ compile.
لا يزال InitializeComponent(); أول سطر حتى تكون label2 موجودة؛ ثم يخزن الـ parameter في الـ field ويعرض في الـ label.
وعلى جانب المستدعي:
Form2 frm = new Form2("Hello from Form 1");frm.Show();
تنتقل الـ string إلى داخل الـ form الثانية. يمكن تمرير أي شيء يقبله الـ constructor، حتى الـ form الأولى نفسها: constructor مثل public Form2(Form1 owner) يمكن أن يخزن owner في field ثم يشغل owner.Text = "..."; لتغيير عنوان الـ form الأولى. هذا اختياري وغير مستخدم في هذا الدرس؛ لكنه يوضح أن object الـ form يمرر كأي object آخر.
sequenceDiagram participant P as Program.Main participant F1 as Form1 (main form) participant F2 as Form2 P->>F1: Application.Run(new Form1()) F1->>F2: new Form2("Hello from Form 1") F2->>F2: InitializeComponent(), label2.Text = message F1->>F2: Show() F1-->>P: user closes Form1 P-->>F2: application exits, Form2 closes too
المفهوم 8: TreeView وPictureBox (تذكير من الجزء الأول)
التعريف. يسرد الـ TreeView عناصر كشجرة من nodes، بالطريقة التي يعرض بها متصفح الملفات الفولدرات؛ ويعرض الـ PictureBox صورة على الـ form. تم بناء كلاهما في الجزء الأول.
الغرض. تضعهما المهمة 4 لاحقا في هذا الدرس على صفحتين من TabControl، فالتذكير هنا هو فقط عن كيفية فتح الـ editors الخاصة بهما.
طريقة العمل. حدد الـ TreeView، ابحث عن Nodes (قيمتها مكتوبة (Collection)) وانقر زر ... الذي يظهر يمين القيمة؛ يفتح TreeNode Editor. انقر Add Root وحدد Text الخاص بالـ root؛ ومع تحديد الـ root انقر Add Child لكل ابن وحدد Text الخاص به؛ انقر OK. أما بالنسبة للـ PictureBox، ابحث عن Image، وانقر زر ... الخاص بها، وانقر Import في النافذة التي تفتح، واختر ملف صورة، وانقر OK. تقص (clipped) الصورة الأكبر من الـ PictureBox لحجم الـ box، فاختر صورة صغيرة.
مثال. root node اسمها Controls ولها أبناء Label, Button, وCheckBox، وPictureBox تعرض ملف صورة صغيرا.
المفهوم 9: أين يجب أن يوضع كودك، ومتى تستخدم Windows Forms
التعريف. الـ "code-behind" هو الكود الموجود في ملف كود الـ form نفسها. الـ class library هي مشروع دون window خاصة به؛ يرجع إليه (reference) مشروع Windows Forms ويستدعي methods الخاصة به. الـ business logic هي قواعد البرنامج (مثلا، التحقق من صحة login)؛ الـ data access هي قراءة وكتابة البيانات المخزنة.
الغرض. form تحمل كل سطر من البرنامج لا تتغير دون إعادة كتابة البرنامج كله.
طريقة العمل. يضع كل شيء في هذا الدرس الكود في ملف كود الـ form، وهذا مناسب بالنسبة لمعمل. في تطبيق حقيقي، ضع الـ business logic والـ data access في class library واترك في مشروع Windows Forms كود واجهة المستخدم فقط: تستدعي الـ form method، تحصل على الـ data، وتعرضها. تفرض ملفات code-behind بآلاف الأسطر إعادة كتابة كاملة في اليوم الذي يجب أن تتغير فيه واجهة المستخدم؛ وتبقى class library دون تغيير.
متى تستخدم Windows Forms. WPF وUWP و.NET MAUI أنواع تطبيقات desktop أخرى في .NET؛ تصف الثلاثة windows الخاصة بها بلغة XAML، وهي markup language أعقد من سحب controls على سطح. توقف دعم UWP منذ فترة. لا تزال Windows Forms موصى بها في حالتين: أداة بسيطة تحتاج إلى العمل بسرعة، وprototype أو proof of concept. تحذير خاص بالـ prototype: يؤخذ prototype يعمل ويبدو حقيقيا على أنه المنتج النهائي، والشخص الذي رآه سيسأل لماذا يحتاج المنتج الحقيقي إلى ثلاثة أشهر إضافية؛ فاعرض الـ prototypes بحذر. تطبيقات business كثيرة مكتوبة منذ سنين لا تزال تعمل على Windows Forms، فستقابلها في الشركات. وهي "سحر" فقط إلى أن تعرف C#: classes، والـ instantiation، وproperties، وevents، والـ inheritance؛ هذا كل ما هي عليه.
مثال. في المهمة 5 لاحقا في هذا الدرس، المقارنة userNameText.Text == "student" && passwordText.Text == "os1" هي business logic. في تطبيق حقيقي، سيكون هذا السطر method داخل class library، bool IsValid(string userName, string password)، وستستدعيها الـ form فقط وتعرض النتيجة.
مهام المعمل
أنشئ مشروعا واحدا للمهام 1 إلى 4؛ والمهمة 5 مشروع منفصل. تحتوي القائمة الكاملة للبرنامج في آخر هذا الدرس على الكود النهائي لكل handler، مع تحديد المهمة التي أضافتها؛ قارن فقط الأجزاء حتى المهمة الحالية التي أنت عليها.
المهمة 1: تجهيز form بالطريقة الصحيحة
حدد ملف كود الـ form الرئيسية، وسمه MainForm.cs، وأكد إعادة تسمية كل المراجع.
المتوقع: تشغل نقطة دخول التطبيق الآن Application.Run(new MainForm());.
لا يزال الـ title يقول "Form1" لأنها property الـ Text، وليست اسم الـ class. حدد Text بـ Lab 2 Main Form.
المتوقع: يقول title bar الخاص بالـ designer "Lab 2 Main Form".
قبل إضافة أي control، حدد الـ form (انقر منطقة فارغة منها)، ابحث عن Font في نافذة الـ Properties، وسعها بالسهم وحدد Size بـ 14.
المتوقع: يكبر الـ form ونص title bar الخاص به، لأن الـ designer يكبر الـ form عند تغيير الـ font الخاصة بها. ثم وسع Size وحدد Width بـ 500 وHeight بـ 400.
المتوقع: يرسم الـ form بحجم 500 على 400 من جديد.
اسحب Label على الـ form؛ حدد Name بـ firstNameLabel وText بـ First name. اسحب TextBox بجانبها إلى أن يظهر خط المحاذاة الإضافي تحت نص الـ label؛ حدد Name بـ firstNameText.
المتوقع: الـ label والـ text box بالخط 14 نقطة والنص الخاص بهما متطابق.
حدد كليهما، Ctrl+C، Ctrl+V، وحرك النسخ إلى الأسفل. حدد Name الخاص بالـ label المنسوخة بـ lastNameLabel وText بـ Last name؛ وحدد Name الخاص بالـ text box المنسوخة بـ lastNameText.
المتوقع: صفان محاذيان، First name وLast name، لكل واحد منهما text box؛ ولا يوجد في القائمة المنسدلة فوق نافذة الـ Properties اسم منته بـ 1.
اسحب Button، وحدد Name بـ sayHelloButton وText بـ Say hello. الآن فقط نفذ double click عليها؛ تنشأ handler اسمها sayHelloButton_Click. اكتب بداخلها:
المتوقع: الـ handler في ملف كود الـ form اسمها sayHelloButton_Click.
حدد TabIndex بـ 10 لـ firstNameText، و20 لـ lastNameText، و30 لـ sayHelloButton. شغل، واكتب first name، Tab، last name، Tab، Space.
المتوقع: يتحرك الـ focus بهذا الترتيب وتظهر message box "Hello ".
غير الكود مؤقتا إلى {firstNameText} {lastNameText} (دون .Text)، شغل، وانقر.
المتوقع: تبدأ الرسالة بـ Hello System.Windows.Forms.TextBox. أرجع .Text.
المهمة 2: كسر الـ designer وتصحيحه
نفذ double click على منطقة فارغة من MainForm في الـ designer.
المتوقع: تظهر MainForm_Load في ملف كود الـ form.
أزل method MainForm_Load كاملة، احفظ، أغلق تبويب الـ designer، وافتحه مرة أخرى.
المتوقع: "The designer cannot process unknown name 'MainForm_Load'" مع رقم سطر ورابط "Go to code".
انقر Go to code. سينقلك إلى السطر في ملف الـ designer الذي يحتوي على MainForm_Load (قد يكون Load += MainForm_Load; أو النسخة الأطول this.Load += new System.EventHandler(this.MainForm_Load);). أزل هذا السطر كاملا، احفظ، وافتح الـ designer مرة أخرى.
المتوقع: يرسم الـ form من جديد.
نفذ double click على الـ form مرة أخرى، ثم أزل الـ event بالطريقة الصحيحة: حدد الـ form، Properties، تبويب البرق، انقر خلية قيمة Load، اضغط Delete، ثم Enter. ثم أزل method MainForm_Load الفارغة من ملف كود الـ form.
المتوقع: يفتح الـ designer بشكل طبيعي، والبحث عن MainForm_Load في ملف الـ designer لا يجد شيئا.
المهمة 3: menu strip وstatus strip
اسحب MenuStrip على الـ form. أنشئ &File بعنصر E&xit، و&Help بعنصر &About.
المتوقع: يظهر "File" و"Help" دون علامة &.
حدد عنصر About وحدد Name بـ aboutMenuItem؛ وحدد Exit وحدد Name بـ exitMenuItem.
المتوقع: تعرض القائمة المنسدلة فوق نافذة الـ Properties الاسمين دون اسم ToolStripMenuItem لهذين العنصرين.
نفذ double click على About؛ تنشأ handler اسمها aboutMenuItem_Click. اكتب بداخلها MessageBox.Show("Lab2App, Operating Systems 1");. نفذ double click على Exit؛ اكتب داخل exitMenuItem_Click المتولدة this.Close();.
المتوقع: توجد handlers اسمها aboutMenuItem_Click وexitMenuItem_Click في ملف كود الـ form.
شغل واضغط Alt.
المتوقع: يحدد تحت F وH خط. H ثم A تظهر رسالة About؛ Alt, F, X تغلق التطبيق.
اسحب StatusStrip على الـ form. انقرها، وانقر الزر الصغير بالسهم الذي يظهر عليها واختر StatusLabel؛ انقر الزر مرة أخرى واختر ProgressBar. انقر الـ label داخل الشريط وحدد Name بـ statusLabel؛ انقر الـ bar وحدد Name بـ statusProgress.
المتوقع: statusLabel وstatusProgress ظاهران في الشريط الموجود في أسفل الـ designer، ومسردان في القائمة المنسدلة الخاصة بالـ Properties.
اسحب Button، وحدد Name بـ workButton وText بـ Do work. نفذ double click عليها؛ تنشأ handler اسمها workButton_Click. أكملها إلى أن تصبح:
يرفع السطر 3 الـ bar بمقدار 20. السطور من 4 إلى 11: إذا وصلت الـ bar إلى 100 يصبح الـ label "Ready"، وإلا "Working...".
المتوقع: يعمل المشروع build دون أخطاء.
شغل وانقر Do work ست مرات.
المتوقع: تمتلئ الـ bar في خمس خطوات (20، 40، 60، 80، 100) ويصبح الـ label "Ready" عند 100. عند النقرة السادسة يتوقف الـ debugger عند سطر statusProgress.Value += 20; ويعرض نافذة exception برسالة تقول إن 120 ليست قيمة صحيحة وتذكر الـ minimum والـ maximum. أوقف الـ debugging قبل تعديل الكود.
ضع محتوى الـ handler داخل if (statusProgress.Value < 100) { ... }، شغل مرة أخرى وانقر ست مرات.
المتوقع: لا تفعل النقرة السادسة شيئا ولا تظهر أي exception.
المهمة 4: TabControl مع TreeView وPictureBox
اسحب TabControl (قسم Containers في الـ Toolbox) على الـ form وكبره حتى يملأ المساحة الفارغة. افتح القائمة المنسدلة فوق نافذة الـ Properties: صفحات الـ TabControl مسردة بأسماء تبدأ بـ tabPage. حدد الصفحة الأولى وحدد Text بـ Tree؛ وحدد الصفحة الثانية وحدد Text بـ Picture.
المتوقع: يقول التبويبان في الـ designer Tree وPicture.
انقر تبويب Tree واسحب TreeView داخل هذه الصفحة؛ حدد Name بـ controlsTree. ابحث عن Nodes، انقر زر ... الخاص بها، Add Root بنص Controls، ثم Add Child ثلاث مرات بنصوص Label, Button, CheckBox, OK.
المتوقع: node مطوية اسمها Controls في الـ designer على صفحة Tree.
انقر تبويب Picture واسحب PictureBox داخل هذه الصفحة؛ حدد Name بـ logoPicture. ابحث عن Image، انقر زر ... الخاص بها، انقر Import، اختر ملف صورة صغيرا، OK.
المتوقع: الصورة ظاهرة في الـ designer على صفحة Picture؛ الصورة الأكبر تقص لحجم الـ box.
شغل وانقر كل تبويب.
المتوقع: الشجرة على التبويب الأول، مع سهم توسيع يكشف الأبناء الثلاثة؛ والصورة على التبويب الثاني. المنيو، وstatus strip، وزر Say hello من المهام 1 إلى 3 لا تزال تعمل.
المهمة 5: تطبيق login كامل بنافذتين من نوع Form
أنشئ مشروعا جديدا ومنفصلا لهذه المهمة.
سم Form1 بـ LoginForm، مؤكدا كل المراجع، وحدد Text بـ Login، وحدد حجم Font الخاص بالـ form بـ 12، ثم حدد Size بـ 400 على 250. أضف userNameLabel (بنص User name)، وuserNameText، وpasswordLabel (بنص Password)، وpasswordText، وloginButton (بنص Login)، مع TabIndex بـ 10، 20، 30 على text boxes الاثنين والزر. في هذا الدرس صندوق الـ password هو TextBox عادية، فتظهر الـ password المكتوبة واضحة (clear text)؛ وهذا متوقع هنا.
المتوقع: يقول عنوان الـ title bar Login؛ وتشغل نقطة الدخول new LoginForm()؛ وقت التشغيل ينتقل الـ Tab بين User name، Password، Login.
أضف form جديدة، سمها WelcomeForm، واسحب عليها Label اسمها welcomeLabel وButton اسمها closeButton بنص Close.
(أ) نفذ double click على closeButton واكتب this.Close(); داخل closeButton_Click المتولدة.
(ب) افتح كود WelcomeForm وغير توقيع الـ constructor من public WelcomeForm() إلى public WelcomeForm(string userName)، ثم أضف سطرا واحدا بعد InitializeComponent(); حتى يصبح الـ constructor:
public WelcomeForm(string userName){ InitializeComponent(); welcomeLabel.Text = $"Welcome, {userName}";}
يأخذ الـ constructor اسم المستخدم، ويبني الـ controls، ويكتب رسالة الترحيب في الـ label؛ وتغلق closeButton_Click window الترحيب فقط.
المتوقع: لا يزال المشروع يعمل build؛ ولم يعد بإمكان إنشاء WelcomeForm دون string (كتابة new WelcomeForm() في أي مكان يظهر تحتها خط أحمر متعرج).
بالرجوع إلى designer الخاص بـ LoginForm، نفذ double click على Login وأكمل الـ method المتولدة حتى تصبح:
private void loginButton_Click(object sender, EventArgs e){ if (userNameText.Text == "" || passwordText.Text == "") { MessageBox.Show("Please enter both the user name and the password."); return; } if (userNameText.Text == "student" && passwordText.Text == "os1") { WelcomeForm welcome = new WelcomeForm(userNameText.Text); welcome.Show(); } else { MessageBox.Show("Wrong user name or password."); }}
تقرأ || OR وتقرأ && AND. السطور من 3 إلى 7: إذا كان أي صندوق فارغا، اعرض رسالة ونفذ return حتى لا يشغل أي شيء بعده. السطور من 9 إلى 13: عند الـ credentials الثابتة، أنشئ form الترحيب بالاسم المكتوب واعرضها. السطور من 14 إلى 17: وإلا، أظهر رسالة الفشل.
المتوقع: توجد handler واحدة اسمها loginButton_Click في ملف كود LoginForm، ويعمل المشروع build.
شغل وكل من الحقلين فارغ، انقر Login.
المتوقع: "Please enter both the user name and the password."
اكتب student / wrong.
المتوقع: "Wrong user name or password."
اكتب student / os1.
المتوقع: window بعنوان WelcomeForm يقول "Welcome, student". انقر Login مرة أخرى: window ترحيب ثانية مستقلة. انقر Close على واحدة: فقط هذه الواحدة تغلق.
أغلق window الـ Login وwindow ترحيب مفتوحة.
المتوقع: يغلق التطبيق كله، لأن LoginForm هي الـ main form في Application.Run(new LoginForm());.
غير نقطة الدخول إلى Application.Run(new WelcomeForm("test"));، ثم شغل.
المتوقع: تفتح window الترحيب أولا، وتقول "Welcome, test"، دون window login؛ وإغلاقها يوقف البرنامج. أرجع Application.Run(new LoginForm()); وشغل مرة أخرى للتأكد من أن window الـ login رجعت.
الخلاصة
الـ form هي class ترث من Form (عن طريق ContainerControl, ScrollableControl, Control, وComponent). الـ controls هي fields تحمل control objects؛ تعدل نافذة الـ Properties الـ properties بلغة C# الخاصة بها، ويمكن للكود تغييرها وقت التشغيل ضمن قواعدها (Value بين Minimum وMaximum، أو exception وقت التشغيل).
الـ class مقسمة إلى ملف كود (الخاص بك) وملف designer (يحتوي InitializeComponent المتولدة). يعرض الـ designer هذا الكود؛ ومرجع إلى handler ناقصة يوقف فتحه. اقرأ الخطأ، ابحث باسم الـ handler وأزل سطر الربط (طويلا أو قصيرا)، أو الأفضل، أزل الـ events من تبويب Events (Delete ثم Enter) حتى لا يحدث هذا أبدا.
يعطي الـ double click الـ event الافتراضية (Click للأزرار، Load لنوافذ من نوع Form)؛ ويعطي تبويب البرق كل الـ events.
جهز form بهذا الترتيب: سمها، وحدد Text وFont (وسعها، وحدد Size) وSize، ثم أضف الـ controls، وسم كل واحدة بـ camelCase قبل عمل double click عليها، وراجع TabIndex. الـ prefixes القديمة من الجزء الأول مقبولة أيضا إذا استخدمت بشكل متناسق؛ ومن هذا الدرس فصاعدا سنستخدم camelCase.
لا تعيد Windows Forms ترتيب الـ controls عند تغيير حجم الـ window؛ صمم على الحجم المقصود. WPF وUWP و.NET MAUI هي البدائل المبنية على XAML؛ ولا تزال Windows Forms الخيار السريع للأدوات الصغيرة والـ prototypes، وكن حذرا بشأن من تعرض عليه prototype يعمل.
عناصر MenuStrip هي أزرار لها Click handlers: سمها (exitMenuItem) قبل الـ double click، وتحدد علامة & اختصار Alt، وthis.Close() تغلق الـ form؛ تحتوي StatusStrip على status label وprogress bar تضافان من الزر بالسهم؛ وتضع TabControl صفحات على form واحدة، كل صفحة container بعنوان Text.
تحدد Application.Run(new Form1()) startup form وعمر التطبيق؛ ويفتح new Form2(...) مع Show() windows مستقلة، وتمرر parameter في الـ constructor data بينها.
تأتي nodes الـ TreeView وصور الـ PictureBox من زر ... الخاص بـ Nodes وImage، كما في الجزء الأول.
اجعل الـ business logic والـ data access في class library واترك في الـ form كود واجهة المستخدم فقط: الـ form هي "مجرد class"، فكل ما تعرفه عن C# ينطبق عليها.
القائمة الكاملة للبرنامج
تجمع القائمة أدناه الكود النهائي لكل handler بني عبر مهام هذا الدرس، مقسمة بحسب المشروع ومحددة بالمهمة التي أضافتها. قارن فقط الأجزاء حتى أي مهمة أنت عليها.
// Reference listing for this lesson. Each block is marked with the task that adds it;// compare only the blocks up to your current task.// Every class below is the editable half of a partial class; the generated half// (control creation and event wiring) lives in the matching designer file.// ===== Project Lab2App: MainForm.cs (Tasks 1 to 4) =====namespace Lab2App{ public partial class MainForm : Form { public MainForm() { InitializeComponent(); } // Task 1: default event of the Button named sayHelloButton. private void sayHelloButton_Click(object sender, EventArgs e) { MessageBox.Show($"Hello {firstNameText.Text} {lastNameText.Text}"); } // Task 2 leaves no code behind: MainForm_Load is created and removed again. // Task 3: Help > About, named aboutMenuItem before double-clicking. private void aboutMenuItem_Click(object sender, EventArgs e) { MessageBox.Show("Lab2App, Operating Systems 1"); } // Task 3: File > Exit closes the main form, which ends the application. private void exitMenuItem_Click(object sender, EventArgs e) { this.Close(); } // Task 3, step 8: the guard added after the sixth click threw an exception. private void workButton_Click(object sender, EventArgs e) { if (statusProgress.Value < 100) { statusProgress.Value += 20; if (statusProgress.Value == 100) { statusLabel.Text = "Ready"; } else { statusLabel.Text = "Working..."; } } } // Task 4 adds no code: the TabControl, TreeView and PictureBox are set in the designer. }}// ===== Project LoginApp: LoginForm.cs (Task 5) =====namespace LoginApp{ public partial class LoginForm : Form { public LoginForm() { InitializeComponent(); } // Task 5, step 3: validate, then open a WelcomeForm that receives the user name. private void loginButton_Click(object sender, EventArgs e) { if (userNameText.Text == "" || passwordText.Text == "") { MessageBox.Show("Please enter both the user name and the password."); return; } if (userNameText.Text == "student" && passwordText.Text == "os1") { WelcomeForm welcome = new WelcomeForm(userNameText.Text); welcome.Show(); } else { MessageBox.Show("Wrong user name or password."); } } }}// ===== Project LoginApp: WelcomeForm.cs (Task 5) =====namespace LoginApp{ public partial class WelcomeForm : Form { // Task 5, step 2 (b): the constructor takes the user name instead of nothing. public WelcomeForm(string userName) { InitializeComponent(); welcomeLabel.Text = $"Welcome, {userName}"; } // Task 5, step 2 (a): closes only this welcome window. private void closeButton_Click(object sender, EventArgs e) { this.Close(); } }}