WPF中文网

Control基类

Control是许多控件的基类。比如最常见的按钮(Button)、单选(RadioButton)、复选(CheckBox)、文本框(TextBox)、ListBox、DataGrid、日期控件等等。这些控件通常用于展示程序的数据或获取用户输入的数据,我们可以将这一类型的控件称为内容控件或数据控件,它们与前面的布局控件有一定的区别,布局控件更专注于界面,而内容控件更专注于数据(业务)。

Control类虽然可以实例化,但是在界面上是不会有任何显示的。只有那些继承了Control的子类(控件)才会在界面上显示,而且所呈现的样子各不相同,为什么会是这样呢?

因为Control类提供了一个控件模板(ControlTemplate),而几乎所有的子类都对这个ControlTemplate进行了各自的实现,所以在呈现子类时,我们才会看到Button拥有Button的样子,TextBox拥有TextBox的样子。

Control基类说

我给你们一张白纸(ControlTemplate),你们可以在上面想画什么就画什么

我们在这一章节并不对模板(Template)进行详细的介绍,只是先阐述模板的概念,接下来,我们先将目光聚焦到Control的结构定义:

public class Control : FrameworkElement
{
public static readonly DependencyProperty BorderBrushProperty;
public static readonly RoutedEvent PreviewMouseDoubleClickEvent;
public static readonly DependencyProperty TemplateProperty;
public static readonly DependencyProperty PaddingProperty;
public static readonly DependencyProperty IsTabStopProperty;
public static readonly DependencyProperty TabIndexProperty;
public static readonly DependencyProperty VerticalContentAlignmentProperty;
public static readonly DependencyProperty HorizontalContentAlignmentProperty;
public static readonly RoutedEvent MouseDoubleClickEvent;
public static readonly DependencyProperty FontStyleProperty;
public static readonly DependencyProperty FontStretchProperty;
public static readonly DependencyProperty FontSizeProperty;
public static readonly DependencyProperty FontFamilyProperty;
public static readonly DependencyProperty ForegroundProperty;
public static readonly DependencyProperty BackgroundProperty;
public static readonly DependencyProperty BorderThicknessProperty;
public static readonly DependencyProperty FontWeightProperty;

public Control();

public FontStyle FontStyle { get; set; }
public FontStretch FontStretch { get; set; }
public double FontSize { get; set; }
public FontFamily FontFamily { get; set; }
public Brush Foreground { get; set; }
public Brush Background { get; set; }
public Thickness BorderThickness { get; set; }
public bool IsTabStop { get; set; }
public VerticalAlignment VerticalContentAlignment { get; set; }
public int TabIndex { get; set; }
public Thickness Padding { get; set; }
public ControlTemplate Template { get; set; }
public FontWeight FontWeight { get; set; }
public Brush BorderBrush { get; set; }
public HorizontalAlignment HorizontalContentAlignment { get; set; }
protected internal virtual bool HandlesScrolling { get; }

public event MouseButtonEventHandler MouseDoubleClick;
public event MouseButtonEventHandler PreviewMouseDoubleClick;

public override string ToString();
protected override Size ArrangeOverride(Size arrangeBounds);
protected override Size MeasureOverride(Size constraint);
protected virtual void OnMouseDoubleClick(MouseButtonEventArgs e);
protected virtual void OnPreviewMouseDoubleClick(MouseButtonEventArgs e);
protected virtual void OnTemplateChanged(ControlTemplate oldTemplate, ControlTemplate newTemplate);

}

我们先来看看Control基类为它的子类们提供了哪些属性

属性说明
FontStyle获取或设置控件的字体结构,类似于Word中字体的常规、斜体或倾斜
FontStretch获取或设置紧缩或在屏幕上展开一种字体的程度。
FontSize获取或设置字体大小。
FontFamily获取或设置控件的字体系列。如:微软雅黑 = "Microsoft YaHei UI"
Foreground获取或设置控件的字体颜色,也就是所谓的前景色画笔,它是一个刷子(Brush)
Background获取或设置一个用于描述控件的背景画笔。
BorderThickness获取或设置控件的边框宽度。
IsTabStop获取或设置一个值,该值指示控件是否包括在选项卡上的导航窗格中。
VerticalContentAlignment获取或设置控件的内容的垂直对齐方式。
TabIndex获取或设置一个值,确定当用户导航控件通过使用 TAB 键元素接收焦点的顺序。
Padding获取或设置在控件中的填充量。
Template获取或设置控件模板。
FontWeight获取或设置指定的字体粗细。
BorderBrush获取或设置一个用于描述一个控件的边框背景画笔。
HorizontalContentAlignment获取或设置控件的内容的水平对齐方式。

大部分的属性都比较好理解,这里着重介绍一下Template属性。如果把人比作是一个Control(控件),那么”着装“就是Template(模板)。在大街上,我们会看到不同着装的人来来往往。

所以Control的Template定义了控件的外观(着装)。

数据模板

与控件模板类似的,还有一个概念叫数据模板。形象地说,还是把人比作控件,那么人体的五脏六腑就是这个控件的数据,而五脏六腑(数据)的外观就是指数据模板。

我们来看一个实际的例子

<Window x:Class="HelloWorld.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:local="clr-namespace:HelloWorld"
        mc:Ignorable="d"
        Title="HelloWorld - www.wpfsoft.com" Height="350" Width="500">
    <Control Margin="10">
        
    </Control>

