Logo

C# Windows Forms, Part 2

26 min read
Lesson slides
1 / 17

Operating Systems I - Lesson 2

C# Windows Forms, Part 2

Read the generated code behind a form, control properties at run time, and build a two-form login application in C#.

By the end of this lesson you will be able to read and repair the generated Windows Forms code behind a form, control its properties at run time, and build a two-form login application with a menu, a status strip, and a tab control.

Objectives

By the end of this lesson you will be able to:

  • Read the generated InitializeComponent() method and explain what each line does: creation of control objects, property values, event wiring.
  • Trace the inheritance chain of a form (Form, ContainerControl, ScrollableControl, Control, Component) and explain what each level contributes.
  • Repair a form's designer file when the designer stops opening, by reading the error message, and remove a handler the correct way through the Events tab.
  • Change control properties at run time and explain why a value outside a property's rules (a ProgressBar Value above Maximum) fails while the program runs.
  • Prepare a new form in the correct order: rename it, set Text, Font and Size, then add controls, name each one before double clicking it, and check the tab order.
  • Explain why a Windows Form does not rearrange its controls when the window is resized, and what WPF is.
  • Use a TabControl, a MenuStrip with named items and keyboard accelerators, and a StatusStrip with a status label and a progress bar.
  • Choose the startup form of an application, open a second form from a button, and pass data to it through its constructor.
  • Build a login application end to end: validation, message boxes, and a welcome form that receives the user name.

Prerequisites

  • Completion of the previous lesson, which covered: creating a Windows Forms App (.NET) project; the roles of a form's code file, its designer file, and the program entry point; the Toolbox and the Properties window; the core controls (Label, TextBox, GroupBox, ListBox, RadioButton, CheckBox, Button, TreeView, PictureBox); creating a handler by double clicking a control and repairing the designer after a handler was deleted the wrong way; focus and TabIndex; a MenuStrip with & accelerators; a second form that receives a string through its constructor; and three setup habits: rename the form, set the font first, keep logic out of the form.
  • Visual Studio with the ".NET desktop development" workload, on Windows.
  • Basic C#: classes, constructors, fields, properties, methods, if/else, string interpolation ($"..."), inheritance and new.

Recap of Part 1

Part 1 built one "User Details" form by dragging controls, naming them, handling a ListBox and a Button event, fixing the tab order, adding a menu, and opening a second form that received a message through its constructor. It also showed the designer error that appears when a handler is deleted from a form's code file only, and how to remove the stale += line.

This lesson does not repeat that walkthrough. It goes one level down: what the generated code actually says, where the window itself comes from (inheritance), what happens when code changes a property while the program runs, how fonts and sizes interact, how to select and align several controls at once, and the TabControl. Where a topic from Part 1 is used again (menus, the status strip, TreeView, PictureBox, a second form), it appears as a short reminder, and the tasks exercise it inside a project prepared "the right way". The lesson ends with a complete login application on two forms.

Concept 1: A form is a class split across two files

Definition. A form is a C# class that inherits from System.Windows.Forms.Form. Every control you drag onto it becomes a private field of that class holding an instance of a control class (Button, TextBox, Label, ProgressBar).

Purpose. This removes the "magic": the designer is a code generator plus a renderer of that code, and when you know where the code lives you can fix it when it breaks instead of starting the project over.

How it works. The class is declared with the partial keyword in two files, and the compiler joins both halves into one class:

FileWho edits itWhat it holds
The form's code fileYouYour event handlers and any code you write
The form's designer fileThe designer (almost never you)InitializeComponent(): creation of every control, its properties, and its event wiring

The designer file is normally collapsed under the form's code file in the project tree. Its header says the method is generated and must not be modified with the code editor, which is true 99 times out of 100; the exception is in Concept 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"]

Example. A shortened piece of a generated InitializeComponent() for a form holding one button and one 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);

