Where the WinForms code goes
The main goal on this page is simple: you should know where each part belongs.
For almost all tasks here, Program.cs stays standard, the controls are created in the Designer, and the logic goes into Form1.cs.
Program.cs
Keep the default startup file unless you change the first form name.
Designer
Create controls in the Designer and set the Name values exactly.
Form code
Paste the logic into Form1.cs or the file named in the section.
FILE: Program.cs
using System;
using System.Windows.Forms;
namespace StudentForms;
internal static class Program
{
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
}
}
Interval checker form
This is the form version of the classic 1 to 100 task. The user types one value and clicks a button.
Build steps
- Create a new Windows Forms App project.
- Put the three controls on Form1 and set their Name values exactly.
- Double-click the button or attach the click event in code.
- Paste the logic into Form1.cs.
- Run the form and test values inside and outside the interval.
Controls to add
- TextBox
txtValue - Button
btnCheck - Label
lblResult
Quick test
- Type 50. The label should say the value is inside the interval.
- Type 150 or text. The label should show a clear error or outside message.
FILE: Form1.cs
using System;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
btnCheck.Click += btnCheck_Click;
}
private void btnCheck_Click(object? sender, EventArgs e)
{
if (!int.TryParse(txtValue.Text, out int value))
{
lblResult.Text = "Please type a whole number.";
return;
}
lblResult.Text = value >= 1 && value <= 100
? "The value is inside 1 to 100."
: "The value is outside 1 to 100.";
}
}
Mouse cursor tracker with pixel and millimeter coordinates
Use a panel as the drawing area. When the mouse moves, update two labels.
Build steps
- Add one large panel to the form. This is the area you will track.
- Add two labels under the panel.
- Paste the code into Form1.cs.
- Move the mouse inside the panel and watch both labels update.
Controls to add
- Panel
pnlTrack - Label
lblPixels - Label
lblMillimeters
Quick test
- Move to the top-left corner. The pixel coordinates should be close to 0, 0.
- Move across the panel and check that the millimeter values also change.
FILE: Form1.cs
using System;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
private const double MmPerPixel = 0.264583;
public Form1()
{
InitializeComponent();
pnlTrack.MouseMove += pnlTrack_MouseMove;
}
private void pnlTrack_MouseMove(object? sender, MouseEventArgs e)
{
lblPixels.Text = $"Pixels: X = {e.X}, Y = {e.Y}";
lblMillimeters.Text =
$"Millimeters: X = {e.X * MmPerPixel:0.00}, Y = {e.Y * MmPerPixel:0.00}";
}
}
Character classifier: digit, vowel, consonant or special symbol
The user types one character. The form decides what kind of character it is.
Build steps
- Add one small text box and set
MaxLengthto 1. - Add a button and one result label.
- Paste the event code into Form1.cs.
- Run and test digits, vowels, consonants, and symbols.
Controls to add
- TextBox
txtCharacter - Button
btnClassify - Label
lblResult
Quick test
- Type A. The result should be vowel.
- Type 7. The result should be digit.
- Type #. The result should be special symbol.
FILE: Form1.cs
using System;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
btnClassify.Click += btnClassify_Click;
}
private void btnClassify_Click(object? sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtCharacter.Text))
{
lblResult.Text = "Type one character first.";
return;
}
char ch = txtCharacter.Text[0];
char lower = char.ToLower(ch);
if (char.IsDigit(ch))
{
lblResult.Text = "Digit";
}
else if ("aeiouy".Contains(lower))
{
lblResult.Text = "Vowel";
}
else if (char.IsLetter(ch))
{
lblResult.Text = "Consonant";
}
else
{
lblResult.Text = "Special symbol";
}
}
}
Quadratic equation solver form
This task is easier to use as a form because the three inputs stay visible all the time.
Build steps
- Add three text boxes for a, b, and c.
- Add one button and one larger label for the result.
- Paste the code into Form1.cs.
- Test one case with two roots, one with one root, and one with no real roots.
Controls to add
- TextBox
txtA - TextBox
txtB - TextBox
txtC - Button
btnSolve - Label
lblResult
Quick test
- For 1, -3, 2 the roots should be 1 and 2.
- For 1, 2, 1 there should be one root: -1.
- For 1, 0, 1 there should be no real roots.
FILE: Form1.cs
using System;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
btnSolve.Click += btnSolve_Click;
}
private void btnSolve_Click(object? sender, EventArgs e)
{
if (!double.TryParse(txtA.Text, out double a) ||
!double.TryParse(txtB.Text, out double b) ||
!double.TryParse(txtC.Text, out double c))
{
lblResult.Text = "Please enter valid numbers.";
return;
}
if (a == 0)
{
lblResult.Text = "a must not be 0.";
return;
}
double d = b * b - 4 * a * c;
if (d < 0)
{
lblResult.Text = "No real roots.";
}
else if (d == 0)
{
double x = -b / (2 * a);
lblResult.Text = $"One root: x = {x:0.###}";
}
else
{
double x1 = (-b + Math.Sqrt(d)) / (2 * a);
double x2 = (-b - Math.Sqrt(d)) / (2 * a);
lblResult.Text = $"Two roots: x1 = {x1:0.###}, x2 = {x2:0.###}";
}
}
}
Driving range / reach distance checker
This version uses fuel, consumption, and trip distance. It is a practical form task with three inputs and one answer.
Build steps
- Add the three inputs and label them clearly.
- Add one button and one result label.
- Paste the code into Form1.cs.
- Run the app and test both reachable and unreachable trips.
Controls to add
- TextBox
txtFuelfor liters left - TextBox
txtConsumptionfor liters per 100 km - TextBox
txtTripDistancefor target distance - Button
btnCheck - Label
lblResult
Quick test
- 50 liters, 5 l/100 km, and 400 km should be reachable.
- 20 liters, 8 l/100 km, and 400 km should not be reachable.
FILE: Form1.cs
using System;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
btnCheck.Click += btnCheck_Click;
}
private void btnCheck_Click(object? sender, EventArgs e)
{
if (!double.TryParse(txtFuel.Text, out double fuel) ||
!double.TryParse(txtConsumption.Text, out double consumption) ||
!double.TryParse(txtTripDistance.Text, out double tripDistance))
{
lblResult.Text = "Please enter valid numbers.";
return;
}
if (consumption <= 0)
{
lblResult.Text = "Consumption must be greater than 0.";
return;
}
double range = fuel / consumption * 100;
lblResult.Text = tripDistance <= range
? $"Reachable. Remaining range is about {range:0.#} km."
: $"Not reachable. Current range is about {range:0.#} km.";
}
}
Hangman word guessing game
Keep the task small: one hidden word, one guessed-letter box, one button, and two labels.
Build steps
- Put the two labels at the top so the word and status stay visible.
- Add the guess text box and button under them.
- Paste the code into Form1.cs.
- Start with one hard-coded word. Add random words later if you want.
Controls to add
- Label
lblWord - Label
lblStatus - TextBox
txtGuesswithMaxLength = 1 - Button
btnTry
Quick test
- Guess letters from the secret word and check that they appear in the mask.
- Guess a wrong letter and confirm the remaining attempts go down.
FILE: Form1.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
private const string SecretWord = "PROGRAM";
private readonly HashSet<char> guessedLetters = new();
private int attemptsLeft = 6;
public Form1()
{
InitializeComponent();
btnTry.Click += btnTry_Click;
UpdateView();
}
private void btnTry_Click(object? sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtGuess.Text))
{
lblStatus.Text = "Type one letter.";
return;
}
char letter = char.ToUpper(txtGuess.Text[0]);
txtGuess.Clear();
if (!char.IsLetter(letter))
{
lblStatus.Text = "Letters only.";
return;
}
if (!guessedLetters.Add(letter))
{
lblStatus.Text = "You already tried that letter.";
return;
}
if (!SecretWord.Contains(letter))
{
attemptsLeft--;
}
UpdateView();
}
private void UpdateView()
{
string masked = string.Join(" ",
SecretWord.Select(ch => guessedLetters.Contains(ch) ? ch : '_'));
lblWord.Text = masked;
if (SecretWord.All(guessedLetters.Contains))
{
lblStatus.Text = "You won.";
btnTry.Enabled = false;
}
else if (attemptsLeft <= 0)
{
lblStatus.Text = $"You lost. Word: {SecretWord}";
btnTry.Enabled = false;
}
else
{
lblStatus.Text = $"Attempts left: {attemptsLeft}";
}
}
}
ASCII converter
This is a good form task because the user can try both directions without restarting the app.
Build steps
- Add both text boxes so the user can fill either side first.
- Add two buttons, one for each conversion direction.
- Paste the code into Form1.cs.
- Test both a character input and a number input.
Controls to add
- TextBox
txtCharacter - TextBox
txtNumber - Button
btnFromChar - Button
btnFromNumber - Label
lblResult
Quick test
- Type A and click the character button. The code should be 65.
- Type 66 and click the number button. The character should be B.
FILE: Form1.cs
using System;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
btnFromChar.Click += btnFromChar_Click;
btnFromNumber.Click += btnFromNumber_Click;
}
private void btnFromChar_Click(object? sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtCharacter.Text))
{
lblResult.Text = "Type one character first.";
return;
}
char ch = txtCharacter.Text[0];
txtNumber.Text = ((int)ch).ToString();
lblResult.Text = $"Character {ch} = {(int)ch}";
}
private void btnFromNumber_Click(object? sender, EventArgs e)
{
if (!int.TryParse(txtNumber.Text, out int code))
{
lblResult.Text = "Type a valid number.";
return;
}
txtCharacter.Text = ((char)code).ToString();
lblResult.Text = $"Code {code} = {(char)code}";
}
}
Train crossing signal
This task is good for timer practice. Use simple colored panels or picture boxes.
Build steps
- Add three small panels or picture boxes for the lights.
- Add one button to start and stop the signal.
- Paste the code into Form1.cs.
- Click the button and watch the red lights alternate.
Controls to add
- Panel
pnlRedLeft - Panel
pnlRedRight - Panel
pnlWhite - Button
btnStart
Quick test
- The red lights should alternate, not blink at the same time.
- The white light should show only when the signal is stopped.
FILE: Form1.cs
using System;
using System.Drawing;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
private readonly Timer cycleTimer = new() { Interval = 400 };
private bool leftLightOn;
public Form1()
{
InitializeComponent();
btnStart.Click += btnStart_Click;
cycleTimer.Tick += cycleTimer_Tick;
ShowStoppedState();
}
private void btnStart_Click(object? sender, EventArgs e)
{
if (cycleTimer.Enabled)
{
cycleTimer.Stop();
ShowStoppedState();
btnStart.Text = "Start signal";
}
else
{
cycleTimer.Start();
btnStart.Text = "Stop signal";
}
}
private void cycleTimer_Tick(object? sender, EventArgs e)
{
leftLightOn = !leftLightOn;
pnlRedLeft.BackColor = leftLightOn ? Color.Red : Color.DarkRed;
pnlRedRight.BackColor = leftLightOn ? Color.DarkRed : Color.Red;
pnlWhite.BackColor = Color.DarkGray;
}
private void ShowStoppedState()
{
pnlRedLeft.BackColor = Color.DarkRed;
pnlRedRight.BackColor = Color.DarkRed;
pnlWhite.BackColor = Color.WhiteSmoke;
}
}
Save, load and sort items in a ListBox
This task gives you a very practical small data manager with only a few controls.
Build steps
- Create the text box, list box, and four buttons.
- Paste the code into Form1.cs.
- Add a few items and test save, load, and sort in that order.
Controls to add
- TextBox
txtItem - ListBox
listBoxItems - Button
btnAdd - Button
btnSave - Button
btnLoad - Button
btnSort
Quick test
- After Save, the file should appear next to the app.
- After Load, the list should show the saved items again.
- After Sort, the list should be alphabetical.
FILE: Form1.cs
using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
private const string FileName = "items.txt";
public Form1()
{
InitializeComponent();
btnAdd.Click += btnAdd_Click;
btnSave.Click += btnSave_Click;
btnLoad.Click += btnLoad_Click;
btnSort.Click += btnSort_Click;
}
private void btnAdd_Click(object? sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(txtItem.Text))
{
return;
}
listBoxItems.Items.Add(txtItem.Text.Trim());
txtItem.Clear();
txtItem.Focus();
}
private void btnSave_Click(object? sender, EventArgs e)
{
string[] items = listBoxItems.Items.Cast<string>().ToArray();
File.WriteAllLines(FileName, items);
}
private void btnLoad_Click(object? sender, EventArgs e)
{
listBoxItems.Items.Clear();
if (File.Exists(FileName))
{
listBoxItems.Items.AddRange(File.ReadAllLines(FileName));
}
}
private void btnSort_Click(object? sender, EventArgs e)
{
string[] items = listBoxItems.Items.Cast<string>().OrderBy(item => item).ToArray();
listBoxItems.Items.Clear();
listBoxItems.Items.AddRange(items);
}
}
IP address viewer with progress bar
This project feels more alive than a plain message box task because the user sees the progress moving.
Build steps
- Add the controls and keep the list box fairly large.
- Paste the code into Form1.cs.
- Click the button and watch the progress bar fill while addresses are shown.
Controls to add
- Button
btnLoad - ListBox
listBoxAddresses - ProgressBar
progressBar1 - Label
lblHost
Quick test
- The list should show at least one address on most machines.
- The button should disable while loading and enable again at the end.
FILE: Form1.cs
using System;
using System.Net;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
btnLoad.Click += btnLoad_Click;
}
private async void btnLoad_Click(object? sender, EventArgs e)
{
btnLoad.Enabled = false;
listBoxAddresses.Items.Clear();
progressBar1.Value = 0;
string host = Dns.GetHostName();
lblHost.Text = $"Host: {host}";
IPAddress[] addresses = await Dns.GetHostAddressesAsync(host);
progressBar1.Maximum = Math.Max(addresses.Length, 1);
foreach (IPAddress address in addresses)
{
listBoxAddresses.Items.Add(address.ToString());
progressBar1.Value = Math.Min(progressBar1.Value + 1, progressBar1.Maximum);
await Task.Delay(250);
}
btnLoad.Enabled = true;
}
}
Leap year checker form
This task is short, but it shows the full beginner WinForms flow: input, event, validation, result.
Build steps
- Add the three controls.
- Paste the code into Form1.cs.
- Run the form and test 2000, 1900, and 2024.
Controls to add
- TextBox
txtYear - Button
btnCheck - Label
lblResult
Quick test
- 2000 should be leap.
- 1900 should not be leap.
- 2024 should be leap.
FILE: Form1.cs
using System;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
btnCheck.Click += btnCheck_Click;
}
private void btnCheck_Click(object? sender, EventArgs e)
{
if (!int.TryParse(txtYear.Text, out int year))
{
lblResult.Text = "Type a valid year.";
return;
}
bool isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
lblResult.Text = isLeap
? $"{year} is a leap year."
: $"{year} is not a leap year.";
}
}
PDF form exporter with hand-drawn signature
This is the most advanced form on the page. It needs one NuGet package, but the build order is still simple if you keep it step by step.
Build steps
- Install the PdfSharp NuGet package first.
- Add the form controls. Make the signature panel fairly wide and give it a border.
- Paste the code into Form1.cs.
- Draw with the mouse inside the panel.
- Click Export and save the PDF.
Controls to add
- TextBox
txtStudentName - TextBox
txtClassName - Panel
pnlSignature - Button
btnClearSignature - Button
btnExportPdf
Quick test
- Draw a short signature line and make sure it appears in the PDF.
- Clear the signature and confirm the panel becomes empty again.
FILE: Form1.cs
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using PdfSharp.Drawing;
using PdfSharp.Pdf;
namespace StudentForms;
public partial class Form1 : Form
{
private Bitmap? signatureBitmap;
private bool isDrawing;
private Point lastPoint;
public Form1()
{
InitializeComponent();
Load += Form1_Load;
pnlSignature.MouseDown += pnlSignature_MouseDown;
pnlSignature.MouseMove += pnlSignature_MouseMove;
pnlSignature.MouseUp += (_, _) => isDrawing = false;
pnlSignature.Paint += pnlSignature_Paint;
btnClearSignature.Click += btnClearSignature_Click;
btnExportPdf.Click += btnExportPdf_Click;
}
private void Form1_Load(object? sender, EventArgs e)
{
signatureBitmap = new Bitmap(pnlSignature.Width, pnlSignature.Height);
ClearSignature();
}
private void pnlSignature_MouseDown(object? sender, MouseEventArgs e)
{
isDrawing = true;
lastPoint = e.Location;
}
private void pnlSignature_MouseMove(object? sender, MouseEventArgs e)
{
if (!isDrawing || signatureBitmap is null)
{
return;
}
using Graphics graphics = Graphics.FromImage(signatureBitmap);
graphics.DrawLine(Pens.Black, lastPoint, e.Location);
lastPoint = e.Location;
pnlSignature.Invalidate();
}
private void pnlSignature_Paint(object? sender, PaintEventArgs e)
{
if (signatureBitmap is not null)
{
e.Graphics.DrawImage(signatureBitmap, 0, 0);
}
}
private void btnClearSignature_Click(object? sender, EventArgs e) => ClearSignature();
private void ClearSignature()
{
if (signatureBitmap is null)
{
return;
}
using Graphics graphics = Graphics.FromImage(signatureBitmap);
graphics.Clear(Color.White);
pnlSignature.Invalidate();
}
private void btnExportPdf_Click(object? sender, EventArgs e)
{
if (signatureBitmap is null)
{
return;
}
using SaveFileDialog dialog = new()
{
Filter = "PDF files|*.pdf",
FileName = "form-output.pdf"
};
if (dialog.ShowDialog() != DialogResult.OK)
{
return;
}
string tempImagePath = Path.Combine(Path.GetTempPath(), "signature.png");
signatureBitmap.Save(tempImagePath);
PdfDocument document = new();
PdfPage page = document.AddPage();
XGraphics graphics = XGraphics.FromPdfPage(page);
XFont titleFont = new("Arial", 16, XFontStyleEx.Bold);
XFont textFont = new("Arial", 11, XFontStyleEx.Regular);
graphics.DrawString("Student form", titleFont, XBrushes.Black, new XPoint(40, 50));
graphics.DrawString($"Name: {txtStudentName.Text}", textFont, XBrushes.Black, new XPoint(40, 90));
graphics.DrawString($"Class: {txtClassName.Text}", textFont, XBrushes.Black, new XPoint(40, 115));
graphics.DrawString("Signature:", textFont, XBrushes.Black, new XPoint(40, 150));
using XImage signatureImage = XImage.FromFile(tempImagePath);
graphics.DrawImage(signatureImage, 40, 165, 220, 80);
document.Save(dialog.FileName);
File.Delete(tempImagePath);
MessageBox.Show("PDF saved.");
}
}
Dice roller with image animation
This project feels fun and still teaches a normal event-plus-timer pattern.
Build steps
- Create a folder called Images in the project output and add
dice1.pngtodice6.png. - Add the picture box, label, and button to the form.
- Paste the code into Form1.cs.
- Click Roll and watch the image change a few times before the final value stays on screen.
Controls to add
- PictureBox
picDice - Label
lblResult - Button
btnRoll
Quick test
- The picture should change several times before stopping.
- The label should show the same final value as the image.
FILE: Form1.cs
using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;
namespace StudentForms;
public partial class Form1 : Form
{
private readonly Random random = new();
private readonly Timer animationTimer = new() { Interval = 100 };
private Image[] diceImages = Array.Empty<Image>();
private int animationSteps;
private int currentValue = 1;
public Form1()
{
InitializeComponent();
Load += Form1_Load;
btnRoll.Click += btnRoll_Click;
animationTimer.Tick += animationTimer_Tick;
}
private void Form1_Load(object? sender, EventArgs e)
{
diceImages = Enumerable.Range(1, 6)
.Select(i => Image.FromFile(Path.Combine(Application.StartupPath, "Images", $"dice{i}.png")))
.ToArray();
picDice.Image = diceImages[0];
lblResult.Text = "Result: 1";
}
private void btnRoll_Click(object? sender, EventArgs e)
{
animationSteps = 10;
btnRoll.Enabled = false;
animationTimer.Start();
}
private void animationTimer_Tick(object? sender, EventArgs e)
{
currentValue = random.Next(1, 7);
picDice.Image = diceImages[currentValue - 1];
lblResult.Text = $"Result: {currentValue}";
animationSteps--;
if (animationSteps <= 0)
{
animationTimer.Stop();
btnRoll.Enabled = true;
}
}
}