WPF中文网

Shape概述

形状是WPF另一大系列控件。WPF所有的形状都继承于Shape基类。那么,WPF提供了哪些可用的形状呢?我们用一张图来说明它的子类。

形状名称说明
Ellipse椭圆形
Line在两个点之间绘制直线。
Rectangle绘制矩形。
Polyline绘制一系列相互连接的直线。
Polygon绘制多边形,它是由一系列相互连接的线条构成的闭合形状。
Path绘制一系列相互连接的直线和曲线。

Shape是一个抽象基类,它不能被实例化,所以我们在使用时只能实例化它的子类。而Shape的父类是FrameworkElement,所以,所有的Shape子类都是一个UIElement 类,因此形状对象可以用在面板和大多数控件中。 由于 Canvas 面板支持其子对象的绝对位置,因此特别适合创建复杂的图形。

我们来看看Shape的定义

一、Shape的定义

public abstract class Shape : FrameworkElement
{
    public static readonly DependencyProperty StretchProperty;
    public static readonly DependencyProperty StrokeDashArrayProperty;
    public static readonly DependencyProperty StrokeDashOffsetProperty;
    public static readonly DependencyProperty StrokeLineJoinProperty;
    public static readonly DependencyProperty StrokeDashCapProperty;
    public static readonly DependencyProperty StrokeMiterLimitProperty;
    public static readonly DependencyProperty StrokeStartLineCapProperty;
    public static readonly DependencyProperty StrokeThicknessProperty;
    public static readonly DependencyProperty StrokeProperty;
    public static readonly DependencyProperty FillProperty;
    public static readonly DependencyProperty StrokeEndLineCapProperty;

    protected Shape();

    public Brush Stroke { get; set; }
    public PenLineCap StrokeEndLineCap { get; set; }
    public PenLineCap StrokeStartLineCap { get; set; }
    public double StrokeThickness { get; set; }
    public Brush Fill { get; set; }
    public double StrokeDashOffset { get; set; }
    public virtual Geometry RenderedGeometry { get; }
    public Stretch Stretch { get; set; }
    public DoubleCollection StrokeDashArray { get; set; }
    public double StrokeMiterLimit { get; set; }
    public PenLineCap StrokeDashCap { get; set; }
    public virtual Transform GeometryTransform { get; }
    public PenLineJoin StrokeLineJoin { get; set; }
    protected abstract Geometry DefiningGeometry { get; }

    protected override Size ArrangeOverride(Size finalSize);
    protected override Size MeasureOverride(Size constraint);
    protected override void OnRender(DrawingContext drawingContext);

}

Shape基类提供了许多公共属性,如下表所示。

二、属性成员

属性名称说明
Stroke获取或设置Shape的边框颜色画刷
StrokeEndLineCap获取或设置Shape描述线的末端的样式
StrokeStartLineCap获取或设置Shape描述线的开头的样式
StrokeThickness获取或设置Shape边框的厚度
Fill获取或设置Shape的内部填充颜色
StrokeDashOffset获取或设置短划线模式内短划线开始处的距离
RenderedGeometry获取或设置Shape的几何
Stretch获取或设置Shape的填充模式
StrokeDashArray获取或设置勾勒形状轮廓的短划线和间隙的模式的值
StrokeMiterLimit获取或设置一个限制到一半的斜接长度比
StrokeDashCap获取或设置 System.Windows.Media.PenLineCap 枚举值,该值指定如何绘制虚线的末端。
GeometryTransform获取或设置Shape的转换
StrokeLineJoin获取或设置Shape的顶点处使用的联接类型。
DefiningGeometry获取Shape的Geometry

Selector继承于ItemsControl,但它是一个抽象类,所以不能被实例化。从命名上看,它是一个选择器。

一、Selector类定义

public abstract class Selector : ItemsControl
{
    public static readonly RoutedEvent SelectionChangedEvent;
    public static readonly RoutedEvent SelectedEvent;
    public static readonly RoutedEvent UnselectedEvent;
    public static readonly DependencyProperty IsSelectionActiveProperty;
    public static readonly DependencyProperty IsSelectedProperty;
    public static readonly DependencyProperty IsSynchronizedWithCurrentItemProperty;
    public static readonly DependencyProperty SelectedIndexProperty;
    public static readonly DependencyProperty SelectedItemProperty;
    public static readonly DependencyProperty SelectedValueProperty;
    public static readonly DependencyProperty SelectedValuePathProperty;

    protected Selector();

    public object SelectedValue { get; set; }
    public object SelectedItem { get; set; }
    public int SelectedIndex { get; set; }
    public bool? IsSynchronizedWithCurrentItem { get; set; }
    public string SelectedValuePath { get; set; }

    public event SelectionChangedEventHandler SelectionChanged;

    public static void AddSelectedHandler(DependencyObject element, RoutedEventHandler handler);
    public static void AddUnselectedHandler(DependencyObject element, RoutedEventHandler handler);
    public static bool GetIsSelected(DependencyObject element);
    public static bool GetIsSelectionActive(DependencyObject element);
    public static void RemoveSelectedHandler(DependencyObject element, RoutedEventHandler handler);
    public static void RemoveUnselectedHandler(DependencyObject element, RoutedEventHandler handler);
    public static void SetIsSelected(DependencyObject element, bool isSelected);
    protected override void ClearContainerForItemOverride(DependencyObject element, object item);
    protected override void OnInitialized(EventArgs e);
    protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e);
    protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e);
    protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue);
    protected virtual void OnSelectionChanged(SelectionChangedEventArgs e);
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item);

}

接下来,我们来看看它提供了哪些可用的属性。

二、Selector类的属性

属性名称说明
SelectedValue获取或设置SelectedValuePath属性指定的元素的属性值
SelectedItem获取或设置当前所选内容中的第一项或如果所选内容为空则返回 null
SelectedIndex获取或设置当前所选内容或返回的第一项的索引为负一 (-1) 如果所选内容为空。
SelectedValuePath获取或设置SelectedItem当前元素的某个属性名,这个元素属性名将决定SelectedValue的值
IsSynchronizedWithCurrentItem是否同步当前项。

SelectedItem和SelectedValue有点类似,都是object类型。但是,他们俩不一定指同一个内容。比如,我们将有这样一个数据实体类。

    public class Person
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public int Age { get; set; }
    }

然后我们实例化多个Person组成一个集合绑定到Items属性中,这个时候选中某一个元素,SelectedItem便等于这个Person元素,但是SelectedValue是什么,要看SelectedValuePath的值,如果SelectedValuePath的值指向的是Person.Name,那么SelectedValue就是一个字符串,如果SelectedValuePath指向的是Person的Age ,那么SelectedValue就是一个int整数,只有不设置SelectedValuePath时,SelectedValue和SelectedItem两者才相等,即Person实例

具体关于这一个重点,我们在下一节讨论ListBox控件时进行演示和讲解。

重庆教主说:

SelectedItem、SelectedValue、SelectedValuePath非常重要,一定要理解透彻哦!另外,还有一个属性叫DisplayMemberPath,它在ItemsControl基类中,意思是设置要显示的属性名,而SelectedValuePath是设置要选择的属性名。

——重庆教主 2023年9月1日

因为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事件表示不确定状态时引发的事件

按钮,几乎每个具有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日

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日

FrameworkElement类继承于UIElement类,继承关系是:Object->DispatcherObject->DependencyObject->Visual->UIElement->FrameworkElement,它也是WPF控件的众多父类中最核心的基类,从这里开始,继承树开始分支,分别是Shape图形类、Control控件类和Panel布局类三个方向。

FrameworkElement类本质上也是提供了一系列属性、方法和事件。同时又扩展 UIElement 并添加了以下功能:

官方文档

1.布局系统定义: FrameworkElement 为中 UIElement定义为虚拟成员的某些方法提供特定的 WPF 框架级实现。 最值得注意的是, FrameworkElement 会密封某些 WPF 核心级布局替代,并改为提供派生类应替代的 WPF 框架级别的等效项。 例如,密封但 FrameworkElementArrangeCore 提供 ArrangeOverride。 这些更改反映了这样一个事实,即在 WPF 框架级别,有一个可以呈现任何 FrameworkElement 派生类的完整布局系统。 在 WPF 核心级别,将构建基于 WPF 的常规布局解决方案的某些成员已就位,但未定义布局系统的实际引擎。
2.逻辑树: 常规 WPF 编程模型通常以元素树的方式表示。 支持将元素树表示为逻辑树,以及支持在标记中定义该树的支持是在 级别实现的 FrameworkElement 。 但请注意, FrameworkElement 故意不定义内容模型,并将该责任留给派生类。
3.对象生存期事件: 了解何时初始化元素 (调用构造函数) 或首次将元素加载到逻辑树中时,这通常很有用。 FrameworkElement 定义多个与对象生存期相关的事件,这些事件为涉及元素的代码隐藏操作(例如添加更多子元素)提供有用的挂钩。
4.支持数据绑定和动态资源引用: 对数据绑定和资源的属性级支持由 DependencyProperty 类实现,并体现在属性系统中,但解析存储为 Expression (数据绑定和动态资源的编程构造) 中存储的成员值的能力由 FrameworkElement实现。
5.风格:FrameworkElement 定义 Style 属性。 但是, FrameworkElement 尚未定义对模板的支持或支持修饰器。 这些功能由控件类(如 和 ContentControl)Control引入。
6.更多动画支持: 某些动画支持已在 WPF 核心级别定义,但 FrameworkElement 通过实现 BeginStoryboard 和相关成员扩展了此支持。

我们来看看这个基类的结构定义:

public class FrameworkElement : UIElement, IFrameworkInputElement, IInputElement, ISupportInitialize, IHaveResources, IQueryAmbient
{
    public static readonly DependencyProperty StyleProperty;
    public static readonly DependencyProperty MaxHeightProperty;
    public static readonly DependencyProperty FlowDirectionProperty;
    public static readonly DependencyProperty MarginProperty;
    public static readonly DependencyProperty HorizontalAlignmentProperty;
    public static readonly DependencyProperty VerticalAlignmentProperty;
    public static readonly DependencyProperty FocusVisualStyleProperty;
    public static readonly DependencyProperty CursorProperty;
    public static readonly DependencyProperty ForceCursorProperty;
    public static readonly RoutedEvent UnloadedEvent;
    public static readonly DependencyProperty ToolTipProperty;
    public static readonly DependencyProperty ContextMenuProperty;
    public static readonly RoutedEvent ToolTipOpeningEvent;
    public static readonly RoutedEvent ToolTipClosingEvent;
    public static readonly RoutedEvent ContextMenuOpeningEvent;
    public static readonly RoutedEvent ContextMenuClosingEvent;
    public static readonly DependencyProperty MinHeightProperty;
    public static readonly DependencyProperty HeightProperty;
    public static readonly RoutedEvent LoadedEvent;
    public static readonly DependencyProperty MinWidthProperty;
    public static readonly DependencyProperty MaxWidthProperty;
    public static readonly DependencyProperty OverridesDefaultStyleProperty;
    public static readonly DependencyProperty UseLayoutRoundingProperty;
    public static readonly DependencyProperty BindingGroupProperty;
    public static readonly DependencyProperty LanguageProperty;
    public static readonly DependencyProperty NameProperty;
    public static readonly DependencyProperty TagProperty;
    public static readonly DependencyProperty DataContextProperty;
    public static readonly RoutedEvent RequestBringIntoViewEvent;
    public static readonly RoutedEvent SizeChangedEvent;
    public static readonly DependencyProperty ActualWidthProperty;
    public static readonly DependencyProperty ActualHeightProperty;
    public static readonly DependencyProperty LayoutTransformProperty;
    public static readonly DependencyProperty InputScopeProperty;
    public static readonly DependencyProperty WidthProperty;
    protected internal static readonly DependencyProperty DefaultStyleKeyProperty;

    public FrameworkElement();

    public Transform LayoutTransform { get; set; }
    public double Width { get; set; }
    public double MinWidth { get; set; }
    public double MaxHeight { get; set; }
    public double Height { get; set; }
    public double MinHeight { get; set; }
    public double ActualHeight { get; }
    public double MaxWidth { get; set; }
    public double ActualWidth { get; }
    public TriggerCollection Triggers { get; }
    public object Tag { get; set; }
    public string Name { get; set; }
    public XmlLanguage Language { get; set; }
    public BindingGroup BindingGroup { get; set; }
    public object DataContext { get; set; }
    public ResourceDictionary Resources { get; set; }
    public DependencyObject TemplatedParent { get; }
    public bool UseLayoutRounding { get; set; }
    public FlowDirection FlowDirection { get; set; }
    public InputScope InputScope { get; set; }
    public Thickness Margin { get; set; }
    public Style Style { get; set; }
    public VerticalAlignment VerticalAlignment { get; set; }
    public bool OverridesDefaultStyle { get; set; }
    public HorizontalAlignment HorizontalAlignment { get; set; }
    public ContextMenu ContextMenu { get; set; }
    public object ToolTip { get; set; }
    public DependencyObject Parent { get; }
    public bool IsInitialized { get; }
    public bool ForceCursor { get; set; }
    public Cursor Cursor { get; set; }
    public Style FocusVisualStyle { get; set; }
    public bool IsLoaded { get; }
    protected override int VisualChildrenCount { get; }
    protected internal InheritanceBehavior InheritanceBehavior { get; set; }
    protected internal virtual IEnumerator LogicalChildren { get; }
    protected internal object DefaultStyleKey { get; set; }

    public event ToolTipEventHandler ToolTipClosing;
    public event ToolTipEventHandler ToolTipOpening;
    public event RoutedEventHandler Unloaded;
    public event DependencyPropertyChangedEventHandler DataContextChanged;
    public event SizeChangedEventHandler SizeChanged;
    public event RequestBringIntoViewEventHandler RequestBringIntoView;
    public event EventHandler<DataTransferEventArgs> SourceUpdated;
    public event EventHandler<DataTransferEventArgs> TargetUpdated;
    public event RoutedEventHandler Loaded;
    public event EventHandler Initialized;
    public event ContextMenuEventHandler ContextMenuClosing;
    public event ContextMenuEventHandler ContextMenuOpening;

    public static FlowDirection GetFlowDirection(DependencyObject element);
    public static void SetFlowDirection(DependencyObject element, FlowDirection value);
    public bool ApplyTemplate();
    public virtual void BeginInit();
    public void BeginStoryboard(Storyboard storyboard, HandoffBehavior handoffBehavior, bool isControllable);
    public void BeginStoryboard(Storyboard storyboard);
    public void BeginStoryboard(Storyboard storyboard, HandoffBehavior handoffBehavior);
    public void BringIntoView();
    public void BringIntoView(Rect targetRectangle);
    public virtual void EndInit();
    public object FindName(string name);
    public object FindResource(object resourceKey);
    public BindingExpression GetBindingExpression(DependencyProperty dp);
    public sealed override bool MoveFocus(TraversalRequest request);
    public virtual void OnApplyTemplate();
    public sealed override DependencyObject PredictFocus(FocusNavigationDirection direction);
    public void RegisterName(string name, object scopedElement);
    public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding);
    public BindingExpression SetBinding(DependencyProperty dp, string path);
    public void SetResourceReference(DependencyProperty dp, object name);
    public bool ShouldSerializeResources();
    public bool ShouldSerializeStyle();
    public bool ShouldSerializeTriggers();
    public object TryFindResource(object resourceKey);
    public void UnregisterName(string name);
    public void UpdateDefaultStyle();
    protected sealed override void ArrangeCore(Rect finalRect);
    protected virtual Size ArrangeOverride(Size finalSize);
    protected override Geometry GetLayoutClip(Size layoutSlotSize);
    protected override Visual GetVisualChild(int index);
    protected sealed override Size MeasureCore(Size availableSize);
    protected virtual Size MeasureOverride(Size availableSize);
    protected virtual void OnContextMenuClosing(ContextMenuEventArgs e);
    protected virtual void OnContextMenuOpening(ContextMenuEventArgs e);
    protected override void OnGotFocus(RoutedEventArgs e);
    protected virtual void OnInitialized(EventArgs e);
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e);
    protected virtual void OnToolTipClosing(ToolTipEventArgs e);
    protected virtual void OnToolTipOpening(ToolTipEventArgs e);
    protected internal void AddLogicalChild(object child);
    protected internal DependencyObject GetTemplateChild(string childName);
    protected internal override DependencyObject GetUIParentCore();
    protected internal override void OnRenderSizeChanged(SizeChangedInfo sizeInfo);
    protected internal virtual void OnStyleChanged(Style oldStyle, Style newStyle);
    protected internal override void OnVisualParentChanged(DependencyObject oldParent);
    protected internal virtual void ParentLayoutInvalidated(UIElement child);
    protected internal void RemoveLogicalChild(object child);

}

属性分析

1.LayoutTransform 属性:获取或设置在执行布局时应应用于此元素的图形转换。这个属性与UIElement类中的RenderTransform属性有相似之处,所以我们在此将两者进行对比说明一下。两个属性都是Transform类型,而Transform是一个抽象类,这个类可以实现控件在平面中的各种转换,包括

  • 旋转 (System.Windows.Media.RotateTransform)
  • 缩放 (System.Windows.Media.ScaleTransform)、
  • 倾斜 (System.Windows.Media.SkewTransform) 、
  • 平移 (System.Windows.Media.TranslateTransform)。

虽然两个属性都可以达到控件的变换效果,但是两者还是有区别的。LayoutTransform属性是在控件布局之前对控件进行变换,而RenderTransform属性是在布局结束后执行控件的变换,LayoutTransform开销比RenderTransform要大,所以,尽量使用RenderTransform属性去实现控件的变换。(我们会在后面专门探讨控件的变换)