Line by line:

  1. this.button1 = new ...Button(); creates the Button object and stores it in the field button1; dragging a button onto the form generated this line.
  2. this.textBox1 = new ...TextBox(); does the same for a TextBox.
  3. Location = new Point(30, 120) is where you dropped the button, as a property value.
  4. Name = "button1" is the Name property shown at the top of the Properties window.
  5. Text = "button1" is the caption.
  6. Click += new EventHandler(this.button1_Click) wires the Click event to the method button1_Click in the form's code file; += adds a handler to an event.
  7. Controls.Add(this.button1) puts the button on the form's surface; a control that is created but not added is invisible.
  8. this.Load += ... wires the form's own Load event to Form1_Load.

Depending on the Visual Studio version, the wiring lines may be generated in a shorter form, button1.Click += button1_Click; and Load += Form1_Load;, without this. and without new System.EventHandler(...). Both forms mean the same thing. When you need to find a wiring line, search for the handler name (Form1_Load), not for the whole line.

The inheritance chain. Put the cursor on Form in the form's code file and jump to its definition: it lives in System.Windows.Forms. Form inherits from ContainerControl, then ScrollableControl, Control and Component, each level adding properties, methods and events. The title bar, the minimize, maximize and close buttons, the gray background and resizing all come from there: inherited, not written. Writing a window from scratch means coding every one of those pieces yourself; inheriting from Form is how the work is not repeated.

classDiagram
    Component <|-- Control
    Control <|-- ScrollableControl
    ScrollableControl <|-- ContainerControl
    ContainerControl <|-- Form
    Form <|-- Form1
    class Form1 {
        -Button button1
        -TextBox textBox1
        +Form1()
        -button1_Click()
    }

Concept 2: Events, the default event, and the broken designer

Definition. An event is raised by a control when an action happens (a click, the mouse hovering, the form loading); an event handler is the method that runs in response.

Purpose. Double clicking a control creates a handler for its default event only. Knowing this explains how to reach the other events and why the designer sometimes refuses to open.

How it works.

  • Every control has many events. Select it, open the Properties window, and click the lightning bolt icon (next to the wrench that shows properties) to see the full alphabetical list: Click, DragDrop, MouseClick, MouseHover, VisibleChanged among them. Double clicking an event name there creates a handler for that event.
  • Double clicking the control itself uses the default event: Click for a Button, Load for the Form (raised when the form loads; typical uses are placing the cursor in the right field or showing a splash screen).
  • To remove a handler correctly: Events tab, click the event's value cell (it shows the method name), press Delete, then press Enter. That removes the wiring line from the designer file; then delete the empty method from the code file. This is the same sequence taught in Part 1.

The broken designer. Delete the method from the code file but leave the wiring in the designer file, and the next time you open the designer you get:

The designer cannot process unknown name 'Form1_Load' at line 153.
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.

Read the message: it names the missing method and the line and offers a "Go to code" button. The line it lands on is the one containing Form1_Load, either this.Load += new System.EventHandler(this.Form1_Load); or the shorter Load += Form1_Load;. It subscribes Form1_Load, which no longer exists, so it has a red squiggle. Delete that one line, save, and reopen the designer. This is the one legitimate reason to edit the designer file.

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

Concept 3: Properties are class properties, and you can change them at run time

Definition. The entries in the Properties window are the C# properties of the control object. The value typed there is only the starting value; code can read or overwrite it later.

Purpose. Once you see the Properties window as a view of ordinary C# properties, every control becomes an object you already know how to use: read a property to get what the user typed, write a property to change what the user sees.

How it works. Hover over .Text in code: it is a string property with a getter and a setter. Reading it (textBox1.Text) gives the current value; assigning it (label2.Text = "Last name") changes the control on screen immediately. Some properties have rules; ProgressBar.Value must stay between Minimum (0 by default) and Maximum (100 by default), and breaking the rule is not a compile error, it is a run time exception.

Example. Assume a scratch form with three Label/TextBox pairs (first name, last name, full name), a Button and a ProgressBar whose Value was set to 45 in the designer, all controls left with their default names (label2, textBox3, progressBar1). This form is only for the example; the tasks use properly named controls.

public Form1()
{
    InitializeComponent();
    label2.Text = "Last name";
    label3.Text = "Full name";
}
 
private void button1_Click(object sender, EventArgs e)
{
    MessageBox.Show($"Hello {textBox1.Text} {textBox2.Text}");
    textBox3.Text = $"{textBox1.Text} {textBox2.Text}";
    progressBar1.Value += 10;
}
  1. InitializeComponent(); must stay first: it creates all controls; code before it would touch fields that are still null.
  2. label2.Text = "Last name"; overwrites a caption at run time; setting it in the designer is better because there is no code to maintain.
  3. MessageBox.Show(...) pops up a standard dialog with the interpolated string. textBox1.Text reads the Text property.
  4. textBox3.Text = ... writes the property, so the third text box shows the full name.
  5. progressBar1.Value += 10; raises the bar by 10 per click. Starting from 45, the clicks reach 55, 65, 75, 85 and 95; the sixth click tries to set 105, which is above Maximum, so it throws an exception saying 105 is not a valid value. Properties have rules, and breaking them fails at run time. (Task 3 later in this lesson uses a bar that starts at 0 and steps by 20, so there the sixth click tries 120.)

Pitfall. $"Hello {textBox1} {textBox2}" (forgetting .Text) compiles and runs, but prints Hello System.Windows.Forms.TextBox, Text: Ahmed ... because the whole object was passed and its default ToString() used. Always name the property you want.

Concept 4: Form size, fonts, and what happens when the window is resized

Definition. Size (Width and Height) is the form's window size at run time. Font is the form's default font, which every control dragged onto the form afterwards takes as its own default.

Purpose. These two properties decide how the whole form looks, and the order in which you set them decides whether you spend five seconds or an hour on layout.

How it works.

  • To set Size, drag the corner handle in the designer or select the form, expand Size in the Properties window and type Width and Height, for example 500 and 400. The designer redraws the form to the typed value.
  • To set Font, select the form (click an empty area of it), find Font in the Properties window, expand it with the small arrow and set Size to 14, 16 or 18 (the button at the right of the Font row opens a font dialog instead). Do this before adding controls: every control dragged afterwards uses that font. Changing it after controls exist scales the whole form (sizes roughly double from 9 to 18 points) and makes a mess, and a control whose font was changed individually keeps its own value instead of following the form. Rule: set the form's defaults first, then add controls, then leave the defaults alone.
  • Windows Forms is "what you see is what you get": a control stays exactly where you placed it. Shrink the window and controls are cut off; maximize it and the extra space stays empty; no scroll bars appear. Code can change this, but it takes work. WPF (Windows Presentation Foundation) is a newer .NET desktop UI technology whose layouts rearrange controls when the window is resized, which is one reason it is preferred for large new applications. In Windows Forms, design the form at the size it will be used.

Example. Task 1 later in this lesson sets Font size 14 first and Size 500 by 400 second; a Label dragged on afterwards appears in the 14 point font with no extra step.

Concept 5: Naming, alignment, selection helpers, and tab order

Definition. The Name property is the variable name of the control field in your class. Alignment lines are the guides the designer draws while you drag a control. TabIndex and TabStop decide where the Tab key goes.

Purpose. Names are what you type in code and what the handler names are built from; alignment and multi selection make a tidy form fast; tab order is what keyboard users rely on.

Naming. Because Name is a private variable, name it like one: camelCase and readable in English, for example firstNameLabel, firstNameText, sayHelloButton. Pair names consistently: firstNameLabel with firstNameText, lastNameLabel with lastNameText. Hungarian notation means putting the control type as a prefix of the name (lblFirstName, txtUser); the platform vendor once recommended it and now recommends against it, because firstNameLabel reads the way you would say it. Part 1 used the older txt/lst/btn prefixes, and this course accepts either style as long as it is applied consistently across a form; from this lesson on the camelCase style is used, which is the convention now recommended.

Name before you double click. The handler name comes from the control's name at the moment you double click: button1 gives button1_Click, and renaming the button afterwards does not rename the method, so later you cannot tell which buttonN_Click belongs to which button. To find unnamed controls, open the drop down at the top of the Properties window: it lists every control on the form, and anything still ending in 1 has not been named.

Alignment and selection helpers. While dragging, alignment lines show when the control's top or bottom lines up with a neighbour; a TextBox next to a Label shows an extra line so the typed text lines up with the label's text. Ctrl+click selects several controls, a dragged selection rectangle selects a group, and Ctrl+C / Ctrl+V copies and pastes them with their alignment kept; the pasted copies come with default names (label1, textBox1), so rename them right away.

Tab order. TabIndex is the control's position when the user presses Tab; TabStop says whether Tab can land on it. Labels cannot receive focus. The numbers need only increase, not be consecutive: 10, 50, 110 is valid and leaves room to insert a control later. After the last stop, Tab wraps to the first, and a focused button (drawn with a highlight outline) is pressed with the Space bar. A form built as "first name, button, last name" tabs in exactly that order, so check the order whenever controls are added out of sequence.

The order in which to prepare a form. This is the workflow every task in this lesson follows:

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"]

Example. A label named firstNameLabel and a text box named firstNameText, copied and pasted as one selection, become a second aligned row that only needs renaming to lastNameLabel and lastNameText; the button below them, named sayHelloButton before its first double click, gets the handler sayHelloButton_Click.

Concept 6: Containers, menus and the status strip

Definition. The Toolbox groups controls. Besides Common Controls (Button, CheckBox, ComboBox, DateTimePicker, NumericUpDown, TextBox, ProgressBar) there are Containers, controls that hold other controls (GroupBox from Part 1, and TabControl), and Menus and Toolbars (MenuStrip and StatusStrip).

Purpose. Containers organize a form that would otherwise be one flat surface; a menu gives keyboard access to commands that would otherwise need a button each; a status strip reports progress without a dialog.

TabControl. A TabControl shows several tabs on one form. Clicking a tab shows what looks like a different form, but it is only a different page of the same form. Each page is a container: drop controls into it and they belong to that page. The Text of a page is the caption written on its tab.

MenuStrip. Drag it anywhere on the form; it docks to the top. Click "Type Here" and type the menu names, following the Windows conventions: a File menu ending with Exit and a Help menu with About. Put an ampersand before the accelerator letter: &File, E&xit, &Help, &About. The ampersand is not displayed; the letter after it is underlined when the user presses Alt, so Alt, F, X runs Exit without a mouse. Two items in one menu must not share a letter; a submenu may reuse letters since only its own items are in context. Each item is a button with a Click event, and the designer names each entry after its text plus ToolStripMenuItem (aboutToolStripMenuItem), a name it is better not to keep: set Name first (aboutMenuItem, exitMenuItem), then double click, so the generated handler reads aboutMenuItem_Click.

private void aboutMenuItem_Click(object sender, EventArgs e)
{
    MessageBox.Show("Operating Systems 1, Lab 2");
}
 
private void exitMenuItem_Click(object sender, EventArgs e)
{
    this.Close();
}
  1. The first handler shows a dialog when About is clicked or Alt, H, A is pressed.
  2. this.Close(); closes the current form: this is the form object, Close is inherited from Form. On the main form it ends the application.

StatusStrip. Drag it onto the form; it docks to the bottom, similar to the status bar at the bottom of Visual Studio that shows build progress. In the designer, click the StatusStrip: a small button with a down arrow appears on it. Click it and choose StatusLabel (class ToolStripStatusLabel), then click it again and choose ProgressBar (class ToolStripProgressBar). They are named toolStripStatusLabel1 and toolStripProgressBar1 by default; click each item inside the strip to select it and set its Name in the Properties window.

private void startButton_Click(object sender, EventArgs e)
{
    statusProgress.Value += 5;
    statusLabel.Text = "Working...";
}
  1. Each click moves the bottom progress bar by 5 (statusProgress is the renamed ToolStripProgressBar).
  2. The status label becomes "Working...". At 100 you could set it to "Ready" and hide the bar; the same Minimum/Maximum rule from Concept 3 applies to this bar.

Concept 7: Multiple forms

Definition. A form is a class, so a project can hold several form classes and create any number of instances of each. Part 1 introduced this; here the focus is on which form starts the application and on the constructor.

Purpose. Real applications open a details window or a welcome window from a main window, and the data shown there has to travel from one form object to the other.

Startup form and lifetime. The application's entry point looks like this:

static void Main()
{
    ApplicationConfiguration.Initialize();
    Application.Run(new Form1());
}
  1. Main is the entry point of the application.
  2. ApplicationConfiguration.Initialize(); applies the application wide configuration.
  3. Application.Run(new Form1()); creates one instance of Form1 and runs the application with it as the main form: closing it closes everything, even if other forms are open. To start with a different form, replace Form1 here with the other class name; Task 5 later in this lesson does this.

Adding and opening a second form. Add a new form to the project and call it Form2. Opening it from a button on Form1:

private void openButton_Click(object sender, EventArgs e)
{
    Form2 frm = new Form2();
    frm.Show();
}
  1. new Form2() creates a new instance of the form class, like any other class.
  2. frm.Show() displays it; Show is inherited from Form. Every click creates another independent window: typing in one does not change the others, because each is a separate object.

Passing data through the constructor. A form is a class, so its constructor can take parameters. Form2 here has one Label dragged onto it and left with its default name label2 (Task 5 uses the proper name welcomeLabel). In Form2's code file:

private string formMessage;
 
public Form2(string message)
{
    InitializeComponent();
    formMessage = message;
    label2.Text = formMessage;
}
  1. formMessage is a private field that keeps the value for later use.
  2. The constructor now requires a string; new Form2() without an argument no longer compiles.
  3. InitializeComponent(); still comes first so that label2 exists; then the parameter is stored in the field and shown in the label.

And on the caller's side:

Form2 frm = new Form2("Hello from Form 1");
frm.Show();

The string travels into the second form. Anything a constructor accepts can be passed, including the first form itself: a constructor public Form2(Form1 owner) could store owner in a field and later run owner.Text = "..."; to change the first form's title. That is optional and not used in this lesson; it only shows that a form object is passed around like any other 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

Concept 8: TreeView and PictureBox (reminder from Part 1)

Definition. A TreeView lists items as a tree of nodes, the way a file browser shows folders; a PictureBox displays an image on the form. Both were built in Part 1.

Purpose. Task 4 later in this lesson places them on the two pages of a TabControl, so the reminder here is only about where the editors open from.

How it works. Select the TreeView, find Nodes (its value reads (Collection)) and click the ... button that appears at the right of the value; the TreeNode Editor opens. Click Add Root and set the root's Text; with the root selected click Add Child for each child and set its Text; click OK. For the PictureBox, find Image, click its ... button, click Import in the window that opens, choose an image file, and click OK. An image larger than the PictureBox is clipped to the box's size, so pick a small image.

Example. A root node Controls with children Label, Button and CheckBox, and a PictureBox showing a small image file.

Concept 9: Where your code should live, and when to use Windows Forms

Definition. The "code-behind" is the code in the form's own code file. A class library is a project that has no window of its own; the Windows Forms project references it and calls its methods. Business logic is the rules of the program (for example, validating a login); data access is reading and writing stored data.

Purpose. A form that holds every line of the program cannot be replaced without rewriting the program.

