بداية سريعة باستخدام WPF. الجزء 1. ملزم ، INotifyPropertyChanged و MVVM

مرحبا بالجميع!


لأسباب مختلفة ، يستخدم معظمنا تطبيقات سطح المكتب ، على الأقل متصفحًا :) والبعض منا بحاجة إلى كتابة الخاصة بنا. في هذه المقالة ، أريد مراجعة عملية تطوير تطبيق سطح مكتب بسيط باستخدام تقنية Windows Presentation Foundation (WPF) وتطبيق نمط MVVM. أولئك الذين يرغبون في مواصلة القراءة ، من فضلك ، تحت القط.


أعتقد أنه ليس من الضروري أن نقول أن WPF هو تطوير Microsoft :) تم تصميم هذه التقنية لتطوير تطبيقات سطح المكتب لنظام التشغيل Windows ، بدءًا من Windows XP. لماذا؟ ويرجع ذلك إلى حقيقة أن WPF يعمل على منصة .NET الأساسية ، والمتطلبات الدنيا هي Windows XP والإصدارات الأحدث. لسوء الحظ ، لا يعمل WPF على منصات أخرى ، على الرغم من أن هناك احتمالات بأن هذا سيتغير في المستقبل القريب: إطار أفالونيا القائم على WPF قيد التطوير .


ما هو المميز في WPF؟


اختلافان رئيسيان بين WPF وأدوات بناء سطح المكتب الأخرى:


  • لغة ترميز XAML لترميز واجهة النافذة نفسها.
  • التقديم من خلال DirectX ، تسريع رسومات الأجهزة.

لن أخوض في التفاصيل ، لأن هذا ليس موضوع الموضوع تماما. إذا كنت مهتمًا ، فبالتالي google XAML و WPF rendering و milcore.dll و DirectX :)


عن ماذا تتحدث هذه المقالة؟


تحتوي هذه المقالة على تطبيق مثال مبني على تقنية WPF:



سأحاول توجيه مادة المقالة في اتجاه عملي بأسلوب "كرر بعدي" مع التفسيرات.


ماذا نحتاج لتكرار المقال؟


تجربة تطوير بسيطة في C # :) كحد أدنى ، تحتاج إلى فهم بنية اللغة جيدًا. ستحتاج أيضًا إلى جهاز يعمل بنظام Windows (Win 10 في الأمثلة) مع تثبيت Visual Studio عليه (في الأمثلة سيكون عام 2017 ، هناك إصدار مجتمع مجاني). عند تثبيت VS ، ستحتاج إلى تمكين دعم تطوير سطح المكتب لمنصة .NET


الصورة


أيضا في هذا القسم سوف أصف إنشاء مشروع.


أطلقنا VS ، أنشأنا مشروعًا جديدًا ، حدد نوع التطبيق WPF App (.NET Framework) (يمكنك إدخاله في شريط البحث في الجزء العلوي الأيمن) ، وقم بتسميته أيا كان.


الصورة


بعد إنشاء مشروع جديد ، تفتح نافذة محرر الواجهة ، يبدو لي هذا


الصورة


يوجد في الجزء السفلي محرر تخطيط ، وفي الأعلى توجد معاينة لواجهة النافذة ، ولكن يمكنك تغيير الموقع النسبي لمحرر التعليمات البرمجية ومعاينة الواجهة بحيث يتم وضعها في ترتيب أفقي باستخدام هذه الأزرار (على يمين حدود المنطقتين):


الصورة


قبل أن تبدأ


يجب وضع عناصر النافذة (تسمى أيضًا عناصر التحكم من كلمة التحكم ) داخل الحاوية أو داخل عنصر آخر من نوع ContentControl. الحاوية هي عنصر تحكم خاص يسمح لك بوضع عدة عناصر تحكم تابعة للأطفال داخلها وتنظيم ترتيبها المتبادل. أمثلة على الحاويات:


  • الشبكة - يسمح لك بتنظيم العناصر حسب الأعمدة والصفوف ، ويتم تكوين عرض كل عمود أو صف بشكل فردي.
  • StackPanel - يسمح لك بترتيب الأطفال في صف أو عمود واحد.

هناك حاويات أخرى. نظرًا لأن الحاوية هي أيضًا عنصر تحكم ، فقد يكون هناك داخل الحاوية حاويات متداخلة تحتوي على حاويات متداخلة وما إلى ذلك. هذا يسمح لك بترتيب الضوابط بمرونة فيما يتعلق ببعضها البعض. أيضًا ، بمساعدة الحاويات ، لا يمكننا التحكم بمرونة في سلوك عناصر التحكم المتداخلة عند تغيير حجم النافذة.


واجهة MVVM و INotifyPropertyChanged. نسخة من النص.


ستكون نتيجة هذا المثال تطبيقًا يحتوي على عنصري تحكم ، في أحدهما يمكنك تحرير النص ، وفي العرض الآخر فقط. سيتم نقل التغييرات من واحد إلى آخر بشكل متزامن دون نسخ صريح للنص باستخدام التجليد .


لذا ، لدينا مشروع تم إنشاؤه حديثًا ( أطلقت عليه اسم Ex1 ) ، وانتقل إلى محرر التخطيط وقبل كل شيء استبدل الحاوية الافتراضية ( <Grid> </Grid> ) بـ <StackPanel> </StackPanel> . هذه الحاوية ستكون كافية ، لأن سنحتاج إلى وضع عنصري تحكم فقط فوق الآخر. نحدد بشكل صريح كيفية ترتيب المكونات عن طريق إضافة خاصية Orientation = "Vertical" . أضف عنصرين داخل رصة اللوحة: حقل لإدخال النص وحقل لعرض النص. نظرًا لأن عناصر التحكم هذه لن تحتوي على رمز مضمن ، يمكنك وصفها بعلامة إغلاق ذاتي (انظر الرمز أدناه). بعد كل الإجراءات المذكورة أعلاه ، يجب أن يتخذ رمز وصف الحاوية وعناصر التحكم المتداخلة الشكل التالي:


<StackPanel Orientation="Vertical"> <TextBox /> <TextBlock /> </StackPanel> 

الآن دعونا نركز على الغرض من هذا المثال. نريد أنه عند الكتابة في مربع النص ، يتم عرض نفس النص بشكل متزامن في كتلة النص ، مع تجنب عملية النسخ الصريحة. نحن بحاجة إلى نوع من الكيان المتصل ، وهنا نأتي إلى شيء مثل ملزم ، الذي ذكر أعلاه. الربط في مصطلحات WPF هو آلية تسمح لك بربط بعض خصائص عناصر التحكم ببعض خصائص كائن فئة C # وتحديث هذه الخصائص بشكل متبادل عندما يتغير أحد أجزاء الحزمة (يمكن أن يعمل هذا في واحد أو آخر أو كلا الاتجاهين في وقت واحد). بالنسبة لأولئك الذين هم على دراية بـ Qt ، يمكنك رسم تشابه بين الفتحات والإشارات. حتى لا نطيل الوقت ، دعنا ننتقل إلى الرمز.


لذا ، لتنظيم الربط ، تحتاج إلى خصائص عناصر التحكم وبعض الخصائص لفئة C # معينة. أولاً ، دعنا نكتشف كود XAML. يتم تخزين نص كل من عناصر التحكم في خاصية Text ، لذا قم بإضافة ربط لهذه الخصائص. يتم ذلك على النحو التالي:


 <TextBox Text="{Binding}"/> <TextBlock Text="{Binding}"/> 

لقد صنعنا ربطًا ، ولكن في الوقت الحالي ليس من الواضح لماذا :) نحن بحاجة إلى كائن من فئة معينة وبعض الممتلكات في هذا الكائن الذي سيتم إجراء الربط (كما يقولون ، والذي تحتاج إلى ربطه).


إذن ما هذا الفصل؟ يُطلق على هذه الفئة نموذج عرض وتعمل كحلقة وصل بين طريقة العرض (الواجهة أو أجزائها) والنموذج (النموذج ، أي تلك الأجزاء من التعليمات البرمجية المسؤولة عن منطق التطبيق. يتيح لك هذا الفصل (إلى حد ما) ) منطق التطبيق من الواجهة (طريقة العرض ، العرض) يسمى نموذج Model-View-ViewModel (MVVM) . في WPF ، تسمى هذه الفئة أيضًا DataContext .


ومع ذلك ، فإن كتابة فئة نموذج عرض لا يكفي. من الضروري إعلام آلية الربط بطريقة أو بأخرى بتغيير خاصية نموذج العرض أو خاصية العرض. للقيام بذلك ، هناك واجهة خاصة INotifyPropertyChanged ، والتي تحتوي على الحدث PropertyChanged . ننفذ هذه الواجهة في إطار BaseViewModel من الفئة الأساسية. في المستقبل ، سوف نرث جميع نماذج العرض الخاصة بنا من هذه الفئة الأساسية حتى لا تكرر تنفيذ الواجهة. لذا ، أضف دليل ViewModels إلى المشروع ، وأضف ملف BaseViewModel.cs إلى هذا الدليل. نحصل على هيكل المشروع التالي:


الصورة


كود التنفيذ لنموذج العرض الأساسي:


 using System.ComponentModel; namespace Ex1.ViewModels { public class BaseViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } } 

دعونا ننشئ نموذج العرض الخاص بنا لفئة MainWindow ، الموروثة من القاعدة الأساسية. للقيام بذلك ، في نفس الدليل ViewModels ، قم بإنشاء ملف MainWindowViewModel.cs ، حيث سيكون هناك مثل هذا الرمز:


 namespace Ex1.ViewModels { public class MainWindowViewModel : BaseViewModel { } } 

عظيم! الآن نحن بحاجة إلى إضافة خاصية إلى نموذج العرض هذا ، والذي سنقوم بربط نص عناصر التحكم الخاصة بنا به. نظرًا لأن هذا نص ، يجب أن يكون نوع هذه الخاصية سلسلة :


 public string SynchronizedText { get; set; } 

ونتيجة لذلك ، نحصل على مثل هذا الرمز


 namespace Ex1.ViewModels { public class MainWindowViewModel : BaseViewModel { public string SynchronizedText { get; set; } } } 

لذا ، يبدو أنهم فعلوا ذلك. يبقى ربط هذه الخاصية من وجهة نظر وجاهزة. دعنا نفعل ذلك الآن:


 <TextBox Text="{Binding Path=SynchronizedText}"/> <TextBlock Text="{Binding Path=SynchronizedText}"/> 

Nishtyak ، نبدأ المشروع ، نكتب في مربع النص iiiii ... لا يحدث شيء))) حسنًا ، لا بأس ، في الواقع نحن نسير في الطريق الصحيح ، لم نصل إلى النقطة الصحيحة.


أقترح التوقف للحظة والتفكير في ما نفتقده. لدينا وجهة نظر. نموذج العرض أيضا. خصائص مثل zabindili. يتم تنفيذ الواجهة المطلوبة. لقد قمنا بالكثير من العمل لنسخ سطر نص مثير للشفقة ، لماذا نحتاج هذا ؟؟؟!؟! 111


حسنا ، نكتة جانبا. نسينا إنشاء كائن نموذج عرض وشيء آخر (المزيد عن ذلك لاحقًا). وصفنا الصف نفسه ، لكن هذا لا يعني أي شيء ، لأنه ليس لدينا كائنات من هذه الفئة. حسنًا ، أين تريد تخزين رابط لهذا الكائن؟ أقرب إلى بداية المثال ، ذكرت بعض البيانات DataContext المستخدمة في WPF. لذا ، فإن أي ملف شخصي له خاصية DataContext التي يمكننا تعيين رابط لها لنموذج العرض الخاص بنا. فلنفعل ذلك. للقيام بذلك ، افتح ملف MainWindow.xaml واضغط F7 لفتح التعليمات البرمجية لهذا العرض. إنها فارغة تقريبًا ، وتحتوي فقط على مُنشئ فئة النافذة. أضف إنشاء نموذج العرض الخاص بنا إليه ووضعه في DataContext للنافذة (لا تنس أن تضيف باستخدام مساحة الاسم المطلوبة):


 public MainWindow() { InitializeComponent(); this.DataContext = new MainWindowViewModel(); } 

كان الأمر بسيطًا ، ولكنه لا يزال غير كافٍ. ومع ذلك ، عند بدء تشغيل التطبيق ، لا تحدث مزامنة النص. ما الذي يجب فعله أيضًا؟


تحتاج إلى رفع الحدث PropertyChanged عندما تتغير خاصية SynchronizedText وإبلاغ العرض بأنه يجب مراقبة هذا الحدث. لذا ، لتشغيل الحدث ، قم بتعديل كود نموذج العرض:


 public class MainWindowViewModel : BaseViewModel { private string _synchronizedText; public string SynchronizedText { get => _synchronizedText; set { _synchronizedText = value; OnPropertyChanged(nameof(SynchronizedText)); } } } 

ماذا فعلنا هنا؟ أضفنا حقلًا مخفيًا لتخزين النص ، ولفناه في خاصية موجودة ، وعند تغيير هذه الخاصية ، لا نغير الحقل المخفي فحسب ، بل ندعو أيضًا طريقة OnPropertyChanged المحددة في نموذج العرض الأساسي ونرفع حدث PropertyChanged المعلن عنه في واجهة INotifyPropertyChanged ، والذي تم تنفيذه أيضًا في القاعدة عرض النماذج. وتبين أنه في كل مرة يتم فيها تغيير النص ، يقع الحدث PropertyChanged ، والذي يتم تمرير اسم خاصية نموذج العرض الذي تم تغييره إليه.


حسنا ، كل شيء تقريبا ، خط النهاية! يبقى تحديد وجهة النظر التي يجب أن تستمع إلى الحدث PropertyChanged :


 <TextBox Text="{Binding Path=SynchronizedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}"/> <TextBlock Text="{Binding Path=SynchronizedText, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/> 

إلى جانب حقيقة أننا أشرنا إلى أي من المشغلين يجب أن يتم التحديث ، أوضحنا أيضًا في أي اتجاه يتم تتبع هذا التحديث: من العرض إلى عرض النموذج أو العكس. نظرًا لأننا ندخل نصًا في مربع النص ، فنحن مهتمون فقط بالتغييرات في العرض ، لذلك نختار وضع OneWayToSource . في حالة كتلة النص ، كل شيء هو عكس ذلك تمامًا: نحن مهتمون بالتغييرات في نموذج العرض لعرضها في العرض ، لذلك نختار وضع OneWay . إذا أردنا تتبع التغييرات في كلا الاتجاهين ، فلا يمكننا تحديد الوضع على الإطلاق ، أو تحديد TwoWay بشكل صريح.


لذا ، قم بتشغيل البرنامج ، اكتب النص والصوت! يتغير النص بشكل متزامن ، ولم نقم بنسخ أي شيء في أي مكان!


الصورة


شكرا لكم على اهتمامكم ، لتستمر. سنتعامل مع DataTemplate ونمط الأمر.

Source: https://habr.com/ru/post/ar427325/


All Articles