C# Windows Forms, Part 1
By the end of this lesson you will be able to build a working "User Details" Windows Forms application with labeled controls, event handlers, a sensible tab order, a menu, and a second form that receives data from the first.
Objectives
By the end of this lesson you will be able to:
- Explain what a Windows Forms application is and what a Form and a control are in terms of C# classes and objects, and say when Windows Forms is the right desktop project type compared with WPF, UWP and .NET MAUI.
- Create a new Windows Forms project in Visual Studio and identify the role of the form's code file, the program's startup file, the Form Designer, the Toolbox and the Properties window.
- Build a "Hello World" form by placing a Label and changing its Text property.
- Place the core controls (Label, TextBox, GroupBox, ListBox, RadioButton, CheckBox, Button) on a form and set their Name and Text properties, copying a label-and-text-box pair instead of building it twice.
- Fill a ListBox with items using the String Collection Editor, a TreeView with nodes using the TreeNode Editor, and a PictureBox with an image.
- Explain what an event and an event handler are, create handlers by double-clicking a control in the designer, and repair the designer when a handler is deleted the wrong way.
- Write a ListBox SelectedIndexChanged handler that shows the selected item in a MessageBox, and a Button Click handler that reads TextBox values.
- Explain focus and tab order, and set the TabIndex property so the Tab key moves through the form in a sensible order.
- Add a MenuStrip with keyboard accelerators, close the form from a menu item, and open a second form that receives data through its constructor.
- Build and run the complete "User Details" form, and (optional) drive a StatusStrip progress bar and status label from code.
Prerequisites
- Visual Studio installed on Windows with the Windows Forms project templates available (the Desktop filter in the new-project dialog shows "Windows Forms App").
- Basic C# knowledge: classes, objects, inheritance, constructors, methods, properties, variables of type
string, and string concatenation. Windows Forms is built entirely on these ideas, so they must be familiar before you start. - Familiarity with creating and running a console project in Visual Studio (the Start button, the Solution Explorer).
Concept 1: What a Windows Forms application is
Definition. A Windows Forms (WinForms) application is a program that runs on the desktop and shows one or more windows, called forms. A form normally holds a collection of controls such as labels, text boxes, list boxes and buttons. Windows Forms is one of the original project types of .NET, and it is designed as a rapid application development environment: you draw the user interface by dragging controls onto a design surface instead of writing every line of interface code by hand.
Purpose. Windows Forms lets you get a working desktop program quickly. A brand-new project already gives you a window with a title bar, an icon, minimize, maximize and close buttons, and resizing, without writing any code. It suits a quick proof of concept, a small utility you write for yourself, and simple business forms, and many business applications still run on it.
When to use Windows Forms. Windows Forms is not the only desktop project type in .NET. WPF, UWP and .NET MAUI came later. All three describe their interfaces in a markup language called XAML, which is more complex to work with than dragging controls onto a designer, so they are not rapid application development tools in the same way. Windows Forms is nevertheless still one of the most common project types in business: it came with the first version of .NET, about twenty years ago, and it worked the same way as the drag-and-drop designer of Visual Basic 6 that companies already knew, so they built their desktop applications with it and have kept building on them since. UWP has since been deprecated. For a new desktop application today WPF is the usual recommendation; Windows Forms is the right choice for a quick proof of concept, a small tool or utility, and a one-person shop that needs the simplest possible application to maintain. Its main limitation is resizing: a form is whatever size you designed it at, so maximizing the window leaves empty space and shrinking it cuts controls off, with no automatic rearranging and no automatic scroll bars. What you see in the designer is what you get at run time.
How it works. Everything you see on a form is a C# object. A form is a class that inherits from the Form class in the System.Windows.Forms namespace. The Form base class already knows how to draw the window frame, the title bar and the buttons, so your class inherits all of that work. Form is itself the end of a chain of classes: Form inherits from ContainerControl, which inherits from ScrollableControl, which inherits from Control, which inherits from Component. Each class in the chain adds hundreds of members, and your form inherits every one of them. You can walk up the chain yourself: place the cursor on a class name in the code editor and press F12 (Go to Definition); Visual Studio opens that class, and its first line names the class it inherits from, so pressing F12 on that name takes you one step further up.
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
}Every control on the form (a Button, a TextBox, a Label) is also a class from System.Windows.Forms; when you drag a control onto the form, the designer adds a new private variable of that type to your form class and creates an instance of it. Setting a value in the Properties window sets a property of that object; double-clicking a control creates a method that handles one of that object's events.
Example. A simple login screen is a typical Windows Forms application: two labels ("User name" and "Password") describe the two text boxes next to them, the text boxes hold what the user types, and a Login button carries the code that validates the credentials when clicked. Labels describe, text boxes collect input, buttons trigger processing; you will use these three roles all through this lesson.
Concept 2: The project files and the startup flow
Definition. A new Windows Forms project contains two code files you will work with:
- The form class file, holding the code you write for the form, such as event handlers.
- The main program file, containing the startup code for the whole application: the
Mainmethod that runs first and decides which form to open.
Purpose. Knowing which file does what tells you where to look. Interface code and event handlers go in the form class file; the application entry point and the choice of starting form live in the program file.
How it works. When you press Start, Main in the program file runs, initializes the application configuration and calls Application.Run with a new instance of Form1. That form is the main form: the application lives as long as it does, and closing it closes the application. The Form1 class itself is split into two files with the partial keyword. One half is the code file you edit. The other half is generated by the designer; it contains the InitializeComponent method that creates each control, sets its properties and wires its events. The compiler joins the two halves into one class. Almost never edit the generated half by hand: the form you see in the designer is nothing more than that code rendered on screen, so a broken generated file means a designer that cannot open. One of the rare cases in which editing it by hand is the right call, removing a stale event-wiring line, is described in Concept 5 and practised in Task 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"]Example. The part of the generated program file that matters for this lesson is the Main method:
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}Line by line:
static void Main()is the entry point: the first method that runs when you press Start.ApplicationConfiguration.Initialize();sets up the application's configuration before any form is shown. The template generates this line and depends on it; leave it unchanged.Application.Run(new Form1());creates one instance ofForm1and shows it as the main form. ChangeForm1to another form class and that form becomes the starting point of the application.
The template surrounds Main with a class named Program inside the project namespace. Leave everything else in that file exactly as generated.
The generated form code file starts as:
namespace DemoApplication
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}namespace DemoApplicationgroups all the code of the project under the project name.public partial class Form1 : Formdeclares the form class.partialmeans the rest of the class lives in the generated designer file;: Formmeans it inherits from theFormbase class, which supplies the window itself.public Form1()is the constructor, run once whennew Form1()is executed at startup.InitializeComponent();calls the generated method that builds the controls. Code you add after this line runs after every control exists, which is why it is a safe place to change control properties at startup.
Concept 3: The designer, the Toolbox and the Properties window
Definition. The Form Designer is the visual surface where you build the form. The Toolbox (on the left side of Visual Studio) lists every control that can be added to a form, grouped into sections such as Common Controls, Containers and Menus & Toolbars. The Properties window shows the properties of the currently selected control and lets you change their values.
Purpose. Together these three windows replace hand-written interface code: you drag a control from the Toolbox onto the designer, then describe it in the Properties window.
How it works. To add a control, choose it in the Toolbox and drag it onto the form. To open the Properties window, right-click the control and choose Properties, or select the control and press F4 (it is also available from the View menu). Each control has a set of properties that describe it. The three you will use most are:
Name: the variable name of the control object inside the form class. You use this name in code (lstCity,txtUser).Text: the text displayed by the control (the caption of a label or button, the contents of a text box, the title of a group box, the title bar of the form).Size: the width and height of the control in pixels. Expand theSizerow in the Properties window to typeWidthandHeightdirectly, or drag the small square handle at the lower-right corner of the control on the design surface. Both do the same thing: the Properties window shows the value that the designer draws.
A value typed in the Properties window is the starting value of that property when the application launches. Code can read or change the same property while the application runs, for example label1.Text = "First name"; in the constructor or txtUser.Text in a button handler. A drop-down at the top of the Properties window lists every control on the form by name, a quick way to find controls still carrying default names such as button1.
Selecting and copying controls. Click a control to select it; hold Ctrl and click other controls to add them to the selection. Ctrl+C then Ctrl+V copies the selected controls and pastes new copies onto the form, which is the quickest way to build the second label-and-text-box pair of a form: the pasted copies keep the Text of the originals but receive new default names, so rename them right away. Click an empty part of the form and drag to draw a selection rectangle around several controls; they can then be moved as one. When several selected controls share a property, the Properties window shows that property once, and a value typed there is applied to all of them. While a control is being dragged the designer shows alignment guide lines; for a text box placed next to a label, the line that runs through the text box level with the label's text means the typed text will line up with the label's text.
Naming conventions. Two conventions are in common use for control names, and this lesson uses the first:
- Type-prefixed names: a short prefix for the control type followed by the purpose, such as
txtUser,lstCity,btnSubmit. This style was recommended in early versions of .NET and is what the walkthrough below uses. - Descriptive camelCase names: the purpose followed by the control type, such as
userTextBox,cityListBox,submitButton. TheNameproperty is the name of a private variable in the form class, so this style names it the way any other variable would be named, and it reads naturally ("the submit button"). The current .NET naming guidelines favor this style and advise against type prefixes because they read badly.
Either style is acceptable as long as you apply it consistently across the whole form and set the name before creating any event handler (Concept 5 explains why the order matters).
Setup habits. Three habits from experienced Windows Forms developers save rework later. This lesson keeps the class name Form1 so that your file matches the complete listing at the end of this lesson, but apply the habits in your own projects:
- Rename
Form1before adding controls. Select the form's file in Solution Explorer, press F2, type a meaningful name such asMainForm, and accept the prompt that offers to rename every reference to the class. The title bar of the running window still reads "Form1" afterwards, because the title comes from the form'sTextproperty, not from the class name; changeTextseparately. - Set the form's
Fontbefore adding any control. Every control you drag on takes the form's current font as its default. If you change the form font after the controls exist, the designer resizes and rearranges everything, and a control whose font you set individually keeps its own value instead of following the form. Decide the font first, resize the form back to the size you want (theSizeproperty or the corner handle), and then add controls. - Keep processing logic out of the form file. The form's code should contain only what interacts with the user interface: reading and writing control properties and handling events. Calculation, validation and data access belong in a class library: a second project in the same solution that contains only classes and no window, which the form project references and calls. The interface can then be replaced later without rewriting the logic. This lesson does not practise class libraries; the habit is listed here as a pointer for your own projects.
Example. The designer workflow you will repeat for every control in this lesson:
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"]Concept 4: The core controls
Definition. A control is a class from System.Windows.Forms placed on a form as an object. Each kind of control has one job: to describe (Label), to collect text (TextBox), to offer one choice from a list (ListBox, RadioButton), to offer any number of choices (CheckBox), to trigger processing (Button), to group other controls (GroupBox), to show a tree of items (TreeView) or to show an image (PictureBox).
Purpose. A form collects several different kinds of data from the user, and each kind has a control designed for it. Picking the right control means the user cannot enter the wrong kind of value: a radio button cannot hold two genders, a list box cannot hold a city that is not in the list. This lesson builds one form, "User Details", from all of these controls.
How it works. Every control is added the same way (Concept 3): drag it from the Toolbox, set Name if code will refer to it, then set Text. What differs is how each control is filled:
- GroupBox (Toolbox section Containers): groups related controls into a titled section. Set
Textto the section title ("User Details"). - Label: displays text to the user and is almost always paired with another control to explain what that control expects. Set
Text("Name", "Address"). - TextBox: lets the user type text. Set
Nameto something meaningful (txtUser,txtAddress). ItsTextproperty holds whatever the user typed. - ListBox: shows a list of items from which the user picks one. Set
Name(lstCity) and fill theItemsproperty through the String Collection Editor: click theItemsrow, then click the small three-dot (...) button that appears at the right end of the row, type one item per line in the editor, and press OK. - RadioButton: shows options from which the user can choose exactly one. Set
Text("Male", "Female") andName(rdMale,rdFemale). - CheckBox: shows options from which the user can choose any number. Set
Text("C#", "ASP.Net") andName(chkC,chkASP). - Button: the user clicks it to start processing the form. Set
Text("Submit") andName(btnSubmit). - TreeView: lists items in a tree of nodes, the way Windows Explorer shows folders inside folders. Its
Nodesproperty opens the TreeNode Editor: click theNodesrow, click the three-dot (...) button, press Add Root to add a top-level node and set itsText, then with that node selected press Add Child for each node that belongs under it and set theTextof each. When the program runs, the user expands the root node to see its children. - PictureBox: displays an image on the form. Its
Imageproperty opens a window with an Import button: click theImagerow, click the three-dot (...) button, press Import, choose an image file from disk in the dialog that opens, and press OK.
Example. The finished "User Details" form of this lesson is sketched at the start of Task 2: a group box titled "User Details" holds two labels with a text box next to each, a list box of three cities, a Male/Female pair of radio buttons and a C#/ASP.Net pair of check boxes, and a Submit button sits below the group box. Task 5 adds a tree view with one root and three children and a picture box to the right of the group box.
Concept 5: Events and event handlers
Definition. An event is something that happens when an action is performed: a button is clicked, an item is selected in a list box, a form is loaded. An event handler is a method that runs in response to one event.
Purpose. Events are how a form does work. A button on its own does nothing; the code in its Click handler is what validates the login, saves the data or shows a message.
How it works. Every control has a default event, the one most commonly used for that kind of control: Click for a Button, SelectedIndexChanged for a ListBox, Load for the form itself. Click is raised by a mouse click, by a finger tap on a touch screen, and by the Space key while the button has the focus. SelectedIndexChanged is raised, as its name says, when the selected index of the list changes: clicking the item that is already selected does not change the index, so the event is not raised again. Load is raised when the form is loaded; typical uses are showing a splash screen, placing the cursor in the right control, or starting a timer. Double-clicking a control in the designer creates a handler for its default event: Visual Studio opens the form's code file, adds an empty method whose name is <control name>_<event name>, and wires the event to that method in the generated designer file. The wiring is one line of the form control.Event += method, for example:
this.lstCity.SelectedIndexChanged += new System.EventHandler(this.lstCity_SelectedIndexChanged);+= attaches the method on the right to the event on the left, so that every time the list box raises SelectedIndexChanged, the method lstCity_SelectedIndexChanged runs. System.EventHandler is the type Visual Studio uses to describe a method with the (object sender, EventArgs e) signature shown in the examples below. If the generated line in your file looks slightly different, the += and the handler name are what matter. This wiring line is the line that the designer error in Rule 2 below points at. The Properties window also has a lightning-bolt button that lists every event of the selected control, for the cases where you need an event other than the default one: for example MouseClick (a mouse click only, not a tap or the Space key), MouseHover, VisibleChanged, and the drag-and-drop events. Double-clicking an event in that list creates its handler the same way.
Two practical rules follow from how this wiring works:
- Name the control before double-clicking it. The handler name is generated from the control's name at that moment. If you double-click
button1and rename it tobtnSubmitafterwards, the handler staysbutton1_Click, and a form with several buttons becomes hard to read. - Remove a handler from the designer, not by deleting the method. If you double-click by accident (for example on the form itself, which creates
Form1_Load) and then delete only the method from the form's code file, the wiring line in the generated designer file still references a method that no longer exists, and the next time the designer loads the file it refuses to open with an error page reading "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." Read the message: it names the missing method, and its Go to code link jumps to the offending+=line in the generated file. Deleting that one line and saving is one of the rare cases in which editing the generated file by hand is the right call, and the case you will meet in this lesson; the designer opens again afterwards. Better, avoid the situation: select the control, open the events list (lightning bolt) in the Properties window, select the handler name in the event's value cell and delete it. Visual Studio removes the wiring for you, and the now-unused method in the form's code file, if it is still there, can be deleted safely. Task 3 makes you do both.
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 cityExample 1: ListBox handler. After double-clicking lstCity, complete the generated method as follows:
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)is the handler Visual Studio generated.senderis the object that raised the event (here the list box).EventArgsis the class that holds details of the event; for the events in this lesson it carries nothing you need, soeis never read. You do not need to change the method name or the parameters.string text = lstCity.GetItemText(lstCity.SelectedItem);reads the currently selected item through theSelectedItemproperty oflstCity.SelectedItemgives the item as a generalobject, not as astring, so it cannot be assigned to a string variable directly;GetItemTextreturns the text the list box displays for that item, and the result is stored in the variabletext.MessageBox.Show(text);shows a small pop-up window containing the text.MessageBoxis the simplest way to show data in a Windows Forms application.
Example 2: Button handler. After double-clicking 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)is the generated Click handler for the button namedbtnSubmit.string name = txtUser.Text;reads whatever the user typed in the Name text box. The value of a text box is itsTextproperty.string address = txtAddress.Text;does the same for the Address text box.MessageBox.Show("Name: " + name + ", Address: " + address);concatenates the two values into one message and shows it.
The same message can be written with string interpolation: MessageBox.Show($"Name: {txtUser.Text}, Address: {txtAddress.Text}");. A $ before the string lets you place expressions inside { }. Pitfall: {txtUser} instead of {txtUser.Text} compiles and runs, but the message shows the type name of the object followed by its text, for example "System.Windows.Forms.TextBox, Text: Ahmed" instead of "Ahmed", because you passed the whole object instead of its Text property and the object's ToString method was used to turn it into a string.
Concept 6: Focus and tab order
Definition. At any moment exactly one control on the active form has the focus: it is the control that receives what you type. A text box with the focus shows a blinking cursor; a button with the focus shows a highlighted outline. The tab order is the sequence in which the Tab key moves the focus from one control to the next.
Purpose. A user who cannot or does not want to use a mouse fills in the form with the keyboard alone: type, press Tab, type, press Tab, then press Space on the button. That only works if Tab visits the controls in the order they are laid out, and only if the controls that cannot be filled in are skipped.
How it works. Two properties in the Properties window control this:
TabIndex: a number that gives the control's position in the tab order. The designer assigns these numbers in the order you added the controls, which is why the order is often wrong after you rearrange a form. Only the ordering matters, not the values themselves: when Tab is pressed, the form looks for the control that is both able to take focus and next in line byTabIndex. Values need not be consecutive, so 10, 20, 30 works as well as 1, 2, 3 and leaves room to insert a control later without renumbering the rest.TabStop: true when the control can receive focus by tabbing. A Label cannot be tabbed to at all, so itsTabIndexhas no visible effect and Tab skips it.
A set of radio buttons is one choice (Concept 4), and Tab treats it as one stop: it does not visit each button of the set in turn. The arrow keys move the focus between the buttons of the set. After the last control in the order, Tab returns to the first one. While a button has the focus, pressing Space clicks it, so the same message box appears as with a mouse click.
Example. A form with a label, two text boxes and a button. The user starts in the first 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"]The label (TabIndex 0) never appears in the cycle because it cannot receive focus.
Concept 7: Multiple forms
Definition. A form is a class, so an application can have as many form classes as it needs and can create any number of instances of each. A second form is added to the project as a new class (Form2) with its own designer.
Purpose. Real applications open a details window, a settings window or a message window from the main window. Understanding that each window is an object you create with new is what makes this possible.
How it works. Three facts follow from "a form is a class":
Form2 frm = new Form2(); frm.Show();creates an instance ofForm2and displays it.Showis a method inherited fromForm. Running the same two lines again creates a second, independent window; typing in one instance does not change another, because each instance has its own control objects.- The form passed to
Application.Runin the program file is the main form and its lifetime is the lifetime of the application. Closing the main form closes the whole application, even when other forms are still open. Closing a secondary form closes only that window. - A form's constructor can take parameters like any other constructor. Change
public Form2()topublic Form2(string message)and usemessageinside the constructor (for example to set a label'sText). After that,new Form2()no longer compiles: the caller must pass a value,new Form2("Hello from Form1"), which is how data travels from one form to another.
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 tooExample. A Form2 that shows the text it receives:
namespace DemoApplication
{
public partial class Form2 : Form
{
public Form2(string message)
{
InitializeComponent();
lblMessage.Text = message;
}
}
}public Form2(string message)replaces the empty constructor generated by the template. Every caller now has to supply a string.InitializeComponent();still runs first, so the label exists before the next line touches it.lblMessage.Text = message;copies the received string into the label namedlblMessageonForm2, which is how the text becomes visible.
Concept 8: Menus, keyboard accelerators and the status strip
Definition. A MenuStrip is the menu bar of a window (File, Help). Each entry in it is a menu item that behaves like a button: it has a Text, a Name and a Click event. A StatusStrip is the bar along the bottom of a window that reports progress, the way Visual Studio's own bottom bar shows that a build is running.
Purpose. Menus give the user commands that do not deserve a button on the form, and they can be operated entirely from the keyboard, which matters for accessibility and for users who prefer not to reach for the mouse. A status strip tells the user that work is in progress without interrupting them with a message box.
How it works. Drag a MenuStrip from the Menus & Toolbars section of the Toolbox onto the form. A menu bar appears on the form, and the strip's component icon appears in the tray below the design surface, which is where the strip can be selected for editing. Click the empty menu entry on the menu bar and type the menu text; further empty entries appear to its right (for more menus) and below it (for the items of the current menu).
- Putting
&before a letter in a menu item'sTextmakes that letter the accelerator: the&itself is not displayed, the letter is underlined, and pressing Alt followed by the letter activates the item.&Fileopens with Alt, F;E&xitunder it runs with X. Two items in the same menu cannot share a letter, but a submenu may reuse a letter from its parent because only the submenu is active at that moment. - Follow the Windows standard where one exists: File with Exit at the bottom, Help with About. Users already know Alt, F, X closes a program.
- Double-clicking a menu item creates its
Clickhandler exactly as for a button, so name the item first (Concept 5, Rule 1). Inside the handler,this.Close();closes the current form instance; on the main form that ends the application. - A StatusStrip is dragged onto the form the same way and holds items such as a progress bar (
ToolStripProgressBar) and a status label (ToolStripStatusLabel), added through the small add-item drop-down on the strip. Code updates them like any other control, through theirValueandTextproperties. A progress bar has aMinimumand aMaximum, 0 and 100 by default; settingValueoutside that range stops the program with an exception (Example 2).
Example 1: menu handlers. Handlers for an Exit item named mnuExit and an About item named 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)is the handler generated by double-clicking the Exit item after naming itmnuExit.this.Close();closes the form the handler belongs to. BecauseForm1is the main form, the application exits.private void mnuAbout_Click(object sender, EventArgs e)is the handler for the About item.MessageBox.Show("User Details Form, Lab 1");shows the About text in a pop-up, the simplest possible About box.
Example 2: updating a status strip. Two lines added to a button's Click handler, for a progress bar named prgStatus and a status label named lblStatus:
prgStatus.Value += 5;
lblStatus.Text = "Working...";prgStatus.Value += 5;adds 5 to the progress bar's currentValue, so the filled part of the bar grows by 5 out of 100 on each click. WhenValueis already 100 this line tries to set 105, which is aboveMaximum, and the program stops with an exception saying that 105 is not a valid value forValue.lblStatus.Text = "Working...";replaces the status label's text, exactly as settingTexton any other label.
Tasks
The complete program for this whole lesson is in the "Complete program listing" section at the end. It is annotated with the task number that adds each block, so you can compare your file at any point: only the blocks marked with your current task number or lower should be present in your file.
Task 1: Create the project, explore it, and show Hello World
- Launch Visual Studio and choose Create a new project (in older versions: File > New > Project).
- In the project dialog, filter or select the Windows / Desktop project types, then choose Windows Forms App (in older versions it is listed as Windows Forms Application). If two entries are shown, one for .NET and one for .NET Framework, pick the .NET one. .NET Framework is the older platform; the .NET entry is the current one, and it comes with a newer, rewritten Form Designer. The steps in this lesson were written for the .NET entry. Because the designer was rewritten, an occasional glitch is normal: if the designer fails to render the form at some point, close the designer tab and reopen it by double-clicking the form's file.
- Name the project
DemoApplication, choose a location on disk, and click Create (or OK). If asked for a framework version, accept the default. - In Solution Explorer, confirm that the solution contains the form's code file and the program file. Expand the form's file and note the generated designer file beneath it.
- Double-click the form's file to open the Form Designer. Open the Toolbox (left side, or View > Toolbox) and the Properties window (right-click the form and choose Properties, or press F4).
- Open the program file and find the line
Application.Run(new Form1());. - Press Start without changing anything.
Expected output: an empty window titled "Form1" appears. It can be moved, resized, maximized, minimized and closed with the X button. Closing it ends the program and returns Visual Studio to design mode.
- Optional exploration: right-click the form's file in Solution Explorer and choose View Code. Place the cursor on the word
Forminpublic partial class Form1 : Formand press F12.
Expected output: Visual Studio opens the definition of the Form class in the System.Windows.Forms namespace, a long list of members your form inherits. The first line of the class reads public class Form : ContainerControl; press F12 on ContainerControl to move one step up the chain shown in Concept 1, and close these tabs when you have seen enough.
- Return to the Form Designer for
Form1. In the Toolbox, find Label under Common Controls and drag it onto the form. - With the label selected, open the Properties window and locate the Text property. Replace
label1withHello Worldand press Enter. - Observe that the label on the design surface now reads "Hello World".
- Press Start.
Expected output: the form appears with the text "Hello World" displayed at the position where you dropped the label. Stop the program and delete the label (select it in the designer and press Delete) before the next task, so the form is empty again.
Task 2: Build the User Details form layout
Build the form in this order. For every control that code will refer to (text boxes, list box, radio buttons, check boxes, button) set Name first and Text second; labels and the group box only need Text, because no code refers to them. The target layout is:
+-- Form1 -----------------------------------------------+
| |
| +-- User Details ---------------------------------+ |
| | | |
| | Name [_________________] +-----------+ | |
| | | Mumbai | | |
| | Address [_________________] | Bangalore | | |
| | | Hyderabad | | |
| | +-----------+ | |
| | ( ) Male ( ) Female | |
| | | |
| | [ ] C# [ ] ASP.Net | |
| | | |
| +-------------------------------------------------+ |
| |
| [ Submit ] |
| |
+--------------------------------------------------------+The sketch shows only the text that is visible on the form. Control names (txtUser, lstCity and the others) are given in the steps below and never appear on the form.
- Drag a GroupBox (Toolbox section Containers) onto the form. In the Properties window expand Size and set Width to
360and Height to320; you can also drag the corner handle to resize it later. If the group box does not fit, select the form and enlarge it the same way through its own Size property. Set the group box's Text toUser Details. - Drag one Label into the group box, near the top-left. Set its Text to
Name. - Drag one TextBox into the group box, to the right of the label. Set its Name to
txtUser. Use the alignment guide lines the designer shows while dragging: a line through the text box level with the label's text means the typed text will line up with the label text. - Copy the pair (Concept 3, "Selecting and copying controls"): click the label, hold Ctrl and click the text box so both are selected, press Ctrl+C, then Ctrl+V. Drag the pasted pair below the first one. The pasted copies receive default names, so rename them now: set the copied label's Text to
Addressand the copied text box's Name totxtAddress. - Drag a ListBox into the group box, to the right of the text boxes. Set its Name to
lstCity. Click the Items row, click the small three-dot (...) button at the right end of the row to open the String Collection Editor, enterMumbai,BangaloreandHyderabadon three separate lines, and click OK. - Drag two RadioButton controls into the group box below the text boxes, side by side. Set the first one's Name to
rdMaleand Text toMale; set the second one's Name tordFemaleand Text toFemale. - Drag two CheckBox controls into the group box below the radio buttons, side by side. Set the first one's Name to
chkCand Text toC#; set the second one's Name tochkASPand Text toASP.Net. - Drag a Button onto the form below the group box (outside it). Set its Name to
btnSubmitand Text toSubmit. - Open the drop-down at the top of the Properties window and confirm that no text box, list box, radio button, check box or button still carries a default name such as
textBox1. Labels and the group box may keep their default names (label1,groupBox1and whatever the pasted label received). - Compare your designer against the sketch above, then press Start.
Expected output: a window with a "User Details" section containing the Name and Address labels and text boxes, a list showing Mumbai, Bangalore and Hyderabad, the Male and Female radio buttons, the C# and ASP.Net check boxes, and a Submit button below the section. You can type in the text boxes, select a city, choose only one of Male or Female, and tick both check boxes at once. The Submit button does nothing yet.
Task 3: Handle the ListBox and Submit events, then break and repair the designer
- In the Form Designer, double-click the
lstCitylist box. Visual Studio opens the form's code file and adds an empty method namedlstCity_SelectedIndexChanged. - Inside the method, add the two lines from Concept 5, Example 1:
string text = lstCity.GetItemText(lstCity.SelectedItem);
MessageBox.Show(text);- Expand the form's file in Solution Explorer, open the generated designer file, and search for
lstCity_SelectedIndexChanged. Read the wiring line and close the file without editing it.
Expected output: one line inside InitializeComponent that contains lstCity.SelectedIndexChanged += and ends with lstCity_SelectedIndexChanged, in the form this.lstCity.SelectedIndexChanged += new System.EventHandler(this.lstCity_SelectedIndexChanged);. This is the += wiring described in Concept 5.
- Press Start and click each city in turn. Then click the city that is already selected a second time.
Expected output: each click on a different city shows a message box containing exactly that city's name, for example "Bangalore". Clicking the message box's OK button closes it and returns to the form. Clicking the already-selected city shows no message box: the selected index did not change, so SelectedIndexChanged was not raised (Concept 5).
- Stop the program. In the Form Designer, double-click
btnSubmit. Visual Studio adds an empty method namedbtnSubmit_Clickto the form's code file. - Inside the method, add the code from Concept 5, Example 2:
string name = txtUser.Text;
string address = txtAddress.Text;
MessageBox.Show("Name: " + name + ", Address: " + address);- Compare your code with the "Complete program listing" section at the end of this lesson. The two handlers marked Task 3 should match yours line for line. The constructor in that listing has one extra line marked Task 4 that you have not written yet, and the listing also contains three handlers marked Task 6; ignore those blocks and their comments for now.
- Press Start. Type
Ahmedin the Name box andMansourain the Address box, then click Submit. - Click OK, clear both boxes, and click Submit again.
Expected output: with "Ahmed" and "Mansoura" typed, the message box reads "Name: Ahmed, Address: Mansoura". With both boxes empty, the message box reads "Name: , Address: " because the Text property of an empty text box is an empty string.
Break and repair the designer (Concept 5, Rule 2). The next steps break the designer on purpose, so that you meet the error once here rather than alone in a project of your own.
- Stop the program. In the Form Designer, double-click an empty part of the form itself (not a control). Visual Studio opens the form's code file and adds an empty method
private void Form1_Load(object sender, EventArgs e). - Delete that whole method from the code file (only the method, nothing else) and save the file.
- Close the designer tab, then double-click the form's file in Solution Explorer to reopen the designer. If the form still renders, close the tab and reopen it once more; the error appears when the designer reloads the file.
Expected output: the designer does not show the form. In its place is an error page reading "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 is a line number in the generated designer file), with a Go to code link beneath it.
- Click Go to code. Visual Studio opens the generated designer file at the line
this.Load += new System.EventHandler(this.Form1_Load);insideInitializeComponent, underlined with a red squiggle becauseForm1_Loadno longer exists. - Delete that one line, save the file, and double-click the form's file in Solution Explorer again.
Expected output: the designer opens and shows the form exactly as before. The form's code again matches the Task 3 blocks in the complete listing.
- Now remove a handler the right way. Double-click an empty part of the form again, so that
Form1_Loadis created again. Return to the designer, select the form, click the lightning-bolt button at the top of the Properties window, and find the Load row: its value cell readsForm1_Load. - Click the value cell, select the text
Form1_Loadand delete it, then click another row of the Properties window. Open the form's code file: if the emptyForm1_Loadmethod is still there, delete it now; it is no longer wired to anything. - Save all files, close the designer tab and reopen it.
Expected output: the designer opens normally. Searching the generated designer file for Form1_Load finds nothing, and the form's code matches the Task 3 blocks in the complete listing.
Task 4: Change the title from code and fix the tab order
- Open the form's code file. In the constructor, after
InitializeComponent();, add:
this.Text = "User Details Form";this is the current form object and Text is the same property you saw in the Properties window; setting it here changes the window title at startup, overriding the designer value.
- Press Start and confirm the window title. While the program runs, click inside the Name box and press Tab repeatedly. Write down the order in which the focus moves (blinking cursor in a text box, highlighted item in the list, highlighted outline on a button or option).
- Stop the program. Set the TabIndex property of each control in the designer to the values in this table, which number the controls in the order you want Tab to visit them, starting with the group box that holds most of them:
| Control | TabIndex |
|---|---|
| groupBox1 | 0 |
| txtUser | 1 |
| txtAddress | 2 |
| lstCity | 3 |
| rdMale | 4 |
| rdFemale | 5 |
| chkC | 6 |
| chkASP | 7 |
| btnSubmit | 8 |
The labels can keep whatever value they have: Tab never stops on a label. The values only need to increase in the intended order; 10, 20, 30 would work equally well and leave gaps for controls added later.
- Press Start, click in the Name box, press Tab through the whole form without typing anything, and press Space while Submit has the focus. Click OK, then press Tab once more.
Expected output: the window title reads "User Details Form" even though the Properties window still shows the old value, because the constructor line runs after the designer values are applied. The focus visits, in this order: Name, Address, the city list, the Male/Female pair (one stop, because a set of radio buttons is one choice; see Concept 6), C#, ASP.Net, Submit. Pressing Space while Submit has the focus shows the message box "Name: , Address: " (both boxes are empty on this run), the same box a mouse click produces. After OK, the extra Tab returns the focus to the Name box. If your run visits the controls in a different order, recheck each value against the table.
Task 5: Add a TreeView and a PictureBox
- Widen the form if needed (select the form, expand Size, increase Width), so there is empty space to the right of the group box.
- Drag a TreeView (Common Controls) onto the form to the right of the group box. Set its TabIndex to
9, so that it comes after Submit in the tab order. - In the Properties window click the Nodes row, then the three-dot (...) button, to open the TreeNode Editor. Click Add Root, set the new node's Text to
Root. - With
Rootselected, click Add Child and set the child's Text toLabel. Repeat Add Child twice more forButtonandCheckbox, soRoothas three children. Click OK. - Drag a PictureBox (Common Controls) onto the form below the tree view.
- Click the Image row, then the three-dot (...) button. In the window that opens click Import, choose any image file on your computer (for example a .png or .jpg), and click OK.
- Press Start. Expand the tree, then click in the Name box and Tab through the form once.
Expected output: the tree view shows a collapsed node "Root" with a small expander next to it; clicking the expander reveals the three child nodes "Label", "Button" and "Checkbox". The picture box shows the chosen image. The User Details section and the Submit button still behave as in Task 4, and the tab cycle gains one stop at the end: Name, Address, the city list, the Male/Female pair, C#, ASP.Net, Submit, the tree view, then back to Name. Tab skips the picture box, which only displays an image, just as it skips labels.
Task 6: Add a menu and open a second form
- In Solution Explorer, right-click the
DemoApplicationproject, choose Add > New Form (the exact label depends on the version: Form (Windows Forms) or Windows Form), keep the nameForm2.cs, and click Add. The designer for the new form opens. - Drag a Label onto
Form2. Set its Name tolblMessageand its Text toMessage. - Right-click the new form's file in Solution Explorer and choose View Code. Change the constructor so the file matches Concept 7, Example: the constructor becomes
public Form2(string message)and the linelblMessage.Text = message;followsInitializeComponent();. - Return to the
Form1designer. In the Toolbox open Menus & Toolbars and drag a MenuStrip onto the form. A menu bar appears on the form. Click the empty entry on the menu bar, type&Fileand press Enter. In the empty entry that appears below File typeShow &Details, press Enter, then typeE&xitand press Enter. Click the empty entry to the right of File, type&Help, press Enter, and in the entry below it type&About. - Name the three items you will write code for: select Show Details and set Name to
mnuShowDetails; select Exit and set Name tomnuExit; select About and set Name tomnuAbout. File and Help may keep their default names. - Double-click Show Details and complete the 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(...)creates a new instance ofForm2, passing the same "Name:, Address: " string the Submit button builds, which the Form2constructor copies into its label.frm.Show();displays that instance as a separate window.
- Double-click Exit and add
this.Close();inside the handler; double-click About and addMessageBox.Show("User Details Form, Lab 1");inside the handler (Concept 8, Example 1). - Compare your code with the "Complete program listing" section at the end of this lesson. It should now match that listing completely, comments aside.
- Press Start. Type
Ahmedin the Name box andMansourain the Address box. Press and release Alt, then F, then D. - Change the Name box to
Saraand press Alt, F, D again. - Press Alt, H, A.
- Close
Form1with its X button while theForm2windows are still open. - Press Start again and press Alt, F, X.
Expected output: in the designer the menu shows File and Help with the letters F, D, x, H and A underlined and no & visible. At run time, Alt, F, D opens a Form2 window whose label reads "Name: Ahmed, Address: Mansoura". The second Alt, F, D opens another window reading "Name: Sara, Address: Mansoura" while the first window still reads "Ahmed": two instances, two sets of values. Alt, H, A shows a message box reading "User Details Form, Lab 1". Closing Form1 closes every Form2 window as well and returns Visual Studio to design mode, because Form1 is the main form. On the second run, Alt, F, X ends the program without touching the mouse.
Optional extension: a status strip. The complete program listing at the end of this lesson stops before this block; do it after step 13 if time allows.
- In the Toolbox section Menus & Toolbars, drag a StatusStrip onto the form. A bar appears along the bottom of the form.
- Click the small add-item drop-down on the strip and add a ProgressBar; click it again and add a StatusLabel. Select the progress bar and set its Name to
prgStatus; select the label, set its Name tolblStatusand its Text toReady. - Open the form's code file and add the two lines from Concept 8, Example 2 at the top of
btnSubmit_Click, before the existing three lines:
prgStatus.Value += 5;
lblStatus.Text = "Working...";- Press Start and click Submit repeatedly, clicking OK on each message box.
Expected output: on the first click the label at the bottom changes from "Ready" to "Working..." and the bar fills by 5; every further click adds 5, and after 20 clicks the bar is full. On the 21st click the program stops in Visual Studio with an exception reporting that the value 105 is not valid for Value, which must lie between the minimum and the maximum: the bar's range is 0 to 100 and 105 is outside it. Stop the program.
Summary
- A Windows Forms application is a desktop program made of forms; a form is a class that inherits from
Form(and through it fromContainerControl,ScrollableControl,ControlandComponent), and every control on it is an object of a class fromSystem.Windows.Forms. F12 on a class name shows what it inherits. - Windows Forms is the rapid, drag-and-drop desktop project type; WPF, UWP and .NET MAUI use XAML and are less rapid. Windows Forms fits proofs of concept, small utilities and simple business forms, and does not rearrange controls when the window is resized.
- The program file holds
Main, which callsApplication.Run(new Form1()); the form passed there is the main form, and closing it ends the application even if other forms are open. - The form's code file is the editable half of a partial class; the generated designer file is the other half, containing
InitializeComponent. Editing it by hand is right only in rare cases, such as removing a dangling+=event-wiring line after a handler was deleted the wrong way. - The Toolbox supplies controls, the designer places them, and the Properties window sets their properties.
Nameis the variable name used in code;Textis what the user sees;Sizeis width and height. Ctrl+click selects several controls, Ctrl+C and Ctrl+V copy them. Use one naming convention consistently, name controls before creating handlers, and set the form's font before adding controls. - Values set in the Properties window are starting values; code can read or change the same properties at runtime (
txtUser.Text,this.Text = ...). - GroupBox groups, Label describes, TextBox collects text, ListBox offers a list (filled with the String Collection Editor), RadioButton picks one option, CheckBox picks many, Button triggers processing, TreeView shows nodes (filled with the TreeNode Editor), PictureBox shows an image (Image > Import).
- An event is an action that happens on a control; double-clicking a control creates a handler for its default event (
Clickfor buttons,SelectedIndexChangedfor list boxes,Loadfor the form), wired withcontrol.Event += handlerin the designer file. Remove unwanted handlers from the events list rather than by deleting the method; if the designer breaks, read the error and use Go to code. lstCity.GetItemText(lstCity.SelectedItem)gives the selected list item as text;txtUser.Textgives what the user typed;MessageBox.Show(...)displays a result.TabIndexorders the Tab key's stops (only the ordering matters, gaps are fine); labels are never stops; a set of radio buttons is one stop; Space clicks a focused button.new Form2(message)plusShow()opens an independent window and passes data through the constructor;this.Close()closes the current form.- In a menu item's
Text,&before a letter makes it an Alt-key accelerator; one letter per item within a menu; follow the File > Exit and Help > About conventions. A StatusStrip's progress bar and label are updated throughValueandText, andValuemust stay withinMinimumandMaximum.
Complete program listing
This is the finished code-behind file for the "User Details" form built across Tasks 1 through 6. Each block is marked with the task that adds it, so at any point in the lesson only the blocks up to your current task should be present in your own file.
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");
}
}
}