How it works. Everything in this lesson puts code in the form's code file, which is fine for a lab. In a real application, put business logic and data access in a class library and keep only user interface code in the Windows Forms project: the form calls a method, gets the data, and displays it. Code-behind files of thousands of lines force a rewrite the day the user interface must change; a class library carries over unchanged.

When to use Windows Forms. WPF, UWP and .NET MAUI are the other .NET desktop application types; all three describe their windows in XAML, a markup language that is more complex than dragging controls onto a surface. UWP has since been deprecated. Windows Forms is still recommended in two cases: a small utility that just needs to work quickly, and a prototype or proof of concept. Prototype caution: a working prototype that looks real is taken for the finished product, and the person who saw it will ask why the real thing takes three more months; show prototypes carefully. Many business applications written years ago still run on Windows Forms, so you will meet it in companies. And it is "magic" only until you know C#: classes, instantiation, properties, events and inheritance are all it is.

Example. In Task 5 later in this lesson the comparison userNameText.Text == "student" && passwordText.Text == "os1" is business logic. In a real application that line would be a method in a class library, bool IsValid(string userName, string password), and the form would only call it and show the result.

Lab tasks

Create one project for Tasks 1 to 4; Task 5 is a separate project. The complete program listing at the end of this lesson holds the final code of every handler, marked with the task that adds it; compare only the blocks up to your current task.

Task 1: Prepare a form the right way

  1. Select the main form's code file, rename it to MainForm.cs, and confirm renaming all references. Expected: the application entry point now runs Application.Run(new MainForm());.
  2. The title still reads "Form1" because it is the Text property, not the class name. Set Text to Lab 2 Main Form. Expected: the designer's title bar reads "Lab 2 Main Form".
  3. Before adding any control, select the form (click an empty area), find Font in the Properties window, expand it with the arrow and set Size to 14. Expected: the form and its title text grow, because the designer scales the form when its font changes. Then expand Size and set Width to 500 and Height to 400. Expected: the form is drawn at 500 by 400 again.
  4. Drag a Label onto the form; set Name to firstNameLabel and Text to First name. Drag a TextBox next to it until the extra alignment line under the label's text appears; set Name to firstNameText. Expected: the label and text box are in the 14 point font and their text lines up.
  5. Select both, Ctrl+C, Ctrl+V, and move the copies down. Set the copied label's Name to lastNameLabel and Text to Last name; set the copied text box's Name to lastNameText. Expected: two aligned rows, First name and Last name, each with a text box; the drop down at the top of the Properties window lists no name ending in 1.
  6. Drag a Button, set Name to sayHelloButton and Text to Say hello. Only now double click it; a handler named sayHelloButton_Click is created. Inside it write:
    MessageBox.Show($"Hello {firstNameText.Text} {lastNameText.Text}");
    Expected: the handler in the form's code file is named sayHelloButton_Click.
  7. Set TabIndex to 10 for firstNameText, 20 for lastNameText, 30 for sayHelloButton. Run, type a first name, Tab, a last name, Tab, Space. Expected: focus moves in that order and a message box shows "Hello ".
  8. Temporarily change the code to {firstNameText} {lastNameText} (no .Text), run, click. Expected: the message starts with Hello System.Windows.Forms.TextBox. Restore the .Text.

Task 2: Break and repair the designer

  1. Double click an empty area of MainForm in the designer. Expected: MainForm_Load appears in the form's code file.
  2. Delete the whole MainForm_Load method, save, close the designer tab, and reopen it. Expected: "The designer cannot process unknown name 'MainForm_Load'" with a line number and a "Go to code" link.
  3. Click Go to code. It lands on the line in the designer file that contains MainForm_Load (it may read Load += MainForm_Load; or the longer this.Load += new System.EventHandler(this.MainForm_Load);). Delete that whole line, save, reopen the designer. Expected: the form renders again.
  4. Double click the form once more, then remove the event the correct way: select the form, Properties, lightning bolt tab, click the Load value cell, press Delete, then press Enter. Then delete the empty MainForm_Load method from the form's code file. Expected: the designer opens normally and a search for MainForm_Load in the designer file finds nothing.

