WPF Reusability Factor – User Controls & Custom Controls

There is always seen a perplexity/confusion exist in terms of User Controls and Custom Controls in regards to WPF. They both work alike in terms of reusability, like you can reuse them over and over, again and again. But there are also finite number of differences between them. And in today's post I’ll highlight them with the help of examples.

A User Control is actually a composite of other UI controls, that means it is composing it with the help of other controls and that can be styled and reused as needed. You compose the existing controls in a User Control using XAML like we do in case of Window or so. And the interaction is done in the code behind, if needed. User Control is derived from a ContentControl (Figure-1). That means it Can only have one Child by nature that you set with the help of its content property. In order to compose with more elements, you need to have a Composite Control like a Panel as its root, like we see in case of compositing the Window itself.

Lets now turn our focus to the Custom Control. By Custom Control we mean it is customizable by the end user/consumer. Lets see how! You derive it from Control or ContentControl or other rich controls like RadioButton control or checkbox control etc. It takes part in the themes as well as skinning mechanism for the end users/consumers with the help of Generic theme defined under Themes/Generic.xaml, where you redefine the ControlTemplate or Visual Tree for this custom control and also provide with a default style as needed. Custom controls provides a much more flexible model in terms of customization as well as reusability as compared to User Controls. If the consumer wants to apply a different theme, all he needs to do is to change the control-template/style of that custom control and that's it, it is customized.

UserControls-CustomControls Figure-1

I’ll explain you all these concepts with the help of a concrete example. Here is how the Project looks like:

image Figure-2

I have created a MainWindow that is composed of a Grid that has two rows. First Row contains a Custom Control “GlassRadioControl” and Row#2 contains a User Control (MessageBoxControl) that mimics more like a MessageWindow or so. Class Hierarchy is shown in the Figure-1 above. Here is the XAML for it.

Code Snippet
  1. <Window x:Class="WpfTestApp.MainWindow"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     xmlns:local="clr-namespace:WpfTestApp"
  5.     Title="[Custom Control] vs [User Control]" Height="300" Width="500">
  6.     <Window.Resources>
  7.         <Style x:Key="MessageBoxControlStyle1" TargetType="{x:Type local:MessageBoxControl}">
  8.             <Setter Property="Background" Value="LightGreen" />
  9.             <Setter Property="BorderBrush" Value="Gray" />
  10.             <Setter Property="BorderThickness" Value="2" />
  11.             <Setter Property="Template">
  12.                 <Setter.Value>
  13.                     <ControlTemplate TargetType="{x:Type local:MessageBoxControl}">
  14.                         <Border SnapsToDevicePixels="true"
  15.                             Background="{TemplateBinding Background}"
  16.                             BorderBrush="{TemplateBinding BorderBrush}"
  17.                             BorderThickness="{TemplateBinding BorderThickness}"
  18.                             Padding="{TemplateBinding Padding}">
  19.                         <ContentPresenter
  20.                             HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
  21.                             VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
  22.                             SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
  23.                         </Border>
  24.                     </ControlTemplate>
  25.                 </Setter.Value>
  26.             </Setter>
  27.         </Style>
  28.     </Window.Resources>
  29.     <Grid Margin="5" Background="Beige">
  30.         <Grid.RowDefinitions>
  31.             <RowDefinition Height="57" />
  32.             <RowDefinition />
  33.         </Grid.RowDefinitions>
  34.         <GroupBox Header="Custom Control's Example" Margin="3">
  35.             <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
  36.                 <local:GlassRadioControl DockPanel.Dock="Top" IsChecked="True" LeftColumnWidth="77" />
  37.                 <local:GlassRadioControl DockPanel.Dock="Top" IsRadioChecked="{x:Null}" LeftColumnWidth="77"/>
  38.                 <local:GlassRadioControl IsRadioChecked="False" LeftColumnWidth="77"/>
  39.             </StackPanel>
  40.         </GroupBox>
  41.         <GroupBox Grid.Row="1" Header="User Control's Example" Margin="3">
  42.             <local:MessageBoxControl Message="Hello World"
  43.                 Style="{DynamicResource MessageBoxControlStyle1}" />
  44.         </GroupBox>
  45.     </Grid>
  46. </Window>

