The State of Things – Look-less Controls

This post carries on from the basic introduction I have written about what it means to be a responsible developer when developing a control that will be used by others internally or externally within applications. In the first post I put together a simple search control with text input as shown before:

This is the place where most developers end the control development. However, I will show here that with a little more work we can define a theme for this control and separate out the graphical design from the control logic thus defining a “Look-less” control. This has the advantage a being restyled inside Expression Blend or VS2010 facilitating code reuse design flexibility.

The steps are relatively simple:

  1. Prepare the control project for theming
  2. Remove the XAML from the original control and place it in the theme directory
  3. Perform minor changes to the code behind

Prepare the control project for theming

This part is relatively simple. The project is extended with a “Themes” directory and two XAML resource files are added; one for the referencing resource file “generic.xaml” and the other for the control itself “SampleControl.xaml”. The solution now looks as follows:

The XAML in the generic.xaml is as follows:

<ResourceDictionary 
             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" 
             >
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/SampleControls;component/themes/SearchControl.xaml"/>
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

The XAML in the SearchControl.xaml is initially as follows:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:i4="http://schemas.intelligence4.ch/SampleControls/2011" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d">
</ResourceDictionary>

Remove the XAML

This step is also relatively simple when Expression Blend is available – and who would not be using this tool for XAML design? Open up a new project in either VS2010 or Expression Blend. Drop the SearchControl onto the design surface and using Blend right click on the control instance in the “Objects and timelines” tab and select “Edit Template” and “Edit a Copy…”. If you have added this window to the project where the SearchControl is located it is then possible to select the XAML resource file that was already prepared in the above step.

 

Update the control’s code

Now we can throw away the original XAML and keep the code behind. To do this add a C# file to your project as shown below.

Finally the code for the control:

using System.Drawing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
 
namespace SampleControls
{
    /// <summary>
    /// Interaction logic for SearchControlV3.xaml
    /// </summary>
    [ContentProperty("SearchText")]
    [TemplatePart(Name = "PART_Search", Type = typeof(Button))]
    [TemplatePart(Name = "PART_SearchText", Type = typeof(TextBox))]
    public class SearchControl : Control
    {
        /// <summary>
        /// Using a DependencyProperty as the backing store for SearchText.
        /// </summary>
        public static readonly DependencyProperty SearchTextProperty =
            DependencyProperty.Register("SearchText", typeof(string), typeof(SearchControl),
                                        new FrameworkPropertyMetadata(string.Empty));
 
        /// <summary>
        /// Using a DependencyProperty as the backing store for IsSearchEnabled.
        /// </summary>
        public static readonly DependencyProperty IsSearchEnabledProperty =
            DependencyProperty.Register("IsSearchEnabled", typeof(bool), typeof(SearchControl),
                                        new FrameworkPropertyMetadata(true));
 
        /// <summary>
        /// </summary>
        public static readonly RoutedEvent SearchTextChangedEvent =
            EventManager.RegisterRoutedEvent("SearchTextChanged",
                                             RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SearchControl));
 
        /// <summary>
        /// Using a DependencyProperty as the backing store for SearchCommandTarget.
        /// </summary>
        public static readonly DependencyProperty SearchCommandTargetProperty =
            DependencyProperty.Register("SearchCommandTarget", typeof(IInputElement), typeof(SearchControl),
                                        new FrameworkPropertyMetadata(null));
 
        /// <summary>
        /// Using a DependencyProperty as the backing store for SearchCommandParameter.
        /// </summary>
        public static readonly DependencyProperty SearchCommandParameterProperty =
            DependencyProperty.Register("SearchCommandParameter", typeof(object), typeof(SearchControl),
                                        new FrameworkPropertyMetadata(null));
 
        /// <summary>
        /// Using a DependencyProperty as the backing store for SearchCommand.
        /// </summary>
        public static readonly DependencyProperty SearchCommandProperty =
            DependencyProperty.Register("SearchCommand", typeof(ICommand), typeof(SearchControl),
                                        new FrameworkPropertyMetadata(null));
 
