مدتی هست که مشغول مطالعه و یادگیری WPF از طریق
مطالب سایت هستم؛ به همین خاطر تصمیم گرفتم مطلبی را حول محور اینترفیس
ICommnad گردآوری کنم و در اختیار کاربران سایت قرار دهم.
سرفصلهای این مطلب :
• Command چیست
• اینترفیس ICommand چیست
• چرا اینترفیس ICommand
• ایجاد UI مورد نیاز
• چگونگی استفاده از ICommand
• استفاده از INotifyPropertyChanged
Command چیست ؟
در برنامه نویسی WPF به هر کلاسی که اینترفیس ICommand را پیاده سازی کند، اصطلاحا Commnad گوییم. تفاوت کوچکی بین یک Event و Command وجود دارد. رخدادها برای کنترلهای UI ساخته و تخصیص داده میشوند؛ اما Commandها انتزاعیتر هستند و تمرکز آنها بر روی نحوهی انجام کارها میباشد.
برای تعاملات در برنامهها از Commandها و Eventها استفاده میکنیم. ما در WPF دو روش برای تعامل با UI داریم:
1- استفاده از Eventها و نوشتن کدهای مورد نیاز در بخش CodeBehind (رعایت نکردن الگوی MVVM).
WPF تعداد زیادی RoutedEvent پیش ساخته (Built In) دارد که از آنها میتوان در این روش استفاده کرد.
2- استفاده از Command و درگیر کردن کدهای اجرایی نوشته شده در ViewModel با استفاده از Command ها.
در زمان استفاده از الگوی MVVM مجاز به نوشتن کد در بخش CodeBehind نیستیم. بنابراین از سیستم رخدادهای طراحی شدهی در WPF که RoutedEvent میباشد، نمیتوان استفاده کرد. به این خاطر که رخدادها در ViewModel قابل دسترسی نمیباشد.
اینترفیس ICommand:
این اینترفیس سه عضو دارد که آنها را در جدول زیر مشاهده میکنید:
نام عضو | توضیحات |
Bool CanExecute(object parameter) | این تابع پارامتری از نوع object را دریافت میکند و یک مقدار bool را به خروجی تابع میفرستد. اگر مقدار خروجی متد، true باشدcommand اجرا خواهد شد و در غیر اینصورت اتفاقی رخ
نخواهد داد. اغلب ازDelegate ها به عنوان پارامتر این تابع استفاده میشود؛Delegate های پیش ساختهای همچون Action,Predicate,Func |
Event EventHandler CanExecuteChanged | این رویداد برای آگاه سازی UI
که به Command متصل است، استفاده میشود .بر اساس خروجی تابع CanExecute، این رویداد اتفاق میافتد. |
Void Execute(Object parameter) | این متد کار اصلی را در Commandها انجام میدهد. این متد تنها زمانی اجرا میشود که متدCanExecute مقدار true
را بازگرداند. این تابع پارامتری را از نوع object دریافت میکند، اما
عموما ما یکDelegate
را به آن ارسال میکنیم. Delegate ارجاعی را به متدی، در خود نگاه میدارد که انتظار داریم در صورت اجرایcommand ، اجرا شود. |
چرا اینترفیس ICommand : هستهی اصلی Commandها در WPF، اینترفیس ICommand میباشد. این اینترفیس بهصورت گستردهای در الگوی MVVM و Prism استفاده شده است و استفادهی از آن محدود به MVVM نمیباشد. تعداد زیادی Commnad بصورت پیش ساخته وجود دارند که این اینترفیس را پیاده سازی کردهاند .کلاس پایه RoutedCommand اینترفیس ICommand را پیاده سازی کرده است و WPF فرمانهای زیر را فراهم کرده است:
• MediaCommnads
• ApplicationCommnads
• NavigationCommands
• ComponentCommnads
• EditingCommnads
در این مطلب به دنبال ایجاد برنامهای هستیم که حاصل جمع مفدار دو Textbox را پس از فشردن کلید جمع در یک textblock نمایش دهد.
ساخت UI مورد نیاز :
گام اول :
با اجرای ویژوال استودیو، برنامهای را با نام ICommnadSample ایجاد کنید. ساختار پروژه به شکل زیر است:
همانطور که میبینید View و ViewModel و در نهایت Commandها در پوشههای مجزایی ساماندهی شدهاند.
گام دوم:
ابتدا قالب گرافیکی را مشخص میکنیم. در پوشهی Views یک UserControl را به نام CalculatorView.xaml ایجاد کرده و کد زیر را در آن نوشتیم:
<Grid DataContext="{Binding Source={StaticResource calculatorVM}}" Background="#FFCCCC">
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition/>
<RowDefinition Height="80"/>
<RowDefinition Height="44"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<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 Sample"/>
<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"/>
<Button Grid.Row="2" Grid.Column="0" Content="+" Margin="10,0,0,0" HorizontalAlignment="Left" Height="50" Width="50" FontSize="30" Command="{Binding AddCommand}"/>
<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>
برای استفاده از این UserControl در پنجرهی اصلی برنامه (فایل MainWindows.Xaml) به شکل زیر عمل میکنیم:
ابتدا فضای نام View را به فایل MainWindows.xaml اضافه میکنیم :
xmlns:myview="clr-namespace:ICommnadSample.Views"
ایجاد تگ برای استفاده از View تولید شده در Grid اصلی برنامه :
<Grid>
<myview:CalculatorView/>
</Grid>
گام سوم :
همانطور که مشاهده میکنید، کنترلهایی که در عملیات انقیاد دادهها (DataBinding) شرکت میکنند، از طریق خاصیت Binding و معرفی خصوصیت مورد نظر مشخص میشوند.
برای این منظور در پوشهی ViewModels و به کلاس CalculatorViewModel سه خصوصیت بههمراه فیلد خصوصی، به شکل زیر اضافه میکنیم:
private double firstValue;
private double secondValue;
private double output;
public double FirstValue
{
get { return firstValue; }
set
{
firstValue = value;
OnPropertyChanged("FirstValue");
}
}
public double SecondValue
{
get { return secondValue; }
set
{
secondValue = value;
OnPropertyChanged("SecondValue");
}
}
public double Output
{
get { return output; }
set
{
output = value;
OnPropertyChanged("Output");
}
}
چگونگی استفادهی از اینترفیس ICommand :
برای ایجاد ارتباط بین Command ها و UI میبایست اینترفیس ICommand توسط کلاس مورد نظر ما پیاده سازی شود. در ابتدا کلاسی با عنوان PlusCommnad ایجاد و از اینترفیس مورد نظر ارث بری میکنیم.
هدف ما شبیه سازی رویداد کلیک دکمهی موجود در صفحه با استفاده از Command میباشد.
گام چهارم:
پس از پیاده سازی اینترفیس لازم است تا کمی تغییر در بدنه متدها ایجاد کنیم:
public class PlusCommand : ICommand
{
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;
}
همانطور که میبینید در ابتدا فیلدی از جنس کلاس CalculatorViewModel ایجاد و از طریق سازندهی کلاس آن را مقدار دهی کردیم (قصد داریم نمونهای از ViewModel مورد نظر را به این کلاس ارسال کنیم).
در ادامه بصورت دستی (Hard Code) مقدار بازگردانده شده را برای تابع CanExecute به مقدار true تغییر دادیم و متد تابع Execute را به شکلی تغییر دادیم تا تابع Add را در CalculatorViewModel، اجرا کند.
گام پنجم:
از کلاس PlusCommand در CalculatorViewModel، یک شیء ساخته و در سازندهی CalculatorViewModel آن را مقدار دهی میکنیم:
private PlusCommand plusCommand;
public CalculatorViewModel()
{
plusCommand = new PlusCommand(this);
}
گام ششم:
در فایل CalculatorView ارجاعی را به فضای نام کلاس CalculatorViewModel ایجاد میکنیم :
xmlns:vm="clr-namespace:ICommnadSample.ViewModels"
پس از اضافه کردن فضای نام، از تگ UserControl.Resource برای رجیستر کردن CalculatorViewModel با کلید calculatorVM جهت مشخص کردن منبع داده استفاده کردیم.
<UserControl.Resources>
<vm:CalculatorViewModel x:Key="calculatorVM" />
</UserControl.Resources>
گام هفتم:
اضافه کردن تابع Add در CalculatorViewModel برای عملیات جمع :
public void Add()
{
Output = firstValue + secondValue;
}
گام هشتم:
تعریف یک Command برای عملیات جمع به نام AddCommand. این همان خصوصیتی است که باید بجای رویداد کلیک دکمه از طریق خاصیت Command موجود در کنترل و ویژگی Binding به کنترل متصل شود.
public ICommand AddCommand {
get
{
return plusCommand;
}
}
نحوهی استفاده:
Command="{Binding AddCommand}"
گام نهم :
برای تکمیل عملیات انقیاد دادهها، کلاسی به نام ViewModelBase تعریف شده است. این کلاس از اینترفیس INotifyPropertyChange ارث بری کرده و اعضای این کلاس را پیاده سازی کرده است.
public class ViewModelBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
پیاده سازی این اینترفیس سبب میشود تا کلاسهای ViewModel ایی که احتیاج به این اینترفیس برای عملیات انقیاد دادهها دارند، تنها با ارث بری، از این ظرفیت استفاده کنند و نیازی به پیاده سازی این اینترفیس در هر کلاس نباشد.
گام دهم:
ارث بری کلاس CalculatorViewModel از کلاس ViewModelBase:
public class CalculatorViewModel : ViewModelBase
در این مرحله هر کنترلی را که قصد داریم با تغییر منبع داده بروز شود و یا با تغییر وضعیتش منبع داده تغییر کند، اعلام میکنیم:
OnPropertyChanged("FirstValue");
برای هر سه خصوصیت ViewModel کد زیر را در بلاک Set تکرار میکنیم (توجه شود که پارامتر ارسالی باید نام پراپرتی مورد نظر باشد)
کد کامل کلاس CalculatorViewModel به شکل زیر است:
public class CalculatorViewModel : ViewModelBase
{
private double firstValue;
private double secondValue;
private double output;
private PlusCommand plusCommand;
public CalculatorViewModel()
{
plusCommand = new PlusCommand(this);
}
public double FirstValue
{
get { return firstValue; }
set
{
firstValue = value;
OnPropertyChanged("FirstValue");
}
}
public double SecondValue
{
get { return secondValue; }
set
{
secondValue = value;
OnPropertyChanged("SecondValue");
}
}
public double Output
{
get { return output; }
set
{
output = value;
OnPropertyChanged("Output");
}
}
public ICommand AddCommand
{
get
{
return plusCommand;
}
}
internal void Add()
{
Output = firstValue + secondValue;
}
}
حال میتوانید برنامه را اجرا و تست کنید:
برای درک بهتر عملیات انقیاد دادها میتوانید به این مقاله مراجعه کنید.