2.Width属性:这是表示控件的宽度。与之相关的还有以下几个属性。

  • ActualWidth:获取此元素的呈现宽度。只读属性。
  • MaxWidth:获取或设置一个控件的最大宽度。
  • MinWidth:获取或设置一个控件的最小宽度。

3.Height属性:这是表示控件的高度,与之相关的还有以下几个属性。

  • ActualHeight:获取此元素的呈现高度。只读属性。
  • MaxHeight:获取或设置一个控件的最大高度。
  • MinHeight:获取或设置一个控件的最小高度。

4.Tag属性:这个属性非常重要,它是object类型,意味着可以保存任意类型的对象值。它就像FrameworkElement类身上的一个小口袋,但确能容纳万物。我们通常会将一些与控件相关的数据临时存放在Tag属性中,当把控件作为参数传递时,小口袋里面的对象也随之传递过去了。

5.Name属性:获取或设置控件的标识名称。在同一个窗体、页、用户控件中,Name标识是唯一的。设置了控件的名称后,我们就可以在后端代码直接以这个标识去引用控件。

6.Margin属性:获取或设置控件的外边距。如下所示,我们定义了一个button的margin,距离左边、上边、右边和下边的像素分别是20、40、60、80。

 <Grid>
     <Button Content="WPF中文网" Margin="20 40 60 80" />
</Grid>

Padding属性说明

与Margin相对应的是Padding,表示设置控件的内边距。但是这个属性并不在FrameworkElement中,而在Control类中,从本节第一张图所示,说明只有内容控件才具有Padding,而Shape和Panel是没有Padding属性的。

7.HorizontalAlignment属性:设置控件的水平对齐方式。这个对齐方式是相对于父元素而言的,比如我们有一个Button控件,在外面还包裹了一层Grid控件,那么,设置Button控件的HorizontalAlignment属性,可以将Button控件分别显示在Grid控件的左边、中间、右边三个位置。

8.VerticalAlignment属性:设置控件的垂直对齐方式。与HorizontalAlignment属性类似,只是对方的方向不同,可以设置控件在垂直方向上是居于顶部、中间、还是底部三个位置。

总结:上述两个属性的值都是枚举型,它们都有一个共同的值,那就是stretch,表示是拉伸的方式填充父元素的布局。

9.ToolTip属性:获取或设置用户界面 (UI) 中为此元素显示的工具提示对象。指鼠标移到控件上方时显示的提示内容,它是一个object类型,意味着可以显示任意布局外观。

10.Parent属性:获取此元素的逻辑父元素。它是一个只读属性。

接下来,我们将介绍几个比较重要的属性,这些属性是WPF框架中非常核心的知识概念,需要单独形成章节来学习,在这里,我们只是通过这些属性来引出其概念。

WPF样式(Style)

什么是样式?简单来说,是指控件呈现时的样子。比如我们上班时会穿上工作服,休假时会穿上更个性化的衣服,我还是那个我,但是身上的衣服却不同,不管是颜色、款式,甚至包括我们的头饰、妆容,都会有所不同。

对于控件而言,同样都是button按钮,有的按钮是方的,有的是圆的,有的是蓝色,有的是红色,有的有文字,有的有图标,如果做到这些不同的样式呢?答案是Style属性。

11.Style属性:获取或设置此元素呈现时所使用的样式。(关于Style样式我们会专门拿一章节来探讨)

与Style相关的还有一个属性,叫FocusVisualStyle,顾名思义,控件在获得焦点时的样式。

WPF资源(ResourceDictionary)

什么是资源?资源,也就是资源字典,也就是ResourceDictionary类,它提供一个哈希表/字典实现,其中包含组件所使用的 WPF 资源以及 WPF 应用程序的其他元素。我们可以把WPF的控件、窗体、Application应用所用到的一切资源都放在其中,将多个ResourceDictionary元素合并起来形成一个ResourceDictionary元素(ResourceDictionary也是一个隐式集合)。所以FrameworkElement类设计一个资源属性。

12.Resources 属性:获取或设置本地定义的资源字典。关于Resources资源我们会专门拿一章节来探讨)

WPF的数据上下文(DataContext)

我们曾经在前面的《DependencyObject类》一文中提到过数据驱动模式,控件的值绑定某个“变量”,当“变量”的值发生改变,控件的值也跟着改变,反过来说,当控件的值发生改变,“变量”的值也跟着改变。那么这个特指的“变量”是什么?它和我们今天要介绍的数据上下文有什么关系?

答案是,这个“变量”其实也是一个属性,且必须是一个属性(重点),它是谁的属性?WPF说,它是某个ViewModel类的属性。

假定我们有一个View窗体,窗体有一个TextBox控件;又假如我们还有一个ViewModel实体,这个实体中有一个叫Name的属性。如果我们要将TextBox控件的Text属性和ViewModel实体的Name属性成功的建立绑定关系,必备的条件是什么?

首先,由于View窗体继承于FrameworkElement类,所以每个窗体(或控件)都有一个叫DataContext的数据上下文属性。所以必备的条件是:ViewModel实体必须先赋值给View窗体的DataContext,ViewModel的Name属性才能绑定到TextBox控件的Text属性。换言之,领导之间要先搭好桥,下属和下属才好配合工作。这就是DataContext的概念和用途。(关于DataContext数据上下文我们会专门拿一章节来探讨)

13.DataContext属性:获取或设置元素参与数据绑定时的数据上下文。

14.ContextMenu属性:设置与获取控件的上下文菜单 ,就是鼠标在控件上右键时弹出来的菜单。

15.Cursor属性:获取或设置在鼠标指针位于此元素上时显示的光标。

友情提示

上述所介绍的属性,是WPF中所有控件都有的属性哦,所以,学一个FrameworkElement类,就把所有控件都学了30%呢。——重庆教主

事件分析

FrameworkElement类提供了12个事件,一般比较常用的是:Initialized、Loaded、Unloaded、SizeChanged等事件。

方法成员

FrameworkElement类还提供了一些方法成员。

1.FindName(String):表示查找某个元素。比如我们在窗体中要查找某个控件。

2.FindResource(Object):查找某个资源。如果在调用对象上找不到该资源,则接下来搜索逻辑树中的父元素,然后搜索应用程序、主题,最后搜索系统资源。实在找不到就抛出异常。

3.TryFindResource(Object):尝试去找某个资源。建议使用这个方法。

4.RegisterName (string , object );注册控件的名称到父控件上。

button2 = new Button();
button2.Name = "Button2";
            
// 注册button2的名称到myMainPanel控件上
myMainPanel.RegisterName(button2.Name, button2);
button2.Content = "Button 2";
button2.Click += new RoutedEventHandler(button2Clicked);
myMainPanel.Children.Add(button2);

5.SetBinding(DependencyProperty, BindingBase)和SetBinding(DependencyProperty, String),这两个成员都和绑定相关,我们将在后面做专题介绍。

最后,我们来看哪些类会继承这个FrameworkElement基类,以便于了解我们接下来要学哪些内容。

Microsoft.Windows.Themes.BulletChrome
Microsoft.Windows.Themes.ScrollChrome
System.Windows.Controls.AccessText
System.Windows.Controls.AdornedElementPlaceholder
System.Windows.Controls.ContentPresenter
System.Windows.Controls.Control
System.Windows.Controls.Decorator
System.Windows.Controls.Image
System.Windows.Controls.InkCanvas
System.Windows.Controls.ItemsPresenter
System.Windows.Controls.MediaElement
System.Windows.Controls.Page
System.Windows.Controls.Panel
System.Windows.Controls.Primitives.DocumentPageView
System.Windows.Controls.Primitives.GridViewRowPresenterBase
System.Windows.Controls.Primitives.Popup
System.Windows.Controls.Primitives.TickBar
System.Windows.Controls.Primitives.Track
System.Windows.Controls.TextBlock
System.Windows.Controls.ToolBarTray
System.Windows.Controls.Viewport3D
System.Windows.Documents.Adorner
System.Windows.Documents.AdornerLayer
System.Windows.Documents.DocumentReference
System.Windows.Documents.FixedPage
System.Windows.Documents.Glyphs
System.Windows.Documents.PageContent
System.Windows.Interop.HwndHost
System.Windows.Shapes.Shape

好,关于FrameworkElement类的成员,我们就先讲这么多,在介绍完WPF的父类之后,下一章,我们正式开始学习WPF控件。

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

UIElement类继承了Visual类,在WPF框架中排行老四(第4个基类)。它位于程序集:PresentationCore.dll之中,命名空间:System.Windows。

这个基类非常非常重要,理解了这个类,就理解了WPF所有控件1/3的知识与用法。我们先来看一下它的全貌。