Figure-3

Here is the output of the User Control’s:

image Figure-4

and the XAML is shown below.

Code Snippet
  1. <UserControl x:Class="WpfTestApp.MessageBoxControl"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  4.  
  5.     <Grid>
  6.         <Grid.RowDefinitions>
  7.             <RowDefinition Height="*"/>
  8.             <RowDefinition Height="37"/>
  9.         </Grid.RowDefinitions>
  10.         <Border BorderThickness="1" Opacity="0.75">
  11.             <TextBlock x:Name="MessageText" Text="Message here" Margin="2" VerticalAlignment="Center" />
  12.         </Border>
  13.         <Border Grid.Row="1" Background="LightGray" Opacity="0.65" >
  14.             <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5">
  15.                 <Button Content="Cancel" Margin="1" Width="100" Click="ButtonCancelClick" />
  16.                 <Button Content="OK" IsDefault="True" Click="ButtonOkClick" Margin="1" Width="100" />
  17.             </StackPanel>
  18.         </Border>
  19.     </Grid>
  20. </UserControl>

Figure-5

Basically we composed a TextBlock control and Two button controls and lay them out in the Grid and we have the UserControl ready. And the instance is created at line 42, Figure-3 above.

Lets now move on to the Custom Control, GlassRadioControl. This Control is derived from RadioButton (See Figure-1). And what’s the rationale behind extending the RadioButton, is actually we wanted the same behavior as of RadioButton but it should have a look and feel of a specialized split button. This could have done by using just the Control as a base with a little more hard work. Here is what we wanted to accomplish in this Custom-Control:

1) When it is selected, it should have an outline around it.
2) The button has to be split to show text on your Left and visual representation on the right, that is based on some state. Like When it is ok to Run, shows a Tick mark, while if its in progress shows revolving/animated Arrowed-Circle and when there is an error shows a cross, etc. Here is how it looks like for different states:


image

 Figure-6

In order to achieve this, I have modified the control template in the Themes/Generic.xaml, and here is how it looks like (see line 69):

Code Snippet
  1. <ResourceDictionary
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     xmlns:local="clr-namespace:WpfTestApp">
  5.  
  6.     <Style x:Key="CrossPathStyle" TargetType="{x:Type Path}" >
  7.         <Setter Property="Data" Value="M0,0L10,10 M10,0L0,10"/>
  8.         <Setter Property="Stroke" Value="#FFFF0000"/>
  9.         <Setter Property="StrokeThickness" Value="1.25"/>
  10.         <Setter Property="StrokeStartLineCap" Value="Round"/>
  11.         <Setter Property="StrokeEndLineCap" Value="Round"/>
  12.         <Setter Property="StrokeLineJoin" Value="Round"/>
  13.         <Setter Property="RenderTransformOrigin" Value="0.479,0.475"/>
  14.         <Setter Property="Opacity" Value="0.75"/>
  15.         <Setter Property="RenderTransform">
  16.             <Setter.Value>
  17.                 <TransformGroup>
  18.                     <ScaleTransform ScaleX="1" ScaleY="1"/>
  19.                     <SkewTransform AngleX="0" AngleY="0"/>
  20.                     <RotateTransform Angle="0"/>
  21.                     <TranslateTransform X="0" Y="0"/>
  22.                 </TransformGroup>
  23.             </Setter.Value>
  24.         </Setter>
  25.     </Style>
  26.  
  27.     <Style x:Key="TickPathStyle" TargetType="{x:Type Path}">
  28.         <Setter Property="Data" Value="M0,5L3,10 10,0"/>
  29.         <Setter Property="Stroke" Value="#FF90EE90"/>
  30.         <Setter Property="StrokeThickness" Value="1.25"/>
  31.         <Setter Property="StrokeStartLineCap" Value="Round"/>
  32.         <Setter Property="StrokeEndLineCap" Value="Round"/>
  33.         <Setter Property="StrokeLineJoin" Value="Round"/>
  34.         <Setter Property="RenderTransformOrigin" Value="0.479,0.475"/>
  35.         <Setter Property="Opacity" Value="0.75"/>
  36.         <Setter Property="RenderTransform">
  37.             <Setter.Value>
  38.                 <TransformGroup>
  39.                     <ScaleTransform ScaleX="1" ScaleY="1"/>
  40.                     <SkewTransform AngleX="0" AngleY="0"/>
  41.                     <RotateTransform Angle="0"/>
  42.                     <TranslateTransform X="0" Y="0"/>
  43.                 </TransformGroup>
  44.             </Setter.Value>
  45.         </Setter>
  46.     </Style>
  47.     
  48.     <Style x:Key="ArrowRoundPath" TargetType="{x:Type Path}">
  49.         <Setter Property="RenderTransformOrigin" Value="0.556,0.5" />
  50.         <!--<Setter Property="Margin" Value="0,0,0,10" />-->
  51.         <Setter Property="Stretch" Value="Uniform" />
  52.         <Setter Property="Stroke" Value="#BF0000FF" />
  53.         <Setter Property="StrokeThickness" Value="2.75" />
  54.         <Setter Property="StrokeMiterLimit" Value="2.5" />
  55.         <Setter Property="Data" Value="M6.8783809,21.019825 C5.7961721,19.096961 5.1789828,16.872546 5.1789828,14.499944 5.1789828,7.228976 10.982436,1.333335 18.142628,1.333335 25.30132,1.333335 31.104814,7.228976 31.104814,14.499944 31.104814,21.771013 25.30132,27.666664 18.142628,27.666664 15.331431,27.666664 12.729834,26.756829 10.606237,25.213419 M9.1658801,15.547488 L1.333335,19.323817 7.9703076,22.081687 9.1658801,15.547488 z" />
  56.         <Setter Property="RenderTransform">
  57.             <Setter.Value>
  58.                 <TransformGroup>
  59.                     <ScaleTransform ScaleX="-1" ScaleY="1"/>
  60.                     <SkewTransform AngleX="0" AngleY="0"/>
  61.                     <RotateTransform Angle="0"/>
  62.                     <TranslateTransform X="0" Y="0"/>
  63.                 </TransformGroup>
  64.             </Setter.Value>
  65.         </Setter>
  66.         <Setter Property="Opacity" Value="0.75"/>
  67.     </Style>
  68.     
  69.     <ControlTemplate x:Key="GlassRadioTemplate" TargetType="{x:Type local:GlassRadioControl}">
  70.         <ControlTemplate.Resources>
  71.             <Storyboard x:Key="OnLoaded1">
  72.                 <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="path2" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" RepeatBehavior="Forever">
  73.                     <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
  74.                     <SplineDoubleKeyFrame KeyTime="00:00:01.0000000" Value="360"/>
  75.                 </DoubleAnimationUsingKeyFrames>
  76.             </Storyboard>
  77.             <Storyboard x:Key="Timeline1">
  78.                 <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="glow" Storyboard.TargetProperty="(UIElement.Opacity)">
  79.                     <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1"/>
  80.                 </DoubleAnimationUsingKeyFrames>
  81.             </Storyboard>
  82.             <Storyboard x:Key="Timeline2">
  83.                 <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="glow" Storyboard.TargetProperty="(UIElement.Opacity)">
  84.                     <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
  85.                 </DoubleAnimationUsingKeyFrames>
  86.             </Storyboard>
  87.         </ControlTemplate.Resources>
  88.         <Border BorderBrush="#FFFFFFFF" BorderThickness="1,1,1,1" CornerRadius="4,4,4,4">
  89.             <Border x:Name="border" Background="#7F000000" BorderBrush="#FF000000" BorderThickness="1,1,1,1" CornerRadius="4,4,4,4">
  90.                 <Grid>
  91.                     <Grid.RowDefinitions>
  92.                         <RowDefinition Height="0.55*"/>
  93.                         <RowDefinition Height="0.45*" />
  94.                     </Grid.RowDefinitions>
  95.                     <Border Opacity="0" HorizontalAlignment="Stretch" x:Name="glow" Width="Auto" Grid.RowSpan="2" CornerRadius="4,4,4,4">
  96.                         <Border.Background>
  97.                             <RadialGradientBrush>
  98.                                 <RadialGradientBrush.RelativeTransform>
  99.                                     <TransformGroup>
  100.                                         <ScaleTransform ScaleX="1.702" ScaleY="2.243"/>
  101.                                         <SkewTransform AngleX="0" AngleY="0"/>
  102.                                         <RotateTransform Angle="0"/>
  103.                                         <TranslateTransform X="-0.368" Y="-0.152"/>
  104.                                     </TransformGroup>
  105.                                 </RadialGradientBrush.RelativeTransform>
  106.                                 <GradientStop Color="#B28DBDFF" Offset="0"/>
  107.                                 <GradientStop Color="#008DBDFF" Offset="1"/>
  108.                             </RadialGradientBrush>
  109.                         </Border.Background>
  110.                     </Border>
  111.                     <Grid Grid.Row="0" Grid.RowSpan="2" Margin="0">
  112.                         <Grid.ColumnDefinitions>
  113.                             <ColumnDefinition Width="{TemplateBinding LeftColumnWidth}" />
  114.                             <ColumnDefinition Width="{TemplateBinding RightColumnWidth}" />
  115.                         </Grid.ColumnDefinitions>
  116.  
  117.                         <ContentPresenter Grid.Column="0" Margin="3,0,0,0" x:Name="contentPresenter"
  118.                                 Content="{TemplateBinding Content}"
  119.                                 ContentTemplate="{TemplateBinding ContentTemplate}"
  120.                                 HorizontalAlignment="Center"
  121.                                 VerticalAlignment="Center" />
  122.  
  123.                         <Border Grid.Column="1" BorderBrush="Silver" BorderThickness="1.5,0,0,0" />
  124.                         <Border Grid.Column="1" BorderBrush="Black" BorderThickness="1.25,0,0,0" />
  125.                         <Viewbox Grid.Column="1" x:Name="view1" Visibility="Visible" Margin="5" RenderTransformOrigin="0.5,0.5">
  126.                             <Path x:Name="path" Style="{StaticResource CrossPathStyle}" />
  127.                         </Viewbox>
  128.                         <Viewbox Grid.Column="1" x:Name="view2" Visibility="Collapsed" Margin="2">
  129.                             <Path x:Name="path2" Style="{StaticResource ArrowRoundPath}"/>
  130.                         </Viewbox>
  131.                     </Grid>
  132.                     <Border HorizontalAlignment="Stretch" Margin="0,0,0,0" x:Name="shine" Width="Auto" CornerRadius="4,4,0,0">
  133.                         <Border.Background>
  134.                             <LinearGradientBrush EndPoint="0.494,0.889" StartPoint="0.494,0.028">
  135.                                 <GradientStop Color="#99FFFFFF" Offset="0"/>
  136.                                 <GradientStop Color="#33FFFFFF" Offset="1"/>
  137.                             </LinearGradientBrush>
  138.                         </Border.Background>
  139.                     </Border>
  140.                 </Grid>
  141.             </Border>
  142.         </Border>
  143.         <ControlTemplate.Triggers>
  144.             <Trigger Property="IsChecked" Value="True">
  145.                 <Setter Property="BorderBrush" TargetName="border" Value="#FFFFA500"/>
  146.             </Trigger>
  147.             <Trigger Property="IsPressed" Value="True">
  148.                 <Setter Property="Opacity" TargetName="shine" Value="0.4"/>
  149.                 <Setter Property="Background" TargetName="border" Value="#CC000000"/>
  150.                 <Setter Property="Visibility" TargetName="glow" Value="Hidden"/>
  151.             </Trigger>
  152.             <Trigger Property="IsMouseOver" Value="True">
  153.                 <Trigger.EnterActions>
  154.                     <BeginStoryboard Storyboard="{StaticResource Timeline1}"/>
  155.                 </Trigger.EnterActions>
  156.                 <Trigger.ExitActions>
  157.                     <BeginStoryboard x:Name="Timeline2_BeginStoryboard" Storyboard="{StaticResource Timeline2}"/>
  158.                 </Trigger.ExitActions>
  159.             </Trigger>
  160.             <Trigger Property="IsRadioChecked" Value="True">
  161.                 <Setter Property="Content" Value="{Binding Path=TickContentText}" />
  162.                 <Setter TargetName="view2"
  163.                             Property="Visibility"
  164.                             Value="Collapsed" />
  165.                 <Setter TargetName="path"
  166.                             Property="Style"
  167.                             Value="{StaticResource TickPathStyle}" />
  168.             </Trigger>
  169.             
  170.             <Trigger Property="IsRadioChecked" Value="False">
  171.                 <!--<Setter TargetName="contentPresenter" Property="Content" Value="{Binding Path=CrossContentText}" />-->
  172.                                 <Setter Property="Content" Value="{Binding Path=CrossContentText}" />
  173.             </Trigger>
  174.  
  175.             <Trigger Property="IsRadioChecked" Value="{x:Null}">
  176.                 <Setter Property="Content" Value="{Binding Path=ProgressContentText}" />
  177.                 <Setter TargetName="view2"
  178.                             Property="Visibility"
  179.                             Value="Visible" />
  180.                 <Setter TargetName="view1"
  181.                             Property="Visibility"
  182.                             Value="Collapsed" />
  183.             </Trigger>
  184.             <!--<EventTrigger RoutedEvent="FrameworkElement.Loaded">
  185.                 <BeginStoryboard Storyboard="{StaticResource OnLoaded1}"/>
  186.             </EventTrigger>-->
  187.         </ControlTemplate.Triggers>
  188.     </ControlTemplate>
  189.  
  190.     <Style TargetType="{x:Type local:GlassRadioControl}">
  191.         <Setter Property="Template" Value="{StaticResource GlassRadioTemplate}"/>
  192.     </Style>
  193.     
  194.     <!--<Style TargetType="{x:Type local:GlassRadioControl}">
  195.         <Setter Property="Template">
  196.             <Setter.Value>
  197.                 <ControlTemplate TargetType="{x:Type local:GlassRadioControl}">
  198.                     <Border Background="{TemplateBinding Background}"
  199.                             BorderBrush="{TemplateBinding BorderBrush}"
  200.                             BorderThickness="{TemplateBinding BorderThickness}">
  201.                     </Border>
  202.                 </ControlTemplate>
  203.             </Setter.Value>
  204.         </Setter>
  205.     </Style>-->
  206. </ResourceDictionary>

 Figure-7

