V dnešním článku si ukážeme jak napsat vlastní “WatermarkedTextBox”. Jedná se o klasický TextBox, který je rozšířen o vodoznak (doplňující informace co se zde má např. vyplnit).
Začínáme
Ve VS vytvoříme nový projekt typu “WPF Custom Control Library” (Standardně se vytvoří i nová třída CustomControl.cs, kterou můžeme smazat) a pojmenujeme ho jako “CustomControls”. Pro začátek stačí když si vytvoříme hlavní třídu “WatermarkedTextBox”
[TemplatePart(Name = "PART_TextBox", Type = typeof(TextBox))]
[TemplatePart(Name = "PART_TextBlock", Type = typeof(TextBlock))]
public class WatermarkedTextBox : TextBox
{
static WatermarkedTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(WatermarkedTextBox),
new FrameworkPropertyMetadata(typeof(WatermarkedTextBox)));
}
}
WatermarkedTextBox se bude skládat z klasického TextBoxu (pojmenovaný jako PART_TextBox) a TextBlocku (PART_TextBlock) sloužící pro zobrazení vodoznaku. Pomocí metody OverrideMetada, kterou voláme v konstruktoru řekneme, že chceme využít styl definovaný v souboru Generic.xaml
<Style TargetType="local:WatermarkedTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:WatermarkedTextBox">
<Grid>
<TextBox x:Name="PART_TextBox"/>
<TextBlock x:Name="PART_TextBlock"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Aby jsme si mohli tento hrubý základ otestovat, vytvoříme novou WPF aplikaci, přidáme referenci na knihovnu CustomControls a vložíme WatermarkedTextBox do hlavního okna MainWindow
<Window xmlns:my="clr-namespace:CustomControls;assembly=CustomControls"
x:Class="CustomControls.Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<my:WatermarkedTextBox Height="50" Margin="10"/>
</Grid>
</Window>
Po spuštění aplikace se nám zobrazí náš control, ale žádný vodoznak není vidět a navíc nejde do něho ani psát. Pojďme tedy přidat základní funkcionalitu.
Základní funkcionalita
Pro vytvoření základní funkcionality budeme potřebovat následující vlastnosti:
- Watermark – text, který se zobrazí jako vodoznak
- WatermarkForeground – barva vodoznaku
- WatermarkStyle – styl vodoznaku
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(
"Watermark",
typeof(string),
typeof(WatermarkedTextBox),
new PropertyMetadata("Zadejte text..."));
public static readonly DependencyProperty WatermarkStyleProperty = DependencyProperty.Register(
"WatermarkStyle",
typeof(Style),
typeof(WatermarkedTextBox));
public static readonly DependencyProperty WatermarkForegroundProperty = DependencyProperty.Register(
"WatermarkForeground",
typeof(Brush),
typeof(WatermarkedTextBox),
new PropertyMetadata(new SolidColorBrush(Color.FromArgb(255, 134, 134, 134))));
[Description("Vrati nebo nastavi vodoznak")]
public string Watermark
{
get { return (string)GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
[Description("Vrati nebo nastavi styl vodoznaku")]
public Style WatermarkStyle
{
get { return (Style)GetValue(WatermarkStyleProperty); }
set { SetValue(WatermarkStyleProperty, value); }
}
[Description("Vrati nebo nastavi barvu vodoznaku")]
public Brush WatermarkForeground
{
get { return (Brush)GetValue(WatermarkForegroundProperty); }
set { SetValue(WatermarkForegroundProperty, value); }
}
Všechny potřebné vlastnosti jsou definovány jako jednoduché Dependency property a není potřeba provádět v kódu na pozadí nějaké další věci. O vše ostatní se postaráme přímo v XAMLu.
Nejdříve rozšíříme TextBlock, který se bude starat o zobrazení vodoznaku:
<TextBlock x:Name="PART_TextBlock"
Style="{TemplateBinding WatermarkStyle}"
Foreground="{TemplateBinding WatermarkForeground}"
Text="{TemplateBinding Watermark}"/>
Nyní umí náš TextBlock zobrazit defaultní vodoznak (můžete si to vyzkoušet spuštěním aplikace). Problém je stále v tom, že do TextBoxu nelze psát. Tento problém vyřešíme nastavením vlastnosti IsHitTestVisible na false u elementu TextBlock.
Další problém nám spočívá vtom, že když začneme psát do TextBoxu, vodoznak nezmizí, ale je stále vidět což nepůsobí dobře. U tohoto řešení využijeme triggery, kterými definujeme chování vodoznaku:
- Pokud je hodnota Textu - null zobraz vodoznak
- Pokud je hodnota Textu prázdný řetězec – zobraz vodoznak
- Pokud je hodnota Textu nějaký řetězec – schovej vodoznak
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Text, ElementName=PART_TextBox}" Value="{x:Null}">
<Setter TargetName="PART_TextBlock" Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Text.Length, ElementName=PART_TextBox}" Value="0">
<Setter TargetName="PART_TextBlock" Property="Visibility" Value="Visible"/>
</DataTrigger>
</ControlTemplate.Triggers>
Na závěr ještě upravíme chování tabelátoru nastavením vlastnosti IsTabStop celému WatermarkedTextBoxu na False a vnitřímu TextBoxu na true.
na závěr ještě kompletní souhrn:
WatermarkedTextBox:
namespace CustomControls
{
using System.Windows.Media;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
[TemplatePart(Name = "PART_TextBox", Type = typeof(TextBox))]
[TemplatePart(Name = "PART_TextBlock", Type = typeof(TextBlock))]
public class WatermarkedTextBox : TextBox
{
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(
"Watermark",
typeof(string),
typeof(WatermarkedTextBox),
new PropertyMetadata("Zadejte text..."));
public static readonly DependencyProperty WatermarkStyleProperty = DependencyProperty.Register(
"WatermarkStyle",
typeof(Style),
typeof(WatermarkedTextBox));
public static readonly DependencyProperty WatermarkForegroundProperty = DependencyProperty.Register(
"WatermarkForeground",
typeof(Brush),
typeof(WatermarkedTextBox),
new PropertyMetadata(new SolidColorBrush(Color.FromArgb(255, 134, 134, 134))));
static WatermarkedTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(WatermarkedTextBox),
new FrameworkPropertyMetadata(typeof(WatermarkedTextBox)));
}
[Description("Vrati nebo nastavi vodoznak")]
public string Watermark
{
get { return (string)GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
[Description("Vrati nebo nastavi styl vodoznaku")]
public Style WatermarkStyle
{
get { return (Style)GetValue(WatermarkStyleProperty); }
set { SetValue(WatermarkStyleProperty, value); }
}
[Description("Vrati nebo nastavi barvu vodoznaku")]
public Brush WatermarkForeground
{
get { return (Brush)GetValue(WatermarkForegroundProperty); }
set { SetValue(WatermarkForegroundProperty, value); }
}
}
}
Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomControls">
<Style TargetType="local:WatermarkedTextBox">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:WatermarkedTextBox">
<Grid>
<TextBox x:Name="PART_TextBox" IsTabStop="True"/>
<TextBlock x:Name="PART_TextBlock"
Style="{TemplateBinding WatermarkStyle}"
Foreground="{TemplateBinding WatermarkForeground}"
Text="{TemplateBinding Watermark}"
IsHitTestVisible="False"
Visibility="Hidden"/>
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Text, ElementName=PART_TextBox}" Value="{x:Null}">
<Setter TargetName="PART_TextBlock" Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Text.Length, ElementName=PART_TextBox}" Value="0">
<Setter TargetName="PART_TextBlock" Property="Visibility" Value="Visible"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainWindow.xaml:
<Window xmlns:my="clr-namespace:CustomControls;assembly=CustomControls"
x:Class="CustomControls.Demo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<Style x:Key="watermarkStyle" TargetType="{x:Type my:WatermarkedTextBox}">
<Setter Property="WatermarkForeground" Value="#FF868686" />
<Setter Property="WatermarkStyle">
<Setter.Value>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Margin" Value="10,0,0,0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="TextWrapping" Value="NoWrap" />
</Style>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<my:WatermarkedTextBox x:Name="wtxt1" Height="50" Margin="10"/>
<my:WatermarkedTextBox x:Name="wtxt2" Height="50" Margin="8,186,12,75"
Watermark="Vyplnte pozadovane udaje"
Style="{StaticResource watermarkStyle}"/>
</Grid>
</Window>
Implementace vlastního WatermarkedTextBoxu byla vcelku jednoduchá a výsledný projekt si můžete stáhnout na adrese https://skydrive.live.com/redir.aspx?cid=35f7223b2eb524fd&resid=35F7223B2EB524FD!314&parid=35F7223B2EB524FD!313
Lukáš