آشنایی با ICommand Interface در WPF

در این مقاله قصد داریم درباره ICommand Interface و استفاده آن در پیاده سازی های عمومی Command زمانی که با الگوی MVVM در برنامه های WPF کار میکنید صحبت خواهیم کرد. ابتدا درباره Command ها خواهیم فهمید و سپس نگاهی خواهیم داشت به اجزاء ICommand interface .

آشنایی با ICommand Interface  در WPF

در این مقاله قصد داریم درباره ICommand Interface  و استفاده آن در پیاده سازی های عمومی Command زمانی که با الگوی  MVVM در برنامه های WPF کار میکنید صحبت خواهیم کرد. ابتدا درباره Command ها خواهیم فهمید و سپس نگاهی خواهیم داشت به اجزاء ICommand interface  .  برای یادگیری استفاده از ICommand  یک برنامه نمایشی در WPF خواهیم ساخت. در برنامه های نمایشی از الگوی MVVM  استفاده می شود.

Command  چیست ؟

در زمینه  WPF  ، فرمان یا Command  یک کلاس است برای پیاده سازی رابط  ICommand .  تفاوتهایی میان Command  و Event وجود دارد. Event  تعریف می شود  و با کنترل  UI در ارتباط است . اما Command  بیشتر انتزاعی است و بر روی آنکه چه کاری قرار است انجام شود تمرکز دارد. یک  Command  می تواند با چندین UI Control/ option همراه شود. برای مثال ، یک دستور ذخیره کردن می تواند با یک دکمه ذخیره اجرا شود، یا با فشردن ctrl  و  S یا با انتخاب گزینه ذخیره در منو اجرا شود. برای داشتن تعاملات در یک برنامه باید هر دوی Event  و  Command  را استفاده کنیم.

  در برنامه های WPF ، دو گزینه برای فراهم کردن تعاملات در UI داریم :

     . گزینه اول استفاده از Event  و نوشتن کد در فایل  Code-behind است که می خواهیم بر روی یک رویداد خاص اجرا شود،  اگر نخواهیم  از الگوی  MVVM استفاده کنیم. در  WPF  برای استفاده از این گزینه تعداد زیادی  RoutedEvent  موجود است. 

     . گزینه دوم نوشتن کد block  است که می خواهیم در ViewModel اجرا شود و کد را با استفاده از Command فراخوانی کند، اگر از الگوی MVVM  پیروی کنیم. اگر از این الگو استفاده کنیم ، عموما نباید کدی در code-behine  نوشته شود. و نمی توانیم از RoutedEvents استفاده کنیم زیرا در ViewModels قابل دسترس نیستند. به همین دلیل است که باید از Command  ها برای اجرای دلخواه code block  نوشته شده در ViewModel استفاده شود.  بنابراین Command  چیزی بین view و viewModel برای تعاملات است.

ICommand Interface

ICommand  سه عضو به صورت زیر دارد:

     Bool CanExecute  : متد CanExecute یک شیء به عنوان پارامتر میگیرد و به صورت  bool برمیگرداند. اگر  true برگردانده شود دستورات اجرا می شود. اگر  false برگرداند دستور نمی تواند اجرا شود.  اغلب یک delegate  به عنوان شیء به این متد پاس داده می شود . برای استفاده از delegate  داخلی مانند  action , predicate یا  func .

event EventHandler  : برای اطلاع کنترل  UI همراه با دستورات اگر  Command  بتواند اجرا شود. بر پایه اطلاع رسانی این event ، کنترل  UI  آن را به عنوان فعال / غیرفعال تغییر دهد . در واقع زمانی که متد  CanExecute  اجرا شود و تغییری در خروجی متد وجود داشته باشد ، از این رویداد استفاده می شود.

Void Execute  :  متدی است که کار واقعی در نظر گرفته شده برای Command  را در بر دارد. این متد فقط زمانی اجرا می شود که متد  CanExecute  مقدار  true  را برگرداند . یک شیء به عنوان آرگومان گرفته و عموما یک delegate  به متد پاس می دهد. delegate  مرجع متدی را نگهداری میکند که بعد از Command قرار است اجرا شود.

چرا از ICommand  استفاده کنیم؟

Icommand  یک رابط هسته ای برای دستورات  در WPF است .  در الگوی MVVM و برنامه های بر پایه Prism استفاده می شود. اما استفاده از رابط ICommand  محدود به MVVM نمیشود. دستورات داخلی زیادی هستند که این رابط را به وسیله WPF/ Silverlight/ Prism framework پیاده سازی میکنند. کلاس پایه RoutedCommand رابط ICommand  را پیاده سازی میکند. و  WPF دستورات  MediaCommands , ApplicationCommands, NavigationCommands, ComponentCommands و  EditingCommands را ارائه می دهد که از کلاس  RoutedCommand استفاده میکنند.

بررسی اجمالی نسخه نمایشی نرم افزار

برای نشان دادن پیاده سازی رابط ICommand یک نسخه نمایشی از یک ماشین حساب ساده استفاده کرده ایم. ماشین حساب ما دو ورودی با textbox ، چهار عملگر با Button و یک خروجی با Lable دارد. بعد از وارد کردن مقادیر ورودی بر روی یکی از عملگرها کلیک کرده و نتیجه را در خروجی مشاهده میکنید.

 

