(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

Create a Custom Control - Inheriting from TextBox

(16 votes)
Phil Middlemiss
>
Phil Middlemiss
Joined Mar 02, 2010
Articles:   3
Comments:   10
More Articles
17 comments   /   posted on Jun 30, 2010
Categories:   Controls

This article is compatible with the latest version of Silverlight.


Introduction

When a control almost does what you want it to – if only it had another button or behaved slightly differently – you may be able to extend it by writing a custom control. Custom controls let you change an existing control or write a completely new control.

In this article I am going to describe the steps to extend an existing control. It will inherit from a TextBox and add a watermark and a button to clear existing text.

Here is the final result being used as a filter for a data grid, I’ve wired up the WatermarkedTextBox to act as a filter for the items in the list. The “Styled” Checkbox will apply a new theme to the app, including the WatermarkedTextBox:

You can grab the sample project, including the source code for the control, here. To compile it you need to either have Blend 4 installed, or Visual Studio 2010 with the free Blend 4 SDK.

UserControl vs. Custom Control

UserControls are great when you need a quick way to group some related controls together, such as an address form, or a grid-based browser with navigation controls. Sometimes they come in handy when an existing control almost does what you need it to but just needs a little enhancing. There are advantages and disadvantages of using a UserControl and when the disadvantages start to outweigh the advantages it’s time to look at a custom control.

Step 1 – Create the Projects

EditInVisualStudio Start a new project in Expression Blend (you can also create it in Visual Studio). Select a “Silverlight Application + Website” project type and give it a name. This is going to be the test-bed for the new control.

At this point it’s easier to create the control library in Visual Studio. Blend is not designed for serious code entry and it doesn’t help us create any custom visual elements for our control.

Right click the main solution at the top of the “Projects” tab and select “Edit in Visual Studio”. Once in Visual Studio, right-click the main solution and select “Add” and “New Project…”. Choose the “Silverlight Class Library” project type and give it a name – this will be the project containing the new custom control. Visual Studio will create a default class in the new project that you can delete and then right click the new project and select “Add” and “New Item…”. Choose the “Class” item type and give it the name “WatermarkedTextBox”.

Step 2 – Subclass TextBox

The new control will subclass TextBox. It will also have a default style, referenced in the constructor like this:

 public class WatermarkedTextBox : TextBox
 {
     /// <summary>
     /// Initializes a new instance of the <see cref="WatermarkedTextBox"/> class.
     /// </summary>
     public WatermarkedTextBox()
     {
         DefaultStyleKey = typeof(WatermarkedTextBox);
     }
 }

The code above tells Silverlight to look for a style that uses TargetType=”WatermarkedTextBox” for this control. It will look for the default style in a specific folder in the project named “Themes”. Right-click the project containing the WatermarkedTextBox class and select “Add” and “New Folder”; name the folder “Themes”. Now add the resource library by right-clicking the “Themes” folder, selecting “Add” and “New Item…”. Choose the “Silverlight Resource Dictionary” item type and name it “generic.xaml”. All Silverlight control libraries must have their default styles defined in a resource dictionary file called “generic.xaml” in a folder called “Themes”.

Step 3 – Create the Default Style

The control needs to have a default style defined in generic.xaml. You can create this by hand, but when you are extending an existing control, it is much easier to start with the default style from the control you are inheriting from. The easiest way to get the default style from the inherited TextBox class is to do the following:

  1. swap back to Blend and, in MainPage.xaml, create a new TextBox on the design surface.
  2. reset any properties that Blend changed when you added it.
  3. right-click the TextBox and select “Edit Template” and “Edit a Copy”.
  4. choose “Apply to all” rather than giving it a Name (key), and choose “This document” for the “Define in” option

Now switch to XAML view for MainPage.xaml and you will see, in the Resources section of the UserControl, a control template with the key “ValidationToolTemplate” and a style with the TargetType set to “TextBox”. Copy this XAML and paste it, back in Visual Studio, into the generic.xaml resource dictionary.

The Resource dictionary needs a namespace declaration for the local control like this:

 <ResourceDictionary 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:local="clr-namespace:CustomControls" 
     xmlns:sys="clr-namespace:System;assembly=mscorlib"
 >

The “local” allows you to reference the new control and the “sys” allows you to reference types in the System namespace in the core (which we will be using later). Change the TargetType from “TextBox” to “local:WatermarkedTextBox”:

<Style TargetType="local:WatermarkedTextBox">

We now have our WatermarkedTextBox at the same level as the basic TextBox. We could compile it now and use it in our project, but it wouldn’t behave any differently from a regular TextBox.

Step 4 – Add Custom Properties

Now we start creating the custom functionality. In this case, for the WatermarkedTextBox, I am going to add the following properties:

  • Watermark : the text that displays when the TextBox is empty.
  • WatermarkStyle : a Style that will be applied to the Watermark
  • WatermarkForeground : quick access to the brush used for the Watermark. This will override the Foreground specified in the WatermarkStyle property.
  • TextRemoverStyle : a Style that will be applied to the button that clears any entered text
  • TextRemoverToolTip : text for a tooltip for the text removal button
  • IsUpdateImmediate : a Boolean property (which requires the system namespace reference in the XAML) that, if set to True, will cause any Binding source on the WatermarkTextBox.Text property to be updated whenever the text changes, rather than just when the control loses focus (which is the default behavior for the TextBox). This is especially useful if the control is used as a filter for a collection of items.

I added the WatermarkForeground property as a quicker way to set the foreground brush for the watermark text rather than having to edit the WatermarkStyle, and to use as an example of precedence in TemplateBinding default XAML when I get to it.

Custom properties don’t have to be dependency properties, but it is a good idea to use dependency properties if you want them to support binding. In the downloadable sample project, I have created a dependency property and a class property for each of the above. Here is the code for the Watermark property:

 public class WatermarkedTextBox : TextBox
 {
     /// <summary>
     /// Watermark text dependency property
     /// </summary>
     public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(
         "Watermark",
         typeof(string),
         typeof(WatermarkedTextBox),
         new PropertyMetadata("Enter text here"));
      
     /// <summary>
     /// Gets or sets the watermark.
     /// </summary>
     /// <value>The watermark.</value>
     [Description("Gets or sets the watermark")]
     [Category("Watermark")]
     public string Watermark
     {
         get { return (string)GetValue(WatermarkProperty); }
         set { SetValue(WatermarkProperty, value); }
     }
 ...

There are some important points to note about this code:Watermark Category

  • The default value for the dependency property (“Enter text here”) should have a matching setter in the default style XAML. In this case, the style has a setter for the Watermark property that matches the default value given in the PropertyMetadata for the WatermarkProperty.
  • The Description attribute (in the System.ComponentModel namespace) is picked up by Blend and used as a tooltip for the property.
  • The Category attribute is used by Blend to create a new grouping category on the Properties tab, shown in the image to the right.
  • The accessors for the Watermark property do nothing other than get/set the value of the dependency property.

The other properties all follow the same pattern.

Step 5 – Add Custom Elements

In this step we add any extra visual elements to the control template in the default style XAML. For this control I’ll add a TextBlock for the watermark and a Button for the text-remover:

 <TextBlock x:Name="Watermark" Style="{TemplateBinding WatermarkStyle}" Foreground="{TemplateBinding WatermarkForeground}"
            Text="{TemplateBinding Watermark}" IsHitTestVisible="False" Grid.ColumnSpan="2"/>
 <Button x:Name="TextRemover" Height="14" HorizontalAlignment="Center" Margin="0,3,3,3"
     Style="{TemplateBinding TextRemoverStyle}" VerticalAlignment="Center" Width="14" Content="Button"
     Grid.Column="1" IsTabStop="False" RenderTransformOrigin="0.5,0.5"
     ToolTipService.ToolTip="{TemplateBinding TextRemoverToolTip}">
 </Button>

Use “{TemplateBinding …}” to bind properties on the visual elements to the properties on the control class. In the XAML above you can see that I’ve bound both the Style and the Foreground properties of the TextBlock. As mentioned earlier, the Foreground is there for convenience and will override the Foreground brush specified in the style.

The default WatermarkStyle is defined in the XAML as a normal Setter like this:

 <Setter Property="WatermarkStyle">
     <Setter.Value>
         <Style TargetType="TextBlock">
             <Setter Property="HorizontalAlignment" Value="Left" />
             <Setter Property="Margin" Value="3,0,0,0" />
             <Setter Property="VerticalAlignment" Value="Center" />
             <Setter Property="FontStyle" Value="Italic" />
             <Setter Property="TextWrapping" Value="NoWrap" />
         </Style>
     </Setter.Value>
 </Setter>

The TextRemoverStyle is defined similarly, except it has a control template as well. To create the control template for the TextRemoverStyle, I created a button back in Blend, customized the template and style to achieve the look I was after and then copied the XAML from that button, and pasted it into the control template for the TextRemoverStyle.

Step 6 – Add Custom States

This control is going to have two new state groups:

  • WatermarkStates : A state for when the watermark is visible and a state for when it is hidden
  • TextRemoverStates: A state for when the text-remover button is visible and a state for when it is hidden.

For now we will just add empty place holders in the XAML:

 <VisualStateGroup x:Name="TextRemoverStates">
     <VisualState x:Name="TextRemoverVisible">
     </VisualState>
     <VisualState x:Name="TextRemoverHidden">
     </VisualState>
 </VisualStateGroup>
 <VisualStateGroup x:Name="WatermarkStates">
     <VisualState x:Name="WatermarkVisible">
     </VisualState>
     <VisualState x:Name="WatermarkHidden">
     </VisualState>
 </VisualStateGroup>

These are placed in the same location as the inherited state groups for the TextBox. It isn’t very easy to design good looking states and transitions in XAML by hand, so we will come back to this at the end and use Blend.

Blend will know what to do with our states if we decorate our class with the TemplateVisualState attribute for each state we added. We can also use the TemplatePart attribute to let Blend know that our control expects specific visual elements to work with. The StyleTypedProperty lets Blend know the target type of the two style properties we added:

 [StyleTypedProperty(Property = "TextRemoverStyle", StyleTargetType = typeof(Button)),
  StyleTypedProperty(Property = "WatermarkStyle", StyleTargetType = typeof(TextBlock)),
  TemplatePart(Name = "TextRemover", Type = typeof(Button)),
  TemplatePart(Name = "Watermark", Type = typeof(TextBlock)),
  TemplateVisualState(Name = "WatermarkVisible", GroupName = "WatermarkStates"),
  TemplateVisualState(Name = "WatermarkHidden", GroupName = "WatermarkStates"),
  TemplateVisualState(Name = "TextRemoverVisible", GroupName = "TextRemoverStates"),
  TemplateVisualState(Name = "TextRemoverHidden", GroupName = "TextRemoverStates")]
  public class WatermarkedTextBox : TextBox
  {

Step 7 – Add Custom Behavior

There are a few things left to do to make our code and the new visual elements work together. The OnApplyTemplate method must be overridden to wire up the text remover button:

 public override void OnApplyTemplate()
 {
     base.OnApplyTemplate();
     
     // remove old button handler
     if (null != this.textRemoverButton)
     {
         this.textRemoverButton.Click -= this.TextRemoverClick;
     }
   
     // add new button handler
     this.textRemoverButton = GetTemplateChild("TextRemover") as Button;
     if (null != this.textRemoverButton)
     {
         this.textRemoverButton.Click += this.TextRemoverClick;
     }
   
     this.UpdateState();
 }

And event handlers need to be hooked up in the constructor for events that are important to this control:

 public WatermarkedTextBox()
 {
     DefaultStyleKey = typeof(WatermarkedTextBox);
     this.GotFocus += this.TextBoxGotFocus;
     this.LostFocus += this.TextBoxLostFocus;
     this.TextChanged += this.TextBoxTextChanged;
 }
  
 private void TextBoxGotFocus(object sender, RoutedEventArgs e)
 {
     VisualStateManager.GoToState(this, "WatermarkHidden", false);
   
     this.isFocused = true;
     this.UpdateState();
 }
   
 private void TextBoxLostFocus(object sender, RoutedEventArgs e)
 {
     this.isFocused = false;
     this.UpdateState();
 }
   
 private void TextBoxTextChanged(object sender, TextChangedEventArgs e)
 {
     this.UpdateState();
   
     if (!this.IsUpdateImmediate)
     {
         return;
     }
   
     BindingExpression binding = this.GetBindingExpression(TextBox.TextProperty);
     if (null != binding)
     {
         binding.UpdateSource();
     }
 }

The “UpdateSource” method causes the control to transition correctly to the appropriate visual state:

 private void UpdateState()
 {
     if (string.IsNullOrEmpty(this.Text))
     {
         VisualStateManager.GoToState(this, "TextRemoverHidden", true);
         if (!this.isFocused)
         {
             VisualStateManager.GoToState(this, "WatermarkVisible", true);
         }
     }
     else
     {
         VisualStateManager.GoToState(this, "TextRemoverVisible", true);
         VisualStateManager.GoToState(this, "WatermarkHidden", false);
     }
 }

Step 8 – Define Default Visual States

The last thing to do is to define the visual changes in the states for the controls. The easiest way to do this is to follow these steps:

  1. get the control to the point where it compiles.
  2. go into Blend and create an instance of the control on the design surface and customize the template for the created instance.
  3. make the changes to each element in the template for each state
  4. view the XAML and copy-paste the TextRemoverStates and the WatermarkStates into the appropriate section in generic.xaml.

That pasted XAML now becomes part of the default template for the control when you rebuild it.

Summary

In this article I described in 8 steps how to extend an existing TextBox control into a WatermarkedTextBox custom control. I showed how you can use Blend to extract the default styles and control templates from an existing control and how to add your own visual elements in generic.xaml and wire them up to the new type to create a new custom control.

References:
Sample Data: MSXML SDK. Artwork one, two, three.


Subscribe

Comments

  • -_-

    RE: Create a Custom Control - Inheriting from TextBox


    posted by RIchard B. on Aug 20, 2010 17:09

    This example also works for WPF with a minor change. Change the name of the scrollviewer to "PART_ContentHost".

    Great example!

     

     

  • PhilM

    RE: Create a Custom Control - Inheriting from TextBox


    posted by PhilM on Aug 22, 2010 22:34

    Great tip Richard, thanks!

  • -_-

    RE: Create a Custom Control - Inheriting from TextBox


    posted by GreenLion on Sep 13, 2010 15:16

    The Part with the XAML copying is confusing.

    Could you explain why we have to do this? i dont get it...

  • PhilM

    RE: Create a Custom Control - Inheriting from TextBox


    posted by PhilM on Sep 15, 2010 22:26

    GreenLion, the purpose of that part of the tutorial is to end up with some nice animations in the default control template. It is much easier to create XAML for animations in Blend than doing it by hand. So I build the control without the animations and use Blend to create a new control template for it- a copy of the existing one - and add the animations.

    Now I have the XAML for the control with animations so I copy that back into the default.xaml for the control and it becomes the new default control template. Sorry if the article was unclear on this.

  • -_-

    RE: Create a Custom Control - Inheriting from TextBox


    posted by LG on Feb 08, 2011 23:01

    Is there anyway you could compile this into a DLL so that other projects could use it?

  • PhilM

    RE: Create a Custom Control - Inheriting from TextBox


    posted by PhilM on Feb 08, 2011 23:07

    LG, to do so you would just need to create a new project to contain it. You can follow this tutorial for the steps to do that.

  • -_-

    RE: Create a Custom Control - Inheriting from TextBox


    posted by LG on Feb 09, 2011 16:26

    Thanks for the quick reply Phil. Great tutorial btw!

    I've got one more question. Assume that in the ResourceDictionary of the Custom Control I have only one Border. The width and height of the border are bound to the BorderWidth and BorderHeight properties as you describe it in your article. Now when I go into my main application and add the custom control I do something like this:

    <controls:MyBorder BorderWidth="100" BorderHeight="100" />

    ...and the binding works, and I get a Border 100x100. But the Width and Height of the control still hasn't changed.
    My question would be how would you go if you also wanted to resize the Width and Height to be the same as that of the Border? Is there anyway you can overwrite Width/Height or even better, bind them to BorderWidth/BorderHeight?

  • PhilM

    RE: Create a Custom Control - Inheriting from TextBox


    posted by PhilM on Feb 09, 2011 21:16

    LG, if you want to have the BorderWidth and BorderHeight map directly to the Width and Height then I would recommend dropping the BorderWidth and BorderHeight properties all-together. The FrameworkElement class comes with all the width and height properties you need and you may just be complicating things by trying to add more (unless they function quite differently).

    If you removed your two properties then you could just set the Border alignment to stretch both vertically and horizontally and let the FrameworkElement base class manage the dimensions of the control for you.

    If the Border really does behave differently then you would have to use code to respond to changes in the BorderWidth and BorderHeight properties (you register a handler when you register the dependency properties). The handler could then adjust the Width and Height properties to suit.

  • -_-

    RE: Create a Custom Control - Inheriting from TextBox


    posted by Devendra C. on Mar 15, 2011 03:16

    Hi,

    This is a good article. But i didnt understand few things in it. in the article above, where it says "Step 5 – Add Custom Elements" and then textblock and a button, where do you add that?
    Do you have a source code for this article? So i can check where exactly codes are written and in which files. If not then no problemo.

    I am just creating a test app, but i could not figure out where to write the parts from Step 5 and 2 of the boxes from Step 6.

    I am sorry if you think my questions are a little stupid, but i am a database guy and i am making my own website in silverlight by reading and learning from internet.

  • PhilM

    RE: Create a Custom Control - Inheriting from TextBox


    posted by PhilM on Mar 15, 2011 04:37

    Hi Devendra, good on you for getting your hands dirty with a bit of GUI development!

    You can find the source code here. That should show you where to put the XAML from the various steps.

  • Bondz

    Re: Create a Custom Control - Inheriting from TextBox


    posted by Bondz on Oct 15, 2012 18:42
    WPF version. I've tried converting this to WPF but it doesn't work and yes, I've changed the slider name. Can you please give me some pointers or ;) upload a WPF source?
  • Bondz

    Re: Create a Custom Control - Inheriting from TextBox


    posted by Bondz on Oct 16, 2012 19:01
    I was just being stupid for a day there, thanks though, nice work.
  • radekpl

    Re: Create a Custom Control - Inheriting from TextBox


    posted by radekpl on Dec 04, 2012 19:08

    How to implement selectAll() metod?

    this.selectAll(); doesn't work.

    Thanks a lot.

  • Vivane

    Re: Create a Custom Control - Inheriting from TextBox


    posted by Vivane on Mar 13, 2013 16:07

    Can some give me wpf version?

  • Vivane

    Re: Create a Custom Control - Inheriting from TextBox


    posted by Vivane on Mar 14, 2013 13:54

    Hello!

    I want to create textremover click event or  command property, so as I can bind those properties to a ViewModel for a view that hosts the custom textbox created above. How can I do that? Help....

  • Vivane

    Re: Create a Custom Control - Inheriting from TextBox


    posted by Vivane on Mar 14, 2013 19:55

    Hello friends:-

    My project requirments are as follows:-

    We need a form for creating Sales Quotation, with the following features:-

    1. Custom textbox with a button inside 

    2. User can enter name of a new customer into the textbox, 

    3. If the customer already exists  the user can click the button inside the textbox to open new window that holds list of existing customers from which user can select the existing customer.

    4. After selecting the customer other business logic will continues as usual.

    Up to the time being, I have made changes to the existing  code provide in this thread so as to achieve my project goals. I have customized the source code as follows:-

    1. Changes in WatermarkedTextBox custom class:-

    // Create a custom routed event by first registering a RoutedEventID
            // This event uses the bubbling routing strategy
            public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
                "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(WatermarkedTextBox));
     
            // Provide CLR accessors for the event(Tap is just an event name)
            public event RoutedEventHandler Tap
            {
                add { AddHandler(TapEvent, value); }
                remove { RemoveHandler(TapEvent, value); }
            }
     
            // This method raises the Tap event
            void RaiseTapEvent(object sender, RoutedEventArgs e)
            {
                MessageBox.Show("Button has been clicked!");//debug
                RoutedEventArgs newEventArgs = new RoutedEventArgs(TapEvent);
                RaiseEvent(newEventArgs);
            }
    ...
    if (null != this.textRemoverButton)
                {
                    //this.textRemoverButton.Click += this.TextRemoverClick;//
                    this.textRemoverButton.Click += this.RaiseTapEvent;
                }
    ...

    2. Invoking the custom event

    <my:WatermarkedTextBox Tap="watermarkedTextBox1_Tap"   Name="watermarkedTextBox1" " />

    3. View Code behind

    private void watermarkedTextBox1_Tap(object sender, RoutedEventArgs e)
            {
                MessageBox.Show("Current textbox text:-" + this.watermarkedTextBox1.Text);//debug
                MessageBox.Show("Click event handled successfully!");//debug
            }

    Questions:-

    1. Is this how we have to do things? I'm very new to programming, please help!

    2. I want to have this event bind to the viewModel for Im using MVVM pattern. How can I achieve this?

    I request any help and suggestion from you all.

    Thanks.


  • Vivane

    Re: Create a Custom Control - Inheriting from TextBox


    posted by Vivane on Mar 14, 2013 20:52

    Hello friends:-

    My project requirments are as follows:-

    We need a form for creating Sales Quotation, with the following features:-

    1. Custom textbox with a button inside 

    2. User can enter name of a new customer into the textbox, 

    3. If the customer already exists  the user can click the button inside the textbox to open new window that holds list of existing customers from which user can select the existing customer.

    4. After selecting the customer other business logic will continues as usual.

    Up to the time being, I have made changes to the existing  code provide in this thread so as to achieve my project goals. I have customized the source code as follows:-

    1. Changes in WatermarkedTextBox custom class:-

    // Create a custom routed event by first registering a RoutedEventID
            // This event uses the bubbling routing strategy
            public static readonly RoutedEvent TapEvent = EventManager.RegisterRoutedEvent(
                "Tap", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(WatermarkedTextBox));
     
            // Provide CLR accessors for the event(Tap is just an event name)
            public event RoutedEventHandler Tap
            {
                add { AddHandler(TapEvent, value); }
                remove { RemoveHandler(TapEvent, value); }
            }
     
            // This method raises the Tap event
            void RaiseTapEvent(object sender, RoutedEventArgs e)
            {
                MessageBox.Show("Button has been clicked!");//debug
                RoutedEventArgs newEventArgs = new RoutedEventArgs(TapEvent);
                RaiseEvent(newEventArgs);
            }
    ...
    if (null != this.textRemoverButton)
                {
                    //this.textRemoverButton.Click += this.TextRemoverClick;//
                    this.textRemoverButton.Click += this.RaiseTapEvent;
                }
    ...

    2. Invoking the custom event

    <my:WatermarkedTextBox Tap="watermarkedTextBox1_Tap"   Name="watermarkedTextBox1" " />

    3. View Code behind

    private void watermarkedTextBox1_Tap(object sender, RoutedEventArgs e)
            {
                MessageBox.Show("Current textbox text:-" + this.watermarkedTextBox1.Text);//debug
                MessageBox.Show("Click event handled successfully!");//debug
            }

    Questions:-

    1. Is this how we have to do things? I'm very new to programming, please help!

    2. I want to have this event bind to the viewModel for Im using MVVM pattern. How can I achieve this?

    I request any help and suggestion from you all.

    Thanks.


Add Comment

Login to comment:
  *      *