Task 3: Menu strip and status strip

  1. Drag a MenuStrip onto the form. Create &File with the item E&xit, and &Help with the item &About. Expected: "File" and "Help" with no visible ampersands.
  2. Select the About entry and set Name to aboutMenuItem; select Exit and set Name to exitMenuItem. Expected: the drop down at the top of the Properties window shows both names and no ToolStripMenuItem name for these two.
  3. Double click About; a handler named aboutMenuItem_Click is created. Inside it write MessageBox.Show("Lab2App, Operating Systems 1");. Double click Exit; inside the generated exitMenuItem_Click write this.Close();. Expected: two handlers named aboutMenuItem_Click and exitMenuItem_Click in the form's code file.
  4. Run and press Alt. Expected: F and H underlined. H then A shows the About message; Alt, F, X closes the application.
  5. Drag a StatusStrip onto the form. Click it, click the small down arrow button that appears on it and choose StatusLabel; click the button again and choose ProgressBar. Click the label inside the strip and set Name to statusLabel; click the bar and set Name to statusProgress. Expected: statusLabel and statusProgress are visible in the strip at the bottom of the designer and listed in the Properties drop down.
  6. Drag a Button, set Name to workButton and Text to Do work. Double click it; a handler named workButton_Click is created. Complete it so it reads:
    private void workButton_Click(object sender, EventArgs e)
    {
        statusProgress.Value += 20;
        if (statusProgress.Value == 100)
        {
            statusLabel.Text = "Ready";
        }
        else
        {
            statusLabel.Text = "Working...";
        }
    }
    Line 3 raises the bar by 20. Lines 4 to 11: if the bar has reached 100 the label reads "Ready", otherwise "Working...". Expected: the project builds with no errors.
  7. Run and click Do work six times. Expected: the bar fills in five steps (20, 40, 60, 80, 100) and the label reads "Ready" at 100. On the sixth click the debugger pauses on the statusProgress.Value += 20; line and shows an exception window whose message says 120 is not a valid value and names the minimum and maximum. Stop debugging before editing the code.
  8. Wrap the body of the handler in if (statusProgress.Value < 100) { ... }, run again and click six times. Expected: the sixth click does nothing and no exception appears.

Task 4: TabControl with a TreeView and a PictureBox

  1. Drag a TabControl (Toolbox section Containers) onto the form and enlarge it to fill the free space. Open the drop down at the top of the Properties window: the pages inside the TabControl are listed with names starting tabPage. Select the first page and set its Text to Tree; select the second page and set its Text to Picture. Expected: the two tabs in the designer read Tree and Picture.
  2. Click the Tree tab and drag a TreeView into that page; set Name to controlsTree. Find Nodes, click its ... button, Add Root with text Controls, then Add Child three times with texts Label, Button, CheckBox, OK. Expected: a collapsed Controls node in the designer on the Tree page.
  3. Click the Picture tab and drag a PictureBox into that page; set Name to logoPicture. Find Image, click its ... button, click Import, choose a small image file, OK. Expected: the image is visible in the designer on the Picture page; a larger image is clipped to the box's size.
  4. Run and click each tab. Expected: the tree on the first tab, with an expander that reveals the three children; the image on the second tab. The menu, status strip and Say hello button from Tasks 1 to 3 still work.

Task 5: End-to-end login with a second form

