Re-templating a WatermarkedTextBox

Hosted by Silverlight Streaming 

Ta-da! Here's a new look for the WatermarkedTextBox (a.k.a. WTB) in Silverlight 2 Beta 1. This was relatively simple to do just by changing the template.

Here is the XAML for the template:

<ControlTemplate x:Key="wtbTemplate" TargetType="WatermarkedTextBox">

<Grid x:Name="RootElement" >

<Grid.Resources>

<Storyboard x:Key="Disabled State">

<DoubleAnimation Storyboard.TargetName="DisabledVisual"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.0"/>

<DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

Storyboard.TargetProperty="Opacity"

To="0.5" Duration="0:0:0.0"/>

<DoubleAnimation Storyboard.TargetName="WatermarkElement"

Storyboard.TargetProperty="Opacity"

To="0" Duration="0:0:0.0"/>

</Storyboard>

<Storyboard x:Key="Disabled Watermarked State">

<DoubleAnimation Storyboard.TargetName="DisabledVisual"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.0"/>

<DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

Storyboard.TargetProperty="Opacity"

To="0" Duration="0:0:0.0"/>

<DoubleAnimation Storyboard.TargetName="WatermarkElement"

Storyboard.TargetProperty="Opacity"

To="0.5" Duration="0:0:0.0"/>

</Storyboard>

<Storyboard x:Key="Normal State">

<DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.0"/>

<DoubleAnimation Storyboard.TargetName="WatermarkElement"

Storyboard.TargetProperty="Opacity"

To="0" Duration="0:0:0.0"/>

</Storyboard>

<Storyboard x:Key="Focused State">

<DoubleAnimation Storyboard.TargetName="FocusVisual"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.1"/>

<DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.0"/>

<DoubleAnimation Storyboard.TargetName="WatermarkElement"

Storyboard.TargetProperty="Opacity"

To="0" Duration="0:0:0.0"/>

</Storyboard>

<Storyboard x:Key="MouseOver State">

<!--<DoubleAnimation Storyboard.TargetName="FocusVisual"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.1"/>-->

<DoubleAnimation Storyboard.TargetName="MouseOverVisual"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.1"/>

<DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.0"/>

<DoubleAnimation Storyboard.TargetName="WatermarkElement"

Storyboard.TargetProperty="Opacity"

To="0" Duration="0:0:0.0"/>

</Storyboard>

<Storyboard x:Key="Normal Watermarked State">

<DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

Storyboard.TargetProperty="Opacity"

To="0" Duration="0:0:0.0"/>

<DoubleAnimation Storyboard.TargetName="WatermarkElement"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.0"/>

</Storyboard>

<Storyboard x:Key="MouseOver Watermarked State">

<!--<DoubleAnimation Storyboard.TargetName="FocusVisual"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.1"/>-->

<DoubleAnimation Storyboard.TargetName="MouseOverVisual"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.1"/>

<DoubleAnimation Storyboard.TargetName="ELEMENT_Content"

Storyboard.TargetProperty="Opacity"

To="0" Duration="0:0:0.0"/>

<DoubleAnimation Storyboard.TargetName="WatermarkElement"

Storyboard.TargetProperty="Opacity"

To="1" Duration="0:0:0.0"/>

</Storyboard>

</Grid.Resources>

<!-- To get a white background -->

<Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch"

RadiusX="5" RadiusY="5" Opacity="1" Fill="White"/>

<!-- Simple drop shadow effect on Mouse Over -->

<Grid x:Name="MouseOverVisual" Opacity="0">

<Rectangle Height="10" Width="10" RadiusX="5" RadiusY="5"

HorizontalAlignment="Right" VerticalAlignment="Bottom"

Stroke="Gray" StrokeThickness="4" Margin="0,0,-2,-2">

<Rectangle.Clip>

<RectangleGeometry Rect="4,4,6,6"/>

</Rectangle.Clip>

</Rectangle>

<Rectangle Width="3" RadiusX="2" RadiusY="2" HorizontalAlignment="Right"

VerticalAlignment="Stretch" Fill="Gray" Margin="0,5,-2,3"/>

<Rectangle Height="3" RadiusX="2" RadiusY="2" HorizontalAlignment="Stretch"

VerticalAlignment="Bottom" Fill="Gray" Margin="5,0,3,-2"/>

</Grid>

<Grid>

<Grid.RowDefinitions>

<RowDefinition Height="3*"/>

<RowDefinition Height="5*"/>

</Grid.RowDefinitions>

