Skip to main content

.NET User Controls

Introduction

User controls are one of the most powerful features in .NET desktop development, allowing developers to create reusable UI components that can be used across multiple forms or windows in an application. Whether you're building Windows Forms or WPF applications, user controls help in maintaining consistency, improving code organization, and enhancing maintainability.

In this tutorial, we'll explore what user controls are, why they're useful, and how to create and implement them in both Windows Forms and WPF applications.

What Are User Controls?

A user control is essentially a custom, reusable component that encapsulates UI elements and related functionality into a single unit. Think of it as creating your own custom control by combining existing controls and adding specific behaviors.

For example, if your application requires an address input panel with fields for street, city, state, and zip code on multiple forms, rather than recreating this panel everywhere, you can create a single user control and reuse it.

Benefits of Using User Controls

  • Code reusability: Write once, use many times
  • Consistency: Maintain the same look and feel throughout your application
  • Maintainability: Change the control in one place and it updates everywhere
  • Encapsulation: Bundle related UI elements and logic together
  • Simplified development: Break complex UI into manageable components

Creating User Controls in Windows Forms

Step 1: Creating a Basic User Control

Let's start by creating a simple user control for collecting user information:

  1. In Visual Studio, right-click your project in Solution Explorer
  2. Select Add → New Item
  3. Choose "User Control" from the templates
  4. Name it "UserInfoControl.cs" and click Add

Now, let's design our user control:

csharp
using System;
using System.Windows.Forms;

namespace MyApplication.Controls
{
public partial class UserInfoControl : UserControl
{
public UserInfoControl()
{
InitializeComponent();
}

// Add your event handlers and custom methods here
}
}

Step 2: Design the User Control

In the designer, add the following controls:

  • Two Labels: "Name:" and "Email:"
  • Two TextBoxes: txtName and txtEmail
  • One Button: btnSubmit

Your user control should look something like this in the designer view.

Step 3: Adding Properties and Events

Let's add properties to expose the input values and events to notify when the submit button is clicked:

csharp
using System;
using System.Windows.Forms;

namespace MyApplication.Controls
{
public partial class UserInfoControl : UserControl
{
// Event that will be raised when the Submit button is clicked
public event EventHandler<UserInfoEventArgs> UserSubmitted;

public UserInfoControl()
{
InitializeComponent();
btnSubmit.Click += BtnSubmit_Click;
}

// Properties to get/set the values
public string UserName
{
get { return txtName.Text; }
set { txtName.Text = value; }
}

public string UserEmail
{
get { return txtEmail.Text; }
set { txtEmail.Text = value; }
}

private void BtnSubmit_Click(object sender, EventArgs e)
{
// Validate inputs
if (string.IsNullOrWhiteSpace(UserName) || string.IsNullOrWhiteSpace(UserEmail))
{
MessageBox.Show("Please fill in all fields.");
return;
}

// Raise the event
UserSubmitted?.Invoke(this, new UserInfoEventArgs(UserName, UserEmail));
}
}

// Custom event args to pass user information
public class UserInfoEventArgs : EventArgs
{
public string Name { get; }
public string Email { get; }

public UserInfoEventArgs(string name, string email)
{
Name = name;
Email = email;
}
}
}

Step 4: Using the User Control in a Form

Now let's use our user control in a form:

csharp
using System;
using System.Windows.Forms;
using MyApplication.Controls;

namespace MyApplication
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();

// Create an instance of our user control
UserInfoControl userInfoControl = new UserInfoControl();
userInfoControl.Location = new System.Drawing.Point(50, 50);

// Subscribe to the UserSubmitted event
userInfoControl.UserSubmitted += UserInfoControl_UserSubmitted;

// Add the control to the form
this.Controls.Add(userInfoControl);
}

private void UserInfoControl_UserSubmitted(object sender, UserInfoEventArgs e)
{
MessageBox.Show($"Received submission from {e.Name} with email {e.Email}");
// Process the submitted data
}
}
}

Creating User Controls in WPF

WPF user controls are very similar in concept but use XAML for their UI definition.

Step 1: Creating a Basic WPF User Control

  1. Right-click your WPF project in Solution Explorer
  2. Select Add → New Item
  3. Choose "User Control (WPF)" from the templates
  4. Name it "UserInfoControl.xaml" and click Add

The XAML for the control will look something like this:

xml
<UserControl x:Class="MyWpfApplication.Controls.UserInfoControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="150" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>

<Label Grid.Row="0" Grid.Column="0" Content="Name:" Margin="5"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtName" Margin="5"/>

<Label Grid.Row="1" Grid.Column="0" Content="Email:" Margin="5"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtEmail" Margin="5"/>

<Button Grid.Row="2" Grid.Column="1" x:Name="btnSubmit"
Content="Submit" Margin="5" Click="BtnSubmit_Click"/>
</Grid>
</UserControl>

