نمط MVVM في WPF مع تمرين عملي لتطبيق To-Do List
في هذا الدرس، سنتناول بالتفصيل نمط MVVM (Model-View-ViewModel) وهو أحد الأنماط الشهيرة لتصميم التطبيقات في WPF. سنوضح كيفية فصل منطق العمل عن واجهة المستخدم باستخدام MVVM، وكيفية تحسين هيكلية التطبيق باستخدام هذا النمط. سنتعرف أيضًا على كيفية تطبيقه من خلال تمرين عملي لتطوير تطبيق بسيط لإدارة المهام (To-Do List).
1. مقدمة إلى نمط MVVM
1.1 مفهوم MVVM وفوائده
MVVM هو نمط تصميم شائع في تطبيقات WPF وXamarin، حيث يساعد على فصل منطق العمل (Model) عن واجهة المستخدم (View) باستخدام وسيط بينهما يسمى ViewModel. الهدف الأساسي من MVVM هو توفير Separation of Concerns، بحيث يمكن تطوير كل جزء من التطبيق بشكل مستقل دون أن تتداخل المهام.
1.2 مكونات MVVM: Model وView وViewModel
يتكون نمط MVVM من ثلاثة أجزاء رئيسية:
- Model: يمثل البيانات الحقيقية ومنطق العمل للتطبيق. يتعامل مع قاعدة البيانات أو أي مصدر للبيانات.
- View: واجهة المستخدم، وتُبنى عادةً باستخدام XAML. تعرض البيانات التي توفرها ViewModel.
- ViewModel: الوسيط بين Model وView. يحتوي على الخصائص (Properties) والأوامر (Commands) التي ترتبط بـ View عبر Data Binding.
الفصل بين هذه المكونات يسهل عملية تطوير التطبيقات وصيانتها، حيث يتم فصل منطق البيانات عن واجهة المستخدم.
1.3 المقارنة مع الأنماط الأخرى (MVC وMVP)
يختلف MVVM عن الأنماط الأخرى مثل MVC وMVP. على سبيل المثال:
- MVC: يفصل Model وView عبر Controller، ويُستخدم بشكل شائع في تطبيقات الويب.
- MVP: يشبه MVVM لكن الـ Presenter في MVP يتحكم بشكل مباشر في View، بينما في MVVM يتواصل View مع ViewModel عبر Data Binding.
MVVM يتيح تفاعلاً أفضل بين View وViewModel، خصوصًا في بيئات مثل WPF وXAML، حيث يُستخدم الربط البياني (Data Binding) بشكل مكثف.
2. تنفيذ نمط MVVM
2.1 إنشاء Model: البيانات والمنطق
Model هو الجزء المسؤول عن إدارة البيانات. على سبيل المثال، في تطبيق To-Do List، يمكن أن يكون Model هو تعريف للمهام (Task) بخصائص مثل العنوان، الوصف، وحالة الإكمال.
public class TodoItem
{
public string Title { get; set; }
public string Description { get; set; }
public bool IsCompleted { get; set; }
}
2.2 تصميم ViewModel: الخصائص والأوامر
ViewModel يحتوي على الخصائص التي ترتبط بـ View من خلال Data Binding. كما يحتوي على الأوامر (Commands) التي تعالج تفاعل المستخدم مع واجهة المستخدم. يُستخدم INotifyPropertyChanged
لتحديث View عندما تتغير البيانات في ViewModel.
public class TodoViewModel : INotifyPropertyChanged
{
private ObservableCollection _items;
public ObservableCollection Items
{
get { return _items; }
set { _items = value; OnPropertyChanged(); }
}
public ICommand AddItemCommand { get; }
public TodoViewModel()
{
Items = new ObservableCollection();
AddItemCommand = new RelayCommand(AddItem);
}
private void AddItem(object parameter)
{
Items.Add(new TodoItem { Title = "New Task" });
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
2.3 بناء View: XAML والكود الخلفي
يتم إنشاء View باستخدام XAML ويكون مرتبطًا بالـ ViewModel عبر Data Binding. في المثال التالي، يتم عرض قائمة المهام (To-Do List) وتطبيق Command لإضافة مهمة جديدة.
<Window x:Class="TodoApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding TodoViewModel}">
<StackPanel>
<ListBox ItemsSource="{Binding Items}" DisplayMemberPath="Title" />
<Button Content="Add Task" Command="{Binding AddItemCommand}" />
</StackPanel>
</Window>
في هذا المثال، ItemsSource
يرتبط بمجموعة المهام في ViewModel، وCommand
يُستخدم لربط الأوامر بالأزرار لتنفيذ العمليات.
3. الربط البياني (Data Binding) في MVVM
3.1 ربط View بخصائص ViewModel
يتم الربط بين View وViewModel باستخدام Data Binding
. على سبيل المثال، يمكنك ربط عناصر تحكم مثل TextBox
وListBox
بخصائص ViewModel لجعلها تتفاعل مع البيانات.
<TextBox Text="{Binding NewTaskTitle}" />
3.2 استخدام ObservableCollection
يُستخدم ObservableCollection
في MVVM عند التعامل مع مجموعات البيانات لأنها تدعم التحديث التلقائي للواجهة عند إضافة أو حذف العناصر. هذا يجعلها الخيار الأمثل لعرض القوائم الديناميكية في WPF.
4. تمرين عملي: تطبيق To-Do List
في هذا التمرين، سنقوم ببناء تطبيق To-Do List بسيط باستخدام نمط MVVM. سنتعرف على كيفية إعداد Model، وViewModel، وتصميم View باستخدام Data Binding.
4.1 إعداد مشروع جديد
- افتح Visual Studio.
- اختر Create a new project.
- اختر WPF App (.NET Core) أو WPF App (.NET Framework) بناءً على الإصدار الذي تفضله.
- أعط المشروع اسمًا مثل
TodoApp
.
4.2 تصميم Model للمهام (To-Do Items)
- إضافة ملف Model جديد:
- أضف مجلدًا جديدًا إلى المشروع باسم
Models
. - أنشئ ملف C# جديدًا باسم
TodoItem.cs
داخل هذا المجلد.
- أضف مجلدًا جديدًا إلى المشروع باسم
- تعريف Model:
- افتح
TodoItem.cs
وأضف الخصائص التالية:
- افتح
public class TodoItem
{
public string Title { get; set; }
public string Description { get; set; }
public bool IsCompleted { get; set; }
}
4.3 إنشاء ViewModel لإدارة المهام
- إضافة ملف ViewModel جديد:
- أضف مجلدًا جديدًا إلى المشروع باسم
ViewModels
. - أنشئ ملف C# جديدًا باسم
TodoViewModel.cs
داخل هذا المجلد.
- أضف مجلدًا جديدًا إلى المشروع باسم
- تعريف ViewModel:
- افتح
TodoViewModel.cs
وقم بإضافة الكود التالي:
- افتح
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
public class TodoViewModel : INotifyPropertyChanged
{
public ObservableCollection<TodoItem> Items { get; set; }
private string _newTaskTitle;
public string NewTaskTitle
{
get => _newTaskTitle;
set
{
_newTaskTitle = value;
OnPropertyChanged();
}
}
private TodoItem _selectedTask;
public TodoItem SelectedTask
{
get => _selectedTask;
set
{
_selectedTask = value;
OnPropertyChanged();
}
}
public ICommand AddTaskCommand { get; }
public ICommand RemoveTaskCommand { get; }
public TodoViewModel()
{
Items = new ObservableCollection<TodoItem>();
AddTaskCommand = new RelayCommand(AddTask);
RemoveTaskCommand = new RelayCommand(RemoveTask);
}
private void AddTask(object parameter)
{
if (!string.IsNullOrWhiteSpace(NewTaskTitle))
{
Items.Add(new TodoItem { Title = NewTaskTitle });
NewTaskTitle = string.Empty;
}
}
private void RemoveTask(object parameter)
{
if (parameter is TodoItem task && Items.Contains(task))
{
Items.Remove(task);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
4.4 بناء واجهة المستخدم (View) باستخدام Data Binding
- فتح MainWindow.xaml:
- افتح ملف
MainWindow.xaml
.
- افتح ملف
- تعديل XAML لواجهة المستخدم:
- قم بتعديل XAML ليصبح بالشكل التالي:
<Window x:Class="TodoApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TodoApp"
Title="To-Do List" Height="350" Width="400" FlowDirection="RightToLeft">
<Window.DataContext>
<local:TodoViewModel />
</Window.DataContext>
<StackPanel Margin="10">
<TextBlock>أدخل المهمة</TextBlock>
<TextBox Name="TaskTextBox" Text="{Binding NewTaskTitle, UpdateSourceTrigger=PropertyChanged}" Margin="0,0,0,10"/>
<Button Content="إضافة مهمة" Command="{Binding AddTaskCommand}" Margin="0,0,0,10"/>
<ListBox ItemsSource="{Binding Items}" DisplayMemberPath="Title" Height="200" Margin="0,0,0,10"
SelectedItem="{Binding SelectedTask}" />
<Button Content="حذف المهمة المحددة" Command="{Binding RemoveTaskCommand}" CommandParameter="{Binding SelectedTask}" Margin="0,0,0,10" />
</StackPanel>
</Window>
4.5 إضافة RelayCommand
- إضافة ملف RelayCommand.cs:
- قم بإنشاء ملف جديد باسم
RelayCommand.cs
في المشروع.
- قم بإنشاء ملف جديد باسم
- تعريف RelayCommand:
- أضف الكود التالي إلى
RelayCommand.cs
:
- أضف الكود التالي إلى
using System;
using System.Windows.Input;
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
4.5.1 لماذا نستخدم RelayCommand بدلاً من ICommand؟
تعريف RelayCommand: هي فئة تمثيلية (Wrapper) توفر طريقة بسيطة ومختصرة لتنفيذ أوامر (Commands) في WPF. تُستخدم عادةً لتنفيذ العمليات في ViewModel دون الحاجة إلى إنشاء فئات أوامر مخصصة.
لماذا نستخدم RelayCommand:
- تُبسط عملية إنشاء الأوامر، حيث تتيح لك إنشاء الأوامر مباشرة من ViewModel دون الحاجة إلى كتابة فئات منفصلة.
- تُتيح لك تمرير الإجراءات (Actions) والمعاملات (Parameters) بشكل مباشر، مما يجعل الكود أكثر اختصارًا ونظافة.
- توفر طريقة مرنة لتحديد ما إذا كان يمكن تنفيذ الأمر (CanExecute) وإعادة التقييم عند الحاجة.
- تقليل التكرار في الكود، حيث يمكن استخدام نفس `RelayCommand` لعدة أوامر مختلفة دون تكرار الكود.
4.6 تشغيل التطبيق واختباره
- تشغيل التطبيق:
- اضغط على
F5
لتشغيل التطبيق. - أضف بعض المهام وتأكد من إمكانية إضافتها وحذفها.
- اضغط على
يمكنك العثور على المشروع الكامل على GitHub من خلال الرابط التالي: WPF MVVM TODO List Project.
الخلاصة
لقد قمت الآن ببناء تطبيق To-Do List باستخدام نمط MVVM في WPF. تم فصل منطق التطبيق عن واجهة المستخدم بنجاح باستخدام ViewModel، وتم تطبيق Data Binding بين View وViewModel. يمكنك توسيع هذا التطبيق لإضافة المزيد من الميزات مثل تحرير المهام وتغيير حالتها.
اترك تعليقاً