ابتدا یک برنامه WPF ایجاد کرده، فایل ها و پوشه های مورد نیاز را به آن اضافه کنید تا در مرحله بعد در آنها کد نویسی کنیم.

 

برای شروع لایه نمایشی رابط کاربری را ایجاد میکنیم. برای انجام این کار یک  Grid با چهار ردیف و چهار ستون ایجاد میکنیم. در فایل CalculatorView.xaml کدهای زیر را وارد کنید.

<Grid DataContext="{Binding Source={StaticResource calculatorVM}}" Background="#FFCCCC">
    <Grid.RowDefinitions>
        <RowDefinition Height="80"></RowDefinition>
        <RowDefinition></RowDefinition>
        <RowDefinition Height="80"></RowDefinition>
        <RowDefinition Height="44"></RowDefinition>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
    </Grid.ColumnDefinitions>

    <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4" FontSize="25" VerticalAlignment="Top" HorizontalAlignment="Center" Foreground="Blue" Content="ICommand Demo"/>
    <Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Margin="10,0,0,0" VerticalAlignment="Bottom" FontSize="20"  Content="First Input"/>
    <Label Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" Margin="10,0,0,0" VerticalAlignment="Bottom" FontSize="20"  Content="Second Input"/>

    <TextBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Margin="10,0,0,0" FontSize="20" HorizontalAlignment="Left" Height="30"  Width="150" TextAlignment="Center" Text="{Binding FirstValue, Mode=TwoWay}"/>
    <TextBox Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Margin="10,0,0,0" FontSize="20" HorizontalAlignment="Left"  Height="30" Width="150" TextAlignment="Center" Text="{Binding SecondValue, Mode=TwoWay}"/>

    <Rectangle Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" Fill="LightBlue"></Rectangle>
    <Button Grid.Row="2" Grid.Column="0" Content="+"  Margin="10,0,0,0" HorizontalAlignment="Left" Height="50" Width="50" FontSize="30" Command="{Binding AddCommand}"></Button>
    <Button Grid.Row="2" Grid.Column="1" Content="-"  Margin="10,0,0,0" HorizontalAlignment="Left" Height="50" Width="50" FontSize="30" Command="{Binding SubstractCommand}"></Button>
    <Button Grid.Row="2" Grid.Column="2" Content="*"  Margin="10,0,0,0" HorizontalAlignment="Left" Height="50" Width="50" FontSize="30" Command="{Binding MultiplyCommand}"></Button>
    <Button Grid.Row="2" Grid.Column="3" Content="%"  Margin="10,0,0,0" HorizontalAlignment="Left" Height="50" Width="50" FontSize="30" Command="{Binding DivideCommand}"></Button>

    <Label Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" FontSize="25" Margin="10,0,0,0" HorizontalAlignment="Left" Height="50"  Content="Result : "/>
    <TextBlock Grid.Row="3" Grid.Column="2" Grid.ColumnSpan="2" FontSize="20" Margin="10,0,0,0" Background="BlanchedAlmond" TextAlignment="Center"  HorizontalAlignment="Left" Height="36" Width="150" Text="{Binding Output}"/>
</Grid>

اکنون به فایل MainWindow.xaml رفته و فضای نام CalculaterView.xaml را اضافه کنید پس از آن می توانید به CalculatorView.xaml  در MainWindow دسترسی داشته باشید.

xmlns:views="clr-namespace:SimpleCommandDemoApp.Views"

درون گرید اصلی  MainWindow.xaml یک تگ برای CalculatorView.xaml اضافه میکنیم.

<Grid>
    <views:CalculatorView/>
</Grid>

ویژگی ها و یا Proprty های  textbox ها و  Lable  را در CalCulatorView.xaml به ViewModel  خودش که  CalculatorViewModel نامیده می شود ضمیمه میکنیم.

همانطور که میبینید در XAML ، خواص UI و خواص ViewModel محدودی داریم. "FirstValue" محدود به ویژگی "Text " در  textbox اول می شود . و به همین صورت برای ورودی دوم . "Output" محدود به "Content"  در Lable  می شود.

ویژگی "FirstValue" به صورت زیر نوشته می شود و به همین صورت برای "SecondValue" و "Output"  عمل خواهیم کرد.

public double FirstValue
{
    get
    {
        return firstValue;
    }
    set
    {
        firstValue = value;
    }
}

 

چگونه می توان از ICommand  استفاده کرد؟

برای ارتباط بین UI Controls و Commands نیاز به ایجاد یک کلاس دستورات برای پیاده سازی رابط  ICommand  است. ابتدا باید یک Command Class برای هر یک از قابلیتهای  Button ها ایجاد کنیم. بعدا خواهیم دید چگونه می توان برای بکاربردن این قابلیت ها  از کلاس عمومی دوباره استفاده کرد. در اینجا برای دکمه  Add دستورات را خواهیم نوشت .

با پیروی کردن از الگوی MVVM ، در این موارد برای بکاربردن چنین قابلیت هایی نیاز است تا رابط  ICommand  را پیاده سازی کنیم. برای پیاده سازی آن در عملگر جمع کد های زیر را وارد میکنیم.

public class PlusCommand : ICommand
{
    // Creating private field of CalculatorViewModel and passing calculatorViewModel into the constructor
    private CalculatorViewModel calculatorViewModel;
    public PlusCommand(CalculatorViewModel vm)
    {
        calculatorViewModel = vm;
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }
    public void Execute(object parameter)
    {
        calculatorViewModel.Add();
    }

    public event EventHandler CanExecuteChanged;
}

 

اکنون یک فیلد خصوصی از کلاس PlusCommand در CalculatorViewModel ایجاد کرده و یک نمونه از آن می سازیم.

private PlusCommand plusCommand;
public CalculatorViewModel()
{
    plusCommand = new PlusCommand(this);
}

 

فضای نام CalculatorViewModel را در CalculatorView.xaml ثبت میکنیم.

xmlns:vm="clr-namespace:SimpleCommandDemoApp.ViewModels"

بعد از فضای نام تگ UserControl.Resources را برای ثبت CalculatorViewModel با مشخص کردن کلید CalculatorVM اضافه میکنیم. کلید مشابهی برای فراهم کردن اتصال  DataContext و Grid  استفاده میکنیم.

<UserControl.Resources>
    <vm:CalculatorViewModel x:Key="calculatorVM" />
</UserControl.Resources>

متد Add را در  CalculatorViewModel پیاده سازی میکنیم.

public void Add()
{
   Output = firstValue + secondValue;
}

یک دستور با نام AddCommand  در CalculatorViewModel ایجاد میکنیم.

internal ICommand AddCommand
{
   get
   {
       return plusCommand;
       // return new RelayCommand(Add);
   }
}

 

برنامه را اجرا کنید و دکمه جمع را فشار دهید. سازنده  CalculatorViewModel فراخوانی خواهد شد.  از این سازنده یک نمونه از PlusCommand  خواهیم ساخت . هنگام اجرای AddCommand ، ابتدا متد CanExecute فراخوانی خواهد شد که true را بازمیگرداند. متد اجرا شده ، متد Add  را از  CalculatorViewModel فراخوانی خواهد کرد. تا اینجا اگر برنامه را اجرا کنیم ، مقادیر وارد شده را خواهیم دید. اما نتیجه برای ما نشان داده نمی شود. برای دیدن نتیجه باید INotifyPropertyChanged interface  را پیاده سازی کنیم.

هدف از INotifyPropertyChanged  اعلام کردن تغییرات اتفاق افتاده در یک ویژگی به همه منابع آن (UI Controls/ViewModel) است. معمولا در UI Property ها  INotifyPropertyChanged  پیاده سازی شده است. اکنون لازم است این interface  برای  ViewModel  هم پیاده سازی شود. بنابراین هر تغییری درون  ViewModel رخ بدهد به  UI  اطلاع داده شود.

همانطور که در بالا مشاهده شد Output ، نام ویژگی CalculatorViewModel است که به خاصیت  Content از Lable محدود شده بود. output یک ویژگی عمومی است و به  UI محدود می شود. با کمک  INotifyPropertyChanged  محتوای lable بروزرسانی می شود.

 INotifyPropertyChanged به صورت زیر در کلاس  ViewModelBase نوشته می شود:

public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                 }
            }
    }

CalculateViewModel از ViewModelBase  ارث بری میکند، یک خط کد درون بلوکی از همه سه ویژگی عمومی به صورت زیر اضافه می شود. 

OnPropertyChanged("FirstValue");

OnePropertyChanged یک متد است که نام  ویژگی را به عنوان آرگومان میگیرد. متدی که پیاده سازی کردیم هر زمان که تغییری در ویژگی ها چه در UI  و چه در  ViewModel رخ دهد فراخوانی می شود.

تا اینجا چهار کلاس دستور برای بکارگیری  رویداد کلیک چهار دکمه عملگر نوشتیم . اکنون با استفاده از action delegate  همه چهار کلاس دستور را  به وسیله ایجاد یک کلاس دستور عمومی با نام RelayCommand حذف خواهیم کرد. کدهای زیر را در RelayCommand  می نویسیم.

اکنون لازم است یکی از خطوط کد تغییر کند. در بلوک get از  ویژگی  AddCommand  خط اول کامنت می شود مانند زیر :

public ICommand AddCommand
    {
        get
        {
            //return plusCommand;
            return new RelayCommand(Add);
        }
    }

قبل از برگشتن از این بلوک ، سازنده RelayCommand  فراخوانی خواهد شد. قادر خواهیم بود نام  متد  Add را در متغیره محلی  WorkToDo مشاهده کنیم :

 

اکنون همه عملیات ها می توانند فقط با استفاده از کلاس RelayCommand  بکار گرفته شوند. ومیتوانیم همه چهار کلاس دستور را که در پوشه Commands/Specific ایجاد کرده بودیم حذف کنیم.

فایل های ضمیمه
دانلود نسخه ی PDF این مطلب