Pokud se v Silverlight koukneme na kontrol TextBox, zjistíme, že již obsahuje vlastnost Watermark, ta je však bohužel implementována takto:
throw new NotImplementedException();
Pojďme to udělat lépe.
Zavedeme kontrol ExtendedTextBox poděděný z TextBox kontrolu, a v něm založíme novou (pomoci klíčového slova new) dependency property Watermark.
/// <summary>
/// Watermark dependency property
/// </summary>
public new static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register("Watermark", typeof(object), typeof(ExtendedTextBox), new PropertyMetadata(OnWatermarkPropertyChanged));
/// <summary>
/// Watermark content
/// </summary>
/// <value>The watermark.</value>
public new object Watermark
{
get { return (object)GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
V kontrolu dále zavedeme nové stavy pro VisualStateManager - Watermarked a Unwatermarked . Tyto stavy budeme přepínat na událost TextChanged pomoci metody GoToState.
/// <summary>
/// ExtendedTextBox control constructor
/// </summary>
public ExtendedTextBox()
{
this.DefaultStyleKey = typeof(ExtendedTextBox);
this.TextChanged += OnTextChanged;
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
SetWatermarkState(true);
}
private void SetWatermarkState(bool useTransitions)
{
//Update the WatermarkStates group
if (this.Watermark != null && string.IsNullOrEmpty(this.Text))
{
GoToState(this, useTransitions, "Watermarked", "Unwatermarked");
}
else
{
GoToState(this, useTransitions, "Unwatermarked");
}
}
private static void GoToState(Control control, bool useTransitions, params string[] stateNames)
{
if (control == null)
{
throw new ArgumentNullException("control");
}
if (stateNames != null)
{
foreach (string str in stateNames)
{
if (VisualStateManager.GoToState(control, str, useTransitions))
{
return;
}
}
}
}
Poslední částí řešení bude definovat nový styl pro náš kontrol ExtendedTextBox. Vyjdeme ze standardního stylu pro TextBox, rozšíříme ho o nový VisualStateGroup WatermarkStates a pod ContentElement přidáme ContentControl Watermark.
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="impcontrols:ExtendedTextBox">
<Grid x:Name="RootElement">
<VisualStateManager.VisualStateGroups>
...
<VisualStateGroup x:Name="WatermarkStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0" />
</VisualStateGroup.Transitions>
<VisualState x:Name="Unwatermarked" />
<VisualState x:Name="Watermarked">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Watermark" Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="1" Opacity="1">
<Grid>
<Border x:Name="ReadOnlyVisualElement" Background="#5EC9C9C9" Opacity="0"/>
<Border x:Name="MouseOverBorder" BorderBrush="Transparent" BorderThickness="1">
<ScrollViewer x:Name="ContentElement" BorderThickness="0" IsTabStop="False" Padding="{TemplateBinding Padding}"/>
</Border>
<ContentControl x:Name="Watermark" Opacity="0" IsTabStop="False" IsHitTestVisible="False" Content="{TemplateBinding Watermark}" Foreground="#FF999999" Background="{TemplateBinding Background}" FontFamily="{TemplateBinding FontFamily}" FontSize="{TemplateBinding FontSize}" FontStretch="{TemplateBinding FontStretch}" FontStyle="{TemplateBinding FontStyle}" FontWeight="{TemplateBinding FontWeight}" Margin="{TemplateBinding Padding}" VerticalAlignment="Top"/>
</Grid>
</Border>
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
Celý kód kontrolu (obsahuje i změny z minula) je dostupný zde ExtendedTextBox.cs a jeho styl zde ExtendedTextBox.xaml.
Obdobně by bylo možné toto řešení převzít i do WPF.
Pokud chceme watermark dodat i pro kontrol PasswordBox, je zde drobný problém v tom, že kontrol PasswordBox je v Silverlight implementován jako sealed, a tak ho nemůžeme podědit. Budeme postupovat jinak. Vlastnost Watermark pro tento kontrol zavedeme jako attached dependency property v nové třídě, kterou nazveme PasswordBoxExtender. Implementace je jinak velice podobná jako v případě TextBoxu. Třídu si můžete prohlédnou zde PasswordBoxExtender.cs.
Také obdobně vytvoříme nový styl pro PasswordBox, ve kterém ale musíme jako Content přidávaného kontrolu ContentControl Watermarku použít odkaz na attached vlastnost:
Content="{TemplateBinding local:PasswordBoxExtender.Watermark}"
Styl je opět dostupný zde PasswordBoxStyles.xaml.