WPF中的用户控件和自定义控件详解

news2024/7/6 18:16:39

总目录


文章目录

  • 总目录
  • 一、什么是用户控件和自定义控件
    • 1 用户控件和自定义控件
    • 2 相关知识点
    • 2 用户控件和自定义控件的适用场景
  • 二、用户控件的使用
    • 1.通过依赖属性和路由事件封装用户控件的案例
    • 2.通过依赖属性+命令依赖属性封装用户控件的案例
    • 3.只对外提供属性不对外提供操作的用户控件封装案例
  • 三、自定义控件的使用
    • 1. 创建普通类继承现有控件进行扩展 - 圆角的Button
    • 2. 创建自定义控件
    • 3.相关知识点
  • 结语


一、什么是用户控件和自定义控件

1 用户控件和自定义控件

在WPF中,用户控件(UserControl)和自定义控件(CustomControl)都是对UI控件的一种封装方式,目的都是实现封装后控件的重用。
只不过各自封装的实现方式和使用的场景上存在差异。

不同点UserControlCustomControl
是否为复合控件注重控件的复合,可以将多个控件组合成一个复合控件不是复合控件,而是通过继承已有控件,对控件的功能和外观进行扩展
控件代码
组成部分
用户控件由XAML代码和后台代码组成且两者紧密绑定自定义控件由控件的类对象代码(后台代码) 以及Generic.xaml中的样式模板代码组成
是否可模板重写不支持在外部对控件进行模板重写和样式的更改支持在外部对控件的进行模板重写和样式的修改
继承关系继承自UserControl继承自Control
编写过程可以在编辑器中实时查看控件效果,更直观不可实时查看控件效果,不直观

2 相关知识点

在定义用户控件和自定义控件的过程中,会涉及到以下知识点:

  • 当封装的控件,需要对外提供属性 以供设置的时候,我们需要通过 定义依赖属性或者附加属性来实现
  • 当封装的控件,需要对外提供路由事件的时候,我们需要通过定义自定义路由事件来实现
  • 当封装的控件,如果觉得路由事件不好用,希望直接对外提供命令的时候,我们需要通过依赖属性定义一个或多个对外的命令属性来实现

2 用户控件和自定义控件的适用场景

当我们写一个控件的时候,考虑到该控件在当前项目的其他界面会重复使用,或者在后续其他的项目中还会得到复用的时候,我们就可以将该控件封装成一个 自定义的用户控件,或者直接封装成自定义控件。

  • 如果希望封装的控件外观可以通过ControlTemplate 进行更改的时候,使用CustomControl
  • 如果希望控件的样式可以更改的时候,也可使用CustomControl
  • 反之,当我们界面元素较多,需要多个控件进行复合才可组成的时候,可以采用UserControl

二、用户控件的使用

用户控件的封装无非以下三种形式,会分别通过三个案例说明:

  • 需要对外提供属性和事件(复合控件内没有可以使用Command的控件)
  • 只需对外提供属性,属性除了正常的依赖属性外,还需包含ICommand 类型或其派生的依赖属性
  • 只对外提供属性值的设置,所有操作的业务逻辑全部封装到控件内部

这三种形式的封装,有各自的适用场景。

1.通过依赖属性和路由事件封装用户控件的案例

在这里插入图片描述

1.1 先创建用户控件,然后在控件内定义依赖属性和路由事件

在这里插入图片描述

  • 步骤1 :创建用户控件,然后在控件的后台代码中,根据需要对外提供的属性定义依赖属性
        public int Value
        {
            get { return (int)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), new PropertyMetadata(default(int),OnValueChanged));

        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is NumericUpDown numeric)
            {
                if (e.NewValue!=e.OldValue)
                {
                    //主要是要传入要【激发的事件】和【激发事件的对象】
                    RoutedEventArgs args = new RoutedEventArgs(NumericUpDown.ValueChangedEvent, numeric);
                    numeric.RaiseEvent(args);
                }
            }
        }
  • 步骤2:根据需求定义需要对外提供的路由事件
        //【第一步】声明并注册路由事件
        public static readonly RoutedEvent UpClickEvent = EventManager.RegisterRoutedEvent(
         "UpClick", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(NumericUpDown));

        //【第二步】将路由事件包装成 CLR 事件
        public event RoutedEventHandler UpClick
        {
            add { AddHandler(UpClickEvent, value); }
            remove { RemoveHandler(UpClickEvent, value); }
        }


        public NumericUpDown()
        {
            InitializeComponent();
            //【第三步】将合理的业务中激发定义的路由事件
            // 如该场景下适合在 名为up的Button的Click事件中激发路由事件,这样就可以将自定义的路由事件与按钮的Click事件绑定在一起
            this.up.Click += (s, e) =>
            {
                //主要是要传入要【激发的事件】和【激发事件的对象】
                RoutedEventArgs args = new RoutedEventArgs(NumericUpDown.UpClickEvent, s);
                RaiseEvent(args);
            };
        }
  • 步骤3:在用户控件xaml中绑定 定义的依赖属性,通过RelativeSource的方式实现:
<TextBox Height="50" Width="200"
         Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:NumericUpDown},
                        Path=Value, Mode=TwoWay,
                        UpdateSourceTrigger=PropertyChanged}">
</TextBox>

1.2 再者在引用用户控件的地方,绑定相关的值和命令

在这里插入图片描述

  • 步骤1:引用用户控件,将用户控件对外提供的属性 绑定ViewModel中的属性
  • 步骤2:然后根据需要依次将事件转为命令予以绑定
<Window x:Class="WpfApp1.Views.Window1"
        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:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" 
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1.Views"      
        xmlns:control="clr-namespace:WpfApp1.UserControls"
        xmlns:vm="clr-namespace:WpfApp1.ViewModels"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        mc:Ignorable="d"
        Title="Window1" Height="500" Width="1200">
    <Window.DataContext>
        <vm:Window1ViewModel></vm:Window1ViewModel>
    </Window.DataContext>
    <StackPanel Margin="20" Orientation="Horizontal">
        <control:NumericUpDown Value="{Binding Num,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Height="50" Width="300">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="UpClick">
                    <i:InvokeCommandAction Command="{Binding UpCommand}"></i:InvokeCommandAction>
                </i:EventTrigger>
                <i:EventTrigger EventName="DownClick">
                    <i:InvokeCommandAction Command="{Binding DownCommand}"></i:InvokeCommandAction>
                </i:EventTrigger>
                <i:EventTrigger EventName="ValueChanged">
                    <i:InvokeCommandAction Command="{Binding ValueChangedCommand}"></i:InvokeCommandAction>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </control:NumericUpDown>
        <Button Height="50" Width="250" Content="更改值" Command="{Binding ChangeCommand}" Margin="10"></Button>
    </StackPanel>
</Window>

2.通过依赖属性+命令依赖属性封装用户控件的案例

相对上面的封装方式,这种封装方式,可以在使用上给人感觉更方便,因为我们使用Command 不必像事件那样,还需要多一步转命令的操作。

1.1 再者在引用用户控件的地方,绑定相关的值和命令

在这里插入图片描述

  • 步骤1:将需要对外提供的属性和操作(命令)都定义成了依赖属性
    public partial class NumericUpDown : UserControl
    {
        public int Value
        {
            get { return (int)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown), new PropertyMetadata(default(int)));

        public ICommand UpCommand
        {
            get { return (ICommand)GetValue(UpCommandProperty); }
            set { SetValue(UpCommandProperty, value); }
        }

        public static readonly DependencyProperty UpCommandProperty =
            DependencyProperty.Register("UpCommand", typeof(ICommand), typeof(NumericUpDown), new PropertyMetadata( default(ICommand)));

        public ICommand DownCommand
        {
            get { return (ICommand)GetValue(DownCommandProperty); }
            set { SetValue(DownCommandProperty, value); }
        }

        public static readonly DependencyProperty DownCommandProperty =
            DependencyProperty.Register("DownCommand", typeof(ICommand), typeof(NumericUpDown), new PropertyMetadata(default(ICommand)));

        public NumericUpDown()
        {
            InitializeComponent();
        }
    }

如果命令,需要传参,只需将命令参数也定义一个依赖属性即可,如下所示:

//命令参数
 public object CommandParemeter
 {
     get { return (object)GetValue(CommandParemeterProperty); }
     set { SetValue(CommandParemeterProperty, value); }
 }
 public static readonly DependencyProperty CommandParemeterProperty =
     DependencyProperty.Register("CommandParemeter", typeof(object), typeof(NumericUpDown), new PropertyMetadata(default(object)));
  • 步骤2:在用户控件的xaml中予以绑定
    <StackPanel Width="300" Height="50" Orientation="Horizontal">
        <Button x:Name="down" Height="50" Width="50" Content="Down" 
                Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:NumericUpDown},
                                  Path=DownCommand }"></Button>

        <TextBox Height="50" Width="200" VerticalContentAlignment="Center" FontSize="16"
                  Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:NumericUpDown},
                                Path=Value, Mode=TwoWay,
                                UpdateSourceTrigger=PropertyChanged}">
        </TextBox>

        <Button x:Name="up" Height="50" Width="50" Content="Up"
                Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:NumericUpDown},
                                  Path=UpCommand }"></Button>
    </StackPanel>

1.2 再者在引用用户控件的地方,绑定相关的值和命令

在这里插入图片描述

    <StackPanel Margin="20" Orientation="Horizontal">
        <control:NumericUpDown Value="{Binding Num,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Height="50" Width="300"
                               UpCommand="{Binding UpCommand}"
                               DownCommand="{Binding DownCommand}">
        </control:NumericUpDown>
        <Button Height="50" Width="250" Content="更改值" Command="{Binding ChangeCommand}" Margin="10"></Button>
    </StackPanel>

3.只对外提供属性不对外提供操作的用户控件封装案例

在这里插入图片描述

三、自定义控件的使用

1. 创建普通类继承现有控件进行扩展 - 圆角的Button

  • 新建一个MyCornerButton类,继承自Button,定义一个ButtonCornerRadius依赖属性
    public class MyCornerButton: Button
    {
        public CornerRadius ButtonCornerRadius
        {
            get { return (CornerRadius)GetValue(ButtonCornerRadiusProperty); }
            set { SetValue(ButtonCornerRadiusProperty, value); }
        }

        public static readonly DependencyProperty ButtonCornerRadiusProperty =
            DependencyProperty.Register("ButtonCornerRadius", typeof(CornerRadius), typeof(MyCornerButton), new PropertyMetadata(default(CornerRadius)));
    }
  • 在MyCornerButton控件的样式模板中绑定自定义的依赖属性
        <!--此处省略很多样式代码,主要需要注意:
        	1、自定义控件通过TargetType 指定该样式的控件目标类型为自定义控件类
            2、在模板中 将自定义的ButtonCornerRadius依赖属性,绑定到Border的CornerRadius 上
          -->
        <Style x:Key="MyCornerButtonStyle1" TargetType="{x:Type controls:MyCornerButton}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type controls:MyCornerButton}">
                        <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" 
                        		CornerRadius="{TemplateBinding ButtonCornerRadius}" 
                        		BorderThickness="{TemplateBinding BorderThickness}" 
                        		Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
                            <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Border>
                        <ControlTemplate.Triggers>     
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" TargetName="border" Value="#FFBEE6FD"/>
                                <Setter Property="BorderBrush" TargetName="border" Value="#FF3C7FB1"/>
                            </Trigger>
                   
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

通过以上简单的两个步骤,我们就可以自定义一个简单好用的圆角按钮。

另外需额外补充一句:上面的圆角是通过在控件类内部定义依赖属性实现,当然我们也可以通过在一个公共的类中将圆角属性定义成附加属性,然后通过在样式中绑定附加属性来实现;如果我们很多地方都会使用到这个圆角属性,我们可以考虑将其放在公共的辅助类中,定义成附加属性。

2. 创建自定义控件

在这里插入图片描述
当我们通过新建项 创建 自定义控件NumericUpDown的时候,会生成以下内容:

  • NumericUpDown控件的类,主要负责控件的业务逻辑
  • 自动生成Themes 文件夹,以及下属的Generic.xaml 文件,在Generic.xaml编写控件对应的样式模板代码,负责控件的外观展示

在NumericUpDown控件类中会自动生成以下代码:

        static NumericUpDown()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown), new FrameworkPropertyMetadata(typeof(NumericUpDown)));
        }

该代码是设置控件的默认样式,如果没有该代码,则无法展示在Generic.xaml中编写的控件默认样式。
另外我们是可以在该类中定义对外提供的依赖属性和事件/命令等以及编写控件相关的业务逻辑。

