在WPF中想要进行个性化处理,主要可以通过三个方面来实现:控件模板(控件模板、数据模板、数据容器模板)、用户控件(UserControl
)、自定义控件(CustomControl
)。
用户控件-UserControl
一、简单使用
创建用户控件
<UserControl ....>
......
</UserControl>
创建完成后会出现用户控件的xaml文件,打开后看到这个xaml文件中的顶级元素为UserControl
。
UserControl
的用法与Window
是一样的,区别在于UserControl
元素可以被Window
元素包含,而Window
元素只能作为顶级元素存在。
用户控件数据的常规处理
MyUserControl.xaml中
<Grid>
<StackPanel>
<Button Height="30"/>
<TextBox Text="{Binding Value, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor} ,FallbackValue=0}" Height="30"/>
</StackPanel>
</Grid>
MyUserControl.xaml.cs中
public partial class MyUserControl : UserControl
{
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(MyUserControl), new PropertyMetadata(default));
public MyUserControl()
{
InitializeComponent();
}
}
MainWindow.xaml中
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<local:MyUserControl Value="{Binding Value}"/>
</StackPanel>
</Grid>
上面这一段用户控件的编写及使用是想说明一下对于用户控件中的数据,应该通过这种相对关系,找到自身的依赖属性进行数据绑定,从而让数据从外界传递到用户控件的内部。用户控件的内部应该保持完整的封装,就像C#的函数定义一样,对外提供接口,隐藏内部细节。
再看看下面这种做法:
UserControl.xaml中
<TextBox Text="{Binding Value}" Height="30"/>
MainWindow.xaml中
<local:MyUserControl"/>
这种做法,用户控件跟外界耦合了,万一在窗体使用时DataContext
中没有Value属性,就出问题了。
特点
用户控件注重复合控件即控件的组合使用,可以根据控件开发人员自己的意愿进行功能处理,非常灵活。
用户控件主要有如下特点:
- 多个现有控件的组合,组成一个可复用的控件组。
- Xaml和后台代码组成,绑定非常紧密。
- 窗体使用用户控件后,不支持对用户控件模板、样式的重写。
- 继承自
UserControl
类型
二、用户控件的资源调用
UserControl
是一个内容控件,可以通过Template
属性的ControlTemplate
对控件内容进行,同时在UserControl
元素中的内容都会统一存放到ContentPresenter
元素中,可以利用其进行模板样式的复用,具体用法如下。
创建资源字典
创建资源字典DefaultToolBarTemplate.xaml,编写ControlTemplate
,其重点在于利用ContentPresenter
元素来对UserControl
的内容进行布局。
<ResourceDictionary ......>
<ControlTemplate TargetType="UserControl" x:Key="ToolBarTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="这里是复用内容,哪个UserControl使用都是OK的"/>
<!--下面就是内容布局-->
<ContentPresenter Grid.Row="1"/>
</Grid>
</ControlTemplate>
</ResourceDictionary>
在UserControl中引用资源
<UserControl ......>
<UserControl.Resources>
<ResourceDictionary Source="/Assets;component/Styles/DefaultToolBarTemplate.xaml"/>
</UserControl.Resources>
<UserControl.Template>
<!--引用资源,复用模板-->
<StaticResource ResourceKey="ToolBarTemplate"/>
</UserControl.Template>
<Grid>
<!--这里就是用户控件自己的内容了-->
</Grid>
</UserControl>
三、继承处理
在实际项目中,很多时候会创建多个用户控件来完成不同的功能,但是这些控件中往往有部分功能是冗余的,如下列示例代码:
TestUserControlA
<UserControl ......>
<Grid Background="Green">
<Button Content="删除" Width="100" Height="50" Click="Button_Click"/>
</Grid>
</UserControl>
public partial class TestUserControlA : UserControl
{
public TestUserControlA()
{
InitializeComponent();
}
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set { SetValue(DeleteCommandProperty, value); }
}
public static readonly DependencyProperty DeleteCommandProperty =
DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(TestUserControlA), new PropertyMetadata(default));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(TestUserControlA), new PropertyMetadata(default));
private void Button_Click(object sender, RoutedEventArgs e)
{
DeleteCommand?.Execute(CommandParameter);
}
}
TestUserControlB
<UserControl ......>
<Grid Background="Green">
<Button Content="删除" Width="100" Height="50" Click="Button_Click"/>
</Grid>
</UserControl>
public partial class TestUserControlB : UserControl
{
public TestUserControlB()
{
InitializeComponent();
}
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set { SetValue(DeleteCommandProperty, value); }
}
public static readonly DependencyProperty DeleteCommandProperty =
DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(TestUserControlB), new PropertyMetadata(default));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(TestUserControlB), new PropertyMetadata(default));
private void Button_Click(object sender, RoutedEventArgs e)
{
DeleteCommand?.Execute(CommandParameter);
}
}
观察上述代码,可以发现TestUserControlA和TestUserControlB中的两个依赖属性其含义是相同的,这个时候可以考虑进行抽取到父类,来消除冗余,抽取之后结果如下:
父类代码
public class ComponentBase:UserControl
{
public ICommand DeleteCommand
{
get { return (ICommand)GetValue(DeleteCommandProperty); }
set { SetValue(DeleteCommandProperty, value); }
}
public static readonly DependencyProperty DeleteCommandProperty =
DependencyProperty.Register("DeleteCommand", typeof(ICommand), typeof(ComponentBase), new PropertyMetadata(default));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(ComponentBase), new PropertyMetadata(default));
}
TestUserControlA
<local:ComponentBase x:Class="Components.TestUserControlA" ......>
<Grid Background="Green">
<Button Content="删除" Width="100" Height="50" Click="Button_Click"/>
</Grid>
</local:ComponentBase>
public partial class TestUserControlA : ComponentBase
{
public TestUserControlA()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DeleteCommand?.Execute(CommandParameter);
}
}
TestUserControlB
<local:ComponentBase x:Class="Components.TestUserControlB" ......>
<Grid Background="Green">
<Button Content="删除" Width="100" Height="50" Click="Button_Click"/>
</Grid>
</local:ComponentBase>
public partial class TestUserControlB : ComponentBase
{
public TestUserControlB()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
DeleteCommand?.Execute(CommandParameter);
}
}
需要注意的是,TestUserControlA和TestUserControlB 的XAML代码中的UserConrol元素换成了父类的ComponentBase元素,这是应为在进行部分类(partial
)合并时必须是相同的基类。
自定义控件-CustomControl
创建自定义控件
创建完成后,项目中会新增一个MyCustomControl.cs文件和一个含有Generic.xaml的Themes文件夹。
MyCustomControl.cs文件用于处理自定义控件的逻辑,Generic.xaml文件则负责界面显示。
主题文件
思考一个问题,当我们在编辑界面上右键Button
按钮编辑样式-编辑副本,然后xaml文件中会出来Button
的默认模板代码,这些默认模板是从哪里来的?其实就是从Button
的Generic.xaml文件获取的。其实准确来说,WPF会先根据当前运行系统从对应的模板文件中获取默认模板,如果对应的系统模板文件中没有对应的控件模板,才会从控件的Generic.xaml文件中获取默认模板。
试试右键ComboBox
控件->编辑样式->编辑副本,然后在xaml文件的Window元素的属性中会发现多出了一个属性,这个就是根据系统引入的模板文件,即当前系统主题。
需要注意的是,如果xaml文件中引入了如上主题,应用如果放到win7系统,由于win7没有这个主题文件,会导致应用崩溃。
逻辑处理
当逻辑里需要界面对象参与时,可以在后台代码中通过FindName
函数来获取对应的界面对象。
FindName(string name)
:根据指定的元素名称获取对应的元素对象,不存在时返回null
。
public class MyCustomControl : Control
{
......
private void LogicTest()
{
object btn = FindName("btn_Name");
}
}
也可以通过重写父类Control
的OnApplyTemplate
函数,在函数中通过GetTemplateChild
函数获取对象。
GetTemplateChild(string Name)
:在实例化的可视化树中获取指定名称的元素对象,不存在时返回null
。
public override void OnApplyTemplate()
{
var buttonElement = GetTemplateChild("btn_One") as Button;
base.OnApplyTemplate();
}
特点
实现自定义控件需要注重控件对象的功能,必须遵守WPF的控件规则。
自定义控件有如下特点:
- 可以完全自定义实现一个控件或者继承现有控件进行功能扩展并添加新功能。
- 通过后台代码(逻辑控制)和Generic.xaml(样式模板)进行组合来完成控件实现。
- 继承自
Control
类型。