C# WPF Creating User Controls
Within a UI User Interface there are often times where you want to re-use an arrangement of UI Elements like Labels and Textboxes. To do this we want to create a User Control that contains these elements, with little to no application code behind, using the MVVM pattern (Model, View, View-Model). This way we can reuse the User Control and set its Data Context and Bindings to different class structures.
User Control
Within a WPF Project, add a new Folder called “UserControls”.
Right Mouse Click on the Folder and Select Add->User Control (WPF).
Name the new User Control UserFormUC.
Within the XAML delete the local name space and provide an x:Name as shown. This will be used for registering the dependency property’s for the user control, so it should be a generic unique name, so there can never be any duplications that may cause issues with dependency property registration.
<UserControl x:Class="User_Controls.UserControls.UserFormUC" 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="450" d:DesignWidth="800" x:Name="userFormUC"> <Grid> </Grid> </UserControl>
Now we can build out the User control.
<UserControl x:Class="User_Controls.UserControls.UserFormUC" 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="220" d:DesignWidth="400" x:Name="userFormUC"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="20"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="20"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="20"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="40"/> <RowDefinition Height="20"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="4"> <Label Content="First Name" HorizontalAlignment="Left" VerticalAlignment="Center" Width="100" Foreground="White" FontSize="18"/> <TextBox x:Name="firstNameTextBox" Text="{Binding ElementName=userFormUC, Path=UserFirstName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Width="200" Height="30" FontSize="18" Margin="10,0,0,0"/> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="2" Grid.ColumnSpan="4"> <Label Content="Last Name" HorizontalAlignment="Left" VerticalAlignment="Center" Width="100" Foreground="White" FontSize="18"/> <TextBox x:Name="lastNameTextBox" Text="{Binding ElementName=userFormUC, Path=UserLastName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Width="200" Height="30" FontSize="18" Margin="10,0,0,0"/> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="3" Grid.ColumnSpan="4"> <Label Content="Email" HorizontalAlignment="Left" VerticalAlignment="Center" Width="100" Foreground="White" FontSize="18"/> <TextBox x:Name="emailTextBox" Text="{Binding ElementName=userFormUC, Path=UserEmail, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Width="200" Height="30" FontSize="18" Margin="10,0,0,0"/> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="4" Grid.ColumnSpan="4"> <Label Content="Password" HorizontalAlignment="Left" VerticalAlignment="Center" Width="100" Foreground="White" FontSize="18"/> <PasswordBox x:Name="passwordBox" PasswordChanged="passwordBox_PasswordChanged" Width="200" Height="30" FontSize="18" Margin="10,0,0,0"/> </StackPanel> <Button x:Name="submitButton" Grid.Column="1" Grid.Row="5" Grid.ColumnSpan="4" Content="Submit" Width="100" Height="30" FontSize="18" HorizontalAlignment="Right" Margin="0 0 70 0" Command="{Binding ElementName=userFormUC, Path=SubmitCommand}"/> </Grid> </UserControl>
Within the code behind we have to create dependency property’s for; data, animation, styling, and commands.
Dependency properties are a key concept in WPF (Windows Presentation Foundation) and other XAML-based technologies like Silverlight and Windows Runtime (WinRT). They provide a way for classes to define properties whose values can be inherited, constrained, styled, and animated, among other things. Dependency properties are unlike traditional .NET properties in that they are registered with the dependency property system rather than being implemented as simple property accessors.
Here are some key characteristics and features of dependency properties:
- Value Resolution Mechanism: Dependency properties enable a powerful system for value resolution. They can obtain their values from a variety of sources including animations, styles, data bindings, inheritance, default values, and local values (values set directly on the element).
- Change Notification: Dependency properties provide change notification mechanisms, allowing other parts of the system to be notified when their values change. This makes them well-suited for data binding scenarios and enables UI updates when properties change.
- Property Metadata: Dependency properties can have associated metadata that specifies default values, property changed callbacks, coercion callbacks, and validation callbacks. This metadata can be defined when registering the dependency property.
- Attached Properties: Dependency properties can be defined on one class but attached to another. This allows for the creation of behaviors and characteristics that can be attached to any element, such as Grid.Row or Canvas.Left.
- Inheritance: Dependency properties support property value inheritance within the visual tree. This means that child elements can inherit the value of a dependency property from their parent elements, providing a way to propagate property values down the tree.
- Data Binding: Dependency properties are commonly used with data binding in WPF applications. They enable a powerful and flexible way to bind UI elements to data sources.
- Styling and Templating: Dependency properties are crucial for defining the properties that can be targeted by styles and templates, allowing for consistent appearance and behavior across the application.
using System.Security; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; namespace User_Controls.UserControls { public partial class UserFormUC : UserControl { public UserFormUC() { InitializeComponent(); passwordBox.SetBinding(UserFormUC.UserPasswordProperty, new Binding { Source = this, Path = new PropertyPath("Password"), Mode = BindingMode.TwoWay }); } public string UserFirstName { get { return (string)GetValue(UserFirstNameProperty); } set { SetValue(UserFirstNameProperty, value); } } public static readonly DependencyProperty UserFirstNameProperty = DependencyProperty.Register("UserFirstName", typeof(string), typeof(UserFormUC), new PropertyMetadata(string.Empty)); public string UserLastName { get { return (string)GetValue(UserLastNameProperty); } set { SetValue(UserLastNameProperty, value); } } public static readonly DependencyProperty UserLastNameProperty = DependencyProperty.Register("UserLastName", typeof(string), typeof(UserFormUC), new PropertyMetadata(string.Empty)); public string UserEmail { get { return (string)GetValue(UserEmailProperty); } set { SetValue(UserEmailProperty, value); } } public static readonly DependencyProperty UserEmailProperty = DependencyProperty.Register("UserEmail", typeof(string), typeof(UserFormUC), new PropertyMetadata(string.Empty)); public SecureString UserPassword { get { return (SecureString)GetValue(UserPasswordProperty); } set { SetValue(UserPasswordProperty, value); } } public static readonly DependencyProperty UserPasswordProperty = DependencyProperty.Register("UserPassword", typeof(SecureString), typeof(UserFormUC), new PropertyMetadata(new SecureString())); private void passwordBox_PasswordChanged(object sender, RoutedEventArgs e) { // Convert the password to a SecureString SecureString securePassword = new SecureString(); foreach (char c in passwordBox.Password) { securePassword.AppendChar(c); } UserPassword = securePassword; } public ICommand SubmitCommand { get { return (ICommand)GetValue(SubmitCommandProperty); } set { SetValue(SubmitCommandProperty, value); } } public static readonly DependencyProperty SubmitCommandProperty = DependencyProperty.Register("SubmitCommand", typeof(ICommand), typeof(UserFormUC), new PropertyMetadata(null)); } }
Now that we have the user control defined we can use it within our main window, to do this we must first build the application so that the user control can consumed.
Within the XAML we ahve to declare a new xml namespace I’m using “uc”, for user controls and the path must point towards our user control namespace. now we can use our user control and rebind the dependency property’s to the property’s in the code behind or view-model.
<Window x:Class="User_Controls.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:uc="clr-namespace:User_Controls.UserControls" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" Background="Black"> <Grid> <uc:UserFormUC UserFirstName="{Binding UserFirstName , UpdateSourceTrigger = PropertyChanged , Mode = TwoWay}" UserLastName="{Binding UserLastName , UpdateSourceTrigger = PropertyChanged , Mode = TwoWay}" UserEmail="{Binding UserEmail , UpdateSourceTrigger = PropertyChanged , Mode = TwoWay}" UserPassword="{Binding UserPassword , UpdateSourceTrigger = PropertyChanged , Mode = TwoWay}" SubmitCommand="{Binding SubmitCommand}" Height="300" Width="400"/> </Grid> </Window>
I didn’t use MVVM here i just updated the code behind. Typically I create a ViewModelBase class that implements INotifyPropertyChanged, but since used the code behind we have to implement INotifyPropertyChanged here.
using System.ComponentModel; using System.Runtime.CompilerServices; using System.Security; using System.Windows; using System.Windows.Input; using User_Controls.Commands.Base; namespace User_Controls { public partial class MainWindow : Window , INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public MainWindow() { InitializeComponent(); DataContext = this; } private string _userFirstName = string.Empty; public string UserFirstName { get => _userFirstName; set { _userFirstName = value; OnPropertyChanged(); } } private string _userLastName = string.Empty; public string UserLastName { get => _userLastName; set { _userLastName = value; OnPropertyChanged(); } } private string _userEmail = string.Empty; public string UserEmail { get => _userEmail; set { _userEmail = value; OnPropertyChanged(); } } private SecureString? _userPassword = null; public SecureString? UserPassword { get => _userPassword; set { _userPassword = value; OnPropertyChanged(); } } public ICommand SubmitCommand => new CommandsBase(SubmitCommandExecute); private void SubmitCommandExecute(object? obj) { throw new NotImplementedException(); } } }
I extensively use this for all of my command handling.
using System.Windows.Input; namespace User_Controls.Commands.Base { public class CommandsBase : ICommand { private readonly Action<object?> _executeAction; private readonly Predicate<object?>? _canExecuteAction; public CommandsBase(Action<object?> executeAction) { _executeAction = executeAction; _canExecuteAction = null; } public CommandsBase(Action<object?> executeAction, Predicate<object?> canExecute) { _executeAction = executeAction; _canExecuteAction = canExecute; } public event EventHandler? CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } public bool CanExecute(object? parameter) { return _canExecuteAction == null ? true : _canExecuteAction(parameter); } public void Execute(object? parameter) { if (_executeAction != null) { _executeAction(parameter); } } } }