</Window>

我们在Window窗体中实例化了一个Control类,但是,界面上空无一物。这是因为当前这个Control对象还没有”穿衣服“,也就是说,它的Template并没有内容,实际上,此刻Template属性等于null。

那我们尝试给他穿一件衣服吧。

<Window x:Class="HelloWorld.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:local="clr-namespace:HelloWorld"
        mc:Ignorable="d"
        Title="HelloWorld - www.wpfsoft.com" Height="350" Width="500">
    <Control Margin="10">
        <Control.Template>
            <ControlTemplate TargetType="Control">
                <Border Background="LightBlue">
                    <TextBlock Text="WPF中文网" FontSize="20" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </ControlTemplate>
        </Control.Template>
    </Control>
</Window>

我们为Control的Template实例化了一个ControlTemplate对象,并在这个对象中增加了一个Border,在Border中又增加了一个TextBlock子元素,于是Control就有了这样一件新衣服。

在这里,我们要明白一个要点是,Control类的Template属性是ControlTemplate类型的。所以上面的代码才必须这样写才可以。而ControlTemplate又是什么东东?为什么在xaml中实例化时,后面要跟一句TargetType="Control"?关于这一点,我们将在后面专门拿一章节来阐述ControlTemplate以及它的父类。

事件

在这一小节里,您只要能明白Template的概念就行了。除了这个属性,Control类还提供了两个事件,它们分别是PreviewMouseDoubleClick和MouseDoubleClick。

事件名称说明
PreviewMouseDoubleClick表示鼠标双击或多次单击时触发的事件
MouseDoubleClick表示鼠标双击或多次单击时触发的事件

以Preview开头的事件叫隧道事件或预览事件,而MouseDoubleClick没有以Preview开头,所以它叫冒泡事件。

WPF的前端代码其实是一棵树,当我们在某个目标控件上进行鼠标操作时,所引发的事件有两个方向,一是从Winow根节点一直路由到目标控件上,看起来就好像是从外面一直沿着这棵树路由引发至里面,这就像我们开车进入隧道一样,所以Preview开头的事件叫隧道事件。

冒泡事件事件的路由方向相反,是从目标控件位置开始,一直路由引发至最外层的Window窗体。

关于路由事件,我们会在后面拿一章节专门讲解,这里只提一下这个概念。

通常,我们并不会直接实例化Control基类,确实这样做对我们实际帮助不大,我们要使用的——是它膝下各个子控件,而在这众多的子控件中,Button是最常见最简单的控件了。不过,Button的基类是ButtonBase,ButtonBase的基类是ContentControl,ContentControl的基类是Control。如果我们要探讨Button控件,看样子必须要先介绍一下ContentControl基类和ButtonBase基类才行了。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:015-《Control基类》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月22日

ContentControl是一个神奇的类!

为什么这么说呢,因为它有一个Content属性,关键是这个属性的类型是object。也就是说,本质上,它可以接收任意引用类型的实例。

但是,通常情况下,Content属性接收UI控件。因为,ContentControl控件最终会把Content属性里面的内容显示到界面上。

我们先看看它的结构定义:

public class ContentControl : Control, IAddChild
{
    public static readonly DependencyProperty ContentProperty;
    public static readonly DependencyProperty HasContentProperty;
    public static readonly DependencyProperty ContentTemplateProperty;
    public static readonly DependencyProperty ContentTemplateSelectorProperty;
    public static readonly DependencyProperty ContentStringFormatProperty;

    public ContentControl();

    public DataTemplate ContentTemplate { get; set; }
    public bool HasContent { get; }
    public object Content { get; set; }
    public string ContentStringFormat { get; set; }
    public DataTemplateSelector ContentTemplateSelector { get; set; }
    protected internal override IEnumerator LogicalChildren { get; }

    public virtual bool ShouldSerializeContent();
    protected virtual void AddChild(object value);
    protected virtual void AddText(string text);
    protected virtual void OnContentChanged(object oldContent, object newContent);
    protected virtual void OnContentStringFormatChanged(string oldContentStringFormat, string newContentStringFormat);
    protected virtual void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate);
    protected virtual void OnContentTemplateSelectorChanged(DataTemplateSelector oldContentTemplateSelector, DataTemplateSelector newContentTemplateSelector);

}

那么,我如果非要把其它类型的对象(比如字符串)强行塞给Content属性呢?

<Window x:Class="HelloWorld.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:local="clr-namespace:HelloWorld"
        mc:Ignorable="d"
        Title="HelloWorld - www.wpfsoft.com" Height="350" Width="500">
    <ContentControl Foreground="Red" FontSize="36" HorizontalAlignment="Center" VerticalAlignment="Center">
        WPF中文网
    </ContentControl>
</Window>

如上所示,我们在ContentControl 内部只写了一句“WPF中文网”,并设置了Foreground和FontSize等属性,居然不但没报错,还将字符串显示出来了,这真是太神奇了!

重庆教主友情提示

别忘记了ContentControl 继承于Control 基类,所以我们的ContentControl 也可以设置Foreground和FontSize哦!

ContentTemplate属性(内容模板)

这个属性表示获取或设置用来显示的内容的数据模板,说白了,就是决定“WPF中文网”这几个字的穿着,如果没有设置数据模板,它将以默认的数据模板来显示这几个字。接下来,我们演示一下这个属性的用法,并简要说明其中的关系。

<Window x:Class="HelloWorld.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:local="clr-namespace:HelloWorld"
        mc:Ignorable="d"
        Title="HelloWorld - www.wpfsoft.com" Height="350" Width="500">
    <ContentControl Foreground="Red" FontSize="36" HorizontalAlignment="Center" VerticalAlignment="Center">
        <ContentControl.ContentTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}" Foreground="Green" FontSize="16"/>
            </DataTemplate>
        </ContentControl.ContentTemplate>
        WPF中文网
    </ContentControl>
</Window>

ContentControl类的ContentTemplate属性是DataTemplate类型,所以我们在xaml中实例化了一个DataTemplate(数据模板)对象,并在其中增加了一个TextBlock控件,将TextBlock控件的Text属性写成了Binding形式,并设置了字体颜色和大小。

最终呈现的效果上图所示,字体颜色为绿色,大小为16。虽然ContentControl也设置了字体颜色为红色,大小为36,但是已经失效了。好比ContentControl给Content提供了一件红色的外衣,但是,我们又特地提供了一件绿色的外衣,于是,ContentControl就被绿了。

关于数据模板中的TextBlock控件的Text属性写成了Binding(绑定)形式,这是指将ContentContent控件的Contnet属性绑定到TextBlock控件的Text属性中,写成伪代码就是:

TextBlock.Text = ContentContent.Content

这是您第一次在这个系列中看到Binding(绑定)的出现。我们将在后面专门拿一章节进行探讨。这里能理解这个ContentTemplate就行了。

ContentControl控件能不能容纳多个子控件?

不能!因为ContentControl控件只能显示Content属性里面的内容,而Content属性是object,只能接收一个对象。

HasContent属性:表示ContentControl是否有内容。

ContentStringFormat属性:获取或设置ContentControl要显示字符串的格式。

ContentTemplateSelector属性:模板选择器, 我们会在模板一章节介绍。

常规用法

<Window x:Class="HelloWorld.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:local="clr-namespace:HelloWorld"
        mc:Ignorable="d"
        Title="HelloWorld - www.wpfsoft.com" Height="350" Width="500">
    <ContentControl Foreground="Red" FontSize="60" HorizontalAlignment="Center" VerticalAlignment="Center">
        <Button Content="WPF中文网"/>
    </ContentControl>
</Window>

这次我们在ContentControl放了一个Button,需要注意一点的是:Button的字号会随着ContentControl的设置而变化,但是字体颜色不会随着ContentControl的设置而变化。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:016-《ContentControl基类》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月22日

按钮,几乎每个具有UI界面的软件都会有它的身影,而按钮的形式也是有多种多样的,我们在这里简单的罗列一下。

按钮名称说明
Button普通按钮
CheckBox复选框按钮
RadioButton单选框按钮
ToggleButton是CheckBox、RadioButton的基类,表示可以切换状态
RepeatButton重复,表示从按下到弹出过程中重复引发Click事件
GridViewColumnHeader表示GridViewColumn 的列标题,其实它也是一个按钮
DataGridColumnHeader表示DataGrid 列标题,也是一个按钮
DataGridRowHeader表示DataGrid 行标题,也是一个按钮

上面便是WPF中的按钮体系,这些按钮都有一个共同的基类ButtonBase,所以,我们了解清楚了ButtonBase,对于学习上面这些按钮,有莫大的帮助。

一、ButtonBase概述

ButtonBase是一个抽象类,所以,它不能被实例化。我们只能在它的子类中去使用它提供的一些属性、事件或方法成员。它只有一个事件,就是Click单击事件,毕竟鼠标双击事件在它的Control基类就有了嘛。另外,它还有一个非常厉害的Command属性,这个属性其实是一个接口,起什么作用呢?就是在单击按钮时,去执行这个Command属性所指定的一个具体命令。

这个Command命令是WPF命令系统里面的角色,也是WPF优于Winform的一个具体表现,Command命令也是MVVM模式中最重要的一环。我们会在后面专门探讨WPF的命令系统。

接下来,我们先看看ButtonBase的结构定义:

public abstract class ButtonBase : ContentControl, ICommandSource
{
    public static readonly RoutedEvent ClickEvent;
    public static readonly DependencyProperty CommandProperty;
    public static readonly DependencyProperty CommandParameterProperty;
    public static readonly DependencyProperty CommandTargetProperty;
    public static readonly DependencyProperty IsPressedProperty;
    public static readonly DependencyProperty ClickModeProperty;

    protected ButtonBase();

    public IInputElement CommandTarget { get; set; }
    public object CommandParameter { get; set; }
    public ICommand Command { get; set; }
    public bool IsPressed { get; protected set; }
    public ClickMode ClickMode { get; set; }
    protected override bool IsEnabledCore { get; }

    public event RoutedEventHandler Click;

    protected override void OnAccessKey(AccessKeyEventArgs e);
    protected virtual void OnClick();
    protected virtual void OnIsPressedChanged(DependencyPropertyChangedEventArgs e);
    protected override void OnKeyDown(KeyEventArgs e);
    protected override void OnKeyUp(KeyEventArgs e);
    protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e);
    protected override void OnLostMouseCapture(MouseEventArgs e);
    protected override void OnMouseEnter(MouseEventArgs e);
    protected override void OnMouseLeave(MouseEventArgs e);
    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e);
    protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e);
    protected override void OnMouseMove(MouseEventArgs e);
    protected internal override void OnRenderSizeChanged(SizeChangedInfo sizeInfo);

}

二、ButtonBase的属性

属性名称 说明
CommandTarget 获取或设置要对其引发指定的命令的元素。
CommandParameter 获取或设置一个命令参数,这个参数是传递给Command 属性所指向的命令。
Command 获取或设置要在按此按钮时调用的命令。
IsPressed 获取当前按钮是否处于激活状态。
ClickMode 获取或设置按钮的单击模式
IsEnabledCore 获取的值 System.Windows.ContentElement.IsEnabled 属性。

三、ButtonBase方法

ButtonBase还提供了两个虚方法,分别是OnClick()和OnIsPressedChanged()。说明这两个方法也是可以重写的,OnClick表示在按钮单击时执行的方法。

——重庆教主 2023年8月22日

Button因为继承了ButtonBase,而ButtonBase又继承了ContentControl,所以,Button可以通过设置Content属性来设置要显示的内容。例如

<Button Content="WPF中文网"/>

我们使用Button的时机,通常是鼠标点击事件需要有响应操作时,所以,Button的Click事件是最好的选择。接下来,我们先看看它的结构定义:

public class Button : ButtonBase
{
    public static readonly DependencyProperty IsDefaultProperty;
    public static readonly DependencyProperty IsCancelProperty;
    public static readonly DependencyProperty IsDefaultedProperty;

    public Button();

    public bool IsDefault { get; set; }
    public bool IsCancel { get; set; }
    public bool IsDefaulted { get; }

    protected override void OnClick();
    protected override AutomationPeer OnCreateAutomationPeer();

}

属性分析

属性说明
IsDefault用户通过按 ENTER 键时调用的默认按钮。
IsCancel用户可以通过按 ESC 键来激活取消按钮。
IsDefaulted获取按钮是否为按 ENTER 键时调用的默认按钮。

我们通过一个例子来分析Button控件的用法与特点。

前端代码

<Window x:Class="HelloWorld.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:local="clr-namespace:HelloWorld"
        mc:Ignorable="d"
        Title="HelloWorld - www.wpfsoft.com" Height="350" Width="500">
    <Button x:Name="_button" 
            Content="退出" 
            Width="100" 
            Height="25" 
            Click="_button_Click" IsDefault="True"/>
</Window>

后端代码

namespace HelloWorld
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void _button_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }
    }
}

如上所示,我们在Window窗体中写了一个Button按钮,然后设置了一些属性,我们一一进行分析。

x:Name和Name的区别

第一个设置是x:Name="_button"。首先要解释x:Name是什么意思。在这里的x表示一个命令空间,也就是xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml",Name指的是这个控件的名称。请注意,由于Button继承了FrameworkElement类,而FrameworkElement类也有一个Name属性,但是这里设置的x:Name="_button"并不是引用了FrameworkElement类的Name属性,而是指在xaml中为Button定义了一个叫“_button"的名称,并把这个“_button"映射到了Button的Name属性上,以便于我们在后端可以通过”_button“去引用这个按钮。

也就是说,如果某个控件本身也有一个Name属性,那么前端的x:Name就赋值给控件Name属性。

Content属性

这是ContentControl控件的内容属性,用来设置Button的显示内容,除了是字符串,也可以设置为其它内容,比如一个图标、一个其它元素。

Width属性

Width也不是Button本身的属性,而是FrameworkElement的宽度,由于Button继承了FrameworkElement,所以Width就成了按钮的宽度属性。

Height属性

与上面的Width类似,同属于FrameworkElement的高度属性,在此成了Button的高度属性。

Click事件

Click是一个事件,但不是Button的事件,而是它的基类ButtonBase的事件,事件和委托概念关系密切,因为要订阅一个事件,需要写一个回调函数,而这个回调函数的签名要和这个事件的声明委托签名保持一致。我们来看看Click的委托签名是什么样子的。

public delegate void RoutedEventHandler(object sender, RoutedEventArgs e);

这个委托规定了回调函数的签名,第一,要求回调函数的返回值是void,第二,要求回调函数有两个参数,且参数1是object类型,参数2是RoutedEventArgs类型。于是,我们在后端代码中写了这样一个回调函数。

        private void _button_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

最后在前端,把这个回调函数的名称赋值给Click事件即可完成了在xaml代码中的事件订阅。

Click="_button_Click"

IsDefault属性

这个属性是Button自身的属性,这里设置为true,表示这个button是一个默认按钮,我们按下F5启动程序后,直接按回车键,就相当于用鼠标点击了按钮,最终执行了回调函数里面的代码。即this.Close()语句。

this.Close()表示关闭当前窗体。

通过C#代码订阅事件

我们还可以通过C#代码提供的事件订阅符号+=去订阅事件,接下来,我们将上面的例子简单修改一下,去掉在xaml中的订阅方式,在后端代码的构造函数中订阅事件。

前端代码

<Button Name="_button" 
        Content="退出" 
        Width="100" 
        Height="25" 
        IsDefault="True"/>

后端代码

 /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            _button.Click += _button_Click;
        }

        private void _button_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }
    }

最后F5调试,效果是一模一样的。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:017-《Button按钮》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月23日

因为ToggleButton作为CheckBox(复选框)和RadioButton(单选框)的基类,我们在学习CheckBox和RadioButton之前要先了解一下这个基类。

public class ToggleButton : ButtonBase
{
    public static readonly RoutedEvent CheckedEvent;
    public static readonly RoutedEvent UncheckedEvent;
    public static readonly RoutedEvent IndeterminateEvent;
    public static readonly DependencyProperty IsCheckedProperty;
    public static readonly DependencyProperty IsThreeStateProperty;

    public ToggleButton();

    public bool IsThreeState { get; set; }
    public bool? IsChecked { get; set; }

    public event RoutedEventHandler Checked;
    public event RoutedEventHandler Indeterminate;
    public event RoutedEventHandler Unchecked;

    public override string ToString();
    protected virtual void OnChecked(RoutedEventArgs e);
    protected override void OnClick();
    protected override AutomationPeer OnCreateAutomationPeer();
    protected virtual void OnIndeterminate(RoutedEventArgs e);
    protected virtual void OnUnchecked(RoutedEventArgs e);
    protected internal virtual void OnToggle();
}

ToggleButton基类提供了两个属性和三个事件

IsThreeState属性为true表示控件支持3个状态,IsChecked属性为true表示当前控件已被选中。Checked事件表示选中时引发的事件,Unchecked事件表示从选中状态改为未选状态时引发的事件,Indeterminate事件表示不确定状态时引发的事件

CheckBox继承于ToggleButton,而ToggleButton才继承于ButtonBase基类。

CheckBox控件的结构定义:

public class CheckBox : ToggleButton
{
    public CheckBox();

    protected override void OnAccessKey(AccessKeyEventArgs e);
    protected override AutomationPeer OnCreateAutomationPeer();
    protected override void OnKeyDown(KeyEventArgs e);

}

CheckBox自身没有什么特别内容。一切都使用它的父类提供的属性、方法和事件。我们举例来说明它的用法。

前端代码

<Window x:Class="HelloWorld.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:local="clr-namespace:HelloWorld"
        mc:Ignorable="d"
        Title="HelloWorld - www.wpfsoft.com" Height="350" Width="500">
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        <TextBlock Text="今晚吃什么菜?" Margin="5"/>
        <CheckBox Name="_checkbox1" Content="红烧牛肉" Margin="5"/>
        <CheckBox Name="_checkbox2" Content="麻婆豆腐" Margin="5"/>
        <CheckBox Name="_checkbox3" Content="夫妻肺片" Margin="5"/>
        <Button x:Name="_button" Content="查看菜单"  Click="_button_Click"/>
    </StackPanel>
</Window>

后端代码

namespace HelloWorld
{
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }

        private void _button_Click(object sender, RoutedEventArgs e)
        {
            string order = string.Empty;
            if (_checkbox1.IsChecked == true)
                order += _checkbox1.Content + ",";
            if (_checkbox2.IsChecked == true)
                order += _checkbox2.Content + ",";
            if (_checkbox3.IsChecked == true)
                order += _checkbox3.Content;
            if(!string.IsNullOrEmpty(order))
                MessageBox.Show(order);
        }
    }
}

}

如上图所示,这是前端代码呈现的界面。F5启动后,我们勾选两个选项,然后点击查看菜单,观察结果。

我们通过判断CheckBox的IsChecked属性,来获取前端用户的选择,这通常是CheckBox控件最常用的用法,由于IsChecked是一个依赖属性,它还可以参与绑定,形成MVMM的应用模式,待我们讲到数据绑定章节,还会进一步讲解控件属性的绑定应用。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:018-《CheckBox复选框》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月23日

RadioButton也继承于ToggleButton,作用是单项选择,所以被称为单选框。本质上,它依然是一个按钮,一旦被选中,不会清除,除非它”旁边“的单选框被选中。

public class RadioButton : ToggleButton
{
    public static readonly DependencyProperty GroupNameProperty;

    public RadioButton();

    public string GroupName { get; set; }

    protected override void OnAccessKey(AccessKeyEventArgs e);
    protected override void OnChecked(RoutedEventArgs e);
    protected override AutomationPeer OnCreateAutomationPeer();
    protected internal override void OnToggle();

}

这个控件有一个重要属性叫GroupName——分组名称。默认值是一个空字符串。用来指定哪些RadioButton之间是互相排斥的。

我们将前面的例子简单修改一下代码。

前端代码

<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
    <TextBlock Text="今晚吃什么菜?" Margin="5"/>
    <RadioButton Name="_RadioButton1" Content="红烧牛肉" Margin="5"/>
    <RadioButton Name="_RadioButton2" Content="麻婆豆腐" Margin="5"/>
    <RadioButton Name="_RadioButton3" Content="夫妻肺片" Margin="5"/>
    <Button x:Name="_button" Content="查看菜单"  Click="_button_Click"/>
</StackPanel>

后端代码

namespace HelloWorld
{
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }

        private void _button_Click(object sender, RoutedEventArgs e)
        {
            string order = string.Empty;
            if (_RadioButton1.IsChecked == true)
                order += _RadioButton1.Content + ",";
            if (_RadioButton2.IsChecked == true)
                order += _RadioButton2.Content + ",";
            if (_RadioButton3.IsChecked == true)
                order += _RadioButton3.Content;
            if(!string.IsNullOrEmpty(order))
                MessageBox.Show(order);
        }
    }
}

F5运行之后,我们会发现,无论我们怎么选,始终只有一个RadioButton按钮被选中。

如果我们希望RadioButton按分组进行单项选择,该怎么办呢?

我们可以使用GroupName分组属性,两两一组,让用户始终都只能选择一荤一素两个菜,请看代码。

前端代码

<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
    <TextBlock Text="今晚吃什么菜?" Margin="5"/>
    <RadioButton Name="_RadioButton1" Content="红烧牛肉" GroupName="荤菜" Margin="5"/>
    <RadioButton Name="_RadioButton2" Content="糖醋排骨" GroupName="荤菜" Margin="5"/>
    <RadioButton Name="_RadioButton3" Content="麻婆豆腐" GroupName="素菜" Margin="5"/>
    <RadioButton Name="_RadioButton4" Content="清炒时蔬" GroupName="素菜" Margin="5"/>
    <Button x:Name="_button" Content="查看菜单"  Click="_button_Click"/>
</StackPanel>

后端代码

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

private void _button_Click(object sender, RoutedEventArgs e)
{
    string order = string.Empty;
    if (_RadioButton1.IsChecked == true)
        order += _RadioButton1.Content + ",";
    if (_RadioButton2.IsChecked == true)
        order += _RadioButton2.Content + ",";
    if (_RadioButton3.IsChecked == true)
        order += _RadioButton3.Content + ",";
    if (_RadioButton4.IsChecked == true)
        order += _RadioButton4.Content;
    if (!string.IsNullOrEmpty(order))
        MessageBox.Show(order);
}

此时再操作时我们发现,红烧牛肉和糖醋排骨只能二选一,麻婆豆腐和清炒时蔬也只能二选一。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:019-《RadioButton单选框》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月23日

RepeatButton,顾名思义,重复执行的按钮。就是当按钮被按下时,所订阅的回调函数会不断被执行。那么,多长时间执行一次?

我们先看看它的结构定义:

public class RepeatButton : ButtonBase
{
    public static readonly DependencyProperty DelayProperty;
    public static readonly DependencyProperty IntervalProperty;

    public RepeatButton();

    public int Delay { get; set; }
    public int Interval { get; set; }

    protected override void OnClick();
    protected override AutomationPeer OnCreateAutomationPeer();
    protected override void OnKeyDown(KeyEventArgs e);
    protected override void OnKeyUp(KeyEventArgs e);
    protected override void OnLostMouseCapture(MouseEventArgs e);
    protected override void OnMouseEnter(MouseEventArgs e);
    protected override void OnMouseLeave(MouseEventArgs e);
    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e);
    protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e);

}

一、属性分析

RepeatButton 自身提供了两个整型属性,分别是Delay 和Interval 。

Delay 属性:表示延时重复执行的毫秒数,就是说,RepeatButton被按下后会立即执行一次回调函数,如果您不松开鼠标,在等待Delay 毫秒后,就开始进行重复执行阶段。

Interval 属性:表示重复执行回调函数的时间间隔毫秒数。

接下来,我们观察下面的代码的运行结果。

二、RepeatButton 应用示例

<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        <TextBlock Text="牙膏已在手" Margin="5 7 5 5"/>
        <RepeatButton Name="_Button1" Content="开始挤牙膏" Delay="1000" Interval="500" Click="_Button1_Click"  Margin="5"/>
    </StackPanel>
namespace HelloWorld
{
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
        int count = 1;
        private void _Button1_Click(object sender, RoutedEventArgs e)
        {
            Console.WriteLine($"重复时间:{DateTime.Now.ToLongTimeString()} {DateTime.Now.Millisecond},重复次数:{count++}");
        }
    }
}

我们以生活中挤牙膏为例,当开始挤的动作发生后,我们会持续一小会儿,然后才松开,此时牙膏停止往外溢出。观察下面的输出结果:

重复时间:14:37:28 476,重复次数:1
重复时间:14:37:29 548,重复次数:2
重复时间:14:37:30 51,重复次数:3
重复时间:14:37:30 549,重复次数:4
重复时间:14:37:31 89,重复次数:5
重复时间:14:37:31 598,重复次数:6
重复时间:14:37:32 185,重复次数:7
重复时间:14:37:32 718,重复次数:8

结果显示,第一次和第二次输出时间刚好为1000毫秒,也就是Delay属性在起作用。然后,从第2次开始,每两次之间的时间间隔大约为500毫秒,这是因为Interval属性被设置为500。相信通过这个例子,您已明白这个按钮的用法。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:020-《RepeatButton重复按钮》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月23日

Label控件继承于ContentControl控件,它是一个文本标签,如果您想修改它的标签内容,请设置Content属性。我们曾提过ContentControl的Content属性是object类型,意味着Label的Content也是可以设置为任意的引用类型的。

我们来举个例子。

<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
    <Label Content="这是一个Label标签"/>
    <Label>
        <Label.Content>
            <Button Content="确定" Click="_Button1_Click"/>
        </Label.Content>
    </Label>
</StackPanel>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

private void _Button1_Click(object sender, RoutedEventArgs e)
{
    this.Close();
}

我们给第二个标签的Content属性设置了一个按钮,并对按钮的Click事件做了订阅回调,F5运行,事实证明,此时的Button是可以正常使用 。只不过,通常情况下,我们的Label只是用来显示一段文字,很少在Contnet里面编写其它控件代码。如果要编写其它控件代码以实现更复杂的自定义控件效果,我们建议使用UserControl用户控件。

对于文本的显示,除了可以在Label中显示,我们还有一个控件也可以实现,那就是TextBlock文字块。而且,TextBlock控件直接从FrameworkElement基类继承而来,效率比Label标签更高哦。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:021-《Label标签》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年8月23日

TextBlock是专业处理文本显示的控件,在功能上比Label更全面。先看结构定义,后举例说明。

public class TextBlock : FrameworkElement, IContentHost, IAddChildInternal, IAddChild, IServiceProvider
{
    public static readonly DependencyProperty BaselineOffsetProperty;
    public static readonly DependencyProperty IsHyphenationEnabledProperty;
    public static readonly DependencyProperty TextWrappingProperty;
    public static readonly DependencyProperty TextAlignmentProperty;
    public static readonly DependencyProperty PaddingProperty;
    public static readonly DependencyProperty LineStackingStrategyProperty;
    public static readonly DependencyProperty LineHeightProperty;
    public static readonly DependencyProperty TextEffectsProperty;
    public static readonly DependencyProperty TextDecorationsProperty;
    public static readonly DependencyProperty TextTrimmingProperty;
    public static readonly DependencyProperty ForegroundProperty;
    public static readonly DependencyProperty FontSizeProperty;
    public static readonly DependencyProperty FontStretchProperty;
    public static readonly DependencyProperty FontWeightProperty;
    public static readonly DependencyProperty FontStyleProperty;
    public static readonly DependencyProperty FontFamilyProperty;
    public static readonly DependencyProperty TextProperty;
    public static readonly DependencyProperty BackgroundProperty;

    public TextBlock();
    public TextBlock(Inline inline);

    public FontWeight FontWeight { get; set; }
    public FontStyle FontStyle { get; set; }
    public FontFamily FontFamily { get; set; }
    public string Text { get; set; }
    public TextPointer ContentEnd { get; }
    public Typography Typography { get; }
    public LineBreakCondition BreakAfter { get; }
    public LineBreakCondition BreakBefore { get; }
    public FontStretch FontStretch { get; set; }
    public double BaselineOffset { get; set; }
    public double FontSize { get; set; }
    public TextWrapping TextWrapping { get; set; }
    public Brush Background { get; set; }
    public TextDecorationCollection TextDecorations { get; set; }
    public TextEffectCollection TextEffects { get; set; }
    public double LineHeight { get; set; }
    public LineStackingStrategy LineStackingStrategy { get; set; }
    public Thickness Padding { get; set; }
    public TextAlignment TextAlignment { get; set; }
    public TextTrimming TextTrimming { get; set; }
    public TextPointer ContentStart { get; }
    public bool IsHyphenationEnabled { get; set; }
    public Brush Foreground { get; set; }
    public InlineCollection Inlines { get; }
    protected virtual IEnumerator<IInputElement> HostedElementsCore { get; }
    protected override int VisualChildrenCount { get; }
    protected internal override IEnumerator LogicalChildren { get; }

    public static double GetBaselineOffset(DependencyObject element);
    public static FontFamily GetFontFamily(DependencyObject element);
    public static double GetFontSize(DependencyObject element);
    public static FontStretch GetFontStretch(DependencyObject element);
    public static FontStyle GetFontStyle(DependencyObject element);
    public static FontWeight GetFontWeight(DependencyObject element);
    public static Brush GetForeground(DependencyObject element);
    public static double GetLineHeight(DependencyObject element);
    public static LineStackingStrategy GetLineStackingStrategy(DependencyObject element);
    public static TextAlignment GetTextAlignment(DependencyObject element);
    public static void SetBaselineOffset(DependencyObject element, double value);
    public static void SetFontFamily(DependencyObject element, FontFamily value);
    public static void SetFontSize(DependencyObject element, double value);
    public static void SetFontStretch(DependencyObject element, FontStretch value);
    public static void SetFontStyle(DependencyObject element, FontStyle value);
    public static void SetFontWeight(DependencyObject element, FontWeight value);
    public static void SetForeground(DependencyObject element, Brush value);
    public static void SetLineHeight(DependencyObject element, double value);
    public static void SetLineStackingStrategy(DependencyObject element, LineStackingStrategy value);
    public static void SetTextAlignment(DependencyObject element, TextAlignment value);
    public TextPointer GetPositionFromPoint(Point point, bool snapToText);
    public bool ShouldSerializeBaselineOffset();
    public bool ShouldSerializeInlines(XamlDesignerSerializationManager manager);
    public bool ShouldSerializeText();
    protected sealed override Size ArrangeOverride(Size arrangeSize);
    protected virtual ReadOnlyCollection<Rect> GetRectanglesCore(ContentElement child);
    protected override Visual GetVisualChild(int index);
    protected sealed override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters);
    protected virtual IInputElement InputHitTestCore(Point point);
    protected sealed override Size MeasureOverride(Size constraint);
    protected virtual void OnChildDesiredSizeChangedCore(UIElement child);
    protected override AutomationPeer OnCreateAutomationPeer();
    protected sealed override void OnPropertyChanged(DependencyPropertyChangedEventArgs e);
    protected sealed override void OnRender(DrawingContext ctx);

}

TextBlock提供了非常丰富的文本相关的属性。

属性说明
FontWeight获取或设置TextBlock的字体粗细
FontStyle获取或设置TextBlock的字体样式,如斜体字体
FontFamily获取或设置TextBlock的字体系列,如微软雅黑
Text获取或设置TextBlock的字体内容。
ContentEnd表示获取TextBlock内容的最末尾的TextPointer对象
Typography获取此元素的内容当前有效的版式变体。
FontStretch获取或设置 TextBlock 的常用字体拉伸特征。
BaselineOffset获取或设置文本的每个行相对于基线的偏移量。
FontSize获取或设置TextBlock的字号
TextWrapping获取或设置TextBlock的文字的换行方式
Background获取或设置TextBlock控件的背景颜色(画刷)
TextEffects获取或设置要应用于此元素中的文本内容的效果。
LineHeight获取或设置各行内容的高度。
Padding指示内容区域的边界之间填充空间的宽度
TextAlignment指示文本内容的水平对齐方式。
TextTrimming获取或设置在内容超出内容区域时要采用的文本剪裁行为。
Foreground获取或设置文本内容的字体颜色(画刷)
Inlines这个属性是一个集合,其中的元素表示内联流内容元素,简单点说,一行文本可以看成是一个Inline元素,而TextBlock可以接受多个Inline。Run继承于Inline,实际使用中,我们会创建多个Run实例,可以单独为每个Run对象设置字体字号颜色等等。
ContentStart表示获取TextBlock内容的最开始的TextPointer对象

接下来, 我们将上面常用的属性在例子中得以体现。

<Window x:Class="HelloWorld.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:local="clr-namespace:HelloWorld"
        mc:Ignorable="d" FontSize="16"
        Title="HelloWorld - www.wpfsoft.com" Height="350" Width="500">
    <WrapPanel>
        <TextBlock Text="这是一个TextBlock文字块" Margin="5"/>
        <TextBlock Text="粗体文字" FontWeight="Bold" Margin="5"/>
        <TextBlock Text="粗体文字" FontWeight="Light" Margin="5"/>
        <TextBlock Text="斜体文字" FontStyle="Italic"  Margin="5"/>
        <TextBlock Text="微软雅黑" FontFamily="Microsoft YaHei UI"  Margin="5"/>
        <TextBlock Text="大号字体" FontSize="30"  Margin="5"/>
        <TextBlock Text="红色文字" Foreground="Red" Margin="5"/>
        <TextBlock Text="底色文字" Foreground="Yellow" Background="Red" Margin="5"/>
        <TextBlock Text="内间距文字" Foreground="Yellow" Background="Red" Padding="10" Margin="5"/>
        <TextBlock Background="LightGray" Height="25">
            <Run Foreground="Red">这行文字</Run>
            <Run Foreground="Green">由三部分</Run>
            <Run Foreground="Blue">组成</Run>
        </TextBlock>
        <Grid Width="150" Height="100" Margin="5" Background="LightGoldenrodYellow">
            <TextBlock Text="这段文字体现了文字的文本换行属性TextWrapping" TextWrapping="Wrap" Margin="10"/>
        </Grid>
        <!--使用Run-->
        <Grid>
            <TextBlock x:Name="textblock"  
                       Width="320" 
                       Height="100" 
                       FontSize="15" 
                       FontFamily="微软雅黑" 
                       FontWeight="Black" 
                       FontStretch="Condensed" 
                       Foreground="#dddddd" 
                       Background="Teal" 
                       TextAlignment="Center" 
                       TextWrapping="Wrap" 
                       TextTrimming="CharacterEllipsis" 
                       Margin="10" Padding="5"
                       HorizontalAlignment="Left" 
                       VerticalAlignment="Center" 
                       LineHeight="30" 
                       ToolTip="《临江仙·滚滚长江东逝水》">
		<Run Foreground="#CDB632" TextDecorations="Underline">
			滚滚长江东逝水,浪花淘尽英雄。是非成败转头空。青山依旧在,几度夕阳红。
		</Run>
		<Run Text="白发渔樵江渚上,惯看秋月春风。一壶浊酒喜相逢。古今多少事,都付笑谈中。 ">
		</Run>
            </TextBlock>
        </Grid>
    </WrapPanel>
</Window>

TextBlock大多数的属性应用都比较简单,容易理解。Inlines属性是一个比较强大的属性,深入理解后,可以实现意想不到的效果。另外,TextEffects也是一个非常强大的属性,这需要掌握WPF的动画、触发器、关键帧等知识,才能实现文本的动画特效。我们将在学完动画后,再回头探讨这些内容。

与文本相关的还有两个输入控件,即TextBox和PasswordBox。下一节,我们来探讨TextBox。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:022-《TextBlock文字块》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

copyright @重庆教主 WPF中文网 联系站长:(QQ)23611316 (微信)movieclip (QQ群).NET小白课堂:864486030 | 本文由WPF中文网原创发布,谢绝转载 渝ICP备2023009518号-1