پرینت

آموزش WPF – Property Dependency

آموزش WPF Property Dependency

فهرست مطالب

-         یک سیستم Property جدید

-         تفاوت بین Dependency Property و CLR Property

-         پیشرفت های Dependency Property

-         چگونه یک Dependency Property را تعریف کنید

-         تعریف ابرداده ها برای Property ها

-         یادداشت گذاری بر روی Collection Type Dependency Property

-         ارث بری   PropertyValue

-         Property های متصل شده

 

در این مقاله به معرفی یک سیستم property جدید می پردازیم که بر سیستم WPF property تاکید دارد. ما پیشتر معرفی خواهیم کرد چگونه شما می توانید از این property ها برای تولید callback های سفارشی استفاده کنید، property های متصل شده را ایجاد نمایید؛ و با استفاده از تمامی Depency Property های جدید انیمیشن ها و سبک ها را بکاربرید. 

 

یک سیستم Property جدید

بله، WPF با یک تکنیک جدید از تعریف یک کنترل همراه است. واحد سیستم property جدید یک property Dependency است و کلاس wrapper می تواند یک Dependecy property جدید با نام DependencyObject ایجاد کند. ما از آن برای ثبت یک Depency property درون سیستم property استفاده کنید تا مطمئن شوید که شی property درون آن را شامل می شود و می توانیم مقدار آن property ها را هر زمان که دوست داریم به دست آورده و تنظیم کنیم. ما می توانیم از CLR property معمولی برای پوشش پیرامون یک dependency property و از GetValue و SetValue برای گرفتن و تنظیم مقادیر ارسال شده درون آن استفاده کنیم.

این اغلب همانند سیستم CLR property عمل می کند. مزایای سیستم property جدید چه هستند. و ببنیم تفاوت Depency property و CLR property .

برای کار با dependency property، شما باید کلاس را از DepencyObject مشتق کنید به عنوان یک بازدید کننده سیستم Property جدید درون DependencyObject نگه داشته می شود.

 

تفاوت بین Dependency Property و CLR Property

CLR property تنها یک پوششدهنده متغییرهای private است. آن از متد های Get / Set برای بازیابی و ذخیره سازی مقدار متغییر درون آن استفاده می کند. یک CLR property تنها یک بلاک را به شما ارائه می کند که می توانید هر زمان که یک property ، get یا set می شود کد را برای فراخوانی بنویسید. بدین وسیله سیستم CLR property کاملا ساده است.

به عبارت دیگر ، قابلیت های سیستم Depency property بسیار بزرگ است. ایده Dependency property مقدار property را بر اسا مقدار ورودی های خارجی محاسبه می کند. از این رو شما می توانید به dependency property بگویید با اکثر ویژگی های درون WPF که ما معرفی کرده ایم می تواند کار کند.

 

 

 

مزایای Dependency Property

یک Dependency Property تعدادی بسیاری زیادی مزیت بر CLR property ها دارد.قبل از اینکه ما dependency property خودمان را ایجاد کنیم کمی در مورد این مزیت ها بحث می کنیم:

1.     Property Value Inheritance: منظور از Property Value Inheritance این است که مقدار یک Dependency property می تواند در سلسله مراتبی override شود در چنین روشی مقدار با بالاترین برتری تنظیم خواهد شد.

2.     Data Validation: ما می توانیم وضع کنیم Data Validation (ارزیابی داده) به طور خودکار هر زمان مقدار property تغییر پیدا کرد تریگر شود.

3.     Participation in Animation: Dependency property می تواند تحرک پذیر شود. WPF animation قابلیت های زیادی برای تغییر مقدار در یک فاصله زمانی را دارد. با تعریف یک dependency property ، شما می توانید از Animation از آن Property پشتیبانی کنید.

4.     Participation in Styles (شراکت در طرح ها): طرح ها عناصری هستند که کنترل را تعریف می کنند. ما می توانیم از Style Setters در Dependency Property استفاده کنیم.

5.     Participation in Templates (شراکت در الگو ها): الگو ها عناصری هستند که کل ساختار عنصر را تعریف می کنند. با تعریف Dependency property، ما می توانیم از آن در الگو ها استفاده کنیم.

6.     DataBinding: هر کدام از Dependency property ها می تواند خودش هر زمان مقدار property تغییر کرد، INotifyPropertyChanged را فراخوانی کند. DataBinding از درون پشتیبانی شده است.

7.     CallBack ها: شما می توانید به یک dependency property چند callback داشته باشید، از این رو هر زمان یک property تغییر کرد، یک callback برافراشته می شود.

8.     Resource ها: یک Dependency property می تواند یک Resource داشته باشد. از این رو در XAML، شما می توانید یک Resource را برای تعریف یک Dependency property تعریف کنید.

9.     Metadata override ها: شما می توانید برخی از رفتارهای یک dependency property را با استفاده از PropertyMetaData تعریف کنید.به این ترتیب، override کردن یک ابرداده از یک property مشتق شده نیازی به تعریف مجدد یا پیاده سازی دوباره کل تعریف property نخواهد داشت.

10.            Designer Support: یک dependency property از Visual Studio Designer پشتیبانی می کند. شما می توانید تمامی ویژگی های dependency یک کنترل فهرست شده درون Property Window of the Designer را مشاهده کنید.

 

در این لیست، برخی از ویژگی ها تنها از Dependency Property پشتیبانی می کنند. Animation, Styles, Templates, Property Value Inheritance و غیره می توانند تنها در استفاده از Dependency property سهیم شوند. در صورتی که شما از CLR property به جای موارد این چنینی استفاده کنید، کامپایلر یک خطار را ایجاد خواهد کرد.

 

چگونه یک Dependency Property را تعریف کنید

اکنون با ارائه کد واقعی، چگونگی تعریف یک Dependency Property را مشاهده می کنید.

 

public static readonly DependencyProperty MyCustomProperty =

DependencyProperty.Register("MyCustom", typeof(string), typeof(Window1));

public string MyCustom

{

    get

    {

        return this.GetValue(MyCustomProperty) as string;

    }

    set

    {

        this.SetValue(MyCustomProperty, value);

    }

}

 

در بالای کد، مت یک Dependency property به راحتی تعریف کرده ام. شما از این که چرا یک dependency property به عنوان static اعلان شده است تعجب کرده اید. بله، حتی من هم هنگامی که اولین بار آن را دیدم تعجب کردم. اما بعد از آن، بعد از مطالعه در مورد Dependency property، من دانستم که یک depency property در سطح کلاس تعمییر و نگهداری می شود.، از این رو شما از Class A می خواهید یک property B داشته باشد. Property B برای تمامی اشیا حفظ و نگهداری خواهند شد که کلاس A به طور خاص آن را دارد. از این رو Dependency property یک ناظر را برای تمامی آن property های حفظ شده توسط کلاس A ایجاد می کند.توجه به این نکته مهم است که یک Dependency property به صورت static تعمییر و نگهداری می شود.

 

نامگذاری تبدیل یک dependency property بر این است که آن دارای جلد property مشابهی است که به عنوان اولین آرگومان ارسال شده است. به این ترتیب در مورد ما، نام  جلد "MyCustom" را ما در برنامه خودمان استفاده خواهیم کرد تا اولین آرگومان از متد Register را ارسال کند، و همچنین نام Dependency property همواره با Property برای کلید Wrapper اصلی پسوند گذاری خواهد شد. در مورد ما، نام Dependency Property، MyCustomProperty است. در صورتی که شما این را دنبال نکنید، برخی از این قابلیت ها به طور غیر عادی در SetValue و GetValue عمل خواهد کرد. بنابراین در صورتی که شما بخواهید logic مختص خود در هنگامی که property آورده شده است بنویسید، این ها callback ها را برای آن ها عملیاتی می کنند.

 

تعریف کردن ابر داده ها برای Property ها

بعد از تعریف ساده ترین Dependency property حاضر، کمی پیشرفت به خرج دادیم. یک ابرداده را به DependencyProperty اضافه کردیم. ما از شی کلاس PropertyMetaData استفاده کردیم. در صورتی که شما درون یک FrameworkElement هستید من در یک UserControl یا یک Window جای دارم. شما می توانید به جای PropertyMetaData از FrameworkMetaData استفاده نمایید.

 

static FrameworkPropertyMetadata propertymetadata =

new FrameworkPropertyMetadata("Comes as Default",

 FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |

FrameworkPropertyMetadataOptions.Journal,new

PropertyChangedCallback(MyCustom_PropertyChanged),

new CoerceValueCallback(MyCustom_CoerceValue),

false, UpdateSourceTrigger.PropertyChanged);

 

public static readonly DependencyProperty MyCustomProperty =

DependencyProperty.Register("MyCustom", typeof(string), typeof(Window1),

propertymetadata, new ValidateValueCallback(MyCustom_Validate));

 

private static void MyCustom_PropertyChanged(DependencyObject dobj,

DependencyPropertyChangedEventArgs e)

{

  //To be called whenever the DP is changed.

  MessageBox.Show(string.Format(

     "Property changed is fired : OldValue {0} NewValue : {1}", e.OldValue, e.NewValue));

}

 

private static object MyCustom_CoerceValue(DependencyObject dobj, object Value)

{

//called whenever dependency property value is reevaluated. The return value is the

//latest value set to the dependency property

MessageBox.Show(string.Format("CoerceValue is fired : Value {0}", Value));

return Value;

}

 

private static bool MyCustom_Validate(object Value)

{

//Custom validation block which takes in the value of DP

//Returns true / false based on success / failure of the validation

MessageBox.Show(string.Format("DataValidation is Fired : Value {0}", Value));

return true;

}

 

public string MyCustom

{

get

{

return this.GetValue(MyCustomProperty) as string;

}

set

{

this.SetValue(MyCustomProperty, value);

}

 

}

 

  

 

مسلما این کمی ارتقا یافته تر است. ما یک FrameworkMetaData را تعریف می کنیم که DefaultValue را برای Dependency property به عنوان "آورده های پیش فرض" مشخص کرده بودیم. مسلما اگر ما تنظیم این مقدار DependencyProperty را تغییر ندهیم، شی این مقدار را به صورت پیش فرض انتخاب خواهد کرد.

FrameworkPropertyMetaDataOption یک فرصت را در اختیار شما می گذارد تا این گزینه های ابرداده متنوع را برای dependency property را ارتقا دهد. گزینه های متنوعی در این شمارش مشاهده می کنیم.

 

-         AffectsMeasure: AffectsMeasure را برای عنصر Layout که شی جای داده است فراخوانی می کند.

-         AffectsArrange:AffectsArrange را برای نمای کلی عنصر فراخوانی می کند. 

-         AffectsParentMeasure: AffectsMeasure را برای شرکا فراخوانی می کند.

-         AffectsParentArrange: AffectsArrange را برای کنترل شرکا فراخوانی می کند.

-         AffectsRender: هنگامی که مقدار تغییر می کند کنترل را render می کند.

-         NotDataBindable: Databinding می تواند غیرفعال شود.

-         BindsTwoWayByDefault: در حالت پیش فرض، databinding ، OneWay خواهد بود. در صورتی که property شما یک اتصال پیش فرض TwoWay داشته باشد، می توانید از آن استفاده کنید.

-         Inherits: مطمئن می سازد که کنترل بچه مقدار را از پایه آن به ارث برده است.

 

PropertyChangedCallback زمانی فراخوانی می شود که مقدار property تغییر کند. از این رو آن بعد از اینکه مقدار واقعی تغییر کرد فراخوانی خواهد شد. CoerceValue قبل از اینکه مقدار واقعی تغییر کند فراخوانی می شود. منظور این است که بعد از اینکه CoerceValue فراخوانی شد، مقدار که ما از آن برای property مشخص کرده بودیم برگردانده خواهد شد. بلاک validation قبل از CoerceValue فراخوانی خواهد شد، بسته به اینکه مقدار ارسال شده به property معتبر است یا خیر این رویداد حتمی است. بسته به اعتبار، باید true یا false را بر گرداند. اگر مقدار false است، runtime یک خطا را اعلان می کند.

 

بنابراین در برنامه بالا بعد از اجرای کد،MessageBox موارد زیر را دارد:

 

-         ValidateCallback: شما باید منطق را برای ارزیابی داده های ورودی به عنوان آرگومان Value انتخاب کنید. True مقداری می گیرد ،false خطایی را throw خواهد کرد.

-         CoerceValue: می تواند مقدار را بسته به مقدار ارسال شده به صورت آرگومان تغییر یا اصلاح کرد. همچنین DependencyObject را به عنوان آرگومان دریافت کرد. شما می توانید با استفاده از متد CoerceValueCallback مرتبط با DependencyProperty را با استفاده از CoreValue فراخوانی کنید.

-         PropertyChanged: این Messagebox نهایی است که شما مشاهده می کنید که بعد از اینکه مقدار کاملا تغییر پیدا کرد فراخوانی خواهد شد. شما می توانید از DependencyPropertyChangeEventArgs، OldValue و NewValue را get کنید.

 

 

یادداشت برداری از CollectionType Dependency Property

CollectionType dependency property زمانی است که شما می خواهید یک مجموعه از DependencyObject را درون یک collection نگهدارید. ما این در پروژه خودمان استفاده کرده ایم.اصولا، زمانی که شما یک dependency object ایجاد می کنید و مقدار پیش فرض را به آن ارسال می کنید، مقدار برای هر نمونه از شی ای که شما ایجاد می کنید مقدار پیش فرض نخواهد بود. در عوض، آن مقدار اولیه برای Dependency property است که برای نوع ثبت شده است به این ترتیب، در صورتی که شما می خواهید یک مجموعه شی Dependency را ایجاد نمایید و می خواهید شی مقدار پیش فرض مختص به خود را داشته باشد، باید این مقدار  را برای هر آیتم خاص مجموعه dependency مشخص کنید تا اینکه از تعریف MetaData استفاده کنید. برای مثال،

 

public static readonly DependencyPropertyKey ObserverPropertyKey =

DependencyProperty.RegisterReadOnly("Observer", typeof(ObservableCollection<Button>),

typeof(MyCustomUC),new FrameworkPropertyMetadata(new ObservableCollection<Button>()));

public static readonly DependencyProperty ObserverProperty =

     ObserverPropertyKey.DependencyProperty;

public ObservableCollection<Button> Observer

{

get

{

return (ObservableCollection<Button>)GetValue(ObserverProperty);

}

}

 

در کد بالا، شما می توانید مشاهده کنید که یک DependencyPropertyKey با استفاده از متد RegisterReadOnly اعلان کنید. ObservableCollection معمولا یک مجموعه از کیلد ها (Button) است که یک DependencyObject است. اکنون اگر شما از این مجموعه استفاده می کنید، شما هنگامی که شی UserControl را ایجاد می کنید، مشاهده خواهید کرد،هر کدام از آن ها به جای دربرداشتن dependency property مختص به خود به Dependency Property مشابه ارجاع خواهند شد. طبق تعریف، هر dependencyproperty با استفاده از نوع آن حافظه را اختصاص می دهد، به این ترتیب اگر شی 2 یک نمونه جدید از DependencyProperty ایجاد می کند، آن Object 1 collection را کامل می نویسد. بدین وسیله شی مانند یک کلاس Singleton عمل می کند. برای فائق آمدن براین شرایط، هر زمان یک شی جدید از کلاس ایجاد می شود، باید تنظیم مجموعه را با یک نمونه جدید تغییر دهید.از آن جا که property ، readonly است، شما باید برای ایجاد نمونه جدید با استفاده از DependencyPropertyKey از SetValue استفاده نمایید. 

 

public MyCustomUC()

{

            InitializeComponent();

            SetValue(ObserverPropertyKey, new ObservableCollection<Button>());

}

 

برای هر نمونه، مجموعه دوباره تنظیم خواهد شد و به این ترتیب یک مجموعه واحد برای هر UserControl ایجاد شده شاهد خواهید بود.

 

ارث بری از PropertyValue

DependencyProperty از مقدار ارث بری Property پشتیبانی می کند. طبق تعریف، بعد از اینکه شما یک DependencyObject را ایجاد کردید، شما می توانید به راحتی یک DependencyProperty را برای تمامی کنترل های کودک خود با استفاده از متد AddOwner مرتبط با DependencyProperty ارث بری کنید.

هر کدام از DependencyProperty دارای متد AddOwner است، که یک اتصال به DependencyProperty دیگر تعریف شده ایجاد می کند. خوب شما یک DependencyObject A دارید، که دارای خصوصویتی به نام Width است. شما می خواهید مقدار DependencyObject B از مقدار A ارث بری کند.

 

public class A :DependencyObject

{

public static readonly DependencyProperty HeightProperty =

   DependencyProperty.Register("Height", typeof(int), typeof(A),

new FrameworkPropertyMetadata(0,

FrameworkPropertyMetadataOptions.Inherits));

 

public int Height

{

get

{

return (int)GetValue(HeightProperty);

}

set

{

SetValue(HeightProperty, value);

}

}

 

public B BObject { get; set; }

}

 

public class B : DependencyObject

{

public static readonly DependencyProperty HeightProperty;

 

static B()

{

HeightProperty = A.HeightProperty.AddOwner(typeof(B),

new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits));

}

 

public int Height

{

get

{

return (int)GetValue(HeightProperty);

}

set

{

SetValue(HeightProperty, value);

}

}

}

 

در کد بالا، شما می توانید ببینید، کلاس B از DependencyProperty Height با استفاده از متد AddOwner بدون اعلان در کلاس ارث بری می کند. به این ترتیب، هنگامی که A اعلان شود، در صورتی که شما خصوصیت ارتفاع A را مشخص کرده باشید، به طور خودکار به شی B کودک ارث بری شده انتقال داده خواهد شد.

این شبیه به اشیا معمولی است. هنگامی که شما پس زمینه یک پنجره را مشخص می کنید، به طور خودکار آن از تمامی عناصر کودک ارث بری خواهد کرد، بدین وسیله پس زمینه هر کنترل به طور یکسان رفتار خواهد کرد.

 

به روز رسانی

حتی اگر PropertyValueInhertence برای هر ویژگی dependency وجود دارد، به طور خودکار برای AttachedProperties کار خواهد کرد. من می دانم که Property Value Inheritance تنها زمامی کاری می کند که property به صورت attached در نظرگرفته شده باشد. اگر شما مقدار پیش فرض را برای Attached property تظنیم کنید و همچنین FrameworkMetaData.Inherits را تظنیم کنید، مقدار خصوصیت از Parent to Child به طور خودکار ارث بری خواهد شد و فرصتی برای Children برای تغییر محتوا به دست می آید.

 

Attached Properties  (ویژگی های متصل شده)

Attached property مفهوم جالب دیگری است. Attached property شما را قادر می سازد یک خصوصیت را به یک شی متصل کنید که در خارج از شی با هم مقداری را برای استفاده از آن شی تعریف کنند.

خوب شما یک DockPanel درون اتصالی که می خواهید کنترل های شما ظاهر شود اظهار کرده اید. حالا DockPanel یک AttchedProperty را ثبت می کند.

 

public static readonly DependencyProperty DockProperty =

DependencyProperty.RegisterAttached("Dock", typeof(Dock), typeof(DockPanel),

 new FrameworkPropertyMetadata(Dock.Left,

new PropertyChangedCallback(DockPanel.OnDockChanged)),

 new ValidateValueCallback(DockPanel.IsValidDock));

 

شما می توانید مشاهده کنید، DockProperty درون DockPanel به صورت Attched تعریف شده است.  ما برای ثبت DependencyProperty متصل شده از متد RegisterAttached استفاده می کنیم. به این ترتیب هر UIElement کودک به DoackPanel خصوصیت Dock متصل شده به آن را به دست خواهد آورد بدین وسیله می تواند مقدار خود را به طور خودکار به DockPanel انتقال دهد.

 

خوب یک Attached DependencyProperty را اظهار می کنیم:

public static readonly DependencyProperty IsValuePassedProperty =

DependencyProperty.RegisterAttached("IsValuePassed", typeof(bool), typeof(Window1),

new FrameworkPropertyMetadata(new PropertyChangedCallback(IsValuePassed_Changed)));

 

public static void SetIsValuePassed(DependencyObject obj, bool value)

{

obj.SetValue(IsValuePassedProperty, value);

}

 

public static bool GetIsValuePassed(DependencyObject obj)

{

return (bool)obj.GetValue(IsValuePassedProperty);

}

 

 

در اینجا من یک DependencyObject را اظهار کرده ام که یک مقدار IsValuePassed را نگه می دارد. شی به Window1 بر می گردد، از این رو شما می توانید بک مقدار را به Window1 از هر UIElement ارسال کنید.

 

در کد من، UserControl می تواند مقدار property را به Window ارسال کند.

 <local:MyCustomUC x:Name="ucust" Grid.Row="0" local:Window1.IsValuePassed="true"/>

 

 

شما می توانید در بالا IsValuePassed را ببینید که می تواند از یک UserControl خارجی تظنیم شود، و به پنجره واقعی ارسال خواهد شد. همانطور که مشاهده می کنید، من دو متد static را به طور ویژه مقادیر Set یا Get را اضافه کرده بوده ام. این از کد استفاده خواهد کرد تا ما مقدار را از کد اشیا مناسب ارسال کنیم. شما یک دکمه را اضافه می کنید و می خواهید مقادیر از کد ارسال شوند، در چنین موارد متد static کمک خواهد کرد.

 

private void Button_Click(object sender, RoutedEventArgs e)

{

     Window1.SetIsValuePassed(this, !(bool)this.GetValue(IsValuePassedProperty));

}

 

به همان روش، DockPanel متد SetDock را تعریف می کند.

 

منبع:

http://www.codeproject.com/Articles/140620/WPF-Tutorial-Dependency-Property