WPF教程(七)--依赖属性(3)--附加属性

news/2024/12/30 0:50:22/

一、 只读依赖属性


  以前在对于非WPF的功能来说,对于类的属性的封装中,经常会对那些希望暴露给外界只读操作的字段封装成只读属性,同样在WPF中也提供了只读属性的概念,如一些 WPF控件的依赖属性是只读的,它们经常用于报告控件的状态和信息,像IsMouseOver等属性, 那么在这个时候对它赋值就没有意义了。 或许你也会有这样的疑问:为什么不使用一般的.Net属性提供出来呢?一般的属性也可以绑定到元素上呀?这个是由于有些地方必须要用到只读依赖属性,比如 Trigger等,同时也因为内部可能有多个提供者修改其值,所以用.Net属性就不能胜任了。
  那么一个只读依赖属性怎么创建呢?其实创建一个只读的依赖属性和创建一个一般的依赖属性大同小异。不同的地方就是DependencyProperty.Register变成了DependencyProperty.RegisterReadOnly。和前面的普通依赖属性一样,它将返回一个 DependencyPropertyKey。而且只提供一个GetValue给外部,这样便可以像一般属性一样使用了,只是不能在外部设置它的值罢了。
示例如下:

public partial class WindowReadOnly : Window{public WindowReadOnly (){InitializeComponent();//用SetValue的方法来设置值//创建定时器读取属性信息--一秒更新一次数据源DispatcherTimer timer =new DispatcherTimer(TimeSpan.FromSeconds(1),DispatcherPriority.Normal,(object sender, EventArgs e)=>{int newValue = Counter == int.MaxValue ? 0 : Counter + 1;SetValue(counterKey, newValue);},Dispatcher);}//属性包装器,只提供GetValuepublic int Counter{get { return (int)GetValue(counterKey.DependencyProperty); }}//用RegisterReadOnly来代替Register来注册一个只读依赖属性private static readonly DependencyPropertyKey counterKey =DependencyProperty.RegisterReadOnly("Counter",typeof(int),typeof(WindowReadOnly),new PropertyMetadata(0));}

XAML代码部分:

<Window x:Name="winReadOnly" x:Class="WpfApp1.WindowReadOnly"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="WindowDepend" Height="300" Width="300"><Grid><Viewbox><TextBlock Text="{Binding ElementName=winReadOnly, Path=Counter}" /></Viewbox></Grid>
</Window>

这里写图片描述

 二、附加属性


    另外一种特殊的依赖属性——附加属性。附加属性是一种特殊的依赖属性。这是WPF的特性之一,通俗的理解起来就是,别人有的属性,由于你跟他产生了关系所以你也有了这个属于他的属性。
附加属性是说一个属性本来不属于某个对象,但由于某种需求而被后来附加上,也就是把对象放入一个特定环境后对象才具有的属性就称为附加属性,附加属性的作用就是将属性与数据类型解耦,让数据类型的设计更加灵活,

示例:一个TextBox控件被放在不同的布局容器中时就会有不同的布局属性,这些属性就是由布局容器为TextBox附加上的,附加属性的本质就是依赖属性二者仅仅在注册和包装器上有一点区别。
附加属性是依赖属性的一种特殊形式,它可以让用户在一个元素中设置其他元素的属性。一般来说,附加属性是用于一个父元素定位其他元素布局 的。就像Grid和DockPanel元素就包含附加属性。Grid使用附加属性来指定包含子元素的特定行和列,而DockPanel使用附加属性是来指定子元素应该停靠在面板中的何处位置。
附加属性就是自己没有这个属性,在某些上下文中需要就被附加上去。比如StackPanel的Grid.Row属性,如果我们定义StackPanel类时定义一个Row属性是没有意义的,因为我们并不知道一定会放在Grid里,这样就造成了浪费。
例如,下面转场控件的定义使用了Grid的Row属性来将自身定位到特定的行中。

<Grid><Grid.RowDefinitions><RowDefinition Height="101*"/><RowDefinition Height="80"/><RowDefinition Height="80"/></Grid.RowDefinitions><--!此处stackPanel控件增加了一个Grid.Row="0"附加属性--><StackPanel Grid.Row="0" >

   尽管对于一个普通的WPF开发人员来说,理解依赖和附加属性并不一定是必须的,但是掌握好WPF系统的整个运行机制对于提升WPF应用技术是非常重要的。
使用附加属性,可以避开可能会防止一个关系中的不同对象在运行时相互传递信息的编码约定。一定可以针对常见的基类设置属性,以便每个对象只需获取和设置该属性即可。但是,你可能希望在很多情况下这样做,这会使你的基类最终充斥着大量可共享的属性。它甚至可能会引入以下情况:在数百个后代中,只有两个后代尝试使用一个属性。这样的类设计很糟糕。为了解决此问题,我们使用附加属性概念来允许对象为不是由它自己的类结构定义的属性赋值。在创建对象树中的各个相关对象之后,在运行时从子对象读取此值。
  最好的例子就是布局面板。每一个布局面板都需要自己特有的方式来组织它的子元素。如Canvas画板需要Top和left属性来布局,DockPanel需要Dock属性来布局
下面代码中的Button 就是用了Canvas的Canvas.Top和Canvas.Left=”20” 来进行布局定位,那么这两个就是传说中的附加属性。

<Canvas><--!Canvas.Top以及Canvas.Left属性就是canvas父控件给子控件带来的附加属性--><Button Canvas.Top="20" Canvas.Left="20" Content="Knights Warrior!"/>
</Canvas>

  定义附加属性的方法与定义依赖属性的方法一致,前面我们是使用DependencyProperty.Register来注册一个依赖属性,只是在注册属性时使用的是RegisterAttach()方法。这个RegisterAttached的参数和 Register是完全一致的,那么Attached(附加)这个概念又从何而来呢?
  其实我们使用依赖属性,一直在Attached(附加)。我们注册(构造)一个依赖属性,然后在DependencyObject中通过 GetValue和SetValue来操作这个依赖属性,也就是把这个依赖属性通过这样的方法关联到了这个DependencyObject上,只不过是通过封装CLR属性来达到的。那么RegisterAttached又是怎样的呢?
下面我们来看一个最简单的应用:首先我们注册(构造)一个附加属性

    public class TurnoverManager : DependencyObject{//通过静态方法的形式暴露读的操作public static double GetAngle(DependencyObject obj){return (double)obj.GetValue(AngleProperty);}//通过静态方法的形式暴露写的操作public static void SetAngle(DependencyObject obj, double value){obj.SetValue(AngleProperty, value);}//通过使用RegisterAttached来注册一个附加属性public static readonly DependencyProperty AngleProperty =DependencyProperty.RegisterAttached("Angle", typeof(double), typeof(TurnoverManager), new PropertyMetadata(0.0, OnAngleChanged));//根据附加属性中的值,当值改变的时候,旋转相应的角度。//创建属性时的回调函数private static void OnAngleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e){var element = obj as UIElement;if (element != null){element.RenderTransformOrigin = new Point(0.5, 0.5);element.RenderTransform = new RotateTransform((double)e.NewValue);}}     }

然后,我们在程序中使用这个我们自己定义的附加属性:

<Grid><Grid.RowDefinitions><RowDefinition Height="313*"/><RowDefinition Height="57*"/></Grid.RowDefinitions><Canvas Grid.Row="0"><Ellipse Name="ellipseRed" Fill="Red" Width="100" Height="60" Canvas.Left="56"Canvas.Top="98" local:TurnoverManager.Angle="{Binding         ElementName=sliderAngle, Path=Value}"/><Rectangle Name="ellipseBlue" Fill="Blue" Width="80" Height="80" Canvas.Left="285"Canvas.Top="171" local:TurnoverManager.Angle="45" /><Button  Name="btnWelcome" Content="欢迎光临" Canvas.Left="265" Canvas.Top="48" FontSize="20" local:TurnoverManager.Angle="60"/></Canvas><WrapPanel Grid.Row="1"><Label Content="角度大小" /><Slider x:Name="sliderAngle" Minimum="0" Maximum="240" Width="300" /></WrapPanel>
</Grid>

在XAML中就可以使用刚才注册(构造)的附加属性了:

这里写图片描述

 效果图展示:

这里写图片描述

三,关于WPF对于依赖属性的基本操作:

我们通过下面的这幅图,简单介绍一下WPF属性系统对依赖属性操作的基本步骤:

这里写图片描述

         借用一个常见的图例,介绍一下WPF属性系统对依赖属性操作的基本步骤:
第一步,确定Base Value,对同一个属性的赋值可能发生在很多地方。比如控件的背景(Background),可能在Style或者控件的构造函数中都对它进行了赋值,这个Base Value就要确定这些值中优先级最高的值,把它作为Base Value。
第二步,估值。如果依赖属性值是计算表达式(Expression),比如说一个绑定,WPF属性系统就会计算表达式,把结果转化成一个实际值。
第三步,动画。动画是一种优先级很高的特殊行为。如果当前属性正在作动画,那么因动画而产生的值会优于前面获得的值,这个也就是WPF中常说的动画优先
第四步,强制。如果我们在FrameworkPropertyMetadata中传入了 CoerceValueCallback委托,WPF属性系统会回调我们传入的的delagate,进行属性值的验证,验证属性值是否在我们允许的范围之内。例如强制设置该值必须大于于0小于10等等。在属性赋值过程中,Coerce拥有最高的优先级,这个优先级要大于动画的优先级别。
第五步,验证。验证是指我们注册依赖属性如果提供了ValidateValueCallback委托,那么最后WPF会调用我们传入的delegate,来验证数据的有效性。当数据无效时会抛出异常来通知。


http://www.ppmy.cn/news/51202.html

相关文章

安卓Socket客户端(及刷新页面)

前言:最近学习了一波java语言和基础安卓开发,综合运用写一段安卓Socket客户端,实现控制小车的前进、后退、左转、右转,具体实现代码记录在下面代码 一、安卓Socket客户端 MainActivity.java package com.example.zyf;import java.io.IOException; import java.io.Output…

Python requests模块:发送HTTP请求和处理响应

目录 前言一、requests模块二、requests携带参数的方式三、get请求URL解码编码四、post请求携带数据编码格式五、get请求携带请求头六、post请求携带参数七、requests.session的使用(可以不用带cookie)八、requests模块其他参数九、response对象十、最后 前言 爬虫是什么&…

026:Mapbox GL加载矢量切片数据源

第026个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中加载矢量切片数据源。将矢量源添加到地图。使用其 tileset URL(mapbox:// + tileset ID)添加任何 Mapbox 托管的 tileset。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例…

ABAP常用系统变量 (SY-)及SY-SUBRC

abap系统变量在syst结构里面可以找到 SY-SUBRC: 系统执行某指令后,表示执行成功与否的变量,0表示成功 SY-DBLNT: 被处理过的记录的笔数 SY-UNAME: 当前使用者登入SAP的USERNAME SY-DATUM: 当前系统日期 SY-UZEIT: 当前系统时间 SY-TCODE: 当前执行程序的Transaction code …

《类和对象》(下篇)

本文主要讲解类和对象的一些其他小知识。 文章目录 前情回顾一、用运算符重载写一个输入流和输出流①流插入②流提取③流提取和流插入的优化 二、const成员三、用运算符重载改变数组 1、再谈构造函数1.1 构造函数体赋值(不相当于初始化)1.2 初始化列表①引出初始化列表②怎么用…

ROS学习第四十一节——SLAM建图

https://download.csdn.net/download/qq_45685327/87721374 准备工作 请先安装相关的ROS功能包: 安装 gmapping 包(用于构建地图):sudo apt install ros-melodic-gmapping 安装地图服务包(用于保存与读取地图):sudo apt install ros-melodic-map-server 安装 navigation 包…

JSP数据库连接池的研究与实现(源代码+论文)

在基于JDBC的数据库实际应用开发中&#xff0c;对数据库连接的管理是一个重点也是一个难点&#xff0c;频繁对数据库的连接与关闭操作、多客户对数据库的并发访问,一定程度上决定了WEB系统的响应以及应用性能。使用数据库连接池方式能对数据库的连接进行管理和维护,上层应用程序…

Ceph入门到精通-Ceph之对象存储网关RADOS Gateway(RGW)

一、Ceph整体架构及RGW在Ceph中的位置 1.Ceph的整体架构 Ceph是一个统一的、分布式的的存储系统&#xff0c;具有优秀的性能、可靠性和可扩展性。Ceph支持对象存储&#xff08;RADOSGW&#xff09;、块存储&#xff08;RBD&#xff09;和文件存储&#xff08;CephFS&#xff…