Create a new, separate project for this task.

  1. Rename Form1 to LoginForm, confirming all references, set Text to Login, set the form's Font size to 12, then set Size to 400 by 250. Add userNameLabel (text User name), userNameText, passwordLabel (text Password), passwordText, and loginButton (text Login), with TabIndex 10, 20, 30 on the two text boxes and the button. In this lesson the password box is a plain TextBox, so the typed password is shown in clear text; that is expected here. Expected: the title bar reads Login; the entry point runs new LoginForm(); at run time Tab moves User name, Password, Login.
  2. Add a new form, name it WelcomeForm, and drag onto it a Label named welcomeLabel and a Button named closeButton with text Close. (a) Double click closeButton and write this.Close(); inside the generated closeButton_Click. (b) Open the code of WelcomeForm and change the constructor signature from public WelcomeForm() to public WelcomeForm(string userName), then add one line after InitializeComponent(); so the constructor reads:
    public WelcomeForm(string userName)
    {
        InitializeComponent();
        welcomeLabel.Text = $"Welcome, {userName}";
    }
    The constructor takes the user name, builds the controls, and writes the greeting into the label; closeButton_Click closes only the welcome window. Expected: the project still builds; WelcomeForm can no longer be created without a string (typing new WelcomeForm() anywhere shows a red squiggle).
  3. Back in the LoginForm designer, double click Login and complete the generated method so it reads:
    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.");
        }
    }
    || reads as OR and && reads as AND. Lines 3 to 7: if either box is empty, show a message and return so nothing else runs. Lines 9 to 13: on the fixed credentials, create the welcome form with the typed name and show it. Lines 14 to 17: otherwise report the failure. Expected: one handler named loginButton_Click in LoginForm's code file, and the project builds.
  4. Run with both boxes empty, click Login. Expected: "Please enter both the user name and the password."
  5. Enter student / wrong. Expected: "Wrong user name or password."
  6. Enter student / os1. Expected: a window titled WelcomeForm reading "Welcome, student". Click Login again: a second, independent welcome window. Click Close on one: only that window closes.
  7. Close the Login window while a welcome window is open. Expected: the whole application exits, because LoginForm is the main form in Application.Run(new LoginForm());.
  8. Change the entry point to Application.Run(new WelcomeForm("test"));, then run. Expected: the welcome window opens first, reading "Welcome, test", with no login window; closing it ends the program. Restore Application.Run(new LoginForm()); and run once more to confirm the login window is back.

Summary

  • A form is a class inheriting from Form (through ContainerControl, ScrollableControl, Control and Component). Controls are fields holding control objects; the Properties window edits their C# properties, and code can change them at run time within their rules (Value between Minimum and Maximum, or an exception at run time).
  • The class is split into a code file (yours) and a designer file (generated InitializeComponent). The designer renders that code; a reference to a missing handler stops it opening. Read the error, search for the handler name and remove that wiring line (long or short form), or better, remove events from the Events tab (Delete, then Enter) so it never happens.
  • Double click gives the default event (Click for buttons, Load for forms); the lightning bolt tab gives all events.
  • Prepare a form in this order: rename it, set Text, Font (expand it, set Size) and Size, then add controls, name each in camelCase before double clicking it, and check TabIndex. The older type prefixes from Part 1 are also accepted when used consistently; from this lesson on, camelCase is used.
  • Windows Forms does not rearrange controls when the window is resized; design at the intended size. WPF, UWP and .NET MAUI are the XAML based alternatives; Windows Forms stays the quick choice for small utilities and prototypes, and be careful whom you show a working prototype to.
  • MenuStrip items are buttons with Click handlers: name them (exitMenuItem) before double clicking, an ampersand marks the Alt accelerator, this.Close() closes the form; StatusStrip holds a status label and a progress bar added from its down arrow button; TabControl puts pages on one form, each page a container with a Text caption.
  • Application.Run(new Form1()) chooses the startup form and the application's lifetime; new Form2(...) plus Show() opens independent windows, and a constructor parameter passes data between them.
  • TreeView nodes and PictureBox images come from the ... button of Nodes and Image, as in Part 1.
  • Keep business logic and data access in a class library and only user interface code in the form: a form is "just a class", so everything you know about C# applies.

Complete program listing

The listing below collects the final code of every handler built across the tasks in this lesson, grouped by project and marked with the task that adds it. Compare only the blocks up to whichever task you are on.

// 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();
        }
    }
}