تقييم الموضوع :
  • 0 أصوات - بمعدل 0
  • 1
  • 2
  • 3
  • 4
  • 5
WPF الأساسيات : ربط البيانات DataBinding في WPF
#1
توضح هذه المقالة خطوة بخطوة كيف يمكنك ربط مصادر البيانات Data Sources مع أدوات تحكم WPF Controls - WPF وصفيًا من خلال لغة XAML., أو يدويًا (إجرائيًا) من خلال الكود, هذه المقالة تركّز بشكل أكبر على إيضاح مبادئ ربط البيانات في WPF بشكل عام. ولا تُناقش كيفية استخدام مصادر البيانات بشكل خاص, محتويات المقالة كالتالي:
  • مقدمّة
  • نظام التبعيّة Dependency System في WPF, ومفاهيم ربط البيانات الأساسية
  • ربط البيانات من خلال XAML
  • ربط البيانات إجرائيًا
  • أوضاع الربط والتحديث Data Binding/Updating Modes
  • خيارات الربط Data Binding Options
[SIZE="3"]مقدمة[/SIZE]

ربط البيانات Data Binding هو آلية تسمح بتكوين ارتباط بين مصادر البيانات مع واجهة الاستخدام المرئية Graphical User Interface, مصادر البيانات قد تختلف حسب طبيعة التطبيق, عادةً مصادر البيانات تقيم في إحدى طبقات التطبيق والتي تسمّى Business Logic Layer, هذا الارتباط يُمكن أن يكون أُحادي الاتجاه أو ثنائي الاتجاه, في الوضع الأول يمكن فقط لأحد الطرفين (مصدر البيانات أو عنصر تحكم واجهة الاستخدام) التأثير على الطرف الآخر, وفي الوضع الثاني يكون التأثير بينهما متبادلاُ.

في WPF عادةً ما يُشير المُصطلح Data Source إلى كائن في بيئة الدوت نت( .NET object ) هذا الكائن قد يكون مجموعة Collection مثل Lists أو Arrays , ملف XML, أو كائن خدمة ويب Web service object, أو كائن يشير إلى جدول في قاعدة بيانات Data table, أو كائن POCO بسيط ((Plain Old CLR Object, أو حتى عنصر تحكّم من عناصر WPF مثل Button .

[SIZE="3"]نظام التبعيّة Dependency System في WPF, ومفاهيم ربط البيانات الأساسية[/SIZE]

أضاف WPF نوعًا جديدًا من الخصائص, وهي خصائص التبعيّة Dependency Properties, والتي تم إدخالها لتبسيط المهام التي من أجلها تم تصميم WPF, مثل تنسيق المظهر Styling, ربط البيانات التلقائي Automatic Data Binding, التحريك Animation, والمزيد, قد تبدو هذه الإضافة غامضةُ بعض الشيء بالنسبة لك, لأنها ستُعقّد نوعًا ما الانطباع الذي أخذته عن كائنات Microsoft.NET الكلاسيكيّة: مجرّد حقول Fields, وإجرءات Methods وخصائص Properties بسيطة, لكن بمُجرّد أن تفهم المشاكل التي يحُلها نظام التبعيّة , ستُرحب كثيرًا بهذه الإضافة.

خصائص التبعيًة Dependency Properties تعتمد في الحصول على القيم الخاصة بها على مًوفّر القيمة Value Provider, وقد تعتمد على أكثر من مُوفّر واحد, وقد يكون هذا المُوفّر إما تحريكًا Animation يقوم تلقائيًا بتغيير قيمة تلك الخاصيّة, أو عنصر أبوي Parent Element ينشر قيم خصائصه نحو العناصر الأبناء Child Elements, وهكذا. من أهم ميزات خصائص التبعيّة هو اعتمادها على النظام المبني على التنبيه عند حدوث تغيّر في القيم التي تحملها Change Notifications.

الدافع وراء هذه الإضافة هو تمكين الميزات الغنيّة التي يُقدّمها WPF مباشرةً من خلال لغة تمييز التطبيقات القابلة للامتداد XAML, وما يجعل WPF يعتمد بشكل كبير على استخدام لغة التمييز هذه هو مبالغة نظام WPF في استخدام الخصائص, على سبيل المثال, عنصر التحكّم Button يحتوي على 111 خاصيّة (منها 98 موروثة من النوع Control وفئاته القاعدية), يُمكن تعيين قيم الخصائص لعناصر WPF بسهولة من خلال XAML (بطريقة مباشرة أو باستخدام أدوات التصميم مثل مُصمّم WPF المُضمن مع Visual Studio) , ولكن من دون اللجوء لكتابة بعض الكود الإضافي لن يكون من المُمكن الحصول على النتيجة المُنتظرة فقط بمجرد إسناد بعض القيم من خلال XAML, فلا تتوّقع أنك ستحصل على ميزات خصائص التبعيّة مثل الربط التلقائي باستخدام الخصائص العادية CLR Properties فقط.

كيفية تعريف خصائص التبعيّة

في الواقع كمطوّر WPF لست مُجبرًا على الإلمام بجميع التفاصيل الدقيقة لكيفية تعريف وكتابة خصائص التبعيّة, إلا إذا كنت تخطّط لكتابة أدوات تحكّم خاصة بك Custom Controls, وبشكل عام فإنك تحتاج فقط لفهم كيفية عملها, ومع مرور الوقت ستتمنّى لو كانت جميع الخصائص هي خصائص تبعيّة!

لغات عائلة Microsoft.NET في الواقع لا تتفهّم خصائص التبعيّة, لهذا فإن واجهة برمجة WPF - WPF APIs هي المسؤولة عن إدارة تلك الخصائص, ولهذا السبب عمليًا ستحتاج لاستخدام بعض الفئات الأساسيّة Base Classes الموجودة في WPF APIs من أجل كتابة خصائص التبعيّة, المثال التالي هو أبسط شكل لما يُمكن أن تكون عليه خاصية التبعيّة, وهو عبارة عن تعريف للخاصية Content الموجودة في عنصر التحكّم Button والعناصر الأخرى الموروثة من النوع ContentControl.
PHP كود :
public class ContentControl ControlIAddChild
    
{
        public static 
readonly DependencyProperty ContentProperty =
                    
DependencyProperty.Register("Content"typeof(object),
                    
typeof(ContentControl), new FrameworkPropertyMetadata(null,
                    new 
PropertyChangedCallback(ContentControl.OnContentChanged)));
        public 
object Content
        
{
            
get
            
{
                return 
base.GetValue(ContentProperty);
            }
            
set
            
{
                
base.SetValue(ContentPropertyvalue);
            }
        }
        private static 
void OnContentChanged(DependencyObject dDependencyPropertyChangedEventArgs e)
        {
        ...
        }
     ...
    } 
PHP كود :
Public Class ContentControl
    Inherits Control
    
Implements IAddChild
    
Public Shared ReadOnly ContentProperty As DependencyProperty DependencyProperty.Register("Content"GetType(Object), GetType(ContentControl), New FrameworkPropertyMetadata(Nothing, New PropertyChangedCallback(ContentControl.OnContentChanged)))
    Public 
Property Content() As Object
        Get
            
Return MyBase.GetValue(ContentProperty)
        
End Get
        Set
            MyBase
.SetValue(ContentPropertyvalue)
        
End Set
    End Property
    
Private Shared Sub OnContentChanged(As DependencyObjectAs DependencyPropertyChangedEventArgs)

    
End Sub
     
...
End Class 


الحقل المُشترك ContentProperty هو الذي يُمثّل خاصية التبعيّة, وهو من النوع System.Windows.DependencyProperty, وبالإجماع, جميع الحقول التي تُمثّل خصائص التبعيّة, تكون عامة public, ومُشتركة static, تنتهي تسميتها بالكلمة Property, منصّة WPF الأساسيّة تتطلب اتباع هذا الأسلوب في التعريف للعديد من الأغراض مثل أدوات الترجمة Localization, وأثناء القيام بتحميل ومُعالجة XAM, فإن WPF يخضع في معالجته لهذا الأسلوب.

وفي العادة يتم إنشاء خصائص التبعيّة باستدعاء الإجراء DependencyProperty.Register والذي يتطلب المُدخلات التالية: اسم الخاصًية متبوعًا بالكلمة Propertyكما في المثال السابق(ContentProperty), نوع الخاصيّة (object), ونوع الفئة الحاوية على الخاصيًة (ContentControl), يُمكن اختياريًا باستخدام أحد ال Overloads للإجراء Register السابق تحديد كيفية مُعالجة WPF لتلك الخاصيّة, وكذلك الإجراءات CallBacks التي سيتم استدعاءؤُها عند تغيّر قيمة الخاصيّة, وكذا بعض المعلومات الأخرى. في التعريف السابق تم استدعاء الإجراء Register لإسناد القيمة null للخاصية Content, وتمرير مُفوّض Delegate يشير إلى الإجراء OnContentChanged من أجل التنبيه عند تغيّر قيمة الخاصية Content.

ثم بعد ذلك تأتي الخاصيّة Content والتي يتم من خلال إجرائيها get و set استدعاء الإجرائين GetValue و SetValue على التوالي, والموروثة من النوع System.Windows.DependencyObject, الفئة المنخفضة المستوى, والتي ترث منها جميع الفئات التي تحتوي على خصائص التبعيّة, الإجراء GetValue يقوم باسترداد القيمة التي تمّ تخزينها باستخدام الإجراء SetValue, وفي حال لم يتم استدعاء هذا الإجراء (SetValue) مُسبقًا, سيتم استرداد القيمة الافتراضية والتي تمّ تمريرها للإجراء DependencyProperty.Register. وفي الواقع لست بحاجة لتعريف الخاصية Content, لأنه من المُمكن استدعاء الإجرائين GetValue و SetValue فهما إجراءان عامّان, ولكن استخدام الخصائص يجعل استهلاك خصائص التبعيّة مألوفًا لدى مستخدم هذه الخصائص, ومن جهة أخرى فهي تُمكّن من إسناد الفيم لها من خلال XAML. وقد تستغرب لما لا يستخدم الإجراءان Get/SetValue ال Generics, والسبب مبدئيًا هو ظهور مفاهيم خصائص التبعيّة قبل ظهور Generics.

من جهة أخرى قد تستغرب لما يتم تعريف حقول خصائص التبعيّة على أنها مشتركة static, وحتى تفهم ذلك تخيّل العكس, ماذا لو تم تعريفها على أنها Instance Fields عندها ستحتاج من أجل كل عنصر أو مثيل Instance من Button حوالي 111 حقلاً أي 111 قيمة, ومن كل مثيل من Label حوالي 104 حقلاً أي 104 قيمة, وهكذا... عندها سيزيد استهلاك الذاكرة بشكل مريع! لكن خصائص التبعيّة جاءت لتحل هذه المشكلة, حيث أن الإجرائين Get/SetValue يستخدمان أسلوب فعّال واقتصادي لمنع هذا الاستهلاك الفضيع للذاكرة, حيث هناك من 89 خاصية تبعيّة من بين 111 خاصيّة للنوع Button, و 82 من بين 104 للنوع Label وهذا سيُحسّن بشكل كبير كميّة استهلاك الذاكرة, لا تسن أن الحقول المشتركة Static Fields يتم حفظ قيمها على مستوى النوع وليس على مستوى مثيل ذلك النوع Per-type values .

فوائد خصائص التبعيّة تتجاوز تحسين استهلاك موارد النظام من الذاكرة, وتتعدّى ذلك إلى عزل المطوّر عن الكثير من الأعمال الشاقّة مثل التحقّق من وصول المسارات Threads Access, أو مطالبة العنصر الأبوي Parent Element الذي يحتوي على عنصر تحكّم ما بإعادة رسم نفسه, على سبيل المثال, تغيير قيمة الخاصيًة Background للعنصر Button, يحتاج لمطالبة هذا الأخير بإعادة رسم نفسه, وهذا مًمكن من خلال تمرير القيمة FrameworkPropertyMetaDataOptions.AffectRender للإجراء DependencyProperty.Register, بالإضافة إلى كل ذلك, هناك الفوائد الثلاثة الأخرى التي سيرد ذكرها فيما يلي.

1 - التنبيه بالتغيّرات Change Notification

عندما يحدث تغيّر في قيمة خاصية التبعيّة, يقوم WPF بتشغيل مجموعة من الإجراءات, بناءًا على كيفية تعريف تلك الخاصية, قد يكون هذا الإجراء إعادة رسم عنصر معيّن في واجهة الاستخدام, أو إحداث تغيّر في تصميم واجهة الاستخدام, تحديث ربط البيانات, والمزيد. وإن من أهم ما يُميّز نظام التنبيه بالتغيّرات هذا هو مُشغّلات الخصائص Property Triggers, والتي تسمح لك بتشغيل إجرائك الخاص عند حدوث التغيّر دون الحاجة لكتابة كود إجرائي, على سبيل المثال, قد تريد تغيير لون خلفية عنصر تحكّم Button عند مرور مؤشر الماوس فوقه, واسترداد القيمة السابقة عند مغادرة المؤشر, مبدئيًا ستحتاج للقيام بذلك إلى إرفاق إجرائين للحدثين MouseEnter و MouseLeave,
PHP كود :
<Button MouseEnter="Button_MouseEnter" MouseLeave="Button_MouseLeave" /> 
أما باستخدام مُشغّلات الخصائص, فيُمكنك الحصول على نفس النتيجة دون الاستعانة بالكود الإجرائي, بل يُمكنك الاكتفاء فقط باستخدام XAML, كما يُوضّح المثال التالي:
PHP كود :
<Button>
            <
Button.Style>
                <
Style TargetType="Button">
                    <
Style.Triggers>
                        <
Trigger Property="IsMouseOver" Value="true">
                            <
Setter Property="Background" Value="Blue"/>
                        </
Trigger>
                    </
Style.Triggers>
                </
Style>
            </
Button.Style>
        </
Button
هذا المُشغّل يمكنه العمل بحسب قيمة الخاصية IsMouseOver التي تُصبح True في نفس الوقت الذي يتم فيه إطلاق الحدث MouseEnter, وتُصبح False في نفس الوقت الذي يتم فيه إطلاق الحدث MouseLeave, لاحظ أنك لست مُطالبًا بإعادة القيمة Background إلى قيمتها الابتدائيّة, سيقوم WPF بذلك تلقائيًا من أجلك!
,كما لاحظت فإنه ليس بوُسعك إسناد مُشغّل بشكل مُباشر إلى عناصر WPF, بل ستحتاج لتعريف مظهر Style جديد وتعريف هذا المُشغّل بداخله ومن ثم تعيين هذا المظهر إلى العنصر الهدف كما في المثال السابق.

2 - وراثة الخصائص Property Inheritance

وراثة الخصائص أو "وراثة قيم الخصائص" غير مُرتبطة مطلقًا بمفاهيم الوراثة في البرمجة المُوجهّة بالكائنات OOP, بل تعني انتقال قيمة خاصيّة معينّة من عنصر إلى آخر في شجرة العناصر Elements Tree, على سبيل المثال عندما تقوم بتحديد قيمة خاصيّة التبعيّة FontSize للإطار Window فإن هذه القيمة يتم توريثها لجميع العناصر الموجودة ضمن هذا الإطار مثل عناصر تحكّم Button, Label, إلى غير ذلك, جرّب لمثال التالي:
PHP كود :
<Window x:Class="WPFDependencyOverview.MainWindow"
        
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        
Title="MainWindow"
        
Height="350"
        
Width="525"
        
FontSize="18"
        
SizeToContent="WidthAndHeight"
        
Background="SkyBlue">
    <
StackPanel>
        <
TextBlock FontSize="36"
                   
Text="Property Value Inheritance"
                   
HorizontalAlignment="Center" />
        <
TextBox HorizontalAlignment="Center"
                 
Width="200"
                 
Text="Test"
                 
TextAlignment="Center" />
        <
Button Content="Click me!"
                
HorizontalAlignment="Center" />
    </
StackPanel>
</
Window


لكن هذه ليست قاعدة في كل الحالات, فهناك بعض الحالات التي لا يمكن فيها لعنصر أبوي أن يُورّث قيم أحد خصائصه لأحد عناصره الأبناء, في هذه الحالة إما أن العنصر الإبن يأخذ قيمته من مُوفّر القيمة ذي الأولوية الأكبر من العنصر الأبوي, كما في المثال السابق حيث أن عنصر TextBlock لم يتأثر بحجم الخط الذي تمّ تعيينه للإطار لأنه قد تم تعيين قيمته المحليّة Local Value إلى 36 بكسل والتي لها أولوية أعلى من العنصر الأبوي (Window), بينما تأثر كلّ من Button و TextBox بالقيمة الأبوية 18 بكسل, وإما أن الخاصيّة نفسها لم يتم تكوينها بالشكل الذي يجعلها قادرةُ على توريث قيمتها. في العادة يُمكن الحصول على هذا السلوك (توريث القيمة) من خلال تمرير القيمة FrameworkPropertyMetaDataOptions.Inherits إلى الإجراء DependencyProperty.Register .

3 - إمكانيّة تعدّد مُوفّري القيمة Multiple Value Providers Support

تمّ تسمية خصائص التبعيّة Dependency Properties بهذا الإسم لانها تعتمد في الحصول على قيمهتا على مجموعة من موفّري القيمة Value Providers يعتمد WPF على آلية مُعقّدة بعض الشيء , وتتمّ هذه الآلية وفق ترتيب معيّن وفي مراحل. يمُّر WPF حاليًا على 5 مراحل من أجل تحديد تلك القيمة النهائيّة التي ستحملها الخاصيّة, يُمكن تلخيص تلك المراحل فيما يلي:

المرحلة الأولى: تحديد القيمة الابتدائية Base Value:
يشترك أغلب مُوفّري القيمة في عملية احتساب القيمة الابتدائية لخاصيّة مُحدّدة وفق ترتيب مُعيّن يخضع فيه هذا الترتيب لمبدأ الأسبقية (ما يُسمّى بـ Property Precedence), حيث أن مُوفّر القيمة ذي أعلى أسبقية هو الأحق بأن يُعطي قيمته لتلك للخاصية, يُمكن اختصار هذا الترتيب في القائمة التالية للقيم الناتجة من المُوفّر ذو الأسبقية العُليا إلى المُوفّر ذي الأسبقية السُفلى.
  • القيمة المحليّة Local Value لخاصيًة العنصر نفسه.
  • القيمة التي تمّ تحديدها في مُشفّل خاصيًة في Template العنصر الأبوي (Parent Tempate Trigger).
  • القيمة التي تمّ تحديدها في Template العنصر الأبوي (Parent Template).
  • القيمة التي تمّ تحديدها في مُشغل خاصية في Style العنصر نفسه (Style Trigger), كما يظهر ذلك في المثال الثاني في هذا المقال والخاص بخلفية Button .
  • القيمة التي تمّ تحديدها في مُشغّل خاصيّة في Template العنصر نفسه (Template Trigger).
  • القيمة التي تمّ تحديدها في Style العنصر نفسه بواسطة Style Setter.
  • القيمة التي تمّ تحديدها في مُشغّل خاصيّة في الـ Theme الحالي (Theme Style Trigger)
  • القيمة التي تمّ تحديدها في Style خاص بالـ Theme الحالي بواسطة Style Setter أي (Theme Style Setter)
  • القيمة التي تمّت وراثتها بواسطة ميزة Property Value Inheritance كما ورد في الفقرة السابقة "وراثة الخصائص".
  • القيمة الافتراضية Default Value لخاصيًة العنصر نفسه (التي تم استخدامها عند تعريف خاصيّة التبعيّة من خلال الإجراء DependencyProperty.Register).
- بالنسبة لـ Triggers و Templates و Themes سيتم الحديث عنها لاحقًا بالتفصيل في موضوع مستقل.

إذا لم يكن بوسعك معرفة مُوفّر القيمة الحالي لخاصيًة مُحدّدة, يمكنك استخدام الإجراء المُشترك DependencyPropertyHelper.GetValueSource, كوسيلة مُساعدة لذلك, هذا الإجراء يُعيد بعض البيانت المفيدة, من بينها قيمة من النوع BaseValueSource Enum والتي تُحدد مصدر القيمة الفعلي, يُمكنك مراجعة MSDN Library لمعرفة المزيد حول هذا الإجراء.

المرحلة الثانية: اختبار القيمة Value Evaluating
إذا كانت القيمة العائدة من المرحلة السابقة هي عبارة عن تعبير Expression (كائن مُشتق من النوع System.Windows.Expression) , سيقوم WPF باختبار ذلك التعبير للحصول على قيمة حقيقية, ال Expressions تستخدم في الغالب من أجل ربط البيانات Data Binding وهو ما سنتحدّث عنه في تكملة هذا المقال.

المرحلة الثالثة: تطبيق التحريكات Apply Animations
إذا كان هناك تحريك Animation يتم تطبيقه حاليًا على العنصر, فإن لهذا التحريك القدرة على تغيير قيمة الخاصيّة لهذا العنصر, لهذا من المُمكن للتحريك أن يُلغي مفعول كافة مُوفرّي القيمة السابقين في الخطوة الأولى ويُلغي كذلك القيمة الناتجة عن الخطوة الثانية.

المرحلة الرابعة: تطبيق القيمة الاضطراريّة Apply Corece Value
بعد الحصول على آخر قيمة "من المرحلة السابقة", يقوم WPF بتمرير تلك القيمة إلى المُفوّض CoerceValueCallback (إن تمّ تسجيله عند تعريف خاصيّة التبعيّة من خلال الإجراء DependencyPropery.Register), هذا المُفوّض مسؤول عن إرجاع قيمة جديدة قد تكون مُغايرة كليًا للقيمة الأخيرة بناءًا على منطق Logic مُحدّد, على سبيل المثال أداة ProgressBar تستخدم هذا المُفوّض لتقييد قيمة التقدّم Progress Value إلى أدنى قيمة لها Minimum إذا كانت الفيمة الأخيرة أقل من Minimum, أو إلى أعلى قيمة Maximum إذا كانت القيمة الأخيرة أكبر من Maximum (يُمكن تشبيه هذه الخطوة بـ Validation).

المرحلة الخامسة: التحقّق من صلاحية القيمة Validation
يقوم WPF بتمرير آخر قيمة تمّ تحصيلها من المرحلة السابقة إلى المُفوّض ValueValidateCallback (إن تمّ تسجيله عند تعريف خاصيّة التبعيّة من خلال الإجراء DependencyPropery.Register), هذا المُفوّض يجب أن يُعيد true إن كانت القيمة صالحة, أو يُعيد false إن لم تكن كذلك, الأمر الذي سيُسبّب إطلاق استثناء Exception.

تعابير الربط Binding Expressions

يستخدم WPF لتحقيق ربط البيانات الفئة System.Windows.Data.Binding والتي تعمل على وصل خاصيّتين مع بعضهما البعض ويبقي هذا الاتصال مغتوحًا بينهما, يُمكنك إنشاء هذا الاتصال مرّة واحدة وسيتولّى WPF إبقاهء على مدار عمر التطبيق, ويحتاج WPF لإنشاء الربط إلى أربع مُكونات أساسية:
  • الكائن الهدف Target Object: الكائن الذي يتمّ ربط مصدر البيانات به وإسناد القيمة المطلوبة إلى أحد خصائصه (عادة مايكون هذا الكائن هو أحد عناصر واجهة الاستخدام (DataGrid- ListBox ...)
  • الخاصيّة الهدف Target Property: الحاصيّة الموجودة في الكائن الهدف والتي ستحمل القيمة المطلوبة من خلال هذا الربط.
  • الكائن المَصدر Data Source: الكائن الذي يمتلك القيمة التي سيُقدّمها للكائن الهدف.
  • المسار Path: الذي يُحدّد موضع القيمة في الكائن المَصدر (قد يكون هذا المسار اسم خاصية Property Name, أو مُفهرِس Indexer).
ملاحظة: يجب أن تكون الخاصيّة الهدف عبارة عن Dependency Property, ولحسن الحظ فإن معظم الخصائص في عناصر WPF هي كذلك.

لنفترض على سبيل المثال أنك تريد ربط الخاصية Content لعنصر Label ليقوم بعرض النص الذي يتم إدخاله داخل عنصر TextBox, ولنفترض أن اسم عنصر Label هو lblResult, وأن اسم عنصر TextBox هو txtBox, وعليه ستكون مُكونات الربط الأساسيّة وفق ما سبق كما يلي:
  • الكائن (العنصر) الهدف: هو lblResult, وهو الكائن الذي تريد ربط مصدر البيانات به وعرض القيمة المطلوبة فيه.
  • الخاصيّة الهدف: هي الخاصيًة Content للكائن lblResult, لأنك تريد عرض القيمة المطلوبة من خلال الخاصيّة Content.
  • الكائن المَصدر: وهو txtBox (الكائن الذي يُوفّر القيمة).
  • المسار: وهو اسم الخاصيًة Text في الكائن المَصدر txtBox, لأنك تريد عرض القيمة التي تحملها الخاصيًة Text.

يُتبع ...
الرد }}}}
تم الشكر بواسطة: الشموخ
#2
تابع - الربط عنصرًا إلى عنصر Element To Element Binding


أبسط سيناريو يُمكن به إنجاز عملية الربط هو حينما يكون الكائن المَصدر أحد عناصر WPF, وتكون الخاصيّة المَصدر عبارة عن Dependency Property, وهذا لأن خصائص التبعيّة مبنيّة أساسًا على دعم التنبيه بالتغيّرات Change Notification كما ورد سابقًا, ولهذا عندما تُغيّر من قيمة الخاصية المَصدر في الكائن المَصدر فإن الخاصيّة الهدف في الكائن الهدف تتغيّر تلقائيًا, وهذا ماتريده بالضبط, ولا تحتاج لإتمام ذلك أيّة مُتطلبات إضافية!


وحتى تفهم كيف يُمكن ربط عنصر إلى آخر, لاحظ الصورة التالية التي تحتوي على عنصري تحكّم من عناصر WPF, شريط تمرير Silder, وأداة عرض النّص TextBlock, عندما تقوم بتحريك مُؤشِّر الشريط نحو اليمين يتزايد حجم النّص, وإذا قمت بتحريكه نحو الاتجاه المعاكس يتناقص حجم النّص. من الواضح أنه يُمكنك بسهولة إنجاز هذا السلوك من خلال الكود, من خلال مُعالجة الحدث Slider.ValueChanged ونسخ قيمة موضع المُؤشِّر الحالية وتعيين حجم الخط استنادًا إلى تلك القيمة, ولكن استخدام الربط عنصرًا إلى عنصر سيجعل ذلك أسهل!




