现在的位置: 首页 > 综合 > 正文

WPF 依赖属性(DependencyProperty)

2011年04月29日 ⁄ 综合 ⁄ 共 11734字 ⁄ 字号 评论关闭

下面我们来解释一个重要的概念,依赖属性(DependencyProperty)。

依赖属性测试
public MyWindow()
{
Title
= "Test Dependency Property";
SizeToContent
= SizeToContent.WidthAndHeight;
ResizeMode
= ResizeMode.CanMinimize;
FontSize
= 16;
double[] fnsixe = { 8, 16, 32 };

Grid grid
= new Grid();
Content
= grid;

for (int i = 0; i < 2;i++ )
{
RowDefinition row
= new RowDefinition();
row.Height
= GridLength.Auto;
grid.RowDefinitions.Add(row);
}
for (int i = 0; i < fnsixe.Length;i++ )
{
ColumnDefinition col
= new ColumnDefinition();
col.Width
= GridLength.Auto;
grid.ColumnDefinitions.Add(col);
}
for (int i = 0; i < fnsixe.Length; i++)
{
Button btn
= new Button();
btn.Content
= new TextBlock(new Run("Set window Fontsize To " + fnsixe[i]));
btn.Tag
= fnsixe[i];
btn.HorizontalAlignment
= HorizontalAlignment.Center;
btn.VerticalAlignment
= VerticalAlignment.Center;
btn.Click
+= new RoutedEventHandler(OnWindowFontSize);
grid.Children.Add(btn);
Grid.SetRow(btn,
0);
Grid.SetColumn(btn, i);

btn
= new Button();
btn.Content
= new TextBlock(new Run("Set button Fontsize To " + fnsixe[i]));
btn.Tag
= fnsixe[i];
btn.HorizontalAlignment
= HorizontalAlignment.Center;
btn.VerticalAlignment
= VerticalAlignment.Center;
btn.Click
+= new RoutedEventHandler(OnbuttonFontSize);
grid.Children.Add(btn);
Grid.SetRow(btn,
1);
Grid.SetColumn(btn, i);

}
}

void OnWindowFontSize(object sender, RoutedEventArgs e)
{
Button btn
= sender as Button;
FontSize
= (double)btn.Tag;
}
void OnbuttonFontSize(object sender, RoutedEventArgs e)
{
Button btn
= sender as Button;
btn.FontSize
= (double)btn.Tag;
}

可以看到上面一排按钮的操作会改变整个窗口内所有控件的Fontsize,当没有启动button自己单独的Fontsize属性时,他的Fontsize大小是用的窗口的Fontsize大小,当启用了以后就会使用自己的Fontsize属性。(难道有两个Fontsize属性在button中???)

其实,Fontsize属性是一个依赖属性。

WPF中的依赖属性

首先我们来看看element tree:

看起来以上我们看见的element的结构就是上图(忽略了没有看到的对象)。这个树仅仅表示我们看到的元素。并不是实际的逻辑树。逻辑树的话应该还要包括Grid的RowDefinition和ColumnDefinition,以及TextBlock中的Run对象。他们都不是可视化对象,但是他们都是继承与ContentElement.也不是一个正确的视觉树。

但是我们可以通过上图来理解刚才的依赖属性如何被使用的。

当一个程序明确的规定视觉树上一个对象的fontsize属性,那么属于他的节点之下的所有对象都会沿袭这个属性,然而如果某个子节点明确的设定了自己的fontsize,就不会沿袭父节点的fontsize属性。当我们点击上面一排按钮的时候,将窗口的fontsize设定为一个固定值,在这之后所有没有设置fontsize属性的子节点都会被设定为这个值,在这之中肯定有一个机制来控制这个设置过程。

其实fontsize这个属性是在Contorl中定义的,它具有默认值,默认的值优先级最低,从父节点上沿袭来的fontsize优先级比默认高,而直接对对象本身进行设置的优先级最高。

Uielemnet中的:AllowDrop、Isenabled、IsVisble、SnapToDevicePixels,Frameworkelement所定义的Cultureinfo、FlowDirection、InputScope,Contorl中的FontFamiliy、FontStrerch、FontStyles、FontWeight、Foregroud都是具有以上我们说的优先权机制。(但是backgroud不会沿袭,因为他的默认值为null,呈现出的效果就是透明的,所以感觉就像是沿袭了父节点的设置一样。

对于一个传统的属性,我们一般会如下封装:

public double FontSize

        {

            get { return _FontSize; }

            set {

                _FontSize = value;

                //do something...

            }

        }

这是wpf中fontsize的定义

[CommonDependencyProperty]

        public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(typeof(Control), new FrameworkPropertyMetadata(SystemFonts.MessageFontSize, FrameworkPropertyMetadataOptions.Inherits));

下面是属性的定义:

[Category("Appearance"), Localizability(LocalizationCategory.None), TypeConverter(typeof(FontSizeConverter)), Bindable(true)]

        public double FontSize

        {

            get

            {

                return (double) base.GetValue(FontSizeProperty);

            }

            set

            {

                base.SetValue(FontSizeProperty, value);

            }

        }

说明依赖属性是通过父类的getvalue和setvalue来获取和设定的。

我们来讨论一个依赖属性的产生原因:

1:由于一个类型的依赖属性大多数情况下都保持默认值,例如几百个button的fontsize一般都是一样的,甚至整个一个软件的fontsize都是一种。那么每个对象保存一个fontsize就显得没有那个必要了,我们必须通过一个方式来保存这个属性,来让所有一个类的对象共享这个属性。我们的想法是把他做成一个静态的值。但是如果直接定义一个:

public static string name = string.empty;

但是我们可以看到这样的话,这个属性要是被设置的话,就会将所有的对象的这个属性全部都设置了。因此我们需要一个数据结构来存储这个值。

当要是当前对象设置了自己本身的这个属性的话,就在本地存储一个关于这个属性的值,来保证不会因为一个对象设置了这个值而导致所有的对象该属性都发生变化。

考虑到可能会涉及到属性的继承,所有我们还需要在数据结构中加入当前类型。

所以一个依赖属性应该包括以下内容:name,propertyType,ownerType,defaultValue。

我们用一个字典来存储这些所有的属性,这样的话就可以提高效率。

MyDependencyProperty
class MyDependencyProperty
{
internal static Dictionary<object, MyDependencyProperty> RegisteredDps = new Dictionary<object, MyDependencyProperty>();
internal string Name;
internal object Value;
internal object HashCode;

private MyDependencyProperty(string name, Type propertyName, Type ownerType, object defaultValue)
{
this.Name = name;
this.Value = defaultValue;
this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();
}

public static MyDependencyProperty Register(string name, Type propertyType, Type ownerType, object defaultValue)
{
MyDependencyProperty dp
= new MyDependencyProperty(name, propertyType, ownerType, defaultValue);
RegisteredDps.Add(dp.HashCode, dp);
return dp;
}
}

我们设计了私有的构造函数,只通过Register方法来添加属性,这样可以保证一切的依赖属性都是经过我们的验证的。并且所有的依赖属性一旦都早出来就会立即添加入数据集合。

当我们一个类需要使用这个依赖属性,可以是如下方式:

View Code
class MyDependencyObject
{
public static readonly MyDependencyProperty NameProperty =
MyDependencyProperty.Register(
"Name", typeof(string), typeof(MyDependencyObject), string.Empty);
public object GetValue(MyDependencyProperty dp)
{
return MyDependencyProperty.RegisteredDps[dp.HashCode].Value;
}
public void SetValue(MyDependencyProperty dp, object value)
{
MyDependencyProperty.RegisteredDps[dp.HashCode].Value
= value;
}
public string Name
{
get
{
return (string)GetValue(NameProperty);
}
set
{
SetValue(NameProperty, value);
}
}
}

这样的话所有这个类的对象都不在拥有这个属性的内存,而是所有对象公用MyDependencyProperty类的静态数据集合RegisteredDps来存储所有的属性对象(MyDependencyProperty)。但是这个设计还有一定的问题,虽然解决了所有的依赖属性都存储在一个数据结构中的问题,但是没有解决同一个类的多个对象设置一个依赖属性时,所有的依赖属性都会被设置的问题。所以我们需要在本地添加一个数据集合,当我们设置这个属性的时候,就在这个数据集合中存储这个值,再次之后所有需要这个属性的值都是从本地存储的数据集合中找到的。

MyDependencyObject
class MyDependencyObject
{
//这个list用来存储设置的依赖属性值,包括这个类的所有的依赖属性,都会在设置后存储在这里
private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();
public static readonly MyDependencyProperty NameProperty =
MyDependencyProperty.Register(
"Name", typeof(string), typeof(MyDependencyObject), string.Empty);
public object GetValue(MyDependencyProperty dp)
{
//在数据结构中查找对应的依赖属性是否在本地有值。
EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
if (effectiveValue.PropertyIndex != 0)
{
//如果获取的元素的PropertyIndex不等于0,说明本地存在这个属性的值
return effectiveValue.Value;
}
else
{
//本地不存在就去Dictionary中区查找。
return MyDependencyProperty.RegisteredDps[dp.HashCode].Value;
}
}
public void SetValue(MyDependencyProperty dp, object value)
{
//在数据结构中查找对应的依赖属性是否在本地有值。
EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
if (effectiveValue.PropertyIndex != 0)
{
//如果获取的元素的PropertyIndex不等于0,说明本地存在这个属性的值
effectiveValue.Value = value;
}
else
{
//本地不存在,但是我们进行了Setvalue的调用,所以需要在本地创建一个该属性的数据结构
effectiveValue = new EffectiveValueEntry() { PropertyIndex = dp.Index, Value = value };
_effectiveValues.Add(effectiveValue);
}
}
public string Name
{
get
{
return (string)GetValue(NameProperty);
}
set
{
SetValue(NameProperty, value);
}
}
}
internal struct EffectiveValueEntry
{
internal int PropertyIndex { get; set; }

internal object Value { get; set; }
}

当然依赖属性类中也需要添加属性的index。

View Code
class MyDependencyProperty
{
internal static Dictionary<object, MyDependencyProperty> RegisteredDps = new Dictionary<object, MyDependencyProperty>();
internal string Name;
internal object Value;
internal object HashCode;
private static int globalIndex = 0;//所有的属性值的index将都会大于0
internal int Index;

private MyDependencyProperty(string name, Type propertyName, Type ownerType, object defaultValue)
{
this.Name = name;
this.Value = defaultValue;
this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();
}

public static MyDependencyProperty Register(string name, Type propertyType, Type ownerType, object defaultValue)
{
MyDependencyProperty dp
= new MyDependencyProperty(name, propertyType, ownerType, defaultValue);
globalIndex
++;
dp.Index
= globalIndex;
RegisteredDps.Add(dp.HashCode, dp);
return dp;
}
}

这样我们就解决了,当一个对象依赖属性设置后,其他该类对象的依赖属性也被设置的情况。 在DependencyObject加入了一个_effectiveValues,就是把所有修改过的DP都保存在EffectiveValueEntry里,这样,就可以达到只保存修改的属性,未修改过的属性仍然读取DP的默认值,优化了属性的储存。

但随着实际的使用,又一个问题暴露出来了。使用继承,子类可以重写父类的字段,换句话说,这个默认值应该是可以子类化的。那么怎么处理,子类重新注册一个DP,传入新的默认值?

其实很简单,我们在MyDependencyProperty中添加一个链表(List),存储这个依赖属性被定义它的类的子类对象重写时候的值。这样,因为每个依赖属性其实在clr加载的时候就已经伴随着这个类对象被构造或者调用,这个依赖属性作为一个静态数据结构就已经被在内存中创建了一份,然后其中保存着这个属性的默认值,当一个具体的对象修改自己本身的这个属性的时候,就在本地存储一个这个属性。(这里的本地是指在当前类的实例内存空间中存储一份属性的实例)。当我们需要访问这个属性的时候就先检查本地对象中是否有这个属性,没有就去访问这个静态的内存,那里面是属于所有这个类对象的依赖属性的值。

对于一个子类来说,他可以更新这个父类中关于这个依赖属性的初始值。

如下来做,在我们原有个MyDependencyProperty中添加一个list,这个list不是静态的,但我们使用这个类的子类,那么这个类的子类应该可以去重写这个依赖属性的默认值,当重写的时候,我们首先找到它从父类继承的MyDependencyProperty对象,然后在list中加入当前子类的类型和这个类型依赖属性的默认值。

当使用子类去获取依赖属性的值的时候,首先检查本地有没有设置好的属性值,没有就去MyDependencyProperty中找,找的时候首先检查list中有没有符合要求的,没有再去取默认值。

View Code
public class DependencyObject
{
private List<EffectiveValueEntry> _effectiveValues = new List<EffectiveValueEntry>();

public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(DependencyObject), "Name");

public object GetValue(DependencyProperty dp)
{
//获取一个依赖属性的值,首先看本地是否有设定的值
EffectiveValueEntry effectiveValue = _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
if (effectiveValue.PropertyIndex != 0)
{
return effectiveValue.Value;
}
else
{
//没有本地设定值再去DependencyProperty中查找
PropertyMetadata metadata;
metadata
= DependencyProperty.RegisteredDps[dp.HashCode].GetMetadata(this.GetType());
return metadata.Value;
}
}

public void SetValue(DependencyProperty dp, object value)
{
EffectiveValueEntry effectiveValue
= _effectiveValues.FirstOrDefault((i) => i.PropertyIndex == dp.Index);
if (effectiveValue.PropertyIndex != 0)
{
effectiveValue.Value
= value;
}
else
{
effectiveValue
= new EffectiveValueEntry() { PropertyIndex = dp.Index, Value = value };
_effectiveValues.Add(effectiveValue);
}
}

public string Name
{
get
{
return (string)GetValue(NameProperty);
}
set
{
SetValue(NameProperty, value);
}
}
}