        /// <summary>
        /// Initializes the <see cref="SearchControlV3"/> class.
        /// </summary>
        static SearchControlV3()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(SearchControl),
                new FrameworkPropertyMetadata(typeof(SearchControl)));
        }
 
        private static readonly RoutedUICommand SrcCommand = new RoutedUICommand("Search", "SrchCommand", typeof(SearchControl));
 
        /// <summary>
        /// Gets the SRCH command.
        /// </summary>
        /// <value>The SRCH command.</value>
        public static RoutedUICommand SrchCommand
        {
            get { return SrcCommand; }
        }
 
        /// <summary>
        /// Gets or sets the search command target.
        /// </summary>
        /// <value>The search command target.</value>
        public IInputElement SearchCommandTarget
        {
            get { return (IInputElement)GetValue(SearchCommandTargetProperty); }
            set { SetValue(SearchCommandTargetProperty, value); }
        }
 
        /// <summary>
        /// Gets or sets the search command parameter.
        /// </summary>
        /// <value>The search command parameter.</value>
        public object SearchCommandParameter
        {
            get { return GetValue(SearchCommandParameterProperty); }
            set { SetValue(SearchCommandParameterProperty, value); }
        }
 
        /// <summary>
        /// Gets or sets the search command.
        /// </summary>
        /// <value>The search command.</value>
        public ICommand SearchCommand
        {
            get { return (ICommand)GetValue(SearchCommandProperty); }
            set { SetValue(SearchCommandProperty, value); }
        }
 
        /// <summary>
        /// Gets or sets a value indicating whether this instance is search enabled.
        /// </summary>
        /// <value>
        /// <c>True</c> if this instance is search enabled; otherwise, <c>false</c>.
        /// </value>
        public bool IsSearchEnabled
        {
            get { return (bool)GetValue(IsSearchEnabledProperty); }
            set { SetValue(IsSearchEnabledProperty, value); }
        }
 
        /// <summary>
        /// Gets or sets the search text.
        /// </summary>
        /// <value>The search text.</value>
        public string SearchText
        {
            get { return (string)GetValue(SearchTextProperty); }
            set { SetValue(SearchTextProperty, value); }
        }
 
        /// <summary>
        /// Executes the command source.
        /// </summary>
        /// <param name="command">The command.</param>
        /// <param name="parameter">The parameter.</param>
        /// <param name="target">The target.</param>
        private static void ExecuteCommandSource(ICommand command, object parameter, IInputElement target)
        {
            var command2 = command as RoutedCommand;
 
            if (command2 != null)
            {
                if (command2.CanExecute(parameter, target))
                {
                    command2.Execute(parameter, target);
                }
            }
            else if (command.CanExecute(parameter))
            {
                command.Execute(parameter);
            }
        }
 
        /// <summary>
        /// Handles the Click event of the theButton control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void TheButtonClick(object sender, RoutedEventArgs e)
        {
            ExecuteCommandSource(this.SearchCommand, this.SearchCommandParameter, this.SearchCommandTarget);
        }
 
        /// <summary>
        /// Handles the TextChanged event of the theTextBox control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.TextChangedEventArgs"/> instance containing the event data.</param>
        private void TheTextBoxTextChanged(object sender, TextChangedEventArgs e)
        {
            var searchText = GetTemplateChild("PART_SearchText") as TextBox;
 
            // hook up the evebt handler
            if (searchText != null)
            {
                this.SearchText = searchText.Text;
            }
 
            e.Handled = true;
        }
 
        /// <summary>
        /// When overridden in a derived class, is invoked whenever application code or internal 
        /// processes call <see cref="M:System.Windows.FrameworkElement.ApplyTemplate"/>.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
 
            // Retrieve the Button from the current template
            var browseButton = GetTemplateChild("PART_Search") as Button;
 
            // Hook up the event handler
            if (browseButton != null)
            {
                browseButton.Click += this.TheButtonClick;
            }
 
            var searchText = GetTemplateChild("PART_SearchText") as TextBox;
 
            // hook up the evebt handler
            if (searchText != null)
            {
                searchText.TextChanged += this.TheTextBoxTextChanged;
            }
        }
    }
}

The final outcome

A newly styled SearchControl along with the new style is as shown below.

<Window
        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:SampleControls="clr-namespace:SampleControls;assembly=SampleControls" 
        x:Name="window" 
        mc:Ignorable="d" 
        x:Class="SamplesApplication.MainWindow"
        Title="MainWindow" 
        Height="232" 
        Width="500" 
        >
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.182*"/>
            <ColumnDefinition Width="0.463*"/>
            <ColumnDefinition Width="0.355*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="0.105*"/>
            <RowDefinition Height="0.211*"/>
            <RowDefinition Height="0.055*"/>
            <RowDefinition Height="0.222*"/>
            <RowDefinition Height="0.407*"/>
        </Grid.RowDefinitions>
         <SampleControls:SearchControl x:Name="searchControl" 
            Width="Auto" 
            Grid.Row="1" 
            Grid.Column="1" 
            SearchCommand="{Binding TheSearchCommand, ElementName=window}" 
            SearchCommandParameter="{Binding RelativeSource={RelativeSource Self}}" 
            IsSearchEnabled="{Binding IsChecked, ElementName=toggleButton}" Margin="0" 
       Style="{DynamicResource SearchControlNewStyle}" 
            /> 
        <TextBox x:Name="textBox" 
            TextWrapping="Wrap" 
            Text="{Binding SearchText, ElementName=searchControl}" Margin="5,33.04,71.912,24" 
       Grid.Row="4" Grid.Column="1" d:LayoutOverrides="Height"/>
        <ToggleButton x:Name="toggleButton" 
                      Content="Enable" 
                      Margin="0,33.04,2,24" 
                      Grid.Row="4" Grid.Column="1" 
                      HorizontalAlignment="Right" Width="52.912"/>
    </Grid>
</Window>

The new style:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:SampleControls="clr-namespace:SampleControls;assembly=SampleControls" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d">
    <Style x:Key="SearchControlNewStyle" TargetType="{x:Type SampleControls:SearchControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type SampleControls:SearchControl}">
                    <Grid Margin="-3.375,0,0,0">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="0.468*"/>
                            <ColumnDefinition Width="5"/>
                            <ColumnDefinition Width="0.532*"/>
                        </Grid.ColumnDefinitions>
                        <Button x:Name="PART_Search" 
                            Content="Search..." 
                            IsEnabled="{Binding IsSearchEnabled, RelativeSource={RelativeSource TemplatedParent}}" 
                            Margin="0" Width="Auto" 
                            Grid.Column="2" d:LayoutOverrides="Width" 
                                            >
                            <Button.Background>
                                <LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
                                    <GradientStop Color="#FFF3F3F3" Offset="0"/>
                                    <GradientStop Color="#FFEBEBEB" Offset="0.5"/>
                                    <GradientStop Color="#FFDDDDDD" Offset="0.5"/>
                                    <GradientStop Color="#FFC25656" Offset="1"/>
                                </LinearGradientBrush>
                            </Button.Background>
                        </Button>
                        <TextBox x:Name="PART_SearchText" 
                            Margin="0" 
                            IsEnabled="{Binding IsSearchEnabled, RelativeSource={RelativeSource TemplatedParent}}" 
                            HorizontalAlignment="Stretch" Grid.ColumnSpan="1" d:LayoutOverrides="GridBox" 
                            Background="#FFB45858" Foreground="#FF1F28B2" 
                                        />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Width" Value="100"/>
    </Style>
</ResourceDictionary>

That’s all folks…

Advertisements

~ by Intelligence4 on April 13, 2011.

 
%d bloggers like this: