شرح الأوامر ومعالجة الأحداث في WPF مع مثال عملي لإنشاء آلة حاسبة

Amine
13/09/2024

في هذا الدرس، سنستعرض كيفية التعامل مع الأحداث (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) والتي تمر عبر الشجرة البصرية لعناصر الواجهة. تنقسم هذه الأحداث إلى ثلاثة أنواع:

  1. Bubbling Events: تنتقل من العنصر المستهدف إلى الأعلى في الشجرة البصرية.
  2. Tunneling Events: تنتقل من الجذر إلى العنصر المستهدف، عكس اتجاه الـBubbling.
  3. 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.

WPF Calculator Example

يمكنك العثور على المشروع الكامل على GitHub من خلال الرابط التالي: مشروع آلة الحاسبة باستخدام WPF.

الخلاصة

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

التعليقات

اترك تعليقاً