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

总目录



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

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中用户控件和自定义控件的使用

  • 8
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
WPF,可以通过自定义控件来实现ROI(Region of Interest)功能。ROI是指在图像或界面选择感兴趣的区域,通常用于目标检测、图像处理等应用。 在WPF,可以使用Prism框架的MVVM方式来实现自定义控件的ROI功能。可以使用控件模板(ItemsControl)、可拖动控件(Thumb)、装饰器(Adorner)等来实现。同时,可以使用CommandParameter来传递多个参数,使用GetChildObjectByUid方法来查找特定类型的子控件。 具体的代码示例可以参考以下代码: ```xml <StackPanel Orientation="Horizontal"> <!--FlowDirection默认值为LeftToRight--> <Image x:Name="img" Width="150" Height="100" Margin="10" Source="./huahua.png" Stretch="Fill" FlowDirection="LeftToRight" Cursor="ScrollAll" Opacity="0.7"/> <!--FlowDirection流动方向设置为RightToLeft--> <Image x:Name="img2" Width="150" Height="100" Margin="10" Source="pack://application:,,,/huahua.png" Stretch="Fill" FlowDirection="RightToLeft" Cursor="Hand"/> <!--Source的路径不建议使用磁盘地址--> <Image x:Name="img3" Width="150" Height="100" Margin="10" RenderTransformOrigin="0.5,0.5" Source="Pack://application:,,,/WPFControlsTest;component/huahua.png" Stretch="Fill" > <Image.RenderTransform> <TransformGroup> <RotateTransform Angle="180" CenterX="0" CenterY="0"/> <ScaleTransform ScaleX="1.1" ScaleY="1.1"/> <TranslateTransform X="50" Y="50"/> </TransformGroup> </Image.RenderTransform> </Image> </StackPanel> ``` 以上代码展示了如何设置固定大小的图片,并在其应用了旋转、缩放和平移的变换效果。通过设置不同的属性和参数,可以实现自定义控件的ROI功能。 #### 引用[.reference_title] - *1* *2* [WPF Prism MVVM【动态添加控件并可用鼠标、拖动、缩放、旋转】](https://blog.csdn.net/redfox6843/article/details/126117819)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [WPF 控件专题 Image控件详解](https://blog.csdn.net/BYH371256/article/details/125327082)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值