Very often, while developing WPF applications, we actually feel the need for reusable/redistributable controls. WPF custom controls are shipped in DLL format such that they can be consumed in various applications. Please note, there is a significant difference between custom control and user control. They are different things.
Custom controls basically have one code file and one resource file which has the all the definitions of the layout and binding information.
To explain the functionality, today we will make a very simple WPF custom DataGrid control in which there will be an inbuilt FullTextSearch box and also you can hide-unhide based on your needs.
Project structure
I have created a very basic solution which contains,
- 1 WPF custom control library (SimpuControls) – Which will be shipped as SimpuControls.DLL
- 1 WPF application project (WpfApp1) – To test our custom control.
Let’s develop the SimpuDataGrid which is the custom DataGrid we talked about above. First right click on SimpuControls project and add CustomControl(WPF) to it. Once you add, it will automatically create a cs file and an XAML file. The XAML file is named as Generic.xaml inside Themes folder. Per our requirement, we will be needing two Converters – we will keep them inside a separate “Converter” folder.
Note
Converters are used when you need to interpret one or more type of values to some different type required for the UI element.
BooleanToVisibilityConverter
- namespace SimpuControls.Converters
- {
- public class BooleanToVisibilityConverter : IValueConverter
- {
-
- public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
- {
- if ((bool)value)
- return Visibility.Visible;
-
- return Visibility.Collapsed;
- }
-
- public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
- }
SearchTextToVisibilityConverter
This is a Multi-Value converter which receives an array of objects as input. The first element of the array is the “Search Text” and the second element is the entire object bound to the current grid row. Once these two objects are received, the SearchText is searched in all the properties of the second object using reflection. If found anywhere, return Visible else Collapsed.
- namespace SimpuControls.Converters
- {
- public class SearchTextToVisibilityConverter : IMultiValueConverter
- {
- public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
- {
- if ((string)values[0] == "") return Visibility.Visible;
-
- var searchText = ((string)values[0]).ToLower();
- var row = values[1];
- foreach (var p in row.GetType().GetProperties())
- {
- if (System.Convert.ToString(p.GetValue(row)).ToLower().Contains(searchText))
- return Visibility.Visible;
- }
- return Visibility.Collapsed;
- }
-
- public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
- {
- throw new NotImplementedException();
- }
- }
- }
SimpuControl.cs
The below code shows the custom control i.e SimpuControl class inherits from DataGrid base class. And here we have purposefully added EnableFullTextSearch Boolean dependency property to make user capable of hide/unhide FullTextSearch textbox attached at the top of the DataGrid.
Note
Dependency properties are called Dependency properties because, we can inject dependency from outside through this property.
- namespace SimpuControls
- {
- public class SimpuDataGrid : DataGrid
- {
- static SimpuDataGrid()
- {
- DefaultStyleKeyProperty.OverrideMetadata(typeof(SimpuDataGrid),
- new FrameworkPropertyMetadata(typeof(SimpuDataGrid)));
- }
-
-
- public static readonly DependencyProperty EnableFullTextSearchProperty =
- DependencyProperty.Register("EnableFullTextSearch", typeof(bool),
- typeof(SimpuDataGrid), new UIPropertyMetadata(false));
- public bool EnableFullTextSearch
- {
- get
- {
- return (bool)GetValue(EnableFullTextSearchProperty);
- }
- set
- {
- SetValue(EnableFullTextSearchProperty, value);
- }
- }
- }
- }
Themes/Generic.xaml
In the below code snippet,
- [Yellow] - References to the static converters we have made
- [Green] - Both refer to the SimpuControl class.
- [Aqua] - Is the FullTextSearch textbox placed on the top. This Textbox’s visibility is controlled through the dependency property and the BooleanToVisibility converter we added earlier. Note- the binding is TemplateBinding.
- [Ash] - Is the DataGrid with the RowVisibility controlled by the value of the Search-Text and the SearchTextToVisibility Converter.
- <ResourceDictionary
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:conv="clr-namespace:SimpuControls.Converters"
- xmlns:local="clr-namespace:SimpuControls">
- <conv:SearchTextToVisibilityConverter x:Key="SearchTextToVisibilityConverter"/>
- <conv:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
- <Style TargetType="{x:Type local:SimpuDataGrid}">
- <Setter Property="Template">
- <Setter.Value>
- <ControlTemplate TargetType="{x:Type local:SimpuDataGrid}">
- <Border Background="{TemplateBinding Background}"
- BorderBrush="{TemplateBinding BorderBrush}"
- BorderThickness="{TemplateBinding BorderThickness}">
- <StackPanel Orientation="Vertical" DataContext="{TemplateBinding DataContext}">
- <TextBox Width="200" Margin="2" HorizontalAlignment="Left" x:Name="txtFullTextSearch"
- Visibility="{TemplateBinding EnableFullTextSearch,
- Converter={StaticResource BooleanToVisibilityConverter}}" />
- <DataGrid x:Name="simpuGrid"
- ItemsSource="{TemplateBinding ItemsSource}" CanUserAddRows="{TemplateBinding CanUserAddRows}"
- CanUserDeleteRows="{TemplateBinding CanUserDeleteRows}"
- AutoGenerateColumns="{TemplateBinding AutoGenerateColumns}">
- <DataGrid.ItemContainerStyle>
- <Style TargetType="{x:Type DataGridRow}">
- <Setter Property="Visibility">
- <Setter.Value>
- <MultiBinding Converter="{StaticResource SearchTextToVisibilityConverter}">
- <Binding ElementName="txtFullTextSearch" Path="Text"/>
- <Binding BindsDirectlyToSource="True"/>
- </MultiBinding>
- </Setter.Value>
- </Setter>
- </Style>
- </DataGrid.ItemContainerStyle>
- </DataGrid>
- </StackPanel>
- </Border>
- </ControlTemplate>
- </Setter.Value>
- </Setter>
- </Style>
- </ResourceDictionary>
With this much code, our custom control is ready to be used. Now, lets refer this Control to our simple WPF project (WpfApp1).
To keep everything simple, I have created MainWindow.xaml, MainWindowViewModel.cs and a model class (Person.cs) just to bind the collection of this type to the DataGrid. The detailed code of ViewModel and Person.cs will be available in the code package attached with this article. Lets explain the design part of MainWindow.xaml.
- <Window x:Class="WpfApp1.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:local="clr-namespace:WpfApp1"
- xmlns:cc="clr-namespace:SimpuControls;assembly=SimpuControls"
- mc:Ignorable="d"
- Title="MainWindow" Height="450" Width="800">
- <Window.DataContext>
- <local:MainWindowViewModel/>
- </Window.DataContext>
- <Grid>
- <cc:SimpuDataGrid ItemsSource="{Binding Persons}" EnableFullTextSearch="True">
- <DataGrid.Columns>
- <DataGridTextColumn Header="ID" Binding="{Binding ID}"/>
- <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
- <DataGridTextColumn Header="Email" Binding="{Binding Email}"/>
- </DataGrid.Columns>
- </cc:SimpuDataGrid>
- </Grid>
- </Window>
In the above code,
- [Yellow] - The reference to the custom control assembly.
- [Green] - The reference to the MainWindowViewModel DataContext.
- [Aqua] - Use of SimpuDataGrid custom grid.
- [Ash] - Use of EnableFullTextSearch. If bound to ‘True’, the FullTextSearch textbox on top of DataGrid will be visible else not.
If we run our application, the result will be as given below.
Conclusion
Thus, we can very easily create our custom WPF control with new features. Finally, we can ship the DLL to utilize it in a different application of modules. Hope you enjoyed the article. Don’t forget to leave comments if you have any query.