قبل البدء, لست بحاجة لإجراء أيّة تعديلات على الكائن المَصدر (شريط التمرير), كلّ ما تحتاجه فقط هو ضبط مجال القِيم كما تفعل في العادة. الربط سيكون بداخل العنصر TextBlock, وبدل أن تقوم بإسناد قيمة نصية للخاصيّة FontSize ستستخدم تعبير الربط Binding Expression كما يلي:


PHP كود :
<StackPanel>        <Slider Name="sliderFontSize"                Margin="3"                Minimum="1"                Maximum="32"                Value="12"                TickFrequency="1"                TickPlacement="TopLeft">        </Slider>        <TextBlock Margin="10"                   Text="Simple Text"                   Name="lblSampleText"                   FontSize="{Binding ElementName=sliderFontSize, Path=Value}">        </TextBlock>    </StackPanel


تعابير الربط تستخدم امتدادات التمييز Markup Extensions (ولهذا تم إحاطتُها بـ {}) ,ويبتدأ تعبير الربط بالكلمة Binding لأنك هنا تقوم بإنشاء كائن من الفئة System.Windows.Data.Binding Class, ورغم ذلك يُمكنك تكوين كائن الربط هذا بعدّة طُرق, وفي مثل هذه الحالة نحتاج لإعداد خاصيّتين لهذا الكائن: الخاصيّة ElementName لتحديد اسم العنصر المَصدر, والخاصيّة Path لتحديد الخاصيّة المَصدر. لاحظ أن اسم الخاصيّة Path تم استخدامه هنا بدل الاسم Property لأن هذا المسار قد يكون إما اسم خاصيّة, وإما مُفهرِسًا Indexer, يُمكنك استخدام النقطة "." للفصل بين أسماء خصائص متداخلة مثل: Property1.Property2.Property3.


إنشاء الربط إجرائيًا


يُفضّل دائمًا عند إجراء عمليات الربط أن تستخدم XAML فهو أسهل وأكثر مرونة, ولكن ليس هناك ما يمنعك من إجراء الربط إجرائيًا من خلال الكود, وفي المثال التالي توضيح لكيفية إنشاء الربط المُستخدم في المثال السابق من خلال الكود:


PHP كود :
Binding binding = new Binding()            {                ElementName "sliderFontSize",                Path = new PropertyPath("Value"),};            lblSampleText.SetBinding(TextBlock.FontSizePropertybinding); 


يًمكنك كذلك إلغاء هذا الربط ومسحه إجرائيًا من خلال إجراءات الفئة BindingOperations, الإجراء ClearBinding والذي يأخذ وسيطة من النوع DependencyProperty الذي يحمل الربط الذي تحاول مسحه, بينما الإجراء ClearAllBindings يقوم بمسح كافة الارتباطات لعنصر ما.
PHP كود :
BindingOperations.ClearAllBindings(lblSampleText); 
كِلا الإجرائين السابقين يستدعيان الإجراء ClearValue الموجود في كل عنصر يرث الفئة DependencyObject بما فيها TextBlock, هذا الإجراء ببساطة يقوم بمسح القيمة المحليّة للخاصيّة (والذي هو في هذه الحالة تعبير الربط).


إن استخدام الربط من خلال XAML أكثر شيوعًا من الطريقة الإجرائية, لأنه أسهل وبتطلّب عملاً أقل, ولكن هناك حالتان خاصّتان قد تحتاج فيهما إلى استخدام الطريقة الإجرائية, الأولى عندما تحتاج إلى تكوين الربط بناءًا على شروط مُعيّنة, على سبيل المثال قد تحتاج إلى إجراء بعض عمليات التحقّق مثلاً من مُتغيّرات النظام قبل إنشاء الربط, وفي هذه الحالة يُمكنك تقليل حجم الكود بتعريف كائنات الربط مبدئيًا والاحتفاظ بها في موارد الإطار Window Resources من خلال XAML, وتكوين تلك الكائنات إجرائيًا بالشكل المطلوب كما سلف. وأما الحالة الثانيّة وهي عندما تريد مسح الربط والنخلّص منه, فلا يكفي ان تقوم بإسناد قيمة جديدة إلى الخاصية التي تريد مسحها بغرض التخلّص من الربط, فهذا لن يُؤثّر في الأمر شيئًا, بل ستحتاج لاستخدام الإجراء ClearBinding أو الإجراء ClearAllBindings كما سلف.


أوضاع الربط والتحديث Data Binding/Updating Modes


كما رأيت حتى الآن فإن مسار تغيّر الخصائص كان دائمًا من العنصر المَصدر إلى العنصر الهدف, ولكن من المُمكن بشكل ما أو بآخر أن تتغيّر قيمة الخاصية الهدف مُباشرةّ, ومن الجيّد في هذه الحالة أن ينعكس هذا التغيّر نحو العنصر المَصدر أيضًا, ففي المثال السابق قد تقوم من خلال الكود بتغيير قيمة الخاصية FontSize , عندها سستوقّع للحظة أن شريط التمرير سيقوم بتحديث نفسه تلقائيًا للقيمة ولكن هيهات! يُمكن حلّ هذه المسألة بساطة باستخدام الخاصية Mode للكائن Binding, حيث يُمكن التحكّم من خلالها في أوضاع مسار تغيّر البيانات, وهي كالتالي:
  • الوضع أُحادي الاتجاه OneWay: يتمّ تحديث قيمة الخاصيّة الهدف عندما تتغيّر قيمة الخاصيّة المَصدر.
  • الوضع ثنائي الاتجاه TwoWay: يتمّ تحديث قيمة الخاصيّة المَصدر أو الهدف بتغيّر قيمة الخاصيّة الأخرى, هذا الوضع يُستخدم عادة عند التعامل مع عناصر التحكّم التي يُسمح فيها للمستخدم بالتعديل عليها, مثل DataGrid, TextBox, حيث تنتقل التغيّرات من هذه العناصر مباشرةً إلى الكائن المَصدر المرتبط بها.
  • الوضع أُحادي الاتجاه نحو المَصدر OneWayToSource: هذا الوضع مُعاكس للوضع الأول OneWay. حيث يتمّ تحديث قيمة الخاصيّة المَصدر كُلّما تمّ تحديث الخاصيّة الهدف. هذا الوضع مُفيد جدًا خصوصًا في الحالة التي تكون فيها الخاصيّة المَصدر ليست خاصيّة تبعيّة .
  • الوضع لأول مرّة فقط OneTime: هذا الوضع مُماثل للوضع الأول OneWay باستثناء أن أيّ تغيّر يتمّ لاحقًا على الخاصيّة المَصدر لايتمّ أخذه بالحسبان ولا تتأثّر به الخاصيّة الهدف.
  • الوضع الافتراضي Default: هذا الوضع يعتمد على الخاصيّة الهدف, يُمكن أن يكون هذا الوضع قي هذه الحالة إما ثنائي الاتجاه TwoWay (بالنسبة للخصائص الهدف القابلة للكتابة Writable Properties), أو أحادي الاتجاه, جميع الخصائص تعتمد على هذا الأسلوب, إلا إذا قمت باختيار وضع مُغاير بشكل صريح.



الصورة التالية تُلخّص الأوضاع السابقة:



عندما تتعامل مع الربط ثنائي الاتجاه TwoWay, أو أحادي الاتجاه نحو المَصدر OneWayToSource, فإنك قد ترغب أحيانًا في التحكّم في الكيفية التي يتمّ بها تحديث الهدف, على سبيل المثال, عتدما يقوم المستخدم بإدخال البيانات في عنصر TextBox, قد ترغب في أن يحدث التغيير فقط عندما يقوم المستخدم بالانتقال إلى عنصر التحكم التالي بواسطة المفتاح Tab, أو كلّما قام المستخدم بكتابة حرف أو تغيير حرف في عنصر TextBox, أو أن تتحكّم بنفسك من خلال الكود في الوقت الذي ترغب فيه بإجراء التحديث. الخاصية UpdateSourceTrigger من نوع الترقيم UpdateSourceTrigger Enum والموجودة في الفئة Binding تُتيح لك التحكم في تلك الكيفية من خلال أوضاع هي كالتالي:
  • PropertyChanged: يتمّ فيه تحديث قيمة الخاصيّة المَصدر كُلّما تمّ تحديث الخاصيّة الهدف.
  • LostFocus: يتم تحديث قيمة الخاصيّة المَصدر كُلّما تغيّرت قيمة الخاصيَة الهدف, ولكن فقط عندما يفقد عنصر التحكّم التركيز Focus .
  • Explicit: يتم تحديث قيمة الخاصيّة المَصدر كُلّما تغيّرت الخاصيّة الهدف, ولكن فقط عندما تقوم أنت باستدعاء الإجراء BindingExpression.UpdateSource بشكل صريح من خلال الكود. يُمكنك الحصول على كائن من النوع BindingExpression من خلال الإجراء BindingOperations.GetBindingExpression أو استدعاء الإجراء GetBindingExpression لأي كائن من النوع FrameworkContentElement, أو النوع Frameworkelement.

كما يوجد هناك وضع افتراضي بالنسبة لعملية الربط فإن هناك أيصًا وضعًا افتراضيًا بالنسبة لعملية التحديث, على سبيل المثال, فالخاصيّة TextBox.Text افتراضيًا يكون وضع التحديث الخاص بها هو LostFocus .


خيارات الربط Data Binding Options


حتى الآن جميع الأمثلة السابقة تُركّز على الربط بين عنصرين من عناصر WPF, ولكن في أغلب الأحيان في التطبيقات التي تعتمد على قواعد البيانات Data-Driven Applications مصادر البيانات ليست عناصر WPF (ليست أدوات تحكّم واجهة الاستخدام), بل تكون كما ورد في مقدمّة هذا المقال – كائنات غير مرئية – وتحتاج لتمثيلها من خلال عناصر WPF المرئية, ويجب أن يكون متاحًا الوصول إلى خصائص تلك الكائنات , بحيث تكون تلك الخصائص عامّة Public. عند إجراء الربط مع كائنات ليست من عناصر WPF يجب عليك استبعاد استخدام الخاصية ElementName للفئة Binding, وإنما عليك استخدام أحد البدائل التالية:
  • الخاصيّة Source: تُمثّل هذه الخاصيّة الارتباط Reference الذي يُشير إلى الكائن المَصدر – بمعنى آخر الكائن الذي يمثل مَصدرًا للبيانات-.
  • الخاصيّة RelativeSource: هذه الخاصيّة من النوع System.Windows.Data.RelativeSource Class, وهي تستخدم بغرض الإشارة إلى الكائن نفسه الذي يحتوي على الربط – بمعنى آخر أن الربط يكون من خاصيًة في الكائن المَصدر إلى خاصيّة في الكائن المَصدر نفسه, هذه الخاصيّة مُفيدة عند بناء قوالب أدوات التحكّم Control Templates, و وقوالب البيانات DataTemplates.
  • الخاصيّة DataContext للكائن الهدف: عند عدم استخدام أيّ من الخاصيّتين السابقتين, فإن WPF سيقوم بالبحث ضمن شجرة العناصر Elements Tree, بدءًا من النعصر الحالي (العنصر الهدف) , ويقوم بمعاينة الخاصيّة DataContext لكلّ عنصر , ويستخدم أول قيمة ليست Null يجدها كمَصدر للبيانات, إن الخاصيّة DataContext مُفيدة للغاية, إذا كنت تريد أن تقوم بربط خصائص عديدة لنفس الكائن المَصدر مع عدّة عناصر تحكّم WPF, لأنه بوسعك أن تربط الخاصيّة DataContext لأعلى عنصر (العنصر الأبوي الحاوي Container لعناصر التحكّم الباقية) بدلاً من ربطها مع كل عنصر ضمن شجرة العناصر الأبناء.



الخاصيّة Source


يُمكنك التعامل مع الخاصيّة Source بشكل مُباشر, كلّ ما تحتاجه فقط هو أن يكون الكائن مَصدر البيانات جاهزًا للارتباط به, أبسط طريقة يُمكنك من خلالها استخدام هذه الخاصيّة هو أن تقوم بإسناد كائن ستاتيكي لها, قد يكون هذا الكائنن من كتابتك, او احد كائنات مكتبة Microsoft.NET, كما يُوضّح المثال التالي:
PHP كود :
<TextBlock Text="{Binding Source={x:Static SystemFonts.IconFontFamily}, Path=Source}"/> 
تعبير الرط في المثال السابق يُعيد كائنًا من النوع FontFamily من خلال الخاصيّة SystemFonts.IconFontFamily (لاحظ أنك تحتاج لاستخدام مُلحق الترمير s:Static), ثم بعد ذلك تمّ نحديد المسار Path نحو الخاصيّة FontFamily.Source (والتي تُمثّل اسم الخط), وستكون النتيجة هي ظهور الاسم Segoe UI في Windows Vista و Windows 7., يُمكن أيضًا استخدام الموارد الثابتة Static Resources , المثال التالي ستقوم بإنشاء مورد ثابت من النوع FontFamily والذي يُمثّل الخط Calibri:
PHP كود :
<Window.Resources>        <FontFamily x:Key="MyFont">Calibri</FontFamily>    </Window.Resources
وفيما يلي كيفية جعل TextBlock يستخدم هذا الكائن:
PHP كود :
<TextBlock Margin="10"           Text="Simple Text"           FontFamily="{Binding Source{StaticResource MyFont}}"           Name="lblSampleText"


الخاصيّة RelativeSource


هناك طريقة أخرى لتحديد الكائن مَصدر البيانات, عبر الخاصيّة Binding.RelativeSource, والتي تُشير إلى ذلك الكائن حسب علاقته مع الكائن الهدف, هذه الخاصيّة من النوع RelativeSource, والذي هو في الأصل مُلحق ترميز (موروث من النوع MarkupExtension), وفيما يلي بعض الاستخدامات الشائعة لهذه الخاصيّة:
  • جعل الكائن الهدف هو نفسه الكائن المَصدر:

PHP كود :
{Binding RelativeSource={RelativeSource Self}} 
  • جعل الكائن المَصدر هو الكائن الذي تحمله الخاصيّة TemplatedParent للكائن الهدف (سيتمّ شرح هذه الخاصيّة لاحقًا في موضوع مُنفصل):

PHP كود :
{Binding RelativeSource={RelativeSource TemplatedParent}} 
  • جعل الكائن المَصدر هو أقرب عنصر أبوي Parent Element للكائن الهدف, ويكون هذا العنصر الأبوي من نوع مُحدّد:

PHP كود :
{Binding RelativeSource={RelativeSource FindAncestorAncestorType={x:Type desiredType}}} 
[INDENT]حيث desiredType هو نوع العنصر الأبوي (الكائن المَصدر).[/INDENT]
  • جعل الكائن المَصدر هو أقرب عنصر أيويذي الرتبة n للكائن الهدف , ويكون هذا العنصر الأبوي من نوع مُحدّد:

PHP كود :
{Binding RelativeSource={RelativeSource FindAncestorAncestorLevel=nAncestorType={x:Type desiredType}}} 
[INDENT]حيث desiredType هو نوع العنصر الأبوي (الكائن المَصدر).[/INDENT]


استخدام الخاصيّة RelativeSource مُفيد عند بناء قوالب الأدوات Control Templates , كما أن استخدام الخاصية Self مُفيد أيضًا في الكثير من الأحيان عندما تريد ربط خاصيّة لعنصر مع خاصيّة أخرى لنفس العنصر دون الحاجة لذكر اسم ذلك العنصر في تعبير الربط, المثال التالي سيُعطيك فكرة عن كيفية استخدامها, حيث سيتمّ ربط الخاصية ToolTip لعنصر TextBlock مع الخاصية Text لنفس العنصر, وبهذا سيتم عرض قيمة الخاصيّة Text ضمن ToolTip كلّما مررت فوق العنصر:
PHP كود :
<TextBlock Text="Some data"                   ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}" /> 


الخاصيّة DataContext


في بعض الأحيان, قد يكون لديك عدة عناصر WPF مرتبطة بنفس الكائن المَصدر, على سبيل المثال, افترض أن الكائن المصَدر سيكون كائنًا من النوع Person ويحتوي على الخصائص التالية: FName, LName, Age, وتريد أن تربط كل خاصيّة منها مع TextBox , وعليه سيكون لديك 3 عناصر TextBox ولتكن بداخل عنصر أبوي من نوع StackPanel, كما يُوضّح المثال التالي, في هذه الحالة قد تقوم بكتابة تعابير ربط طويلة نوعًا ما لكل عنصر TextBox لأنك ستقوم بتحديد الخاصية Source في تعابير الربط تلك, بالإضافة إلى المسار طبعًا:
PHP كود :
<StackPanel>        <TextBlock Text="{Binding Source=myPerson, Path=FName}" />        <TextBlock Text="{Binding Source=myPerson, Path=LName}" />        <TextBlock Text="{Binding Source=myPerson, Path=Age}" />    </StackPanel
من الجيّد في هذه الحالة أن تقوم بربط الخاصيةDataContext للعنصر StackPanel, والذي يحتوي على جميع عناصر TextBox (مع العلم أنه بالإمكان أيضًا ربط نفس الخاضية لعنصر أعلى من العنعصر StackPanel مثل Window نفسه, ولكن يُفضّل أن تختار العنصر الأقرب), ولربط الخاضيّة DataContext تحتاج أيضًا لاستخدام تعبير الربط, كما في المثال التالي:
PHP كود :
<StackPanel DataContext="{Binding Source=myPerson}"
والآن يُمكنك حذف الخاصيّة Source من تعابير الربط لعناصر TextBox الثلاثة, لأنه طالما لم يتم تحديد تلك الخاصيّة, فإن WPF سيقوم بفحص قيمة الخاصيّة DataContext لكل عنصر من عناصر TextBox, فإن كانت تلك القيمة Null سيقوم بفحص نفس الخاصيّة للعنصر الأعلى (StackPanel في هذه الحالة), وطالما أنها ليست Null ستصبج القيمة التي تحملها (myPerson) هي القيمة التي سيستخدمها WPF من أجل تعابير الربط لعناصر TextBox .
PHP كود :
<StackPanel DataContext="{Binding Source=myPerson}">        <TextBlock Text="{Binding Path=FName}" />        <TextBlock Text="{Binding Path=LName}" />        <TextBlock Text="{Binding Path=Age}" />    </StackPanel
ملاحظة: إذا قمت باستخدام تعبير ربط مع تحديد الخاصيّة Source له بشكل صريح, فإن WPF سيستخدم قيمة تلك الخاصية حتى لو كانت الخاصيّة DataContext ليست Null .
الرد }}}}
تم الشكر بواسطة:


المواضيع المحتمل أن تكون متشابهة .
الموضوع : الكاتب الردود : المشاهدات : آخر رد
Lightbulb [درس فيديو] شرح عمل DataBinding لليستا بوكس تم تعديل القالب الخاص بها السندبااد 2 600 22-02-16, 10:47 PM
آخر رد: السندبااد
Question [سؤال] سؤال حول عرض صورة من قاعدة البيانات السندبااد 4 878 10-11-14, 10:02 PM
آخر رد: السندبااد
Question [سؤال] ما أفضل وأسهل وأبسط طريقة لربط برنامج دوت نت تقنية WPF بقاعدة البيانات ؟ السندبااد 3 1,331 06-09-14, 05:32 PM
آخر رد: الشاكي لله
  WPF الأساسيات : الأحداث المُوجهّة Routed Events Islam Ibrahim 1 1,005 21-09-12, 10:38 PM
آخر رد: smss

التنقل السريع :


يقوم بقرائة الموضوع: بالاضافة الى ( 1 ) ضيف كريم