<!-- Top of tag-->

<Rectangle Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"

RadiusX="5" RadiusY="5" Fill="LimeGreen"/>

<Rectangle Grid.Row="0" Height="5" HorizontalAlignment="Stretch"

VerticalAlignment="Bottom" Fill="LimeGreen"/>

<TextBlock Grid.Row="0" Text="Hello, my name is ..." FontFamily="Comic Sans MS"

FontSize="24" Foreground="White" VerticalAlignment="Center" Margin="5,0,0,0"/>

<!-- Bottom of tag where the name is typed -->

<Border x:Name="ELEMENT_Content" Grid.Row="1" Background="Transparent"

BorderBrush="Transparent" BorderThickness="0" HorizontalAlignment="Stretch"

VerticalAlignment="Stretch" Padding="10,0,0,0"/>

<ContentControl

Grid.Row="1"

x:Name="WatermarkElement"

IsTabStop="False"

IsHitTestVisible="False"

Content="{TemplateBinding Watermark}"

Foreground="Gray"

Background="{TemplateBinding Background}"

FontFamily="{TemplateBinding FontFamily}"

FontSize="{TemplateBinding FontSize}"

FontStretch="{TemplateBinding FontStretch}"

FontStyle="{TemplateBinding FontStyle}"

FontWeight="{TemplateBinding FontWeight}"

Padding="10,0,0,0"/>

</Grid>

<!-- A nice black border on top of it all-->

<Rectangle StrokeThickness="2" Stroke="Black" Fill="Transparent" RadiusX="5" RadiusY="5"

HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>

<!-- Show a star if the control has focus -->

<Polygon x:Name="FocusVisual" Stroke="Blue" StrokeThickness="1.0" Fill="Blue" Opacity="0"

Stretch="Fill" Height="50" Width="50"

HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-25,-20,0"

Points="317.667,129.333 308.043,112.31 320.505,97.2405 301.341,101.133 290.86,84.6235 288.64,104.052 269.7,108.919 287.492,117.034 286.267,136.551 299.483,122.138 317.667,129.333"/>

<!-- Put a semi-transparent mask over the control if it is disabled -->

<Rectangle x:Name="DisabledVisual" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"

RadiusX="5" RadiusY="5" Opacity="0" Fill="#55AAAAAA"/>

</Grid>

</ControlTemplate>

Here is the XAML for adding the templated WTB to a page:

<WatermarkedTextBox Template="{StaticResource wtbTemplate}" Height="100" Width="300" FontFamily="Comic Sans MS" FontSize="36" Watermark="&lt;Bob&gt;"/>

Here are some details for what it took.

1. Even though I wasn’t changing the watermark or disabled behavior I still had to redefine all of the related template parts.

a. The WatermarkElement ContentControl is used to host the actual watermark text (or other content if applicable).

b. The ELEMENT_Content Border is required because that is how the internal text box code knows where to draw the caret and the text that the user types.

c. The DisabledVisual UI element is not part of the template part contract, but the default disabled state needs it to prevent user interaction with the control.

d. The Disabled State, Disabled Watermarked State, Normal State, and Normal Watermarked State storyboards all needed to be redefined even if the animations they contained didn’t change. This is a side effect of the control template parts model.

2. I wanted to change the layout and the focus and mouse over visuals.

a. I used a nested grid to help layout all of the different pieces of the UI.

b. I created a drop shadow UI element that I called MouseOverVisual and a star UI element that I called FocusedVisual.

c. Notice that the names don’t end with “Element”. The convention for the control template parts model is that only those UI elements that are actually part of the contract and used by the internal control code should end with “Element”.

d. Just adding the visuals wasn’t enough, I needed to update the Focused State, MouseOver State, and MouseOver Watermarked State storyboards to change the opacity of the visuals for the appropriate user actions.

e. Bug Alert! The internal code for the WTB has a flaw that causes the MouseOver states to always override the Focused state. Therefore, if you use the mouse to set the focus on the control, the FocusVisual UI element never gets displayed. However, if you tab to the control to set the focus, the visual will get displayed (try it out – tab between the default WTB and the re-templated WTB). You could always uncomment the commented out code in the MouseOver storyboards to combine the two behaviors. This flaw isn’t obvious in the default WTB because there aren’t separate UI elements for the focus and mouse over states.

3. Lastly, I needed to set the Template property and not the Style property of the target WTB due to the fact that the Template is set in the constructor in WTB. This is discussed a little more in one of my other posts.