In this Custom Control, GlassRadioControl, there are three things noticeable. If you look at the XAML (Figure-7), I have created three styles for the paths and referred them under the ControlTemplate section of the Generic.xaml, resource dictionary under. The Control Template’s Resource section contains the animation logic for the circular motion of the Arrow. Also I took advantage of Triggers in the Control Template to obtain the Conditional Logic for the control, like when it is selected, a Border will be drawn etc.
And here is the Code behind for it. If you look at the code, we defined IsRadioChecked Dependency Property, that is responsible for states, like running or error states or so while LeftColumnWidth and RightColumnWidth Dependency properties are responsible for Setting the Left and Right Width of the Split sections. Also you can set the Ticked Content using TickContentText Property, CrossContentText Property when it sees an Error state as well as the ProgressContentText property for showing the running state.

Code Snippet
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Data;
  8. using System.Windows.Documents;
  9. using System.Windows.Input;
  10. using System.Windows.Media;
  11. using System.Windows.Media.Imaging;
  12. using System.Windows.Navigation;
  13. using System.Windows.Shapes;
  14. using System.Windows.Media.Animation;
  15.  
  16. namespace WpfTestApp
  17. {
  18.     /// <summary>
  19.     /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
  20.     ///
  21.     /// Step 1a) Using this custom control in a XAML file that exists in the current project.
  22.     /// Add this XmlNamespace attribute to the root element of the markup file where it is
  23.     /// to be used:
  24.     ///
  25.     ///     xmlns:MyNamespace="clr-namespace:WpfTestApp"
  26.     ///
  27.     ///
  28.     /// Step 1b) Using this custom control in a XAML file that exists in a different project.
  29.     /// Add this XmlNamespace attribute to the root element of the markup file where it is
  30.     /// to be used:
  31.     ///
  32.     ///     xmlns:MyNamespace="clr-namespace:WpfTestApp;assembly=WpfTestApp"
  33.     ///
  34.     /// You will also need to add a project reference from the project where the XAML file lives
  35.     /// to this project and Rebuild to avoid compilation errors:
  36.     ///
  37.     ///     Right click on the target project in the Solution Explorer and
  38.     ///     "Add Reference"->"Projects"->[Browse to and select this project]
  39.     ///
  40.     ///
  41.     /// Step 2)
  42.     /// Go ahead and use your control in the XAML file.
  43.     ///
  44.     ///     <MyNamespace:GlassRadioControl/>
  45.     ///
  46.     /// </summary>
  47.     public partial class GlassRadioControl : RadioButton
  48.     {
  49.         static GlassRadioControl()
  50.         {
  51.             try
  52.             {
  53.                 DefaultStyleKeyProperty.OverrideMetadata(typeof(GlassRadioControl),
  54.                     new FrameworkPropertyMetadata(typeof(GlassRadioControl)));
  55.             }
  56.             catch
  57.             {
  58.  
  59.             }
  60.         }
  61.  
  62.         public static readonly DependencyProperty IsRadioCheckedProperty = DependencyProperty.Register("IsRadioChecked",
  63.             typeof(bool?),
  64.             typeof(GlassRadioControl),
  65.             new FrameworkPropertyMetadata(true,
  66.             new PropertyChangedCallback(RadioCheckedPropertyChangedCallBack)));
  67.  
  68.         public bool? IsRadioChecked
  69.         {
  70.             get { return (bool?)GetValue(IsRadioCheckedProperty); }
  71.             set { SetValue(IsRadioCheckedProperty, value); }
  72.         }
  73.  
  74.         static void RadioCheckedPropertyChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
  75.         {
  76.  
  77.         }
  78.  
  79.         public static readonly DependencyProperty LeftColumnWidthProperty = DependencyProperty.Register("LeftColumnWidth",
  80.             typeof(GridLength),
  81.             typeof(GlassRadioControl),
  82.             new FrameworkPropertyMetadata(new GridLength(0.25, GridUnitType.Auto),
  83.             new PropertyChangedCallback(LeftColumnWidthPropertyChangedCallBack)));
  84.  
  85.         public GridLength LeftColumnWidth
  86.         {
  87.             get { return (GridLength)GetValue(LeftColumnWidthProperty); }
  88.             set { SetValue(LeftColumnWidthProperty, value); }
  89.         }
  90.  
  91.         static void LeftColumnWidthPropertyChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
  92.         {
  93.         }
  94.  
  95.         public static readonly DependencyProperty RightColumnWidthProperty = DependencyProperty.Register("RightColumnWidth",
  96.              typeof(GridLength),
  97.              typeof(GlassRadioControl),
  98.              new FrameworkPropertyMetadata(new GridLength(0.25, GridUnitType.Auto),
  99.              new PropertyChangedCallback(RightColumnWidthPropertyChangedCallBack)));
  100.  
  101.         public GridLength RightColumnWidth
  102.         {
  103.             get { return (GridLength)GetValue(RightColumnWidthProperty); }
  104.             set { SetValue(RightColumnWidthProperty, value); }
  105.         }
  106.  
  107.  
  108.         static void RightColumnWidthPropertyChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
  109.         {
  110.         }
  111.  
  112.         public static readonly DependencyProperty TickContentTextProperty = DependencyProperty.Register("TickContentText",
  113.             typeof(string),
  114.             typeof(GlassRadioControl),
  115.             new FrameworkPropertyMetadata("Run",
  116.             new PropertyChangedCallback(TickContentTextPropertyChangedCallBack)));
  117.  
  118.         public string TickContentText
  119.         {
  120.             get { return (string)GetValue(TickContentTextProperty); }
  121.             set { SetValue(TickContentTextProperty, value); }
  122.         }
  123.  
  124.         static void TickContentTextPropertyChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
  125.         {
  126.         }
  127.  
  128.         public static readonly DependencyProperty CrossContentTextProperty = DependencyProperty.Register("CrossContentText",
  129.             typeof(string),
  130.             typeof(GlassRadioControl),
  131.             new FrameworkPropertyMetadata("Error",
  132.             new PropertyChangedCallback(CrossContentTextPropertyChangedCallBack)));
  133.  
  134.         public string CrossContentText
  135.         {
  136.             get { return (string)GetValue(CrossContentTextProperty); }
  137.             set { SetValue(CrossContentTextProperty, value); }
  138.         }
  139.  
  140.         static void CrossContentTextPropertyChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
  141.         {
  142.         }
  143.  
  144.         public static readonly DependencyProperty ProgressContentTextProperty = DependencyProperty.Register("ProgressContentText",
  145.             typeof(string),
  146.             typeof(GlassRadioControl),
  147.             new FrameworkPropertyMetadata("Runing",
  148.             new PropertyChangedCallback(ProgressContentTextPropertyChangedCallBack)));
  149.  
  150.         public string ProgressContentText
  151.         {
  152.             get { return (string)GetValue(ProgressContentTextProperty); }
  153.             set { SetValue(ProgressContentTextProperty, value); }
  154.         }
  155.  
  156.         static void ProgressContentTextPropertyChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
  157.         {
  158.         }
  159.  
  160.         public GlassRadioControl()
  161.         {
  162.             // this is important if you want to bind the properties in the triggers or so...
  163.             this.DataContext = this;
  164.         }
  165.  
  166.         public override void OnApplyTemplate()
  167.         {
  168.             base.OnApplyTemplate();
  169.  
  170.             Storyboard storyboard = (Storyboard)this.Template.Resources["OnLoaded1"];
  171.             storyboard.Begin(this, this.Template);
  172.         }
  173.  
  174.     }
  175. }

 Figure-8

Once this Custom Control is developed, you may reuse it over and over and again and again, as you can see Lines 36-38, Figure-3.
Now, the purpose of the test application is to show you how we consume both Custom Controls and User Controls and how they can be reused and customized for our needs. Here is the final output for it:

image

 Figure-9

That's all for now, I’ll elaborate the concept of custom controls in a more like a tutorial, when I cover the Control Templates in context to Another Reusability Factor i.e Styling, Themes and Skinning  , So stay tuned. Enjoy :)

Download File - Sample Project

If you enjoyed reading this blog, leave your valuable feedback and consider subscribing to the RSS feed. You can also subscribe to it by email. Also, you can follow me on Twitter. Thank you!

Technorati Tags: ,,
Comments are closed