引言
在客戶端開發中,要說出現頻率大的控件,必定有TextBox的身影.然而在TextBox的旁邊通常得有個基友Label,形影不離.為此,我們都要寫兩個控件,布局兩次,這樣麻煩且有點浪費時間.不如,我們做狠點,將它們兩個綁在一起算了.
簡單需求
我們需要的控件該是怎樣的.首先,它應該有TextBox的所有特性,其次,它的上方或者左邊應該有個Label.我們可以通過設置屬性,顯示Label的內容.大體上就是這樣.
構建方式的選取
WPF的控件開發有兩種,分為用戶控件和自定義控件.在這次的控件開發中,自定義控件又可以細分為三種,如下:
1.首先是用戶控件,通過繼承UserControl,直接在xaml上布局設計.這種方式開發上比較方便,適用於多個界面上重用不變的模塊.但是專業的控件開發一般不采取這種方式.
2.自定義控件之一,通過繼承Control和模板上采用TextBox和Label的布局構建控件.這種方式功能上的自由度很高,例如可以自定義text屬性,但是要構建大量的依賴項屬性和路由事件.
3.自定義控件之二,通過繼承TextBox和修改采用msdn上提供TextBox的默認模板,這種方式輕量級些,但是要深入理解默認模板的設計,和需要重定義模板的一些觸發器和效果等.
4.自定義控件之三,上面兩種都是比較復雜,我們有折中的做法.通過繼承TextBox和模板上采用TextBox和Label的布局構建控件,本文就是介紹一下這種做法.
新建項目
首先,新建一個WPF用戶控件庫的項目,VS已經幫我們添加了一些東西,如圖:

Themes文件夾下面的特定的Generic.xaml里面就是放我們的模板文件的了,一般我們不直接將模板直接寫在里面,而是每個控件模板分別放在一個資源文件中,再合並在Generic里面.而真正的控件是CustomControl1.cs,里面包含着我們熟悉的各種依賴項屬性和事件.當然,我們得重命名一下,就叫LabelTextBox吧.
無外觀的LabelTextBox
在靜態構造函數中,調用 DefaultStyleKeyProperty.OverrideMetadata,告訴WPF為此控件應用一個新樣式,再新建一個LabelProperty和LabelPosition的依賴項屬性,如下:
public class LabelTextBox : TextBox { static LabelTextBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(LabelTextBox), new FrameworkPropertyMetadata(typeof(LabelTextBox))); } public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(Object), typeof(LabelTextBox), new PropertyMetadata(string.Empty)); public static readonly DependencyProperty LabelPositionProperty = DependencyProperty.Register("LabelPosition", typeof(Position), typeof(LabelTextBox), new PropertyMetadata(Position.Top)); public Object Label { get { return (Object)GetValue(LabelProperty); } set { SetValue(LabelProperty, value); } } public Position LabelPosition { get { return (Position)GetValue(LabelPositionProperty); } set { SetValue(LabelPositionProperty, value); } } } public enum Position { Top, Left }
LabelTextBox的控件模板
上面已經完成了我們的LabelTextBox,但是它還沒有樣式模板.接下來我們來構建它的控件模板.在Themes文件夾下面新建一個資源字典名為LabelTextBox.xaml,其實模板的TextBox和LabelTextBox沒有什么關聯,所以我們給控件模板寫上各種綁定和設置樣式觸發器,如下:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfCustomControl"> <ControlTemplate x:Key="TopLabelTextBoxTemplate" TargetType="{x:Type local:LabelTextBox}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Label Content="{Binding Label, RelativeSource={RelativeSource TemplatedParent}}" Grid.Row="0"/> <TextBox Grid.Row="1" Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="{Binding HorizontalContentAlignment,RelativeSource={RelativeSource TemplatedParent}}" VerticalContentAlignment="{Binding VerticalContentAlignment,RelativeSource={RelativeSource TemplatedParent}}" Background="{Binding Background,RelativeSource={RelativeSource TemplatedParent}}" Foreground="{Binding Foreground,RelativeSource={RelativeSource TemplatedParent}}" TextWrapping="{Binding TextWrapping,RelativeSource={RelativeSource TemplatedParent}}" TextAlignment="{Binding TextAlignment,RelativeSource={RelativeSource TemplatedParent}}" HorizontalScrollBarVisibility="{Binding HorizontalScrollBarVisibility,RelativeSource={RelativeSource TemplatedParent}}" VerticalScrollBarVisibility="{Binding VerticalScrollBarVisibility,RelativeSource={RelativeSource TemplatedParent}}" MaxLength="{Binding MaxLength,RelativeSource={RelativeSource TemplatedParent}}" IsReadOnly="{Binding IsReadOnly,RelativeSource={RelativeSource TemplatedParent}}" IsEnabled="{Binding IsEnabled,RelativeSource={RelativeSource TemplatedParent}}"/> </Grid> </ControlTemplate> <ControlTemplate x:Key="LeftLabelTextBoxTemplate" TargetType="{x:Type local:LabelTextBox}"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <Label HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Label, RelativeSource={RelativeSource TemplatedParent}}" Grid.Column="0"/> <TextBox Grid.Column="1" Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="{Binding HorizontalContentAlignment,RelativeSource={RelativeSource TemplatedParent}}" VerticalContentAlignment="{Binding VerticalContentAlignment,RelativeSource={RelativeSource TemplatedParent}}" Background="{Binding Background,RelativeSource={RelativeSource TemplatedParent}}" Foreground="{Binding Foreground,RelativeSource={RelativeSource TemplatedParent}}" TextWrapping="{Binding TextWrapping,RelativeSource={RelativeSource TemplatedParent}}" TextAlignment="{Binding TextAlignment,RelativeSource={RelativeSource TemplatedParent}}" HorizontalScrollBarVisibility="{Binding HorizontalScrollBarVisibility,RelativeSource={RelativeSource TemplatedParent}}" VerticalScrollBarVisibility="{Binding VerticalScrollBarVisibility,RelativeSource={RelativeSource TemplatedParent}}" MaxLength="{Binding MaxLength,RelativeSource={RelativeSource TemplatedParent}}" IsReadOnly="{Binding IsReadOnly,RelativeSource={RelativeSource TemplatedParent}}" IsEnabled="{Binding IsEnabled,RelativeSource={RelativeSource TemplatedParent}}"/> </Grid> </ControlTemplate> <Style TargetType="{x:Type local:LabelTextBox}" > <Setter Property="Template" Value="{StaticResource TopLabelTextBoxTemplate}"/> <Style.Triggers> <Trigger Property="LabelPosition" Value="Left"> <Setter Property="Template" Value="{StaticResource ResourceKey=LeftLabelTextBoxTemplate}"></Setter> </Trigger> </Style.Triggers> </Style> </ResourceDictionary>
接下來,將資源字典添加到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:WpfCustomControl"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/WpfCustomControl;component/themes/LabelTextBox.xaml" ></ResourceDictionary> </ResourceDictionary.MergedDictionaries> </ResourceDictionary>
編譯通過后就得到我們的控件庫WpfCustomControl.dll.
LabelTextBox的使用
在自己項目引用WpfCustomControl.dll,在xaml文件中添加標記引用,然后就可以直接使用,如下:
<Window x:Class="ControlsTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:control="clr-namespace:WpfCustomControl;assembly=WpfCustomControl" Title="MainWindow" Height="350" Width="525"> <Grid> <control:LabelTextBox Name="txtbox" LabelPosition="Top" Label="標題" Width="100" TextChanged="LabelTextBox_TextChanged_1" Margin="209,136,209.4,136.4"/> </Grid> </Window>
可以看到TextChanged等事件能正常觸發,但是上面說到TextBox和LabelTextBox沒什么關聯,要手動綁定屬性.然而我們沒有為LabelTextBox事件綁定過什么,卻依然生效了.那是因為它們公用一個事件路由,實質是TextBox觸發了TextChanged事件,冒泡到LabelTextBox,觸發了LabelTextBox的TextChanged事件.
小結
本文簡單介紹了如何構建一個自定義控件,其中涉及到依賴項屬性,控件模板,資源,事件的知識點.最后,如果您有更好的建議,請不吝指教.