public class SubDependencyObject : DependencyObject
{
static SubDependencyObject()
{
//子类重写这个依赖属性的默认值
NameProperty.OverrideMetadata(typeof(SubDependencyObject), new PropertyMetadata("SubName"));
}
}

public class DependencyProperty
{
private static int globalIndex = 0;
internal static Dictionary<object, DependencyProperty> RegisteredDps = new Dictionary<object, DependencyProperty>();
internal string Name;
internal object Value;
internal int Index;
internal object HashCode;
//_defaultMetadata表示这个依赖属性在定义类中的值
private PropertyMetadata _defaultMetadata;
//这个List存储所有的依赖属性可能值(子类重写的值)
private List<PropertyMetadata> _metadataMap = new List<PropertyMetadata>();


private DependencyProperty(string name, Type propertyName, Type ownerType, object defaultValue)
{
this.Name = name;
this.Value = defaultValue;
this.HashCode = name.GetHashCode() ^ ownerType.GetHashCode();
//只有父类才会构造这个DependencyProperty对象,将父类的值作为默认值
PropertyMetadata metadata = new PropertyMetadata(defaultValue) { Type = ownerType };
_metadataMap.Add(metadata);
_defaultMetadata
= metadata;
}

public static DependencyProperty Register(string name, Type propertyType, Type ownerType, object defaultValue)
{
DependencyProperty dp
= new DependencyProperty(name, propertyType, ownerType, defaultValue);
globalIndex
++;
dp.Index
= globalIndex;
RegisteredDps.Add(dp.HashCode, dp);
return dp;
}
/// <summary>
/// 子类重写这个依赖属性
/// </summary>
public void OverrideMetadata(Type forType, PropertyMetadata metadata)
{
metadata.Type
= forType;
_metadataMap.Add(metadata);
//将重写的值加入这个依赖属性的数据结构
}

public PropertyMetadata GetMetadata(Type type)
{
//判断list中是否有当前类的依赖属性值
PropertyMetadata medatata = _metadataMap.FirstOrDefault((i) => i.Type == type) ??
_metadataMap.FirstOrDefault((i)
=> type.IsSubclassOf(i.Type));
if (medatata == null)
{
//没有就将默认值返回(子类未重写,返回父类定义的默认值)
medatata = _defaultMetadata;
}
return medatata;//子类重写就返回子类的值。
}
}

internal struct EffectiveValueEntry
{
internal int PropertyIndex { get; set; }

internal object Value { get; set; }
}

public class PropertyMetadata
{
public Type Type { get; set; }
public object Value { get; set; }
//存储子类类型以及它对于的依赖属性的默认值
public PropertyMetadata(object defaultValue)
{
this.Value = defaultValue;
}
}

当然上述的依赖属性方式还是有一定的问题,比如说Class2继承与Class1,Class1包含一个依赖属性dp1,他对于设置的默认值是value1,而class1继承与class0,class中构造了这个依赖属性,并设置默认值为value0,那么用我们上述的方式获取到的Class2的依赖属性dp1的值将是value1.

View Code
public class SubDependencyObject1 : SubDependencyObject
{
}
static void Main(string[] args)
{
SubDependencyObject1 subObj
= new SubDependencyObject1();
Console.WriteLine(subObj.Name);
}

们看到这里输出的是Name而不是SubName。问题出在了这里:

public PropertyMetadata GetMetadata(Type type)

        {

            //判断list中是否有当前类的依赖属性值

            PropertyMetadata medatata = _metadataMap.FirstOrDefault((i) => i.Type == type) ??

                _metadataMap.FirstOrDefault((i) => type.IsSubclassOf(i.Type));

            if (medatata == null)

            {//没有就将默认值就应该继

抱歉!评论已关闭.