Step 2: Adding Code-Behind for the WPF User Control

csharp
using System;
using System.Windows;
using System.Windows.Controls;

namespace MyWpfApplication.Controls
{
public partial class UserInfoControl : UserControl
{
// Define a routed event
public static readonly RoutedEvent UserSubmittedEvent =
EventManager.RegisterRoutedEvent("UserSubmitted", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(UserInfoControl));

// CLR wrapper for the event
public event RoutedEventHandler UserSubmitted
{
add { AddHandler(UserSubmittedEvent, value); }
remove { RemoveHandler(UserSubmittedEvent, value); }
}

public UserInfoControl()
{
InitializeComponent();
}

// Dependency properties for binding
public static readonly DependencyProperty UserNameProperty =
DependencyProperty.Register("UserName", typeof(string), typeof(UserInfoControl));

public static readonly DependencyProperty UserEmailProperty =
DependencyProperty.Register("UserEmail", typeof(string), typeof(UserInfoControl));

public string UserName
{
get { return (string)GetValue(UserNameProperty); }
set { SetValue(UserNameProperty, value); }
}

public string UserEmail
{
get { return (string)GetValue(UserEmailProperty); }
set { SetValue(UserEmailProperty, value); }
}

private void BtnSubmit_Click(object sender, RoutedEventArgs e)
{
// Update dependency properties from textboxes
UserName = txtName.Text;
UserEmail = txtEmail.Text;

// Validate inputs
if (string.IsNullOrWhiteSpace(UserName) || string.IsNullOrWhiteSpace(UserEmail))
{
MessageBox.Show("Please fill in all fields.");
return;
}

// Raise the event
RoutedEventArgs args = new RoutedEventArgs(UserSubmittedEvent, this);
RaiseEvent(args);
}
}
}

Step 3: Using the WPF User Control

In your main window or another XAML file:

xml
<Window x:Class="MyWpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:MyWpfApplication.Controls"
Title="User Information" Height="300" Width="400">
<Grid>
<StackPanel Margin="20">
<TextBlock Text="User Information Form" FontSize="16" FontWeight="Bold" Margin="0,0,0,20"/>

<controls:UserInfoControl x:Name="userInfoControl" UserSubmitted="UserInfoControl_UserSubmitted"/>

<TextBlock x:Name="outputText" Margin="0,20,0,0"/>
</StackPanel>
</Grid>
</Window>

And in the code-behind:

csharp
using System;
using System.Windows;

namespace MyWpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void UserInfoControl_UserSubmitted(object sender, RoutedEventArgs e)
{
var control = sender as Controls.UserInfoControl;
if (control != null)
{
outputText.Text = $"Received: Name = {control.UserName}, Email = {control.UserEmail}";
}
}
}
}

Advanced User Control Techniques

1. User Control Lifecycle

Like other .NET UI elements, user controls have a lifecycle that you can hook into:

csharp
public partial class AdvancedUserControl : UserControl
{
public AdvancedUserControl()
{
InitializeComponent();

// Subscribe to lifecycle events
this.Load += AdvancedUserControl_Load;
this.HandleCreated += AdvancedUserControl_HandleCreated;
this.HandleDestroyed += AdvancedUserControl_HandleDestroyed;
}

private void AdvancedUserControl_Load(object sender, EventArgs e)
{
// Initialize when control is loaded
}

private void AdvancedUserControl_HandleCreated(object sender, EventArgs e)
{
// Handle is created
}

private void AdvancedUserControl_HandleDestroyed(object sender, EventArgs e)
{
// Clean up resources
}
}

2. Creating a Composite User Control

Often, you'll want to create more complex user controls that contain other user controls. This is known as composition:

csharp
public partial class CustomerEntryForm : UserControl
{
private AddressUserControl addressControl;
private ContactUserControl contactControl;

public CustomerEntryForm()
{
InitializeComponent();

// Initialize child controls
addressControl = new AddressUserControl();
addressControl.Location = new Point(10, 100);
this.Controls.Add(addressControl);

contactControl = new ContactUserControl();
contactControl.Location = new Point(10, 250);
this.Controls.Add(contactControl);
}

// Method to collect all data from child controls
public CustomerData GetCustomerData()
{
return new CustomerData
{
Name = txtName.Text,
Address = addressControl.GetAddressData(),
ContactInfo = contactControl.GetContactData()
};
}
}

3. Data Binding in WPF User Controls

WPF user controls excel at data binding:

xml
<UserControl x:Class="MyWpfApplication.Controls.ProductDisplayControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<StackPanel>
<TextBlock Text="{Binding ProductName}" FontWeight="Bold"/>
<TextBlock Text="{Binding Price, StringFormat=Price: ${0:N2}}"/>
<TextBlock Text="{Binding Description}" TextWrapping="Wrap"/>
<Button Content="Add to Cart" Command="{Binding AddToCartCommand}"/>
</StackPanel>
</Grid>
</UserControl>

