شرح الأوامر ومعالجة الأحداث في WPF مع مثال عملي لإنشاء آلة حاسبة
في هذا الدرس، سنستعرض كيفية التعامل مع الأحداث (Events) في WPF واستخدام الأوامر (Commands) لتنفيذ العمليات داخل واجهة المستخدم. تعتبر الأحداث والأوامر أدوات أساسية في بناء تطبيقات تفاعلية وفعالة. سنوضح كيفية إنشاء أوامر مخصصة باستخدام واجهة ICommand
وكيفية تطبيقها مع نمط MVVM (Model-View-ViewModel). سنقوم بإنشاء تمرين عملي لآلة حاسبة بسيطة تعتمد على الأوامر والأحداث لزيادة فهمنا لهذه المفاهيم.
1. معالجة الأحداث في WPF
1.1 ما هي الأحداث في WPF؟
الأحداث (Events) في WPF هي وسيلة أساسية للتفاعل بين المستخدم وواجهة التطبيق. على سبيل المثال، عندما ينقر المستخدم على زر، يتم إرسال حدث (Event) للنظام، ويمكنك معالجة هذا الحدث للقيام بعملية معينة. يمكننا ربط الأحداث في XAML أو من خلال الكود الخلفي (Code-Behind).
معالجة الأحداث في XAML:
<Button Content="انقر هنا" Click="Button_Click" />
في المثال أعلاه، عند النقر على الزر، يتم استدعاء الحدث Click
والذي يرتبط بالدالة Button_Click
المعرفة في الكود الخلفي.
معالجة الأحداث في الكود الخلفي (Code-Behind):
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("تم النقر على الزر!");
}
هذه الدالة يتم استدعاؤها عند الضغط على الزر، وتظهر رسالة عند النقر. يمكن أيضًا معالجة الأحداث مباشرة في XAML باستخدام EventTrigger
:
<Button Content="انقر هنا">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<!-- هنا يمكن وضع الأوامر أو الإجراءات -->
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
1.2 الأحداث الموجهة (Routed Events)
في WPF، هناك نوع مميز من الأحداث يسمى “الأحداث الموجهة” (Routed Events) والتي تمر عبر الشجرة البصرية لعناصر الواجهة. تنقسم هذه الأحداث إلى ثلاثة أنواع:
- Bubbling Events: تنتقل من العنصر المستهدف إلى الأعلى في الشجرة البصرية.
- Tunneling Events: تنتقل من الجذر إلى العنصر المستهدف، عكس اتجاه الـBubbling.
- Direct Events: يتم التعامل مع هذه الأحداث بشكل مباشر من العنصر المستهدف فقط.
مثال على حدث Tunneling:
<StackPanel PreviewMouseDown="StackPanel_PreviewMouseDown">
<Button Content="زر داخل StackPanel" />
</StackPanel>
private void StackPanel_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("تم التقاط الحدث في StackPanel قبل الوصول للزر");
e.Handled = true; // منع الحدث من الاستمرار في الشجرة
}
هنا، الحدث PreviewMouseDown
يتم استقباله أولاً في العنصر StackPanel
قبل أن يصل إلى الزر، وهذا مثال على Tunneling. يمكن منع الحدث من الاستمرار في الشجرة باستخدام e.Handled
.
2. مقدمة إلى الأوامر في WPF
2.1 ما هي الأوامر؟
الأوامر (Commands) في WPF هي آلية تتيح لك فصل منطق التطبيق عن واجهة المستخدم. عوضًا عن معالجة الأحداث بشكل مباشر في الواجهة، يمكنك استخدام الأوامر لتحديد العمليات التي يجب تنفيذها بناءً على تفاعلات المستخدم. يتيح ذلك للمطورين كتابة كود أكثر مرونة وإعادة استخدامه بسهولة.
2.2 واجهة ICommand
واجهة ICommand
توفر بنية أساسية لتطبيق الأوامر في WPF. باستخدامها، يمكن فصل منطق التنفيذ عن واجهة المستخدم، مما يجعل الكود أكثر تنظيماً وقابلاً لإعادة الاستخدام.
الأوامر تعتمد على واجهة ICommand
التي توفر بنية أساسية لتعريف الأوامر. تتكون من ثلاثة أجزاء رئيسية:
Execute(object parameter)
: لتنفيذ منطق الأمر عند استدعائه.CanExecute(object parameter)
: لتحديد ما إذا كان يمكن تنفيذ الأمر أم لا. يُستخدم هذا لتعطيل أو تمكين الأزرار المرتبطة بالأمر.CanExecuteChanged
: يُثار هذا الحدث عندما تتغير حالةCanExecute
لتحديث واجهة المستخدم.
يمكن تمرير المعلمات إلى الأوامر باستخدام الخاصية CommandParameter
:
<Button Content="Add" Command="{Binding AddCommand}" CommandParameter="ParameterValue" />
3. إنشاء أوامر مخصصة
3.1 إنشاء أمر مخصص
لإنشاء أوامر مخصصة، يجب تنفيذ واجهة ICommand
. المثال التالي يوضح كيفية إنشاء أمر مخصص:
public class CustomCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public CustomCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = 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; }
}
}
في هذا المثال، قمنا بإنشاء أمر مخصص باستخدام Action
لتنفيذ الأمر وFunc
للتحقق من إمكانية التنفيذ. يتم استدعاء Execute
عند تنفيذ الأمر، بينما CanExecute
يتحقق من حالة تمكين/تعطيل الأمر. يتم إلقاء الحدث CanExecuteChanged
لتحديث واجهة المستخدم عند تغير الحالة.
4. استخدام الأوامر مع نمط MVVM
4.1 ما هو MVVM؟
MVVM (Model-View-ViewModel) هو نمط تصميم في WPF يساعد على فصل منطق العمل (Model) عن واجهة المستخدم (View). يتم استخدام ViewModel كوسيط بين Model و View حيث يحتوي على منطق العمل ويعالج الأوامر التي يتم تنفيذها من الواجهة.
4.2 تطبيق ViewModel مع الأوامر
public class MainViewModel : INotifyPropertyChanged
{
private string _displayText;
public string DisplayText
{
get => _displayText;
set
{
_displayText = value;
OnPropertyChanged();
}
}
public ICommand AddCommand { get; }
public MainViewModel()
{
AddCommand = new CustomCommand(ExecuteAdd, CanExecuteAdd);
}
private void ExecuteAdd(object parameter)
{
// منطق تنفيذ الأمر هنا
}
private bool CanExecuteAdd(object parameter)
{
// التحقق من إمكانية التنفيذ
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
في هذا المثال، قمنا بإنشاء ViewModel يحتوي على خاصية DisplayText
وأمر AddCommand
. يتم استدعاء ExecuteAdd
لتنفيذ المنطق عند استدعاء الأمر، بينما CanExecuteAdd
يتحقق مما إذا كان يمكن تنفيذ الأمر.
5. تمرين عملي: إنشاء آلة حاسبة بسيطة
الآن سنقوم بتطبيق ما تعلمناه لإنشاء آلة حاسبة بسيطة باستخدام الأوامر وMVVM. سننشئ واجهة مستخدم بسيطة للتحكم في الآلة الحاسبة باستخدام الأوامر.
5.1 تصميم واجهة المستخدم (XAML)
<Window x:Class="WpfCalculator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="آلة حاسبة بسيطة" Height="450" Width="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Text="{Binding DisplayText, UpdateSourceTrigger=PropertyChanged}"
FontSize="24" Margin="10"/>
<UniformGrid Rows="4" Columns="4" Grid.Row="1">
<Button Content="7" Command="{Binding NumberCommand}" CommandParameter="7"/>
<Button Content="8" Command="{Binding NumberCommand}" CommandParameter="8"/>
<Button Content="9" Command="{Binding NumberCommand}" CommandParameter="9"/>
<Button Content="/" Command="{Binding OperationCommand}" CommandParameter="/"/>
<Button Content="4" Command="{Binding NumberCommand}" CommandParameter="4"/>
<Button Content="5" Command="{Binding NumberCommand}" CommandParameter="5"/>
<Button Content="6" Command="{Binding NumberCommand}" CommandParameter="6"/>
<Button Content="*" Command="{Binding OperationCommand}" CommandParameter="*"/>
<Button Content="1" Command="{Binding NumberCommand}" CommandParameter="1"/>
<Button Content="2" Command="{Binding NumberCommand}" CommandParameter="2"/>
<Button Content="3" Command="{Binding NumberCommand}" CommandParameter="3"/>
<Button Content="-" Command="{Binding OperationCommand}" CommandParameter="-"/>
<Button Content="0" Command="{Binding NumberCommand}" CommandParameter="0"/>
<Button Content="." Command="{Binding NumberCommand}" CommandParameter="."/>
<Button Content="=" Command="{Binding CalculateCommand}"/>
<Button Content="+" Command="{Binding OperationCommand}" CommandParameter="+"/>
</UniformGrid>
</Grid>
</Window>
5.2 تنفيذ ViewModel للآلة الحاسبة
public class CalculatorViewModel : INotifyPropertyChanged
{
private string _displayText = "";
public string DisplayText
{
get => _displayText;
set
{
_displayText = value;
OnPropertyChanged();
}
}
public ICommand NumberCommand { get; }
public ICommand OperationCommand { get; }
public ICommand CalculateCommand { get; }
public CalculatorViewModel()
{
NumberCommand = new CustomCommand(ExecuteNumber);
OperationCommand = new CustomCommand(ExecuteOperation);
CalculateCommand = new CustomCommand(ExecuteCalculate);
}
private void ExecuteNumber(object parameter)
{
DisplayText += parameter.ToString();
}
private void ExecuteOperation(object parameter)
{
DisplayText += " " + parameter.ToString() + " ";
}
private void ExecuteCalculate(object parameter)
{
try
{
var result = new DataTable().Compute(DisplayText, null);
DisplayText = result.ToString();
}
catch (Exception ex)
{
// تخصيص رسالة الخطأ للمستخدم
MessageBox.Show("حدث خطأ أثناء الحساب: " + ex.Message);
DisplayText = "خطأ";
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
5.3 ربط ViewModel بالنافذة الرئيسية
في الكود الخلفي للنافذة الرئيسية (MainWindow.xaml.cs
):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new CalculatorViewModel();
}
}
عند تشغيل المشروع، ستظهر الواجهة التالية لآلة الحاسبة. هذه الواجهة تمثل الشكل النهائي للتطبيق الذي قمنا ببنائه باستخدام الأوامر في WPF.
يمكنك العثور على المشروع الكامل على GitHub من خلال الرابط التالي: مشروع آلة الحاسبة باستخدام WPF.
الخلاصة
في هذا الدرس الشامل، تعلمنا كيفية التعامل مع الأحداث والأوامر في WPF وكيفية تنفيذ أوامر مخصصة باستخدام ICommand
وربطها مع نمط MVVM. من خلال التمرين العملي، قمنا بإنشاء آلة حاسبة بسيطة تعتمد على الأوامر، مما يوضح كيفية دمج المفاهيم النظرية مع التطبيق العملي. هذه المفاهيم تعد أساسية في بناء تطبيقات WPF قوية ومرنة.
اترك تعليقاً