文章預設你已經入門WPF了

​ WPF日常開發,經常遇到預設的控制元件功能不滿足需求,怎麼辦?

No1. 自定義控制元件模板

​ 平時開發中,經常遇到比較”俗“的需求,嫌棄控制元件預設的樣子。怎麼辦?哈哈,那就整個容唄..... !

還記得心靈深處的Button嗎?是不是第一印象就是規規矩矩的長方形,好了,這次我們俗一下,把它變成圓形!

上程式碼:

<Button Content="Test1" Width="80" Height="80" FocusVisualStyle="{x:Null}" Background="LightSeaGreen" BorderBrush="DarkBlue">
<Button.Template>
<ControlTemplate TargetType="ButtonBase">
<Grid>
<Ellipse x:Name="ellipseBorder" StrokeThickness="1" Stroke="{TemplateBinding BorderBrush}" Fill="{TemplateBinding Background}"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding Content}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" Value="Orange" TargetName="ellipseBorder"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Stroke" Value="OrangeRed" TargetName="ellipseBorder"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>

上外表:

No2. 重寫控制元件

​ 很多情況下,並不是把控制元件換個外貌就可以解決的,我們不僅要改變外貌,還要改變控制元件的功能,比如說我們經常用的TextBox控制元件,正常的功能就是用來輸入的,但更人性化點,我們想要TextBox能告訴我們當前的文字框應該輸入使用者名稱呢,還是地址呢等等。其實這個就是我們經常看到的水印功能,水印文字肯定要能按需設定,那我們不可能簡單的通過改變下控制元件模板就可以解決的。

​ 通過需求我們知道,新的帶水印的文字框,至少有個水印這麼個依賴屬性,供外部設定。當然新的帶水印文字框和TextBox大概的樣子差不多,但我們也要為新的控制元件定義外貌。

​ 所以第一步,先定義控制元件的功能,上程式碼:

public class WaterMarkTextBox : TextBox
{
static WaterMarkTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(WaterMarkTextBox), new FrameworkPropertyMetadata(typeof(WaterMarkTextBox)));
} public string WaterMark
{
get { return (string)GetValue(WaterMarkProperty); }
set { SetValue(WaterMarkProperty, value); }
} // Using a DependencyProperty as the backing store for WaterMark. This enables animation, styling, binding, etc...
public static readonly DependencyProperty WaterMarkProperty =
DependencyProperty.Register("WaterMark", typeof(string), typeof(WaterMarkTextBox), new PropertyMetadata(null)); }

​ 第二步,再定義控制元件的樣子,上程式碼:

<Style TargetType="{x:Type local:WaterMarkTextBox}">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:WaterMarkTextBox}">
<Grid>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
<ScrollViewer x:Name="PART_ContentHost"
Grid.Column="0"
Margin="0"
Padding="{TemplateBinding Padding}"
VerticalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
IsTabStop="False"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<TextBlock x:Name="PART_Message"
Margin="4 0"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="Center"
Foreground="Gray"
Text="{TemplateBinding WaterMark}"
Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
<Setter TargetName="PART_Message" Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers> </ControlTemplate>
</Setter.Value>
</Setter>
</Style>

​ 請注意NamePART_MessageTextBlock就是用來呈現水印提示訊息的,同時加了個觸發器,實現這樣的功能:如果未輸入任何雷人,則顯示水印,否則就隱藏水印。

​ 控制元件使用,上程式碼:

 <local:WaterMarkTextBox Margin="20,0,0,0" Width="200" Height="50" WaterMark="Please Input your name"/>

上效果:

No3. 附加屬性來試試

​ 重寫控制元件看似很完美了,真的是這樣嗎?

​ 好了,我的需求又來了,現在文字框提示很perfect,開始我的密碼框PasswordBox也要搞個水印啊?怎麼辦?再重寫個帶水印的密碼框?此時有沒有做相同事情的感覺?作為合格的碼農,我們還是要牢記碼農界的警世名言:Don't Repeat Yourself!

​ 此時請回憶下WPF的經典知識點

​ 控制元件A放到Grid中,A要支援設定行和列,控制元件B放到Grid中,B也要支援設定行和列。教程中已經告訴我們不要傻不拉幾在A和B中都去定義行和列的屬性,否則後續C、D......沒完沒了。

​ 此時就是我們應用附加屬性的時候了,在Grid中定義統一的行和列的附加屬性,然後附加應用到A、B上就可以了。

​ 反過來看看我們現在的需求,是不是一樣的套路?我是不是在個公共的地方定義個水印WaterMark附加屬性,然後分別應用到文字框和密碼框就可以了?說對了一半,因為我們文字框和密碼框老的外表沒有顯示水印的地方,所以我們同時還要重新定義下他們的新外表。

​ 話不多說,先上附加屬性定義的程式碼:

public class WaterMarkHelper
{
public static string GetWaterMark(DependencyObject obj)
{
return (string)obj.GetValue(WaterMarkProperty);
} public static void SetWaterMark(DependencyObject obj, string value)
{
obj.SetValue(WaterMarkProperty, value);
} public static readonly DependencyProperty WaterMarkProperty =
DependencyProperty.RegisterAttached("WaterMark", typeof(string), typeof(WaterMarkHelper), new PropertyMetadata(null)); }

上TextBox新的樣式:

<Style x:Key="TextBoxWithWaterMark" TargetType="{x:Type TextBox}">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
<ScrollViewer x:Name="PART_ContentHost"
Grid.Column="0"
Margin="0"
Padding="{TemplateBinding Padding}"
VerticalAlignment="Stretch"
Background="{x:Null}"
BorderThickness="0"
IsTabStop="False"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<TextBlock x:Name="PART_Message"
Margin="4 0"
Padding="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="Center"
Foreground="Gray"
Text="{TemplateBinding local:WaterMarkHelper.WaterMark}"
Visibility="Collapsed" />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Value="">
<Setter TargetName="PART_Message" Property="Visibility" Value="Visible" />
</DataTrigger>
</ControlTemplate.Triggers> </ControlTemplate>
</Setter.Value>
</Setter>
</Style>

請和自定義控制元件中的樣式對比下,其實就是PART_Message對應控制元件的Text繫結的不一樣了,這樣繫結的是TextBox的附加屬性WaterMarkHelper.WaterMark

上應用程式碼:

 <TextBox Style="{StaticResource TextBoxWithWaterMark}" Margin="20,0,0,0" Width="200" Height="50" local:WaterMarkHelper.WaterMark="Please Input your name"/>

請注意,這樣要手動明確應用定義的樣式資源!

上效果:

課後作業:請依葫蘆畫瓢,實現PasswordBox的水印功能

總結

​ 除了這裡列舉的三種方式,其實還可以通過Behavior行為功能,擴充套件一個控制元件的功能,比如著名的拖拽功能!寫到這裡,我想總結的是:工欲善其事必先利其器

​ 當我們基礎紮實之後,我們真的可以跳出柵欄,靈活應用!

原始碼獲取

程式原始碼