What Does It Take To Create A Custom WPF Control

I have always loved the status bar, but even that was too restrictive for me. I like to have a running list of what the program is doing, especially on long running processes and it is essential for continuous integration testing. I use it in every program I write so I decided to save myself time, like when you create a library of your favorite functions and classes (at least you should). I created a themed status control with lazy message que processing.
 
Of course I went with the datagrid for nice clean messaging – I like columns. This status control will have a time stamp, a enum of notations to denote the severity of type of message that follows, its own message que, its own timer, output total message que to file, the ability to resize, and to display one of six built in themes.
 
What we are going to do is create a WPF User Control Library. Basically it is a chunk of reusable code with XAML and code behind. Since I only plan to use it and not inherit from it, a User Control is the way to go. If you want a control or inherit from a control then a WPF Custom Control Library is what you want – but that is for another discussion. This is the pretty simple and straightforward way to set up a control that you use quite often. It is the same approach as with a WinForms control, except this will be in WPF so there will be associated XAML with it.
 
One note, there are ways to use WPF controls in WinForms and vice versa, but it can tend to be an ugly process. So for simplicity sake I am only planning on using this control in a WPF application.
 
What Does It Take To Create A Custom WPF Control
 
So the first step as I mentioned briefly before is to create a new WPF User Control Library project,
 
What Does It Take To Create A Custom WPF Control
 
The reason I mention this is so when you create your own control you know what project type to use. As you will see from the source code I provide the project properties only list it as a class library, the XAML does give you a clue since it will be registered as a <UserControl></UserControl> and in the code behind the partial class StatusControl derives from UserControl. First I start by defining my Grid.
  1. <Grid>  
  2.     <Grid.RowDefinitions>  
  3.         <RowDefinition Height="25" />  
  4.         <RowDefinition Height="100*" />  
  5.         <RowDefinition Height="25" />  
  6.     </Grid.RowDefinitions>  
  7.     <Grid.ColumnDefinitions>  
  8.         <ColumnDefinition Width="100" />  
  9.         <ColumnDefinition Width="450*" />  
  10.     </Grid.ColumnDefinitions>  
Note the “*” to define which row and columns can expand, this allows for smooth resizing. Next, the controls I need to build the overall Status Control are defined. I have met a lot of programmers who have been scared off from creating controls, but there is nothing to them. Think of it in terms of a mini-application that does a very specific function. In this case, this mini application takes care of status messages. For the Web developers, it is like a Web Part (probably get yelled at for that comment) but that is I think, an accurate general comparison. Now for the rest of the interface definition.
 
One static label “Status” and a dynamic one for the date and time. In addition there is a button for executing the ouput of all the messages to file, the datagrid itself, two operational status labels “Que Count:” and the grid status. Status display for the Status control??? Okay, so I may go a bit far in the visual information department but I can’t help myself. I believe you should be able to see what a program is doing.
  1. <Label Name="lblStatus" Content="Status" Grid.Row="0" Grid.Column="0" FontFamily="Copperplate Gothic" FontSize="12" Foreground="{StaticResource MyNavy}" Height="23" Width="Auto" HorizontalAlignment="Center" VerticalAlignment="Center"/>  
  2. <Label Name="lblDateTime" Content="October 11,2018 4:20 PM" Grid.Row="0" Grid.Column="1" FontFamily="Copperplate Gothic" FontSize="12" Foreground="{StaticResource RoyalBlue}" Height="23" Width="Auto" HorizontalAlignment="Center" VerticalAlignment="Center"/>  
  3. <Button Name="btnOutput" Content="Output" Grid.Row="0" Grid.Column="1" FontFamily="Copperplate Gothic" FontSize="12" Foreground="{StaticResource RoyalBlue}" Height="23" Width="Auto" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,10,0" Click="btnOutput_Click"/>  
In the datagrid the data binding is set to the properties that will be in the assigned data source list_StatusOutput, in this case it is the base class StatusOuput which has the exposed properties of StatusDateTime, OuputStatusNote, and OutputStatusMsg.
  1. <DataGrid Name="dgStatus" ItemsSource="{Binding list_StatusOutput}" AutoGenerateColumns="False" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Left" VerticalAlignment="Top" Height="Auto" MinHeight="95" Width="Auto" MinWidth="530" Margin="5,0,5,0" FontFamily="Copperplate Gothic" AlternatingRowBackground="{StaticResource Gainsboro}" ScrollViewer.HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">  
  2.     <DataGrid.Columns>  
  3.         <DataGridTextColumn Binding="{Binding StatusDateTime}" Header="Time Stamp" Width="Auto" MinWidth="100" MaxWidth="125" Foreground="{StaticResource Purple3}" FontSize="10" IsReadOnly="True" CanUserSort="False" CanUserReorder="False" FontFamily="Copperplate Gothic" />  
  4.         <DataGridTextColumn Binding="{Binding OutputStatusNote}" Header="Notation" Width="Auto" MinWidth="100" MaxWidth="100" Foreground="{StaticResource Magenta3}" FontSize="10" IsReadOnly="True" CanUserSort="True" CanUserReorder="False" FontFamily="Copperplate Gothic" />  
  5.         <DataGridTextColumn Binding="{Binding OutputStatusMsg}" Header="Message" Width="Auto" MinWidth="340" Foreground="{StaticResource EmeraldGreen}" FontSize="11" IsReadOnly="True" CanUserSort="False" CanUserReorder="False" FontFamily="Copperplate Gothic" />  
  6.     </DataGrid.Columns>  
  7. </DataGrid>  
The XAML for the rest of the controls are as mentioned above. The combobox cboxTheme is loaded with the theme names; switching between themes is handled by the event notification SelectionChanged and the delegate that will process the event – cboxTheme_SelectionChanged().
  1.     <Label Name="lblQueCnt" Content="Que Count:" Grid.Row="2" Grid.Column="0" FontFamily="Copperplate Gothic" FontSize="10" Foreground="{StaticResource BlueViolet}" Height="23" Width="Auto" HorizontalAlignment="Left" HorizontalContentAlignment="Left" VerticalAlignment="Center" />  
  2.     <Label Name="lblGridStat" Content="Grid Operational..." Grid.Row="2" Grid.Column="1" FontFamily="Copperplate Gothic" FontSize="10" Foreground="{StaticResource Firebrick5}" Height="23" Width="Auto" HorizontalAlignment="Left" HorizontalContentAlignment="Left" VerticalAlignment="Center" />  
  3.     <ComboBox Name="cboxTheme" Grid.Row="2" Grid.Column="1" Height="23" FontFamily="Copperplate Gothic" FontSize="10" Width="Auto" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,10,0" SelectedIndex="0" SelectionChanged="cboxTheme_SelectionChanged">  
  4.         <ComboBoxItem>Default</ComboBoxItem>  
  5.         <ComboBoxItem>Blue Ice Theme</ComboBoxItem>  
  6.         <ComboBoxItem>Blood Theme</ComboBoxItem>  
  7.         <ComboBoxItem>Mean Green Theme</ComboBoxItem>  
  8.         <ComboBoxItem>Golden Theme</ComboBoxItem>  
  9.         <ComboBoxItem>Silver Theme</ComboBoxItem>  
  10.     </ComboBox>  
  11. </Grid>  
All that yields this:
 
What Does It Take To Create A Custom WPF Control
 
Now that I have the control designed, it needs methods now. Methods that will tell it how we want it to process the messages we give it and some maintenance methods. I already know I want an output method attached to the “Output” button and as mentioned a method to handle the theme selection. The following list is a quick summary:
  • AddStatusMsgToQue() – add a message to the que
  • ClearStatusOutput() – clear the message que
  • FlushLazyMsgQue() – flush all messages to the datagrid
  • AdjustClockTick() – adjust message processing clock tick
  • HideThemeCombo() – hide the theme combo
  • ShowThemeCombo() – show the theme combo
  • SetTheme() – sets the display to the selected theme
  • GetCueCount() – returns the number of messages in the que
  • AdjustDataGridWidthHeight() – adjust the datagrid width and height
  • btnOutput_Click() – flush the message que and output all messages to text file
  • cboxTheme_SelectionChanged() – calls SetTheme() with the new selected theme
AddStatusMsgToQue(), does exactly what is says; for input it takes a StatusNotation and a string for the message. StatusNotation is a public enumeration for designating the severity or type of message being added to the que.
  1. /// <summary>  
  2. /// Message Types  
  3. /// </summary>  
  4. public enum StatusNotations  
  5. {  
  6.     Normal,  
  7.     Header,  
  8.     Important,  
  9.     Warning,  
  10.     Error,  
  11.     System,  
  12.     xxx  
  13. }  
Most of the methods mentioned above are simple and self-explanatory especially once you see the source code but I will address FlushLazyMsgQue() and, of course, the one method not discussed yet, the control's own clock tick. The reason I call this a “Lazy” message processor is because it works on a timer. A timer that has a duration between 5 and 120 seconds, once the timer has reached its interval it will process the first message on the que. Thus a message processed every set interval of X number of seconds equals a “Lazy” message queue, or you could call it a timed message queue, but I liked “Lazy” better. Say for example you are running a series of long processes and, in between, you want to the que to catch up. FlushLazyMsgQue does just that, it processes all the current messages in the que and dumps them to the datagrid.
 
I will mention the theme feature, it was an afterthought. Since I like to do WPF programs I wanted a way to theme the control to match whatever color scheme I’m using for the program. I created a Resource Dictionary which defines all the color schemes. The Resource Dictionary is a good way to group like resources, in this case, colors and associated key; but the way I implemented the change by actually setting the forecolor and back color of the controls is inefficient. There are better methods and a lot of articles on themes, I went this route since I don’t have many controls and it was real simple as an afterthought; to just have a routine handle it. It is a switch statement keying on the string name of the selected theme, for example here is the Blue Ice setting from the routine.
  1. case 1:  // Blue Ice Theme  
  2.     brush_Status = (SolidColorBrush)this.TryFindResource("MyNavy");  
  3.     brush_DateTime = (SolidColorBrush)this.TryFindResource("RoyalBlue");  
  4.     brush_QueCnt = (SolidColorBrush)this.TryFindResource("BlueViolet");  
  5.     brush_GridStat = (SolidColorBrush)this.TryFindResource("Firebrick5");  
  6.     brush_TimeStamp = (SolidColorBrush)this.TryFindResource("Purple3");  
  7.     brush_Notation = (SolidColorBrush)this.TryFindResource("Magenta3");  
  8.     brush_Message = (SolidColorBrush)this.TryFindResource("EmeraldGreen");  
  9.     lgb_Theme = (LinearGradientBrush)this.TryFindResource("BlueIceTheme");  
  10.     btnOutput.Foreground = (SolidColorBrush)this.TryFindResource("RoyalBlue");  
  11.     btnOutput.Background = lgb_Theme;  
  12.     this.Background = lgb_Theme;  
  13.     dgStatus.Background = lgb_Theme;  
  14.     dgStatus.AlternatingRowBackground = Brushes.White;  
  15.     break;  
Here is the SolidColorBrush setting from the Resource Dictionary:
  1. <SolidColorBrush x:Key="MyNavy">#000080</SolidColorBrush>  
Once compiled, the output will be a DLL, aka a Dynamic Link Library. To test the control and for this article, I created a WPF app to embed the control in. I created a new WPF application called Test Status Msg Control, once created I right clicked on the References folder and used “Browse” to point it to the newly created StatusMsgControl.DLL.
 
What Does It Take To Create A Custom WPF Control
 
The reference now shows up in the References list and is available to the program but to make it visible to the XAML the following namespace needs to be added,
  1. xmlns:my="clr-namespace:QUSACustomStatus;assembly=QUSACustomStatus"  
To create the control,
  1. <my:StatusControl Name="myStatControl" Grid.Row="1"></my:StatusControl>  
Now my test app looks familiar.
 
What Does It Take To Create A Custom WPF Control
 
The neat thing about WPF is as soon as I create the control it goes live. As I am typing this, it really is June 13th, 10:39pm (CST). The control updates even in the design window. There are two test buttons to test a couple of resizing actions and to cycle through the themes.
 
In the program initialization I go ahead and add two messages to the que.
  1. myStatControl.AddStatusMsgToQue(QUSACustomStatus.StatusControl.StatusNotations.Header, "Hellow How Are You?");  
  2. myStatControl.AddStatusMsgToQue(QUSACustomStatus.StatusControl.StatusNotations.Error, "I hear voices in my head!");  
The default clock tick is 15 seconds but it can be adjusted through the method myStatControl.AdjustClockTick(). The theme can be set through the method and then the theme combo box can be hidden. Here is a test run.
 
What Does It Take To Create A Custom WPF Control
 
Fifteen seconds passed and the first message appears in the datagrid and I hit the Cycle Theme button twice to get the Blood Theme.
 
Hope this inspires more programmers to create their own controls.
 
End Of Line Man!