User Controls and Custom Controls in WPF
Whether in WPF or WinForm, there are user controls (UserControl) and custom controls (CustomControl). These two controls are encapsulation of existing controls to achieve functional reuse. But there are still some differences between the two. This article explains these two controls.
- user control
- Pay attention to the use of composite controls, that is, multiple existing controls form a reusable control group
- Composed of XAML and background code, tightly bound
- Template rewriting is not supported
- Inherited from UserControl
- custom control
- Completely implement a control by yourself, such as inheriting existing controls for function extension and adding new functions
- Combination of background code and Generic.xaml
- Support template rewriting when using
- Inherited from Control
user control
User controls are relatively easy to understand, similar to common WPF forms. It is worth noting that when using binding internally, it needs to be RelativeSource
bound in a way to achieve good encapsulation. A simple case:
- Define user controls
<UserControl
x:Class="WpfApp19.UserControl1"
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:local="clr-namespace:WpfApp19"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<!--下面绑定都要用RelativeSource作为源-->
<StackPanel>
<TextBox
Width="100"
BorderThickness="2"
Text="{Binding value, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<Button
Width="100"
Command="{Binding Command, RelativeSource={RelativeSource AncestorType=UserControl}}"
Content="Ok" />
</StackPanel>
</Grid>
</UserControl>
code behind
//根据需要定义依赖属性
//所需要绑定的值
public int value
{
get {
return (int)GetValue(valueProperty); }
set {
SetValue(valueProperty, value); }
}
public static readonly DependencyProperty valueProperty =
DependencyProperty.Register("value", typeof(int), typeof(UserControl1), new PropertyMetadata(0));
//所需要绑定的命令
public ICommand Command
{
get {
return (ICommand)GetValue(CommandProperty); }
set {
SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(UserControl1), new PropertyMetadata(default(ICommand)));
//所需要绑定的命令参数
public object CommandParemeter
{
get {
return (object)GetValue(CommandParemeterProperty); }
set {
SetValue(CommandParemeterProperty, value); }
}
public static readonly DependencyProperty CommandParemeterProperty =
DependencyProperty.Register("CommandParemeter", typeof(object), typeof(UserControl1), new PropertyMetadata(0));
- use user controls
<Window x:Class="WpfApp19.MainWindow"
...
xmlns:local="clr-namespace:WpfApp19"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:UserControl1 value="{Binding }" Command="{Binding }"/>
</Grid>
</Window>
custom control
CustomControl1.cs
After clicking to add a custom control, a file and a directory will be added , and there is a file Themes
under the directory , which is the style of the custom control. Generic.xaml
The style we often generate for certain controls 编辑模板
- 创建副本
operations is actually Generic.xaml
the style defined in . In addition, sometimes we may encounter a situation, that is, the same software runs under different Windows versions, and the performance may be different, and even some systems cannot run, which is different from the default Theme under different systems. In fact, when the wpf control cannot find a custom style, it will obtain the style from the system. The search order is to first find the assembly where it is located. If the assembly defines a feature, then it will check the attribute value. If the attribute is yes, it means ThemeInfo
there ThemeInfoDictionaryLocation
is None
no A specific theme resource, the value SourceAssembly
means that the specific resource is defined inside the assembly, and the value ExternalAssembly
means that it is outside. If it is still not found, the program will get it in its own themes/generic.xaml
, and generic.xaml
what it gets in is actually related to the default style of the system.
System themes corresponding to different xaml
button case
C# file
public class Switch : ToggleButton
{
static Switch()
{
//通过重写Metadata,控件就会通过程序集themes文件夹下的generic.xaml来寻找系统默认样式
DefaultStyleKeyProperty.OverrideMetadata(typeof(Switch), new FrameworkPropertyMetadata(typeof(Switch)));
}
}
Generic.xaml file under the Themes folder
Note that there can be no Chinese in this file, nor can comments
<Style TargetType="{x:Type local:Switch}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Switch}">
<Grid>
<Border
Name="dropdown"
Width="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
Margin="-23"
CornerRadius="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
Visibility="Collapsed">
<Border.Background>
<RadialGradientBrush>
<GradientStop Offset="1" Color="Transparent" />
<GradientStop Offset="0.7" Color="#5500D787" />
<GradientStop Offset="0.59" Color="Transparent" />
</RadialGradientBrush>
</Border.Background>
</Border>
<Border
Name="bor"
Width="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
Background="Gray"
BorderBrush="DarkGreen"
BorderThickness="5"
CornerRadius="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}">
<Border
Name="bor1"
Width="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}"
Margin="2"
Background="#FF00C88C"
CornerRadius="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight}" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="bor1"
Storyboard.TargetProperty="Background.Color"
To="White"
Duration="0:0:0.5" />
<ColorAnimation
Storyboard.TargetName="bor"
Storyboard.TargetProperty="BorderBrush.Color"
To="#FF32FAC8"
Duration="0:0:0.5" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="dropdown" Storyboard.TargetProperty="Visibil
<DiscreteObjectKeyFrame KeyTime="0:0:0.3">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="bor1" Storyboard.TargetProperty="Background.Color" />
<ColorAnimation Storyboard.TargetName="bor" Storyboard.TargetProperty="BorderBrush.Color" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="dropdown" Storyboard.TargetProperty="Visibil
<DiscreteObjectKeyFrame KeyTime="0:0:0.3">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Use custom controls
<Grid>
<local:Switch Width="100" Height="100" />
</Grid>
Commonly used knowledge points in custom controls
- TemplatePart properties
In custom controls, some controls need to have a name for easy calling, such as getting the specified button in the rewritten OnApplyTemplate() method. This requires that the user cannot modify the name in the template when using the control.
//应用该控件时调用
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
}
So use attributes in front of the class to mark
[TemplatePart(Name = "UpButton", Type = typeof(RepeatButton))]
[TemplatePart(Name = "DownButton", Type = typeof(RepeatButton))]
public class Numeric : Control{
}
- Definition and call of visual state
Visual states can be defined in custom controls to present effects in different states.
Define the visual state in xml, and the visual states under different groups are mutually exclusive
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="FocusStates">
<VisualState Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
Corresponding visual state switching in C#
private void UpdateStates(bool useTransitions)
{
if (IsFocused)
{
VisualStateManager.GoToState(this, "Focused", false);
}
else
{
VisualStateManager.GoToState(this, "Unfocused", false);
}
}
At the same time, you can use attributes to mark in the background class
[TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
public class Numeric : Control{
}
In fact, you can use Trigger to achieve this function
Case study source code download
Share several cases of user controls and custom controls, the effect is as follows: