1. 程式人生 > >模擬實現WPF的依賴屬性及繫結通知機制(3)--依賴物件

模擬實現WPF的依賴屬性及繫結通知機制(3)--依賴物件

下面是依賴對像類的實現:(注,這裡涉及到INotifyPropertyChanged介面,大家可以參考MSDN文件瞭解).

 /// <summary>
    /// 依賴對像,主要提供屬性值和屬性繫結的管理。
    /// </summary>
    public class MyDependencyObject
    {
        private IDictionary<MyDependencyProperty, object> _dict = new Dictionary<MyDependencyProperty, object>();
        private IDictionary<MyDependencyProperty, MyBinding> _bindings = new Dictionary<MyDependencyProperty, MyBinding>();
        public void SetValue(MyDependencyProperty p, object val)
        {
           
            //如果設定值是預設值,則可不儲存,以節省空間.
            object theOldValue = null;
            if (_dict.ContainsKey(p))
            {
                theOldValue = _dict[p];
                //如果已有設定值,且等於當前設定值,則退出
                if (theOldValue == val)
                {
                    return;
                }
                //如果設定值等於預設值,則刪除已有的設定值。
                if (p.DefaultValue == val)
                {
                    _dict.Remove(p);
                    return;
                }
                //設定新的字典值
                _dict[p] = val;
            }
            else
            {
                //如果設定值不等於預設值,則增加設定值,否則不做任何設定。
                if (p.DefaultValue != val)
                {
                    _dict.Add(p, val);
                }
                else
                {
                    return;
                }
               
            }
           
            if (p.PropertyMetadata != null && p.PropertyMetadata.PropertyChangedCallback != null)
            {
                MyDependencyPropertyChangedEventArgs theArgs =
                    new MyDependencyPropertyChangedEventArgs(val, theOldValue, p);
                p.PropertyMetadata.PropertyChangedCallback(this, theArgs);
            }
            //如果是雙向繫結,則需要同步資料到繫結資料來源,這裡假設需要雙向繫結.
            if (_bindings.ContainsKey(p) == true)
            {
                MyBinding theBinding = _bindings[p];
                if (theBinding.TargetObject != null && theBinding.PropertyName != "")
                {
                    System.Reflection.PropertyInfo thePI = theBinding.TargetObject.GetType().GetProperty(theBinding.PropertyName);
                    if (thePI != null && thePI.CanWrite==true)
                    {
                        //對於有索引的設定值比較複雜一點,可利用反射來進行,這裡只是演示簡單屬性。
                        //注意,如果目標類實現了INotifyPropertyChanged介面,並有修改觸發機制,那麼這裡的設定
                        //會觸發目標屬性改變事件,就會觸發MyDependencyObject_PropertyChanged執行,
                        //而MyDependencyObject_PropertyChanged裡又呼叫了SetValue函式,這就會死迴圈,這也是
                        //為什麼前面的程式碼中為什麼要判斷如果已經有的設定值等於當前設定新值直接退出的緣故,就是
                        //為了阻止死迴圈.當然,在目標屬性中set裡面做判斷也可以,但這裡一定要做,
                        //原因大家可以自己想一下。
                        thePI.SetValue(theBinding.TargetObject, val, null);
                    }
                }
            }
        }

        public object GetValue(MyDependencyProperty p)
        {
            //如果被動畫控制,返回動畫計算值。(可能會用到p.Name)

            //如果有本地值,返回本地值
            if (_dict.ContainsKey(p))
            {
                return _dict[p];
            }

            //如果有Style,則返回Style的值

            //返回從視覺化樹中繼承的值

            //最後, 返回依賴屬性的DefaultValue
            return p.DefaultValue;
        }
        /// <summary>
        /// 設定繫結屬性,一樣是模擬微軟的幹活,只不過微軟的這個方法不是在依賴物件裡實現的,
        /// 而是在UIElement裡實現的.
        /// </summary>
        /// <param name="p"></param>
        /// <param name="Binding"></param>
        public void SetBinding(MyDependencyProperty p, MyBinding Binding)
        {
            MyBinding theOld = null;
            //需要先將老的繫結找到並記錄,因為需要解除掛接.
            if (_bindings.ContainsKey(p))
            {
                theOld = _bindings[p];
                _bindings[p] = Binding;
            }
            else
            {
                _bindings.Add(p, Binding);
            }
            //刪除舊的繫結.
            if (theOld != null)
            {
                if (theOld.TargetObject is INotifyPropertyChanged)
                {
                    ((INotifyPropertyChanged)theOld.TargetObject).PropertyChanged -= new PropertyChangedEventHandler(MyDependencyObject_PropertyChanged);
                }
            }

            //如果是單向繫結或者雙向繫結則需要以下掛接。如果只是Onetime則不必要.
            if (Binding.TargetObject is INotifyPropertyChanged)
            {
                ((INotifyPropertyChanged)Binding.TargetObject).PropertyChanged += new PropertyChangedEventHandler(MyDependencyObject_PropertyChanged);
            }

        }
        /// <summary>
        /// 目標屬性發生變化時的處理事件方法。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void MyDependencyObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            MyDependencyProperty p = null;
            //找到繫結屬性所在的依賴屬性
            foreach (var b in _bindings)
            {
                if (b.Value.PropertyName == e.PropertyName)
                {
                    p = b.Key;
                    break;
                }
            }
            //不為空則處理.
            if (p != null)
            {
                System.Reflection.PropertyInfo thePI = sender.GetType().GetProperty(e.PropertyName);
                if (thePI != null && thePI.CanRead == true)
                {
                    object theVal = thePI.GetValue(sender, null);
                    SetValue(p, theVal);
                    //如果目標類INotifyPropertyChanged,繫結模式是ontime,則下面的程式碼就是要接觸與目標屬性的掛接.
                    if (sender is INotifyPropertyChanged)
                    {
                        ((INotifyPropertyChanged)sender).PropertyChanged += new PropertyChangedEventHandler(MyDependencyObject_PropertyChanged);
                    }
                }
            }
        }
    }

為了實現依賴屬性和繫結屬性之間的連動,SetBinding方法至關重要,大家仔細看.