Real-World Example: A Reusable Pagination Control

Let's create a pagination control that could be used in any application that needs to navigate through pages of data:

WinForms Pagination Control

csharp
public partial class PaginationControl : UserControl
{
private int _currentPage = 1;
private int _totalPages = 1;

public event EventHandler<PageChangedEventArgs> PageChanged;

public PaginationControl()
{
InitializeComponent();

btnFirst.Click += (s, e) => GoToPage(1);
btnPrevious.Click += (s, e) => GoToPage(CurrentPage - 1);
btnNext.Click += (s, e) => GoToPage(CurrentPage + 1);
btnLast.Click += (s, e) => GoToPage(TotalPages);

UpdateUI();
}

public int CurrentPage
{
get { return _currentPage; }
set
{
if (value < 1 || value > _totalPages)
throw new ArgumentOutOfRangeException("CurrentPage");

if (_currentPage != value)
{
_currentPage = value;
UpdateUI();
OnPageChanged();
}
}
}

public int TotalPages
{
get { return _totalPages; }
set
{
if (value < 1)
throw new ArgumentOutOfRangeException("TotalPages");

_totalPages = value;

if (_currentPage > _totalPages)
_currentPage = _totalPages;

UpdateUI();
}
}

private void GoToPage(int page)
{
if (page >= 1 && page <= TotalPages)
CurrentPage = page;
}

private void UpdateUI()
{
lblPageInfo.Text = $"Page {CurrentPage} of {TotalPages}";
btnFirst.Enabled = btnPrevious.Enabled = (CurrentPage > 1);
btnNext.Enabled = btnLast.Enabled = (CurrentPage < TotalPages);
}

protected virtual void OnPageChanged()
{
PageChanged?.Invoke(this, new PageChangedEventArgs(CurrentPage));
}
}

public class PageChangedEventArgs : EventArgs
{
public int Page { get; }

public PageChangedEventArgs(int page)
{
Page = page;
}
}

Using the Pagination Control

csharp
public partial class ProductListForm : Form
{
private List<Product> allProducts;
private int pageSize = 10;

public ProductListForm()
{
InitializeComponent();

// Initialize our pagination control
paginationControl.PageChanged += PaginationControl_PageChanged;

// Load data
LoadProducts();
}

private void LoadProducts()
{
// In a real app, this might come from a database
allProducts = ProductRepository.GetAllProducts();

// Set up pagination
int totalPages = (int)Math.Ceiling(allProducts.Count / (double)pageSize);
paginationControl.TotalPages = totalPages > 0 ? totalPages : 1;

// Show first page
DisplayCurrentPage();
}

private void PaginationControl_PageChanged(object sender, PageChangedEventArgs e)
{
DisplayCurrentPage();
}

private void DisplayCurrentPage()
{
int startIndex = (paginationControl.CurrentPage - 1) * pageSize;

// Get items for current page
var currentPageItems = allProducts
.Skip(startIndex)
.Take(pageSize)
.ToList();

// Bind to grid or list view
productListView.DataSource = currentPageItems;
}
}

Best Practices for User Controls

  1. Keep controls focused: Each user control should have a single responsibility.

  2. Expose properties and events: Make your controls customizable through public properties and events.

  3. Document your controls: Add XML documentation comments to describe how to use your controls.

  4. Validate inputs: Perform validation inside the control when appropriate.

  5. Design for reusability: Think about how your control could be used in different contexts.

  6. Consider resource management: If your control uses disposable resources, implement IDisposable.

  7. Test your controls: Create unit tests to ensure your controls behave as expected.

  8. Design responsively: Make sure your controls can resize appropriately.

Common Pitfalls and Solutions

  1. Memory leaks: Be careful with event subscriptions; unsubscribe when necessary.

  2. Performance issues: Avoid expensive operations in paint or layout events.

  3. Threading issues: Remember that UI controls should be accessed from the UI thread only.

  4. Design-time support: Make sure your control initializes properly in the designer.

Summary

User controls are a powerful way to create reusable components in .NET desktop applications. They help improve code organization, maintainability, and consistency across your application. By encapsulating UI elements and related functionality into self-contained units, you can build more modular and maintainable applications.

In this guide, we explored:

  • What user controls are and their benefits
  • How to create user controls in Windows Forms and WPF
  • Adding properties and events to user controls
  • Using user controls in forms and windows
  • Advanced techniques and real-world examples
  • Best practices and common pitfalls

Exercises

  1. Create a custom login user control with username and password fields, and a login button.

  2. Build a data entry form using multiple user controls for different sections.

  3. Create a user control that displays a chart or graph based on data provided to it.

  4. Develop a user control that can be used across both WinForms and WPF (using shared business logic).

  5. Extend the pagination control example to include a "jump to page" feature where users can input a specific page number.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)