public class UIElement : Visual, IAnimatable, IInputElement
{
public static readonly RoutedEvent PreviewMouseDownEvent;
public static readonly DependencyProperty AreAnyTouchesOverProperty;
public static readonly DependencyProperty AreAnyTouchesDirectlyOverProperty;
public static readonly DependencyProperty IsKeyboardFocusedProperty;
public static readonly DependencyProperty IsStylusCaptureWithinProperty;
public static readonly DependencyProperty IsStylusCapturedProperty;
public static readonly DependencyProperty IsMouseCaptureWithinProperty;
public static readonly DependencyProperty IsMouseCapturedProperty;
public static readonly DependencyProperty IsKeyboardFocusWithinProperty;
public static readonly DependencyProperty IsStylusOverProperty;
public static readonly DependencyProperty IsMouseOverProperty;
public static readonly DependencyProperty IsMouseDirectlyOverProperty;
public static readonly RoutedEvent TouchLeaveEvent;
public static readonly RoutedEvent TouchEnterEvent;
public static readonly RoutedEvent LostTouchCaptureEvent;
public static readonly RoutedEvent GotTouchCaptureEvent;
public static readonly RoutedEvent TouchUpEvent;
public static readonly RoutedEvent PreviewTouchUpEvent;
public static readonly RoutedEvent TouchMoveEvent;
public static readonly RoutedEvent PreviewTouchMoveEvent;
public static readonly RoutedEvent TouchDownEvent;
public static readonly RoutedEvent PreviewTouchDownEvent;
public static readonly RoutedEvent DropEvent;
public static readonly RoutedEvent PreviewDropEvent;
public static readonly RoutedEvent DragLeaveEvent;
public static readonly RoutedEvent PreviewDragLeaveEvent;
public static readonly DependencyProperty AreAnyTouchesCapturedProperty;
public static readonly DependencyProperty AreAnyTouchesCapturedWithinProperty;
public static readonly DependencyProperty AllowDropProperty;
public static readonly DependencyProperty RenderTransformProperty;
public static readonly RoutedEvent ManipulationCompletedEvent;
public static readonly RoutedEvent ManipulationBoundaryFeedbackEvent;
public static readonly RoutedEvent ManipulationInertiaStartingEvent;
public static readonly RoutedEvent ManipulationDeltaEvent;
public static readonly RoutedEvent ManipulationStartedEvent;
public static readonly RoutedEvent ManipulationStartingEvent;
public static readonly DependencyProperty IsManipulationEnabledProperty;
public static readonly DependencyProperty FocusableProperty;
public static readonly DependencyProperty IsVisibleProperty;
public static readonly DependencyProperty IsHitTestVisibleProperty;
public static readonly DependencyProperty IsEnabledProperty;
public static readonly DependencyProperty IsFocusedProperty;
public static readonly RoutedEvent DragOverEvent;
public static readonly RoutedEvent LostFocusEvent;
public static readonly DependencyProperty SnapsToDevicePixelsProperty;
public static readonly DependencyProperty ClipProperty;
public static readonly DependencyProperty ClipToBoundsProperty;
public static readonly DependencyProperty VisibilityProperty;
public static readonly DependencyProperty UidProperty;
public static readonly DependencyProperty CacheModeProperty;
public static readonly DependencyProperty BitmapEffectInputProperty;
public static readonly DependencyProperty EffectProperty;
public static readonly DependencyProperty BitmapEffectProperty;
public static readonly DependencyProperty OpacityMaskProperty;
public static readonly DependencyProperty OpacityProperty;
public static readonly DependencyProperty RenderTransformOriginProperty;
public static readonly RoutedEvent GotFocusEvent;
public static readonly RoutedEvent PreviewDragOverEvent;
public static readonly DependencyProperty IsStylusDirectlyOverProperty;
public static readonly RoutedEvent PreviewDragEnterEvent;
public static readonly RoutedEvent StylusMoveEvent;
public static readonly RoutedEvent PreviewStylusMoveEvent;
public static readonly RoutedEvent StylusUpEvent;
public static readonly RoutedEvent PreviewStylusUpEvent;
public static readonly RoutedEvent StylusDownEvent;
public static readonly RoutedEvent PreviewStylusDownEvent;
public static readonly RoutedEvent QueryCursorEvent;
public static readonly RoutedEvent LostMouseCaptureEvent;
public static readonly RoutedEvent GotMouseCaptureEvent;
public static readonly RoutedEvent MouseLeaveEvent;
public static readonly RoutedEvent MouseEnterEvent;
public static readonly RoutedEvent MouseWheelEvent;
public static readonly RoutedEvent PreviewStylusInAirMoveEvent;
public static readonly RoutedEvent PreviewMouseWheelEvent;
public static readonly RoutedEvent PreviewMouseMoveEvent;
public static readonly RoutedEvent MouseRightButtonUpEvent;
public static readonly RoutedEvent PreviewMouseRightButtonUpEvent;
public static readonly RoutedEvent MouseRightButtonDownEvent;
public static readonly RoutedEvent PreviewMouseRightButtonDownEvent;
public static readonly RoutedEvent DragEnterEvent;
public static readonly RoutedEvent PreviewMouseLeftButtonUpEvent;
public static readonly RoutedEvent MouseLeftButtonDownEvent;
public static readonly RoutedEvent PreviewMouseLeftButtonDownEvent;
public static readonly RoutedEvent MouseUpEvent;
public static readonly RoutedEvent PreviewMouseUpEvent;
public static readonly RoutedEvent MouseDownEvent;
public static readonly RoutedEvent MouseMoveEvent;
public static readonly RoutedEvent StylusInAirMoveEvent;
public static readonly RoutedEvent MouseLeftButtonUpEvent;
public static readonly RoutedEvent StylusLeaveEvent;
public static readonly RoutedEvent StylusEnterEvent;
public static readonly RoutedEvent GiveFeedbackEvent;
public static readonly RoutedEvent PreviewGiveFeedbackEvent;
public static readonly RoutedEvent QueryContinueDragEvent;
public static readonly RoutedEvent TextInputEvent;
public static readonly RoutedEvent PreviewTextInputEvent;
public static readonly RoutedEvent LostKeyboardFocusEvent;
public static readonly RoutedEvent PreviewLostKeyboardFocusEvent;
public static readonly RoutedEvent GotKeyboardFocusEvent;
public static readonly RoutedEvent PreviewGotKeyboardFocusEvent;
public static readonly RoutedEvent KeyUpEvent;
public static readonly RoutedEvent PreviewKeyUpEvent;
public static readonly RoutedEvent KeyDownEvent;
public static readonly RoutedEvent PreviewQueryContinueDragEvent;
public static readonly RoutedEvent PreviewStylusButtonUpEvent;
public static readonly RoutedEvent PreviewKeyDownEvent;
public static readonly RoutedEvent StylusInRangeEvent;
public static readonly RoutedEvent PreviewStylusInRangeEvent;
public static readonly RoutedEvent StylusOutOfRangeEvent;
public static readonly RoutedEvent PreviewStylusSystemGestureEvent;
public static readonly RoutedEvent PreviewStylusOutOfRangeEvent;
public static readonly RoutedEvent GotStylusCaptureEvent;
public static readonly RoutedEvent LostStylusCaptureEvent;
public static readonly RoutedEvent StylusButtonDownEvent;
public static readonly RoutedEvent StylusButtonUpEvent;
public static readonly RoutedEvent PreviewStylusButtonDownEvent;
public static readonly RoutedEvent StylusSystemGestureEvent;

public UIElement();

public string Uid { get; set; }
public Visibility Visibility { get; set; }
public bool ClipToBounds { get; set; }
public Geometry Clip { get; set; }
public bool SnapsToDevicePixels { get; set; }
public bool IsFocused { get; }
public bool IsEnabled { get; set; }
public bool IsHitTestVisible { get; set; }
public bool IsVisible { get; }
public bool AreAnyTouchesCapturedWithin { get; }
public int PersistId { get; }
public bool IsManipulationEnabled { get; set; }
public bool AreAnyTouchesOver { get; }
public bool AreAnyTouchesDirectlyOver { get; }
public bool AreAnyTouchesCaptured { get; }
public IEnumerable<TouchDevice> TouchesCaptured { get; }
public IEnumerable<TouchDevice> TouchesCapturedWithin { get; }
public IEnumerable<TouchDevice> TouchesOver { get; }
public CacheMode CacheMode { get; set; }
public bool Focusable { get; set; }
public BitmapEffectInput BitmapEffectInput { get; set; }
public bool IsMouseDirectlyOver { get; }
public BitmapEffect BitmapEffect { get; set; }
public Size RenderSize { get; set; }
public bool IsArrangeValid { get; }
public bool IsMeasureValid { get; }
public Size DesiredSize { get; }
public bool AllowDrop { get; set; }
public CommandBindingCollection CommandBindings { get; }
public InputBindingCollection InputBindings { get; }
public bool HasAnimatedProperties { get; }
public bool IsMouseOver { get; }
public Effect Effect { get; set; }
public bool IsStylusOver { get; }
public bool IsMouseCaptured { get; }
public bool IsMouseCaptureWithin { get; }
public bool IsStylusDirectlyOver { get; }
public bool IsStylusCaptured { get; }
public bool IsStylusCaptureWithin { get; }
public bool IsKeyboardFocused { get; }
public bool IsInputMethodEnabled { get; }
public double Opacity { get; set; }
public Brush OpacityMask { get; set; }
public bool IsKeyboardFocusWithin { get; }
public IEnumerable<TouchDevice> TouchesDirectlyOver { get; }
public Point RenderTransformOrigin { get; set; }
public Transform RenderTransform { get; set; }
protected StylusPlugInCollection StylusPlugIns { get; }
protected virtual bool IsEnabledCore { get; }
protected internal virtual bool HasEffectiveKeyboardFocus { get; }

public event KeyEventHandler KeyUp;
public event EventHandler<TouchEventArgs> TouchMove;
public event EventHandler<TouchEventArgs> PreviewTouchMove;
public event EventHandler<TouchEventArgs> TouchDown;
public event EventHandler<TouchEventArgs> PreviewTouchDown;
public event DragEventHandler Drop;
public event DragEventHandler PreviewDrop;
public event DragEventHandler DragLeave;
public event DragEventHandler PreviewDragLeave;
public event DragEventHandler DragOver;
public event DragEventHandler PreviewDragOver;
public event DragEventHandler DragEnter;
public event DragEventHandler PreviewDragEnter;
public event GiveFeedbackEventHandler GiveFeedback;
public event GiveFeedbackEventHandler PreviewGiveFeedback;
public event QueryContinueDragEventHandler QueryContinueDrag;
public event QueryContinueDragEventHandler PreviewQueryContinueDrag;
public event TextCompositionEventHandler TextInput;
public event EventHandler<TouchEventArgs> PreviewTouchUp;
public event EventHandler<TouchEventArgs> TouchUp;
public event EventHandler<TouchEventArgs> LostTouchCapture;
public event TextCompositionEventHandler PreviewTextInput;
public event EventHandler<ManipulationInertiaStartingEventArgs> ManipulationInertiaStarting;
public event EventHandler<ManipulationDeltaEventArgs> ManipulationDelta;
public event EventHandler<ManipulationStartedEventArgs> ManipulationStarted;
public event EventHandler<ManipulationStartingEventArgs> ManipulationStarting;
public event DependencyPropertyChangedEventHandler FocusableChanged;
public event DependencyPropertyChangedEventHandler IsVisibleChanged;
public event DependencyPropertyChangedEventHandler IsHitTestVisibleChanged;
public event DependencyPropertyChangedEventHandler IsEnabledChanged;
public event RoutedEventHandler LostFocus;
public event EventHandler<TouchEventArgs> GotTouchCapture;
public event RoutedEventHandler GotFocus;
public event DependencyPropertyChangedEventHandler IsKeyboardFocusedChanged;
public event DependencyPropertyChangedEventHandler IsStylusCaptureWithinChanged;
public event DependencyPropertyChangedEventHandler IsStylusDirectlyOverChanged;
public event DependencyPropertyChangedEventHandler IsMouseCaptureWithinChanged;
public event DependencyPropertyChangedEventHandler IsMouseCapturedChanged;
public event DependencyPropertyChangedEventHandler IsKeyboardFocusWithinChanged;
public event DependencyPropertyChangedEventHandler IsMouseDirectlyOverChanged;
public event EventHandler<TouchEventArgs> TouchLeave;
public event EventHandler<TouchEventArgs> TouchEnter;
public event EventHandler LayoutUpdated;
public event KeyboardFocusChangedEventHandler LostKeyboardFocus;
public event KeyboardFocusChangedEventHandler PreviewLostKeyboardFocus;
public event KeyboardFocusChangedEventHandler GotKeyboardFocus;
public event StylusEventHandler PreviewStylusMove;
public event StylusEventHandler StylusMove;
public event StylusEventHandler PreviewStylusInAirMove;
public event StylusEventHandler StylusInAirMove;
public event StylusEventHandler StylusEnter;
public event StylusEventHandler StylusLeave;
public event StylusEventHandler PreviewStylusInRange;
public event StylusEventHandler StylusInRange;
public event StylusEventHandler PreviewStylusOutOfRange;
public event StylusEventHandler StylusOutOfRange;
public event StylusSystemGestureEventHandler PreviewStylusSystemGesture;
public event StylusSystemGestureEventHandler StylusSystemGesture;
public event StylusEventHandler GotStylusCapture;
public event StylusEventHandler LostStylusCapture;
public event StylusButtonEventHandler StylusButtonDown;
public event StylusButtonEventHandler StylusButtonUp;
public event StylusButtonEventHandler PreviewStylusButtonDown;
public event StylusButtonEventHandler PreviewStylusButtonUp;
public event KeyEventHandler PreviewKeyDown;
public event KeyEventHandler KeyDown;
public event KeyEventHandler PreviewKeyUp;
public event StylusEventHandler StylusUp;
public event KeyboardFocusChangedEventHandler PreviewGotKeyboardFocus;
public event StylusEventHandler PreviewStylusUp;
public event StylusDownEventHandler PreviewStylusDown;
public event MouseButtonEventHandler PreviewMouseDown;
public event MouseButtonEventHandler MouseDown;
public event MouseButtonEventHandler PreviewMouseUp;
public event MouseButtonEventHandler MouseUp;
public event MouseButtonEventHandler PreviewMouseLeftButtonDown;
public event MouseButtonEventHandler MouseLeftButtonDown;
public event MouseButtonEventHandler PreviewMouseLeftButtonUp;
public event MouseButtonEventHandler MouseLeftButtonUp;
public event MouseButtonEventHandler PreviewMouseRightButtonDown;
public event MouseButtonEventHandler MouseRightButtonDown;
public event MouseButtonEventHandler PreviewMouseRightButtonUp;
public event MouseButtonEventHandler MouseRightButtonUp;
public event MouseEventHandler PreviewMouseMove;
public event MouseEventHandler MouseMove;
public event MouseWheelEventHandler PreviewMouseWheel;
public event MouseWheelEventHandler MouseWheel;
public event MouseEventHandler MouseEnter;
public event MouseEventHandler MouseLeave;
public event MouseEventHandler GotMouseCapture;
public event MouseEventHandler LostMouseCapture;
public event QueryCursorEventHandler QueryCursor;
public event StylusDownEventHandler StylusDown;
public event DependencyPropertyChangedEventHandler IsStylusCapturedChanged;
public event EventHandler<ManipulationCompletedEventArgs> ManipulationCompleted;
public event EventHandler<ManipulationBoundaryFeedbackEventArgs> ManipulationBoundaryFeedback;

public void AddHandler(RoutedEvent routedEvent, Delegate handler);
public void AddHandler(RoutedEvent routedEvent, Delegate handler, bool handledEventsToo);
public void AddToEventRoute(EventRoute route, RoutedEventArgs e);
public void ApplyAnimationClock(DependencyProperty dp, AnimationClock clock, HandoffBehavior handoffBehavior);
public void ApplyAnimationClock(DependencyProperty dp, AnimationClock clock);
public void Arrange(Rect finalRect);
public void BeginAnimation(DependencyProperty dp, AnimationTimeline animation, HandoffBehavior handoffBehavior);
public void BeginAnimation(DependencyProperty dp, AnimationTimeline animation);
public bool CaptureMouse();
public bool CaptureStylus();
public bool CaptureTouch(TouchDevice touchDevice);
public bool Focus();
public object GetAnimationBaseValue(DependencyProperty dp);
public IInputElement InputHitTest(Point point);
public void InvalidateArrange();
public void InvalidateMeasure();
public void InvalidateVisual();
public void Measure(Size availableSize);
public virtual bool MoveFocus(TraversalRequest request);
public virtual DependencyObject PredictFocus(FocusNavigationDirection direction);
public void RaiseEvent(RoutedEventArgs e);
public void ReleaseAllTouchCaptures();
public void ReleaseMouseCapture();
public void ReleaseStylusCapture();
public bool ReleaseTouchCapture(TouchDevice touchDevice);
public void RemoveHandler(RoutedEvent routedEvent, Delegate handler);
public bool ShouldSerializeCommandBindings();
public bool ShouldSerializeInputBindings();
public Point TranslatePoint(Point point, UIElement relativeTo);
public void UpdateLayout();
protected virtual void ArrangeCore(Rect finalRect);
protected virtual Geometry GetLayoutClip(Size layoutSlotSize);
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters);
protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters);
protected virtual Size MeasureCore(Size availableSize);
protected virtual void OnAccessKey(AccessKeyEventArgs e);
protected virtual void OnChildDesiredSizeChanged(UIElement child);
protected virtual AutomationPeer OnCreateAutomationPeer();
protected virtual void OnDragEnter(DragEventArgs e);
protected virtual void OnDragLeave(DragEventArgs e);
protected virtual void OnDragOver(DragEventArgs e);
protected virtual void OnDrop(DragEventArgs e);
protected virtual void OnGiveFeedback(GiveFeedbackEventArgs e);
protected virtual void OnGotFocus(RoutedEventArgs e);
protected virtual void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e);
protected virtual void OnGotMouseCapture(MouseEventArgs e);
protected virtual void OnGotStylusCapture(StylusEventArgs e);
protected virtual void OnGotTouchCapture(TouchEventArgs e);
protected virtual void OnIsKeyboardFocusedChanged(DependencyPropertyChangedEventArgs e);
protected virtual void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e);
protected virtual void OnIsMouseCapturedChanged(DependencyPropertyChangedEventArgs e);
protected virtual void OnIsMouseCaptureWithinChanged(DependencyPropertyChangedEventArgs e);
protected virtual void OnIsMouseDirectlyOverChanged(DependencyPropertyChangedEventArgs e);
protected virtual void OnIsStylusCapturedChanged(DependencyPropertyChangedEventArgs e);
protected virtual void OnIsStylusCaptureWithinChanged(DependencyPropertyChangedEventArgs e);
protected virtual void OnIsStylusDirectlyOverChanged(DependencyPropertyChangedEventArgs e);
protected virtual void OnKeyDown(KeyEventArgs e);
protected virtual void OnKeyUp(KeyEventArgs e);
protected virtual void OnLostFocus(RoutedEventArgs e);
protected virtual void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e);
protected virtual void OnLostMouseCapture(MouseEventArgs e);
protected virtual void OnLostStylusCapture(StylusEventArgs e);
protected virtual void OnLostTouchCapture(TouchEventArgs e);
protected virtual void OnManipulationBoundaryFeedback(ManipulationBoundaryFeedbackEventArgs e);
protected virtual void OnManipulationCompleted(ManipulationCompletedEventArgs e);
protected virtual void OnManipulationDelta(ManipulationDeltaEventArgs e);
protected virtual void OnManipulationInertiaStarting(ManipulationInertiaStartingEventArgs e);
protected virtual void OnManipulationStarted(ManipulationStartedEventArgs e);
protected virtual void OnManipulationStarting(ManipulationStartingEventArgs e);
protected virtual void OnMouseDown(MouseButtonEventArgs e);
protected virtual void OnMouseEnter(MouseEventArgs e);
protected virtual void OnMouseLeave(MouseEventArgs e);
protected virtual void OnMouseLeftButtonDown(MouseButtonEventArgs e);
protected virtual void OnMouseLeftButtonUp(MouseButtonEventArgs e);
protected virtual void OnMouseMove(MouseEventArgs e);
protected virtual void OnMouseRightButtonDown(MouseButtonEventArgs e);
protected virtual void OnMouseRightButtonUp(MouseButtonEventArgs e);
protected virtual void OnMouseUp(MouseButtonEventArgs e);
protected virtual void OnMouseWheel(MouseWheelEventArgs e);
protected virtual void OnPreviewDragEnter(DragEventArgs e);
protected virtual void OnPreviewDragLeave(DragEventArgs e);
protected virtual void OnPreviewDragOver(DragEventArgs e);
protected virtual void OnPreviewDrop(DragEventArgs e);
protected virtual void OnPreviewGiveFeedback(GiveFeedbackEventArgs e);
protected virtual void OnPreviewGotKeyboardFocus(KeyboardFocusChangedEventArgs e);
protected virtual void OnPreviewKeyDown(KeyEventArgs e);
protected virtual void OnPreviewKeyUp(KeyEventArgs e);
protected virtual void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e);
protected virtual void OnPreviewMouseDown(MouseButtonEventArgs e);
protected virtual void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e);
protected virtual void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e);
protected virtual void OnPreviewMouseMove(MouseEventArgs e);
protected virtual void OnPreviewMouseRightButtonDown(MouseButtonEventArgs e);
protected virtual void OnPreviewMouseRightButtonUp(MouseButtonEventArgs e);
protected virtual void OnPreviewMouseUp(MouseButtonEventArgs e);
protected virtual void OnPreviewMouseWheel(MouseWheelEventArgs e);
protected virtual void OnPreviewQueryContinueDrag(QueryContinueDragEventArgs e);
protected virtual void OnPreviewStylusButtonDown(StylusButtonEventArgs e);
protected virtual void OnPreviewStylusButtonUp(StylusButtonEventArgs e);
protected virtual void OnPreviewStylusDown(StylusDownEventArgs e);
protected virtual void OnPreviewStylusInAirMove(StylusEventArgs e);
protected virtual void OnPreviewStylusInRange(StylusEventArgs e);
protected virtual void OnPreviewStylusMove(StylusEventArgs e);
protected virtual void OnPreviewStylusOutOfRange(StylusEventArgs e);
protected virtual void OnPreviewStylusSystemGesture(StylusSystemGestureEventArgs e);
protected virtual void OnPreviewStylusUp(StylusEventArgs e);
protected virtual void OnPreviewTextInput(TextCompositionEventArgs e);
protected virtual void OnPreviewTouchDown(TouchEventArgs e);
protected virtual void OnPreviewTouchMove(TouchEventArgs e);
protected virtual void OnPreviewTouchUp(TouchEventArgs e);
protected virtual void OnQueryContinueDrag(QueryContinueDragEventArgs e);
protected virtual void OnQueryCursor(QueryCursorEventArgs e);
protected virtual void OnRender(DrawingContext drawingContext);
protected virtual void OnStylusButtonDown(StylusButtonEventArgs e);
protected virtual void OnStylusButtonUp(StylusButtonEventArgs e);
protected virtual void OnStylusDown(StylusDownEventArgs e);
protected virtual void OnStylusEnter(StylusEventArgs e);
protected virtual void OnStylusInAirMove(StylusEventArgs e);
protected virtual void OnStylusInRange(StylusEventArgs e);
protected virtual void OnStylusLeave(StylusEventArgs e);
protected virtual void OnStylusMove(StylusEventArgs e);
protected virtual void OnStylusOutOfRange(StylusEventArgs e);
protected virtual void OnStylusSystemGesture(StylusSystemGestureEventArgs e);
protected virtual void OnStylusUp(StylusEventArgs e);
protected virtual void OnTextInput(TextCompositionEventArgs e);
protected virtual void OnTouchDown(TouchEventArgs e);
protected virtual void OnTouchEnter(TouchEventArgs e);
protected virtual void OnTouchLeave(TouchEventArgs e);
protected virtual void OnTouchMove(TouchEventArgs e);
protected virtual void OnTouchUp(TouchEventArgs e);
protected internal virtual DependencyObject GetUIParentCore();
protected internal virtual void OnRenderSizeChanged(SizeChangedInfo info);
protected internal override void OnVisualParentChanged(DependencyObject oldParent);

}

UIElement类代码分析

第一部分 路由事件

UIElement基类定义了大量的路由事件。什么是路由事件?路由事件和xaml的可视化树概念相关,控件的事件被触发后,会沿着这棵树广播,有两个方向,要么往树的根部广播,要么往树的枝叶广播,如果不广播就是直接事件。

所以,路由事件分为冒泡事件和隧道事件,冒泡,是从触发源为出发点,依次传递到父节点,直到最后的根节点。隧道事件是不管谁是触发源,都是从根节点触发,到子节点,直到触发节点。

从空间上来说,冒泡事件和隧道事件是成对出现的。从时间来说,都是先触发隧道事件,然后是冒泡事件。从命名来说,隧道事件都是以Preview开头的事件。

根据命名规则,我们可以大致猜测出一个结果,带Key的基本都是与键盘相关的事件(如按下键位、抬起键位),带Mouse的基本都是与鼠标相关的事件(如左键单击、双击),带Stylus的基本都是与触摸相关的事件,具体用到哪一类型的事件,再详细查阅一下相关说明文档即可。

重点:关于这些事件的回调函数,即以On开头的方法成员,都被声明成了protected virtual,意思是他们都可以被重载,这使得我们在开发业务时更加方便。

第二部分 依赖属性

UIElement基类还定义了大量的依赖属性。前面的章节中,在DependencyObject类中我们简单提到过依赖属性。在这里我们以UIElement基类的Visibility属性为例。

