zoukankan      html  css  js  c++  java
  • WPF 基础

    1. CLR 属性

    .Net Framework 中的属性又称为 CLR 属性,是对 private 字段的安全访问包装。
    使用 ILSpy 反编译器可以看到 C# 中代码的属性的编译结果是 set._xx 、get._xx 两个方法;
    即使再多的实例,方法也只有一个拷贝,因此 CLR 属性并不会增加内存的负担;
    语法糖衣。

    2. 依赖属性 propdp

    2.1 比 CLR 属性的优势:

    节省实例对内存的开销;
    属性值可以通过 Binding 依赖在其他对象上;

    每个类在实例的时候,会为各个非静态字段开辟一块内存,而这些字段并非都会被用到,这就造成对内存的浪费;
    而依赖属性在需要用到时能够获取默认值、借用其他对象数据或实时分配空间;

    1. 必须使用依赖对象作为依赖属性的宿主。
    namespace System.Windows
    {
        public class DependencyObject : DispatcherObject
        {
            public object GetValue(DependencyProperty dp);
            public void SetValue(DependencyProperty dp, object value);
        }
    }
    
    public class Student : DependencyObject
    {
        public static readonly DependencyProperty NameProperty = 
            DependencyProperty.Register("Name", typeof(string), typeof(Student));
    }
    
    1. public static readonly,命名规定变量名字加上 Property 表明是一个依赖属性;
    2. DependencyProperty.Register("Name", typeof(string), typeof(Student)) 中第 1 个参数指明以哪个 CLR 属性作为这个依赖属性的包装器,目前虽然没有为这个依赖属性准备包装器,将来会使用名为 Name 的 CLR 属性来包装它;
    3. 第 2 个参数表示存储什么类型的值,第 3 个表示宿主的类型;
    Student stu = new Student();
    new System.Threading.Thread(() =>
    {
        while(true)
        {
            System.Threading.Thread.Sleep(1000);
            App.Current.Dispatcher.Invoke(() =>
            {
                stu.SetValue(Student.NameProperty, DateTime.Now.ToString());
            });
        }
    }).Start();
    
    Binding bd = new Binding("Name") { Source = stu };
    this.textBlock.SetBinding(TextBox.TextProperty, bd);
    

    这样,textBlock 就会实时展示当前时间;因此依赖属性天生就是合格的数据源,不需要实现 INotifyPropertyChanged 接口就能在属性的值发生改变时通知与之关联的 Binding 对象;

    • 再次强调,有没有包装器(CLR 属性),这个依赖属性(DependencyProperty)都存在;
    • 使用包装器:
    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value);}
    }
    
    stu.SetValue(Student.NameProperty, DateTime.Now.ToString());
    = 
    stu.Name = DateTime.Now.ToString();
    
    • 如果作死,DependencyProperty.Register 方法第一个参数跟包装器的名称不一致,那么使用 Binding 时,实例化 Binding 传入的参数是 DependencyProperty.Register 第一个参数名称,而不是包装器的名称;
    • DependencyProperty.Register 第 4 个参数 new PropertyMetadata() 用于依赖属性未被显式赋值时,若读取之则获得此默认值。

    2.2 依赖属性值存取的秘密

    被 static 关键字所修饰的依赖属性对象其作用是用来检索真正的属性值,而不是存储值。
    源码:

    class DependencyProperty
    {
        public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback)
        {
            ...
            DependencyProperty dependencyProperty = RegisterCommon(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
            ...
            return dependencyProperty;
        }
        
        private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
        {
            FromNameKey key = new FromNameKey(name, ownerType);
            ...
            DependencyProperty dependencyProperty = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
    
            lock (Synchronized)
            {
                PropertyFromName[key] = dependencyProperty;
            }
            ...
            return dependencyProperty;
        }
        
        private DependencyProperty(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
        {
            ...
            Flags flags;
            lock (Synchronized)
            {
                flags = (Flags)GetUniqueGlobalIndex(ownerType, name);
                RegisteredPropertyList.Add(this);
            }
            ...
            _packedData = flags;
        }
        
        private static Hashtable PropertyFromName = new Hashtable();
        
        public override int GetHashCode()
        {
            return GlobalIndex;
        }
        
        public int GlobalIndex => (int)(_packedData & Flags.GlobalIndexMask);
    }
    
    class DependencyObject
    {
        EffectiveValueEntry[] _effectiveValues;    
        public object GetValue(DependencyProperty dp)
        {
            ...
            return GetValueEntry(LookupEntry(dp.GlobalIndex), dp, null, RequestFlags.FullyResolved).Value;
        }
        
        internal EffectiveValueEntry GetValueEntry(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, RequestFlags requests)
        {
            ...
            EffectiveValueEntry entry = ((requests & RequestFlags.RawEntry) == 0) ? GetEffectiveValue(entryIndex, dp, requests) : _effectiveValues[entryIndex.Index];
            result = (entryIndex.Found ? entry : new EffectiveValueEntry(dp, BaseValueSourceInternal.Unknown));
            ...
            return result;
            
            // 便于理解简化成
            return _effectiveValues[entryIndex.Index];
    	}
    }
    
    1. DependencyProperty 注册时,会根据 CLR 属性名称和宿主类型名称各自 hash 之后做异得到一个 key,然后从 PropertyFromName 从判断是否存在相同的 key,如果还没有,就实例化一个 DependencyProperty 并与 key 作为键值对保存到 PropertyFromName,最后才返回实例化的对象。这个对象有着一个唯一 GlobalIndex;
    2. 每个 DependencyObject 都有一个 EffectiveValueEntry 数组,EffectiveValueEntry 可以理解为一个小房间,每个存在的 EffectiveValueEntry 的 PropertyIndex 属性的值为某个 DependencyProperty 的 GlobalIndex,因此,DependencyProperty 可以理解为一个 key,需要用时,在 EffectiveValueEntry[] 中根据 GlobalIndex 取或赋值 EffectiveValueEntry 的 value 属性的值.
    3. 至此,也就解释了为什么 public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student)); 是 static 而每个对象都可以往里面"存""取"各自的值,因为它只是一个 key,而不是 value。使用 readonly 是为了保持稳定性。

    3. 附加属性 propa

    3.1 讲解

    <TextBlock Grid.Row="1"/>
    <TextBlock Canvas.Top="30"/>
    <TextBlock DockPanel.Dock="Left"/>
    
    1. Row 是 Grid 的属性,Top 是 Canvas 的属性,Dock 是 DockPanel 的属性,TextBlock 并没有准备这些属性;
    2. 附加属性的作为将属性和数据类型(宿主)解耦;
    3. 附加属性的本质也是依赖属性
    public class Student : DependencyObject
    {
        public static int GetMyProperty(DependencyObject obj)
        {
            return (int)obj.GetValue(MyPropertyProperty);
        }
    
        public static void SetMyProperty(DependencyObject obj, int value)
        {
            obj.SetValue(MyPropertyProperty, value);
        }
    
        public static readonly DependencyProperty MyPropertyProperty =
                DependencyProperty.RegisterAttached("MyProperty", typeof(int), typeof(Student), new PropertyMetadata(0));
    }
    
    public class Human : DependencyObject {}
    
    Human human = new Human();
    Student.SetMyProperty(human, 6);
    int value = Student.GetMyProperty(human);
    

    跟依赖属性保存值一样,值依然被保存在 Human 实例的 EffectiveValueEntry 数组中,只是用于在数组中检索值的依赖属性(即附加属性)并不以 Human 类为宿主而是寄宿在 Student 中。关系不大,反正 CLR 属性名和宿主类型名只用来生成 hash code 和 GlobalIndex。

    3.2 例子

    3.2.1 例子 1
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="1"/>
    </Grid>
    
    Grid grid = new Grid();
    grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star)});
    grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto});
    
    TextBlock tb = new TextBlock();
    Grid.SetRow(tb, 1);
    
    grid.Children.Add(tb);
    
    3.2.2 例子 2
    <Slider x:Name="sliderX" Minimum="0" Maximum="300"/>
    <Slider x:Name="sliderY" Minimum="0" Maximum="300"/>
    <Canvas Height="50" Width="500" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Yellow">
        <Rectangle x:Name="rect" Fill="Red" Width="30" Height="30"
                   Canvas.Left="{Binding ElementName=sliderX, Path=Value}"
                   Canvas.Top="{Binding ElementName=sliderY, Path=Value}" />
    </Canvas>
    

    等价于

    <Slider x:Name="sliderX" Minimum="0" Maximum="300"/>
    <Slider x:Name="sliderY" Minimum="0" Maximum="300"/>
    <Canvas Height="50" Width="500" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Yellow">
        <Rectangle x:Name="rect" Fill="Red" Width="30" Height="30"/>
    </Canvas>
    
    this.rect.SetBinding(Canvas.LeftProperty, new Binding("Value") { Source = this.sliderX});
    this.rect.SetBinding(Canvas.TopProperty, new Binding("Value") { Source = this.sliderY});
    
  • 相关阅读:
    5.21 CSS样式表练习
    5.20 c#验证码练习
    5.20 邮箱注册,及网页嵌套,知识点复习
    5.19 网页注册练习
    5.19练习标签及其 定义
    5.16 兔子生兔子,日期时间练习
    5.15 复习;共5题
    5.11 集合 与 特殊集合
    5.11 集合与特殊集合
    WinForm1
  • 原文地址:https://www.cnblogs.com/MichaelLoveSna/p/14444301.html
Copyright © 2011-2022 走看看