آشنایی با ICommand Interface در WPF
دوشنبه 2 آذر 1394در این مقاله قصد داریم درباره ICommand Interface و استفاده آن در پیاده سازی های عمومی Command زمانی که با الگوی MVVM در برنامه های WPF کار میکنید صحبت خواهیم کرد. ابتدا درباره Command ها خواهیم فهمید و سپس نگاهی خواهیم داشت به اجزاء ICommand interface .
در این مقاله قصد داریم درباره 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 ایجاد کرده بودیم حذف کنیم.
- WPF
- 5k بازدید
- 0 تشکر