What Does it Mean to be “Lookless”?

One of the powerful features of Silverlight is the ability to have fine grain control over how your application looks. This power is given to use through “Lookless” controls or more commonly known as Templated Controls.  What this means is that the functionality of the control is contained in a class file and the look and feel is implemented in XAML.  All of the out of the box controls are created this way.  You can test this out in Expression Blend by adding a button to your canvas. Right click on the button select ‘Edit Template’ and see what is generated for you.

In this article I will work through how to create your own templated control.  Even if you never create a real control of your own, it is a good exercise to understand how templating works in Silverlight.  I will be creating a simple control that contains a text box and a button:

image

There is nothing fancy with the control I have created and I probably would not create a control like this.  I did something simple to demonstrate the power of templated controls.

The first thing you need to do is create the control. You can create this control in the current project or in a class library project.  Visual Studio helps in create templated controls by providing a template.  Go to File –> Add and select ‘New Item’.  From the ‘Add New Item’ dialog select ‘Silverlight Templated Control’. I am creating a control called TextWithButton (I know real original).

 

image

This is going to create a class file, a directory called ‘Themes’ (a hold over from WPF) and a XAML file called Generic.  The class file is for your implementation code. The Generic.xaml file is for your default template. Now we have to wire up the template to the implementation.  When you added the control the implementation class inherits from Control.  Control has a property called DefaultStyleKey that we can set in the constructor of our control.

this.DefaultStyleKey = typeof(TextWithButton);

Your control is going to go out to the Generic.xmal and find a style with this key.

Templated controls assume that there are parts (Framework Elements that make up your interface). Inside the Generic.xaml file you create the parts of your control ensuring to set the x:name property.  This will be used to get a reference to the parts from the class file.   You will also want to implement any Visual States that you will be working with.  Here are the visual states for my control:

<VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="GroupCommon">
                                <VisualState x:Name="Unfocused"/>                                
                                <VisualState x:Name="Focused">
                                	<Storyboard>
                                		<ColorAnimationUsingKeyFrames BeginTime="00:00:00" 
                                                                      Duration="00:00:00.0010000" 
                                                                      Storyboard.TargetName="InputText" 
                                                                      Storyboard.TargetProperty="(Control.Background).(SolidColorBrush.Color)">
                                			<EasingColorKeyFrame KeyTime="00:00:00" Value="#FFFAFAFA"/>
                                		</ColorAnimationUsingKeyFrames>
                                		<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
                                                                      Duration="00:00:00.0010000" 
                                                                      Storyboard.TargetName="border" 
                                                                      Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)">
                                			<EasingColorKeyFrame KeyTime="00:00:00" Value="#FFCE4646"/>
                                		</ColorAnimationUsingKeyFrames>
                                	</Storyboard>                                
                                </VisualState>                    
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>

Now you want to override the OnApplyTemplate method. Here is my override:

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _inputTxt = GetTemplateChild(ElementTextBox) as TextBox;
            if (_inputTxt != null)
            {
                LoadText();
            }

            _inputButton = GetTemplateChild(ElementButton) as Button;
            if (_inputButton != null)
            {
                ConfigureButton();
            }
        }

Here I am using GetTemplateChild method, passing in the name of the part, and setting an internal variable casting the results as parts type. It also gives you a chance to call some methods to do some initialization work for you. When working with templated controls it is important to code defensively in order to ensure that a user hasn’t delete a control part you were expecting.

Now we have to tell our implementation which parts to expect.  We do that by setting the TemplatePart attribute on the class:

    [TemplatePart(Name = TextWithButton.ElementTextBox, Type = typeof(TextBox))]
    [TemplatePart(Name = TextWithButton.ElementButton, Type = typeof(Button))]

Here you tell the attribute what is the name of the part and the type.  You also need to specify and VisualStates you are expecting again by setting an attribute on the class:

    [TemplateVisualState(Name = VisualStates.StateFocused, GroupName = VisualStates.GroupFocus)]
    [TemplateVisualState(Name = VisualStates.StateUnfocused, GroupName = VisualStates.GroupFocus)]

I am doing something simple to implement state changes. In the constructor I am subscribing to focus events:

      this.GotFocus += new RoutedEventHandler(TextWithButton_GotFocus);
      this.LostFocus += new RoutedEventHandler(TextWithButton_LostFocus);

Here are the handlers for those events:

        void TextWithButton_LostFocus(object sender, RoutedEventArgs e)
        {
            VisualStateManager.GoToState(this, VisualStates.StateUnfocused, true);
        }

        void TextWithButton_GotFocus(object sender, RoutedEventArgs e)
        {
            VisualStateManager.GoToState(this, VisualStates.StateFocused, true);
        }

At this point you have a templated control that you can add to a page and it will render.  What we haven’t done yet is create any functionality. We are going to want to have the ability to bind to the text property of the text box part and to the command property of the button part.  There are other things that you can do like expose events but I am not going to cover those in this article.  In order to be able to bind to properties on your control you have to create Dependency Properties. 

Here is the property that I created to handled the text:

        public string ControlText
        {
            get { return (string)GetValue(ControlTextProperty); }
            set { SetValue(ControlTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for ControlText. 
        //This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ControlTextProperty =
            DependencyProperty.Register("ControlText",
            typeof(string), typeof(TextWithButton),
            new PropertyMetadata(ControlTextChanged));

Dependency properties allow you to handle when the value is changed. Here is the implementation of the change event handler:

        private static void ControlTextChanged(DependencyObject d,
            DependencyPropertyChangedEventArgs args)
        {
            TextWithButton view = d as TextWithButton;

            if (view != null)
            {
                view.LoadText();
            }
        }

Here I cast the DependencyObject as the control type and call a method on it.  Notice that is the same method I am calling when the template is applied. I can now create a two way binding that will handle text from a view model through to the inner textbox.  Here is the LoadText implementation:

        private void LoadText()
        {
            if (!string.IsNullOrWhiteSpace(ControlText) && _inputTxt != null)
            {
                Binding binding = new Binding();
                binding.Source = this;
                binding.Path = new PropertyPath("ControlText");
                binding.Mode = BindingMode.TwoWay;
                _inputTxt.SetBinding(TextBox.TextProperty, binding);
            }
        }

Note the defensive coding. I do something similar for the button command (see code for implementation). 

I now bind to my ControlText property in my view from my view model:

            <cc:TextWithButton ControlText="{Binding SomeText, Mode=TwoWay}"
                               ButtonCommand="{Binding ButtonClickCMD}"
                               Margin="10"
                               Width="500" />

So let’s recap what we have done we have created a control with encapsulated functionality that can be templated by users without breaking that functionality.  We could have two different instances of this control on the same page that have two different styles:

            <cc:TextWithButton ControlText="{Binding SomeText, Mode=TwoWay}"
                               Margin="10"
                               Width="500" />
            <cc:TextWithButton ControlText="{Binding SomeText, Mode=TwoWay}"
                               ButtonCommand="{Binding ButtonClickCMD}"
                               Margin="10"
                               Width="500"
                               Style="{StaticResource TextWithButtonStyle1}" />

In the static recourse I change the layout and back ground of one of the controls so it looks something like this

image

 

These two controls share the same functionality but as you can see I get the ability to change how they look.

You may not need to create a templated control unless you are building your own control library.  By creating my I own, I now feel more confident when it comes to altering the templates of the native Silverlight controls.  Another good exercise is to look at the source control of the Silverlight Toolkit. Here you will see a similar approach done by the Silverlight Team.

Source Code

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Create a website or blog at WordPress.com

Up ↑

%d bloggers like this: