Saturday, January 5, 2013

Simple Watermark control

Goal

There is no watermark in standard WPF TextBox but it is simple to create one.
Our goal to get the result like this:

Some concepts about control

To get enough functionality we will expand TextBox (for our goal we can use class UserControl and in many cases we should do this, but we want our control behave and have the same properties like regular TextBox).

Our WatermarkTextBox would have two additional properties: text property for watermark content and boolean property for watermark visibility (true if control is not focused and TextBox.Text property is empty).

In WPF every control should provide it's look through classes Style and ContentTemplate in xaml theme file (also you could do this by code - but by xaml it is just easier).

Implementation

In Visual Studio 2010/2012 to implement custom control best of all to use standard Template WPF CustomControl. This template automatically adds default resource file Themes/Generic.xaml and special attribute ThemeInfoAttribute to AssemblyInfo.cs file pointing wpf to use this theme file.

New properties Watermark and IsWatermarkVisible stated as DependencyProperty (it is default way in WPF to create property that participate in control visualization and animation):

public class WatermarkTextBox : TextBox
{
    // DependencyProperty of watermark content. You can change this to UI content, so you can use any template.
    public static readonly DependencyProperty WatermarkProperty =
  DependencyProperty.Register("Watermark", typeof (string), typeof (WatermarkTextBox), new PropertyMetadata("Enter text..."));
    public string Watermark
    {
  get { return (string) GetValue(WatermarkProperty); }
        set { SetValue(WatermarkProperty, value); }
    }

    // DependancyProperty of Watermark visibility.
    public static readonly DependencyProperty IsWatermarkVisibleProperty =
  DependencyProperty.Register("IsWatermarkVisible", typeof (bool), typeof (WatermarkTextBox), new PropertyMetadata(true));
    public bool IsWatermarkVisible
    {
  get { return (bool) GetValue(IsWatermarkVisibleProperty); }
        set { SetValue(IsWatermarkVisibleProperty, value); }
    }
}

To update IsWatermarkVisible properties we should track for TextBox.Text property and focus changes. Doing like so:

protected override void OnTextChanged(TextChangedEventArgs e)
{
    base.OnTextChanged(e);
    UpdateWatermarkVisibility();
}

protected override void OnGotKeyboardFocus(System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
    base.OnGotKeyboardFocus(e);
    UpdateWatermarkVisibility();
}

protected override void OnLostKeyboardFocus(System.Windows.Input.KeyboardFocusChangedEventArgs e)
{
    base.OnLostKeyboardFocus(e);
    UpdateWatermarkVisibility();
}

/// 
/// The only place where visibility can be changed. 
/// This logic can be placed in xaml Style, but it gets a little bit complex - so it may be more desirable to keep logic in code 
/// 
private void UpdateWatermarkVisibility()
{
    IsWatermarkVisible = !IsKeyboardFocused && string.IsNullOrEmpty(Text);
}

Now we add watermark visual element. To do this we provide own ContentTemplate and Style in Themes/Generic.xaml:

    
    <Style TargetType="{x:Type wd:WatermarkTextBox}" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type wd:WatermarkTextBox}">
                    <Border Name="Bd"
                            Background="{TemplateBinding Background}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            BorderBrush="{TemplateBinding BorderBrush}">
                        <Grid>
                            <ScrollViewer x:Name="PART_ContentHost"/>
                            <!--Added watermark textblock-->
                            <TextBlock x:Name="Watermark" Text="{TemplateBinding Watermark}" Visibility="Collapsed" Foreground="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                        </Grid>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled"
                                 Value="false">
                            <Setter TargetName="Bd"
                                    Property="Background"
                                    Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
                            <Setter Property="Foreground"
                                    Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                        </Trigger>
                        <!-- Setting visibility of watermark with trigger -->
                        <Trigger Property="IsWatermarkVisible" 
                                 Value="True">
                            <Setter TargetName="Watermark" 
                                    Property="Visibility" 
                                    Value="Visible"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

It's slightly modified style of classic theme.

Sources for project

NOTE: Default Style key usually stated in Static constructor:

        
static WatermarkTextBox()
{
 // Setting default style key. This used by wpf to get right style from resources.
    DefaultStyleKeyProperty.OverrideMetadata(typeof(WatermarkTextBox), new FrameworkPropertyMetadata(typeof(WatermarkTextBox)));
}

No comments:

Post a Comment