一个自定义的TextBox:
<Window x:Class="WPF1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ed="http://schemas.microsoft.com/expression/2010/drawing"
mc:Ignorable="d"
Title="MainWindow" Height="500" Width="875">
<Window.Resources>
<SolidColorBrush x:Key="TextBox.Static.Border" Color="#FFABAdB3"/>
<SolidColorBrush x:Key="TextBox.MouseOver.Border" Color="#FF7EB4EA"/>
<SolidColorBrush x:Key="TextBox.Focus.Border" Color="#FF569DE5"/>
<Style x:Key="TextBoxStyle1" TargetType="{x:Type TextBox}">
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Border x:Name="border" BorderBrush="#FF5A660B" BorderThickness="1" SnapsToDevicePixels="True">
<ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TextBox Name="dd" Height="30" Width="100" Style="{DynamicResource TextBoxStyle1}"/>
</Grid>
</Window>
上面是一个自定义的TextBox控件,一切安好,使用上没有什么问题。但是,如果把控件模板中ScrollViewer 的x:Name="PART_ContentHost" 去掉,结果就不能在TextBox中输入文本了,这是什么原因呢?Name能影响什么呢?
------解决思路----------------------
没有了Name,程序找不到对像了。
------解决思路----------------------
nono,PART_ContentHost 是专属名词, 就像 DocumentViewer控件,它也有这个命名。 个人觉得 这也就是为什么会有TemplateBinding 的原因,你可以binding它的Width与Height 属性,但是不需要 任何x:name ,但是ScrollViewer就需要。
如果会使用blend工具的话,可以编辑一个控件的副本,会发现有很多以 PART开头的名词。
------解决思路----------------------
我们知道WPF控件是不用有固定形状的,我们可以通过Style来任意改变它的具体表现。
但是,控件本身具有特定的逻辑和作用,比如一个按钮,应该是可以点击的;一个颜色选择控件,应该是可以用来选颜色的。
这里就存在了一个矛盾:如果可以任意改变控件的具体表现,那么如何保证它特有的逻辑和作用呢?
答案就是WPF控件的‘部件’概念。简单的说,就是你可以任意改变我,但是要提供我期待的部件,否则我的逻辑和作用就不能得到保证。如果阅读TextBoxBase的MSDN参考(TextBox继承于TextBoxBase),你会看到如下的特性:
该特性表明TextBoxBase控件期待你提供一个名字叫PART_ContentHost的部件,该部件必须是FrameworkElement。而TextBoxBase将会把具体的TextView和TextEditor放到PART_ContentHost里面。
------解决思路----------------------
这里我用代码解释WPF控件中‘部件’的使用。
1、新建一个WPF自定义控件项目,并取名为MyUpDown。
2、把CustomControl1(包括文件等)换名为MyUpDown。
3、把Generic.xaml和MyUpDown.cs内容换成:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyUpDown">
<Style TargetType="{x:Type local:MyUpDown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyUpDown">
<StackPanel Margin="3" Orientation="Horizontal" Background="{TemplateBinding Background}">
<TextBlock Width="30" VerticalAlignment="Center"
Text="{Binding Path=Value, RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type local:MyUpDown}}}" />
<RepeatButton Width="40" Content="Up" Name="PART_UpButton" />
<RepeatButton Width="40" Content="Down" Name="PART_DownButton" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
[TemplatePart(Name = "PART_UpButton", Type = typeof(ButtonBase))]
[TemplatePart(Name = "PART_DownButton", Type = typeof(ButtonBase))]
public partial class MyUpDown : Control
{
static MyUpDown()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyUpDown), new FrameworkPropertyMetadata(typeof(MyUpDown)));
}
public override void OnApplyTemplate()
{
UpButton = GetTemplateChild("PART_UpButton") as ButtonBase;
DownButton = GetTemplateChild("PART_DownButton") as ButtonBase;
}
ButtonBase upButton;
ButtonBase UpButton
{
get { return this.upButton; }
set
{
if (this.upButton != null) this.upButton.Click -= Button_Click;
this.upButton = value;
if (this.upButton != null) this.upButton.Click += Button_Click;
}
}
ButtonBase downButton;
ButtonBase DownButton
{
get { return this.downButton; }
set
{
if (this.downButton != null) this.downButton.Click -= Button_Click;
this.downButton = value;
if (this.downButton != null) this.downButton.Click += Button_Click;
}
}
void Button_Click(object sender, RoutedEventArgs e)
{
if (object.ReferenceEquals(sender, this.upButton)) this.Value++;
if (object.ReferenceEquals(sender, this.downButton)) this.Value--;
}
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(int), typeof(MyUpDown));
}
4、确保编译成功(控件本身不能运行)。
5、新建一个WPF窗口项目,并添加对MyUpDown项目的引用。
6、从工具栏中,拖一个MyUpDown控件到窗口中,编译运行就可以看到一个类似NumericUpDown的控件。点击Up和Down按钮,可以看到数字的相应变化。
下面,我们来自定义(假设是别人提供的)MyUpDown控件:
<Grid>
<MyUpDown:MyUpDown Value="123" >
<MyUpDown:MyUpDown.Template>
<ControlTemplate TargetType="MyUpDown:MyUpDown">
<Grid>
<TextBox Text="{Binding Path=Value,RelativeSource={RelativeSource AncestorType=MyUpDown:MyUpDown}}" Width="150" Height="30" />
<Button Content="UpOnly" Width="50" Height="30" Margin="200 0 0 0" />
</Grid>
</ControlTemplate>
</MyUpDown:MyUpDown.Template>
</MyUpDown:MyUpDown>
</Grid>
在新的模板中,我们用TextBox来支持直接编辑,同时提供一个按键。编译运行,可以发现MyUpDown可以显示Value的数值,但是,两个按钮不见了,我们提供的UpOnly按钮不起作用。这就是我们没有按照控件期待提供特定名字按钮的结果。
现在,把Button的代码更改为:
<Button Content="UpOnly" Width="50" Height="30" Margin="200 0 0 0" Name="PART_UpButton" />
编译运行,可以发现只有一个按钮,但该UpOnly按钮可以向上调整数字了。
用代码上看,MyUpDown.cs第10行中,当开始应用模板OnApplyTemplate的时候,MyUpDown控件尝试着寻找两个特定名字类型为按钮的部件:
GetTemplateChild("PART_UpButton") as ButtonBase;
...
如果找到按钮部件,则为按钮添加单击事件的响应函数,并在响应函数中相应地对当前数字进行加一或减一,从而达到用按钮调整数字的特定作用(NumericalUpDown)。
如果找不到部件,WPF控件可以根据严重程度,选择抛出异常,或减少功能等措施。