在Generic.xaml中编写的控件默认样式,该xaml文件中的样式,是不需要设置Key的,默认样式会通过TargetType去匹配。

具体案例如下:
在这里插入图片描述

3.相关知识点

  • 当我们在Generic.xaml中编写的控件默认样式时,对模板内相关控件指定了名称,那么当我们通过右键菜单自动生成模板副本的时候,生成的样式中就会和默认样式中的结构以及控件名称一致,如上案例中,在默认样式中,定义了一个up 的Button,那么重写该控件模板的时候,模板内就会自动生成一个名为up的Button。

  • 若要指定控件所需的 FrameworkElement 对象,可使用 TemplatePartAttribute,它指定预期元素的名称和类型。

  • 若要指定控件的可能状态,可使用 TemplateVisualStateAttribute,它指定状态的名称及其所属的 VisualStateGroup。 将 TemplatePartAttribute 和 TemplateVisualStateAttribute 放置在控件的类定义中。

以下示例为 NumericUpDown 控件指定 FrameworkElement 对象和状态。


[TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
[TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
[TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
public class NumericUpDown : Control
{
    public static readonly DependencyProperty BackgroundProperty;
    public static readonly DependencyProperty BorderBrushProperty;
    public static readonly DependencyProperty BorderThicknessProperty;
    public static readonly DependencyProperty FontFamilyProperty;
    public static readonly DependencyProperty FontSizeProperty;
    public static readonly DependencyProperty FontStretchProperty;
    public static readonly DependencyProperty FontStyleProperty;
    public static readonly DependencyProperty FontWeightProperty;
    public static readonly DependencyProperty ForegroundProperty;
    public static readonly DependencyProperty HorizontalContentAlignmentProperty;
    public static readonly DependencyProperty PaddingProperty;
    public static readonly DependencyProperty TextAlignmentProperty;
    public static readonly DependencyProperty TextDecorationsProperty;
    public static readonly DependencyProperty TextWrappingProperty;
    public static readonly DependencyProperty VerticalContentAlignmentProperty;

    public Brush Background { get; set; }
    public Brush BorderBrush { get; set; }
    public Thickness BorderThickness { get; set; }
    public FontFamily FontFamily { get; set; }
    public double FontSize { get; set; }
    public FontStretch FontStretch { get; set; }
    public FontStyle FontStyle { get; set; }
    public FontWeight FontWeight { get; set; }
    public Brush Foreground { get; set; }
    public HorizontalAlignment HorizontalContentAlignment { get; set; }
    public Thickness Padding { get; set; }
    public TextAlignment TextAlignment { get; set; }
    public TextDecorationCollection TextDecorations { get; set; }
    public TextWrapping TextWrapping { get; set; }
    public VerticalAlignment VerticalContentAlignment { get; set; }
}

在这里插入图片描述

  • 通常我们控件模板内的名称,尽量与TemplatePart特性中的名称保持一致,可减少一些不必要的麻烦。

MSDN上的完成应用案例如下:

    <Style TargetType="{x:Type local:NumericUpDown}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:NumericUpDown">
                    <Grid  Margin="3"  Background="{TemplateBinding Background}">
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup Name="ValueStates">

                                <VisualState Name="Negative">
                                    <Storyboard>
                                        <ColorAnimation To="Red" Storyboard.TargetName="TextBlock"  Storyboard.TargetProperty="(Foreground).(Color)"/>
                                    </Storyboard>
                                </VisualState>
                                
                                <VisualState Name="Positive"/>
                            </VisualStateGroup>

                            <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>

                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>

                            <Border BorderThickness="1" BorderBrush="Gray"   Margin="7,2,2,2" Grid.RowSpan="2"  Background="#E0FFFFFF"
                                    VerticalAlignment="Center"   HorizontalAlignment="Stretch">
                                
                                <TextBlock Name="TextBlock"  Width="60" TextAlignment="Right" Padding="5"  
                                           Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                                           AncestorType={x:Type local:NumericUpDown}}, 
                                           Path=Value}"/>
                            </Border>

                            <RepeatButton Name="UpButton" Content="Up" Margin="2,5,5,0" Grid.Column="1" Grid.Row="0"/>
                            <RepeatButton Name="DownButton" Content="Down" Margin="2,0,5,5"  Grid.Column="1" Grid.Row="1"/>

                            <Rectangle Name="FocusVisual" Grid.ColumnSpan="2" Grid.RowSpan="2"  Stroke="Black" StrokeThickness="1" Visibility="Collapsed"/>
                        </Grid>

                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

在ControlTemplate中通过VisualStateManager 管理,该控件在不同的状态下的样式,例如获得焦点,失去焦点等不同状态,也可以通过触发器去实现VisualStateManager 提供视觉管理功能

以下示例演示 NumericUpDown 的逻辑。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;

namespace VSMCustomControl
{
    [TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
    [TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
    [TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
    [TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
    [TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
    [TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
    public class NumericUpDown : Control
    {
        public NumericUpDown()
        {
            DefaultStyleKey = typeof(NumericUpDown);
            this.IsTabStop = true;
        }

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(int), typeof(NumericUpDown),
                new PropertyMetadata(new PropertyChangedCallback(ValueChangedCallback)));

        public int Value
        {
            get { return (int)GetValue(ValueProperty); }
			set { SetValue(ValueProperty, value); }
        }

        private static void ValueChangedCallback(DependencyObject obj,DependencyPropertyChangedEventArgs args)
        {
            NumericUpDown ctl = (NumericUpDown)obj;
            int newValue = (int)args.NewValue;
            
            ctl.UpdateStates(true);
            ctl.OnValueChanged(new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent,newValue));
        }

        public static readonly RoutedEvent ValueChangedEvent =EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,typeof(ValueChangedEventHandler), typeof(NumericUpDown));

        public event ValueChangedEventHandler ValueChanged
        {
            add { AddHandler(ValueChangedEvent, value); }
            remove { RemoveHandler(ValueChangedEvent, value); }
        }

        protected virtual void OnValueChanged(ValueChangedEventArgs e)
        {
            // Raise the ValueChanged event so applications can be alerted
            // when Value changes.
            RaiseEvent(e);
        }

        private void UpdateStates(bool useTransitions)
        {
            if (Value >= 0)
            {
                VisualStateManager.GoToState(this, "Positive", useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, "Negative", useTransitions);
            }

            if (IsFocused)
            {
                VisualStateManager.GoToState(this, "Focused", useTransitions);
            }
            else
            {
                VisualStateManager.GoToState(this, "Unfocused", useTransitions);
            }
        }

        public override void OnApplyTemplate()
        {
            UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
            DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
            //TextElement = GetTemplateChild("TextBlock") as TextBlock;

            UpdateStates(false);
        }

        private RepeatButton downButtonElement;

        private RepeatButton DownButtonElement
        {
            get
            {
                return downButtonElement;
            }

            set
            {
                if (downButtonElement != null)
                {
                    downButtonElement.Click -=
                        new RoutedEventHandler(downButtonElement_Click);
                }
                downButtonElement = value;

                if (downButtonElement != null)
                {
                    downButtonElement.Click +=
                        new RoutedEventHandler(downButtonElement_Click);
                }
            }
        }

        void downButtonElement_Click(object sender, RoutedEventArgs e)
        {
            Value--;
        }

        private RepeatButton upButtonElement;

        private RepeatButton UpButtonElement
        {
            get
            {
                return upButtonElement;
            }

            set
            {
                if (upButtonElement != null)
                {
                    upButtonElement.Click -=
                        new RoutedEventHandler(upButtonElement_Click);
                }
                upButtonElement = value;

                if (upButtonElement != null)
                {
                    upButtonElement.Click +=
                        new RoutedEventHandler(upButtonElement_Click);
                }
            }
        }

        void upButtonElement_Click(object sender, RoutedEventArgs e)
        {
            Value++;
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            Focus();
        }


        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);
            UpdateStates(true);
        }

        protected override void OnLostFocus(RoutedEventArgs e)
        {
            base.OnLostFocus(e);
            UpdateStates(true);
        }
    }

    public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e);

    public class ValueChangedEventArgs : RoutedEventArgs
    {
        private int _value;

        public ValueChangedEventArgs(RoutedEvent id, int num)
        {
            _value = num;
            RoutedEvent = id;
        }

        public int Value
        {
            get { return _value; }
        }
    }
}

通过该案例主要理解三个东西:

  • 1 TemplateVisualState 和VisualStateManager 这部分是 管理视觉效果的,用于设置在不同状态下控件的样式
  • 2 TemplatePart 和 GetTemplateChild方法的使用,通过TemplatePart 预定义控件的组成部分相关信息,通过GetTemplateChild获取指定名称的预定义组件
  • 3 OnApplyTemplate ,每当应用程序代码或内部进程调用FrameworkElement.ApplyTemplate(),都将调用此方法,一般在静态构造函数执行完成后,就会调用该方法,我们可以在该方法中使用GetTemplateChild获取指定名称的预定义组件,给该组件附加一些事件或者业务逻辑。如下图所示:
    在这里插入图片描述

结语

以上就是本文的内容,希望以上内容可以帮助到您,如文中有不对之处,还请批评指正。


参考资料:
控件自定义
讲解WPF中用户控件和自定义控件的使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/518508.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

USBCAN卡和台达AS228T通信

AS228R 如图 A.把CAN卡固定上 B.接一个120欧的终端电阻 C.把来可的USBCAN卡的CANH和CANL分别接入AS228R的CANH和CANL. CAN 接口及网络拓扑 10.2.4.1 CAN 网络实体信号的定义及数据格式 CAN 信号为差分信号&#xff0c;信号电压为 CAN和 CAN-之间的电压差&#xff0c;CAN 和…

怎样删除hao123(浏览器首页被篡改了)

有时候我们打开浏览器发现首页被hao123 ,或者2345 这些浏览器给篡改了 或者打开的时候直接打开2个.这个时候想要删除它们,其他它们本身就是网页的,没有应用 在卸载的地方就不用了,它们就嵌套你的浏览器里面,打开的时候启动了他们, 下面说下方法 1 查看浏览器在什么方法下载…

第3天学习Docker-Docker部署常见应用(MySQL、Tomcat、Nginx、Redis、Centos)

前提须知&#xff1a; &#xff08;1&#xff09;搜索镜像命令 格式&#xff1a;docker search 镜像名 &#xff08;2&#xff09;设置Docker镜像加速器 详见文章&#xff1a;Docker设置ustc的镜像源&#xff08;镜像加速器&#xff09; 1、部署MySQL 拉取镜像&#xff08;这…

Radius:针对Rollup生态的Trustless Shared Sequencing层

1. 引言 前序博客&#xff1a; Rollup去中心化Rollup DecentralizationAztec 征集 Rollup Sequencer去中心化提案 Rollup中单一中心化sequencer的问题在于&#xff1a; 审查MEV extraction MEV extraction的问题不在于中心化本身&#xff0c;而在于sequencer具有操纵交易顺…

CN学术期刊《西部素质教育》简介及投稿邮箱

《西部素质教育》&#xff08;半月刊&#xff09;创刊于2015年&#xff0c;是由青海人民出版社有限责任公司主管/主办的教育类学术期刊&#xff0c;本刊恪守“追踪教育研究前沿&#xff0c;关注教育实践热点&#xff0c;探索创新教育理念&#xff0c;传播教育教学信息&#xff…

测试外包干了4年,我废了...

这是来自一位粉丝的投稿内容如下&#xff1a; 先说一下自己的个人情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某外包公司&#xff0c;干了接近4年的软件测试外包工作&#xff0c;马上2023年秋招了&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在…

JOSEF 约瑟 JZ-7Y-E/06静态中间继电器 拓展端子 接线牢固

​ JZ-7Y-E/06静态中间继电器品牌:JOSEF约瑟名称:静态中间继电器型号:JZ-7Y-E/06额定电压:6220VDC&#xff1b;6380VAC触点容量:10A/250V10A/220VDC功率消耗:≤6W JZ-7Y-E/06静态中间继电器 系列型号&#xff1a; JZ-7Y-E/60静态中间继电器&#xff1b; JZ-7J-E/60…

Spring ( 一 ) 依赖耦合

1.依赖耦合及解耦 1.1.从<<西游记>>说起 1.1.1.主角介绍 使用 Java 语言中编写一个 HouWang 猴王类 // 猴王 public class HouWang {// 成员变量private String mingZi;// 无参构造方法public HouWang() {System.out.println("石破天惊");}// 有参数构…

知识推理——TransE(知识表示、知识嵌入)

记录一下学习TransE的笔记~ 最后更新时间&#xff1a;20230512 1.要解决的问题 &#xff08;1&#xff09;以往的模型&#xff08;Bayesian clustering和energy-based&#xff09;关注于增强模型的表达能力和普遍性&#xff0c;但却增加了模型的复杂性及计算消耗。 &#xff0…

2023.05.12-给PDF文件添加目录

打开PdgCntEditorcloudshare.lanzouw.com/ijZPj03tmnle 直接把有标题的PDF拖进来&#xff0c;就可以识别生成对应的目录了之后点击保存就可以把目录写到PDF文件中了&#xff0c;非常的方便

23种设计模式之原型模式

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章将23种设计模式中的原型设计模式&#xff0c;此篇文章为一天学习一个设计模式系列文章&#xff0c;后面会分享其他模式知识。 如果文章有什么需要改进的地方还请大…

(10)Qt---项目打包

目录 项目打包 1. 设置应用图标** 2. Debug与Release模式*** 3. 动态链接库** 4. 程序打包* 5. 新版本Qt的安装与使用* 项目打包 一款正常的软件产品应该在任何的计算机中运行&#xff0c;不需要单独安装Qt的开发环境&#xff0c;因此需要把之前的项目打包成一个安装包。 1. 设…

Ctfshow [单身杯]

文章目录 web签到easyPHPescapeshellcmdescapeshellargsedawk 姻缘测试 web签到 <?php # -*- coding: utf-8 -*- # Author: h1xa # Date: 2022-03-19 12:10:55 # Last Modified by: h1xa # Last Modified time: 2022-03-19 13:27:18 # email: h1xactfer.com # link: h…

Java EE 进阶---多线程(一)

目录 一、常见的锁策略 乐观锁 vs 悲观锁 重量级锁 vs 轻量级锁 读写锁&#xff06;普通互斥锁 自旋锁&#xff06;挂起等待锁 可重入锁&#xff06;不可重入锁 公平锁&#xff06;非公平锁 synchronized实现了哪些锁策略&#xff1f; 二、Compare And Swap 比较并交换…

File类、IO数据流介绍

文章目录 &#x1f412;个人主页&#x1f3c5;JavaSE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;File类的设计&#x1fa85;数据流的流向 &#x1f3c5;对数据操作的类&#x1f9f8;按单位划分&#x1f9f8;按封装类型划分 &#x1f380;整理File常用方法 &#x1f41…

解析HTTPS的加密

文章目录 简介加密原理对称加密常用的对称加密算法&#xff1a;简单解析DES 非对称加密常用的非对称加密算法&#xff1a;简单解析RSA算法公钥和私钥的制作&#xff1a;加密和解密过程&#xff1a; 加密过程数字证书摘要算法解析一下MD5算法 简介 HTTPS 使用了 SSL (Secure So…

2023最新软件测试八股文,能不能拿心仪Offer就看你背得怎样了

很多同学会问测试面试八股文有必要背吗&#xff1f; 我的回答是&#xff1a;很有必要。你可以讨厌这种模式&#xff0c;但你一定要去背&#xff0c;因为不背你就进不了大厂。 个人感觉目前各个大中厂面试不好的地方是&#xff0c;都在朝着背面试八股文的方向发展&#xff08;曾…

Android Studio实现文件管理器

项目目录 一、项目概述二、开发环境三、详细设计1、布局设计2、程序运行时申请权限3、查看文件4、删除文件5、搜索文件6、新建文件 四、运行演示 一、项目概述 本次带来的文件管理器&#xff0c;能够对SD卡的目录进行管理。主要功能包括新建文件&#xff08;夹&#xff09;、查…

【shell】shell编程之函数

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、概述二、函数的查看和删除1.查看 declare2.删除 declare 三、函数的返回值1.return 返回值2.echo 返回值 四、函数的参数传入与变量范围五、函数的应用1.阶乘2.…

分库分表如何处理主键ID

当关系型数据库数据量过大时&#xff0c;通常会采用分库分表降低数据库查表压力。分库分表有多种&#xff0c;有分一个库多张分表额&#xff0c;有分多个库多张表的。一般分库分表使用ShardingSphere分表&#xff0c;建分片键等。但是分库分表之后&#xff0c;主键ID如何处理呢…