public Visibility Visibility { get; set; }

public static readonly DependencyProperty VisibilityProperty;

上面有两个成员,Visibility是普通的属性成员,VisibilityProperty是WPF的依赖属性成员,以Property结尾的字样作为WPF的依赖属性命名规则。而这两个成员合起来,才能被称为一个完整的依赖属性。这个Visibility 属性表示设置或获取控件的可见性。当我们要设置控件的可见性时,只需要如下设置即可。

<TextBlock Text="WPF中文网" 
                   Visibility="Visible"
                   FontSize="48" 
                   HorizontalAlignment="Center" 
                   VerticalAlignment="Center"/>

Visibility实际上是一个枚举,它包含3个值,分别是Visible、Hidden、Collapsed。其含义分别是显示、隐藏、彻底隐藏(不占布局位置)。

Visibility 状态会影响该元素的所有输入处理。 不可见的元素不会参与命中测试,也不会接收输入事件,即使鼠标位于元素可见时所在的边界上也是如此。

但是在这一节中,我们只是探讨UIElement基类提供了哪些方面的属性,并不详细探讨依赖属性,所以下面我们把目光聚焦到UIElement基类的常用属性上。另外由于WPF中几乎所有控件都继承了这个基类,意思就是说所有的控件都有这些属性可以使用。下面我在描述的时候将采用“控件”两字来解释一些技术细节。

Uid属性:获取或设置控件的唯一标识符,像人们的身份证一样。这个值默认是string.Empty。

Visibility属性:获取或设置控件的可见性。默认是Visible。

ClipToBounds属性:如果该值为true,表示进行裁剪,以适配它的父控件。比如有时候我们外面有一个Panel,里面的控件尺寸太大,势必会“撑破”外面的父控件,为了布局美观,只好削足适履。

Clip属性:用于剪裁区域大小的几何图形。需要注意的是,这个属性和上面的ClipToBounds属性是有区别的。ClipToBounds是裁剪控件自身,Clip是裁剪控件里面的内容。比如Image图像控件,我们在显示一张图时,就可以运用Clip进行裁剪后显示,通常在显示用户头像时裁剪成圆形时使用。如下所示

<Image 
  Source="sampleImages\Waterlilies.jpg" 
  Width="200" Height="150" HorizontalAlignment="Left">
  <Image.Clip>
    <EllipseGeometry
      RadiusX="100"
      RadiusY="75"
      Center="100,75"/>
  </Image.Clip>
</Image>

SnapsToDevicePixels属性:如果该值为true,表示控件的呈现是否应使用特定于设备的像素设置。意思是开启后可以最大限度的防锯齿效果,默认为false。

IsFocused属性:这是一个只读属性,表示当前控件是否有焦点。

IsEnabled属性:如果该值为true,表示禁用控件,反之启用控件。

IsHitTestVisible属性:获取或设置一个值,该值声明是否可以返回此元素作为其呈现内容的某些部分的点击测试结果。

IsVisible属性:这是一个只读属性,表示当前控件是否显示。

Focusable属性:如果该值为true,表示控件可以得到焦点,大部份内容控件都默认可以获得焦点。

IsKeyboardFocused属性:表示该控件是否具有键盘焦点。

IsMouseOver属性:表示鼠标是否在控件上面。通常在设计控件的样式(Style)时会用到。

IsStylusOver属性:表示触笔指针是否在控件的上方。

IsSealed属性:表示当前类型是否为只读类。

Opacity属性:设置控件的透明度,取值范围是0-1之间的double值。

OpacityMask属性:设置一个画笔,作为控件的蒙板。比如我们给一张图片设置一个掩码,就可以使用ImageBrush这种图片画笔来实现。

<Image Height="150" Width="200" Source="sampleImages/Waterlilies.jpg" >
  <Image.OpacityMask>
    <ImageBrush ImageSource="sampleImages/tornedges.png"/>
  </Image.OpacityMask>
</Image>

AllowDrop属性:表示控件是否允许拖拽操作。

RenderTransform属性:(非常重要)如果要设置控件的移动、缩放、旋转,需要这此属性进行设置。

UIElement类总结

通过上述的代码分析,我们大致可以得出以下结论,UIElement基类为我们提供了一系列的鼠标、键盘和触摸事件,并提供了一些常用的依赖属性。它可以呈现继承它的所有控件,为控件布局时调整位置和大小,响应用户的输入,引发一系列的路由事件,并继承了IAnimatable动画接口,用于支持动画的某些方面。

我们熟悉了UIElement的这些属性和事件之后,实际上意味着我们也熟悉了WPF所有控件的这些属性。下一节,我们将探讨UIElement的子类FrameworkElement。

FrameworkElement基类论重要性,完全不亚于UIElement基类。甚至论起与开发者的“亲密度”,FrameworkElement更像是近水的楼台。

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

Visual类是WPF框架中第三个父类,主要是为 WPF 中的呈现提供支持,其中包括命中测试、坐标转换和边界框计算。它位于程序集:PresentationCore.dll库文件中,它的命名空间:System.Windows.Media。

官方引用

Visual 类是派生每个 FrameworkElement 对象的基本抽象。 该类还用作在 WPF 中编写新控件的入口点,在 Win32 应用程序模型中,该类在许多方面可视为窗口句柄 (HWND)。Visual 对象是一个核心 WPF 对象,它的主要作用是提供呈现支持。 用户界面控件如 Button 和 TextBox)派生自 Visual 类,并使用该类来保存它们的呈现数据。 Visual 对象为以下项提供支持:
输出显示:呈现视觉对象的持久、序列化的绘图内容。
转换:针对视觉对象执行转换。
剪裁:为视觉对象提供剪裁区域支持。
命中测试:确定坐标或几何形状是否包含在视觉对象的边界内。
边框计算:确定视觉对象的边框。

换句话说,将来我们要学习的Button、TextBox、CheckBox、Gird、ListBox等所有控件都继承了Visual类,控件在绘制到界面的过程中,涉及到转换、裁剪、边框计算等功能,都是使用了Visual父类的功能。我们先来看一下这个类的结构定义。

public abstract class Visual : DependencyObject, IResource
{
    protected Visual();

    protected DependencyObject VisualParent { get; }
    protected virtual int VisualChildrenCount { get; }
    protected internal DoubleCollection VisualYSnappingGuidelines { get; protected set; }
    protected internal Vector VisualOffset { get; protected set; }
    protected internal Geometry VisualClip { get; protected set; }
    protected internal Rect? VisualScrollableAreaClip { get; protected set; }
    protected internal CacheMode VisualCacheMode { get; protected set; }
    protected internal BitmapEffectInput VisualBitmapEffectInput { get; protected set; }
    protected internal BitmapEffect VisualBitmapEffect { get; protected set; }
    protected internal Effect VisualEffect { get; protected set; }
    protected internal Transform VisualTransform { get; protected set; }
    protected internal BitmapScalingMode VisualBitmapScalingMode { get; protected set; }
    protected internal DoubleCollection VisualXSnappingGuidelines { get; protected set; }
    protected internal double VisualOpacity { get; protected set; }
    protected internal EdgeMode VisualEdgeMode { get; protected set; }
    protected internal ClearTypeHint VisualClearTypeHint { get; set; }
    protected internal TextRenderingMode VisualTextRenderingMode { get; set; }
    protected internal TextHintingMode VisualTextHintingMode { get; set; }
    protected internal Brush VisualOpacityMask { get; protected set; }

    public DependencyObject FindCommonVisualAncestor(DependencyObject otherVisual);
    public bool IsAncestorOf(DependencyObject descendant);
    public bool IsDescendantOf(DependencyObject ancestor);
    public Point PointFromScreen(Point point);
    public Point PointToScreen(Point point);
    public GeneralTransform2DTo3D TransformToAncestor(Visual3D ancestor);
    public GeneralTransform TransformToAncestor(Visual ancestor);
    public GeneralTransform TransformToDescendant(Visual descendant);
    public GeneralTransform TransformToVisual(Visual visual);
    protected void AddVisualChild(Visual child);
    protected virtual Visual GetVisualChild(int index);
    protected virtual GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters);
    protected virtual HitTestResult HitTestCore(PointHitTestParameters hitTestParameters);
    protected virtual void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi);
    protected void RemoveVisualChild(Visual child);
    protected internal virtual void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved);
    protected internal virtual void OnVisualParentChanged(DependencyObject oldParent);

}

代码分析

首先,我们可以看到,Visual类继承了DependencyObject类。另外Visual类是一个抽象类,不可以被实例。Visual类提供了一系列的属性和方法。我们在这里捡一些比较重要的分析一下。

VisualParent属性:这个属性表示获取一个可视化父对象。因为XAML的代码结构就是一棵xml树,每个控件都对象几乎都有一个可视化父对象。

VisualChildrenCount属性:获取当前对象的子元素数量。

VisualOffset属性:指当前可视对象的偏移量值。需要注意的是这个属性被声明成protected internal。啥意思呢?VisualOffset属性只能由同一个程序集的其它类访问,或Visual的子类访问。

protected internal

protected internal 关键字组合是一种成员访问修饰符, 表示受保护的内部成员。

VisualOpacity属性:获取或设置 Visual 的不透明度。

VisualEffect属性:获取或设置要应用于 Visual 的位图效果。

VisualTransform属性:获取或设置 Transform 的 Visual 值。

这些属性都只读为了解Visual类的基础,因为这些属性都被设计成protected internal,我们的控件虽然继承了这个Visual类,但在实际的使用过程中是感知不到这些属性的,自然也不能实操它们。

我们真正能在继承的控件中直接使用的是Visual类中被声明为public的方法成员。它们有以下几个:

  • DependencyObject FindCommonVisualAncestor(DependencyObject otherVisual); //返回两个可视对象的公共上级。
  • bool IsAncestorOf(DependencyObject descendant); //确定可视对象是否为后代可视对象的上级。
  • bool IsDescendantOf(DependencyObject ancestor); //确定可视对象是否为上级可视对象的后代。
  • Point PointFromScreen(Point point); //将屏幕坐标中的 Point 转换为表示 Point 的当前坐标系的 Visual。
  • Point PointToScreen(Point point); //将表示 Point 的当前坐标系的 Visual 转换为屏幕坐标中的 Point。
  • GeneralTransform2DTo3D TransformToAncestor(Visual3D ancestor); //返回一个转换,该转换可用于将 Visual 中的坐标转换为可视对象的指定 Visual3D 上级。
  • GeneralTransform TransformToAncestor(Visual ancestor); //返回一个转换,该转换可用于将 Visual 中的坐标转换为可视对象的指定 Visual 上级。
  • GeneralTransform TransformToDescendant(Visual descendant); //返回一个转换,该转换可用于将 Visual 中的坐标转换为指定的可视对象后代。
  • GeneralTransform TransformToVisual(Visual visual); //返回一个转换,该转换可用于将 Visual 中的坐标转换为指定的可视对象。

由此可见,Visual类所做的事情只为控件呈现相关,但还不是去呈现控件,只是提供呈现的基础。那么,谁又去继承了Visual类,成为继Visual类之后又一个控件的基类呢?答案是UIElement类。

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

Dependency,字面意思为依靠,依赖; 附属国; 附属地;这里为什么会有DependencyObject类的存在?这还要从WPF的依赖属性系统说起。

如果您有Winform的基础,对于控件属性值的赋值一定不陌生。比如:

button1.Text = "确定";

我们将“确定”字符串赋值给一个按钮的Text属性,这样前端的button1的内容为显示为”确定“。如果根据某些业务要求,需要将这个button1的内容翻译成英语“OK"显示呢?其实也很简单。

button1.Text = "OK";

这种在需要的时候主动去改变控件的值的开发模式,我们称为事件驱动模式。

一直以来, 我们都采用事件驱动模式开发软件。也没觉得有多少不便,如果控件的相关属性值需要被多个地方反复改动,大不了我们多写几行代码。那有没有更省事的方式呢?微软说,有!

于是微软在WPF框架中推出了更省事的处理方式——数据驱动模式。什么是数据驱动模式?控件的属性不再被直接赋值,而是绑定了另一个”变量“,当这个”变量“发生改变时,控件的属性也会跟着改变,这样的属性也被称为依赖属性。这有点像初中数学中的y=x这样的函数,y是因变量,x是自变量,y随着x的变化而变化。

我在这里只是比较形象地分享数据驱动模式中一部分概念,因为您可能是第一定接触到这个概念。关于更详细的知识,我们会在后面专门拿一小节来全面的讲解数据驱动模式。

我迫不及待的抛出数据驱动模式,是因为几乎WPF控件的所有属性都可以采用这种模式去更新属性的值,为什么?因为所有控件都继承了DependencyObject这个基类。换句话说,也只有继承了这个基类的控件,才能享受这一特殊待遇。其背后的原理是有一个强大的依赖属性系统在提供属性更改通知服务。

提前阅读

DependencyObject 类表示参与依赖属性系统的对象。属性系统的主要功能是计算属性的值,并提供有关已更改的值的系统通知。 参与属性系统的另一个类 DependencyProperty。 DependencyProperty 允许将依赖属性注册到属性系统,并提供有关每个依赖属性的标识和信息,而 DependencyObject 为基类,使对象能够使用此依赖属性。
INotifyPropertyChanged 类用于通知UI刷新,注重的仅仅是数据更新后的通知。DependencyObject 类用于给UI添加依赖和附加属性,注重数据与UI的关联。如果简单的数据通知,两者都可以实现的。

我们来看一下DependencyObject类的定义,比较常用的是GetValue和SetValue。GetValue表示获取某一个依赖属性的值,由于不确定这个值是什么类型,所以微软把这个函数的返回值设计成object。SetValue表示设置某一个依赖属竹的值,所有它有两个参数,第一个参数dp表示要设置的依赖属性,第二个参数value表示新值。

public class DependencyObject : DispatcherObject
{
    public DependencyObject();

    public DependencyObjectType DependencyObjectType { get; }
    public bool IsSealed { get; }

    public void ClearValue(DependencyProperty dp);
    public void ClearValue(DependencyPropertyKey key);
    public void CoerceValue(DependencyProperty dp);
    public sealed override bool Equals(object obj);
    public sealed override int GetHashCode();
    public LocalValueEnumerator GetLocalValueEnumerator();
    public object GetValue(DependencyProperty dp);
    public void InvalidateProperty(DependencyProperty dp);
    public object ReadLocalValue(DependencyProperty dp);
    public void SetCurrentValue(DependencyProperty dp, object value);
    public void SetValue(DependencyProperty dp, object value);
    public void SetValue(DependencyPropertyKey key, object value);
    protected virtual void OnPropertyChanged(DependencyPropertyChangedEventArgs e);
    protected internal virtual bool ShouldSerializeProperty(DependencyProperty dp);

}

关于依赖属性我们会在后面为大家做专题讲述。下一节,我们来讨论一下Visual基类。

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

话说这个千年老二DispatcherObject类,但在WPF世界中,那也是一位顶级的带头大哥,位高权重。根据常识,像这样的顶级类型,基本上都不怎么干活的,主要是把握方针路线。

.NET为WPF准备了两个线程(WPF应用启动时),分别用于呈现界面(后台线程)和管理界面(UI线程)。后台线程一直隐藏于后台默默运行,我们感知不到,我们唯一能操作的就是UI线程。

绝大多数对象或控件都必须在UI线程上创建,而且,其它后台子线程不能直接访问UI线程上的控件,那么,后台线程非要访问UI线程的控件或对象,该怎么办呢?微软说,这样吧,我在UI线程上给你们提供一个中间商Dispatcher(调度员),将Dispatcher放到一个抽象类DispatcherObject,然后我向你保证,我所有的控件都从这个DispatcherObject类继承,这样当你们在后台线程中要访问控件时,就可以从控件中找到那位中间商Dispatcher,由中间商来完成你要对控件的操作访问。

从此,DispatcherObject在WPF的世界中,便登上了至高无上的宝座,成为了几乎所有类型的终极基类。

而作为DispatcherObject类的成员Dispatcher(调度员)又提供了哪些功能?简单点说,它便是后台线程和前台线程的架海紫金梁,虽然所有的控件都必须在前台UI线程中创建,但是在开发过程中,难免需要在后台线程中去操作控件,于是Dispatcher调度员提供了Invoke和BeginInvoke两个方法,供我们可以安全的访问UI线程中的控件。

官方解释

在 WPF 中, DispatcherObject 只能由 Dispatcher 它与之关联的访问。 例如,后台线程无法更新与 Dispatcher UI 线程上关联的内容Button。 为了使后台线程访问该 Content 属性 Button,后台线程必须将工作委托给 Dispatcher 与 UI 线程关联的工作。 这是通过使用 Invoke 或BeginInvoke。 Invoke 是同步的, BeginInvoke 是异步的。 操作将添加到指定DispatcherPriority位置的队列Dispatcher中。

我们以前面课程中的HelloWorld项目为例,在Grid中添加一个button,在MainWindow的构造函数中增加如下代码。

前端代码

<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" Height="350" Width="500">
    <Grid>
        <Button x:Name="button"/>
    </Grid>
</Window>

后端代码

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

            Task.Factory.StartNew(() =>
            {
                Task.Delay(3000).Wait();

                button.Dispatcher.Invoke(() =>
                {
                    button.Content = "www.wpfsoft.com";
                });
            }); 
        }
    }
}

最后F5运行调试,我们会看到3秒后,button控件的Content属性被我们改成了 "www.wpfsoft.com"。

我们利用Task工厂创建了一个子线程(后台线程),然后调用了button的Dispatcher调度员,其Invoke方法中传入了一个匿名函数,在这个匿名函数中去改变button按钮的Content属性。

为什么button按钮有Dispatcher?因为button按钮继承了WPF的带头大哥DispatcherObject类,而DispatcherObject类有Dispatcher成员。

那么DispatcherObject 类的主要方针路线到底是什么呢?主要有两个职责:

  • 提供对对象所关联的当前 Dispatcher 的访问权限,意思是说谁继承了它,谁就拥有了Dispatcher。
  • 提供方法以检查 (CheckAccess) 和验证 (VerifyAccess) 某个线程是否有权访问对象(派生于 DispatcherObject)。CheckAccess 与 VerifyAccess 的区别在于 CheckAccess 返回一个布尔值,表示当前线程是否有可以使用的对象,而 VerifyAccess 则在线程无权访问对象的情况下引发异常。

那么,谁又在第一时间继承了DispatcherObject,成为了一人之下,万人之上的二当家呢?答案是DependencyObject类,关于这个类,我们在下一节讨论。

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

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

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

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