سلام دوستان .
مطالبی که در زیر میگم ، هر چند تحقیق کردم اما 100 درصد مطمئن نیستم که درست باشه . دوستان لطفا راهنمایی کنن که این مطالب ، درست هست یا کجاها نادرست هست؟ :
یک جریان ، قضیه ی معماری MVVM هست و قضیه ی دیگه ، بالا بردن ماژولاریتی (Modular) و همچنین رعایت اصول Solid در طراحی نرم افزار و لایه ها .
در معماری MVVM ، فقط کافی هست که 3 لایه ی View و ViewModel و Model داشته باشیم اما ارتباط بین لایه های View و ViewModel ، چه توسط Command ها برقرار بشه ، یا نشه و بجاش توسط فراخوانیِ معمولیِ متدها انجام بشه ، معماری MVVM را نقض نمیکنه .
در واقع در MVVM ، مهم اینه که اون 3 لایه وجود داشته باشند و هر کدوم وظایف خودشون را انجام بدن .
به همین دلیل ، در MVVM ، وقتی حتی از Command ها هم استفاده نکنیم ، یا حتی اگه دسترسی مستقیم به اعضا و پروپرتی هایی که درون کلاس های ViewModel مون تعریف کردیم (از درون کلاس های View مون) داشته باشیم ، باعث نقض معماری MVVM نمیشه .
================
اما قضیه ی دوم ، اینه که قابلیت ماژولار بودن برنامه مون را بالا ببریم و همچنین اصول Solid (مخصوصا اصل Dependency Inversion Principle) را رعایت کنیم تا قابلیت نگه داری و تست کدمون را افزایش بدیم و مخصوصا اینکه اساسا معماری MVVM ، برای همین منظور طراحی شد (یعنی برای این طراحی شد که قابلیت نگه داری و تست کدمون را افزایش بده) ، به همین جهت ، خیلی در این معماری توصیه میشه که برای ارتباط بین لایه های View و ViewModel ، از Binding و Command استفاده کنیم . چرا؟
چون Command ها ، رابط و اینترفیسِ ICommand دارند و ارتباط باهاشون وقتی با رابط و اینترفیس انجام بشه ، اصل Dependency Inversion Principle در Solid را رعایت کرده و ماژولار بودن و قابلیت نگه داری و تست کد را به این ترتیب ، افزایش داده .
درست میگم؟
حالا میگم برای افزایش این بهره وری و افزایش قابلیت نگه داری کد ، مثل اغلب لایه های دیگه که با اینترفیس با هم در ارتباط هستند ، برای لایه ی ViewModel مون هم یه اینترفیس درست کنیم که اعضایی که نیاز به ارتباط برقرار کردن باهاشون داریم مثل پروپرتی ای از نوع ICommand و احیانا رویدادی که اگه نیاز شد ، پیغام ها (شامل موفقیت آمیز بودن یا ارورها) را به لایه ی View برای نمایش دادن ، منتقل کنه ، و لایه ی View ، فقط با این اینترفیس از ViewModel در ارتباط باشه (مستقیما با کلاس ها و اعضاش ارتباط نداشته باشه) (و این اینترفیس ، از این دست اعضا داشته باشه تا کلاسی که این را پیاده سازی میکنن ، هر کلاسی بتونن باشن و هر عضوی بتونن داشته باشن) . تا باعث بشه اصل DIP در Solid بیشتر رعایت بشه .
مثل کد زیر :
// ViewModel Layer
public interface IStudentViewModel
{
event EventHandler NotifyToViewEvent;
ICommand GetStudentCommand{get;set;}
}
public class StudentViewModel : IStudentViewModel
{
public event EventHandler NotifyToViewEvent;
public ICommand GetStudentCommand{get;set;}
public StudentViewModel()
{
this.GetStudentCommand = new DelegateCommand(this.GetStudentExecute);
}
private void GetStudentExecute()
{
this.NotifyToViewEvent?.Invoke(this, new EventArgs());
}
}
// View Layer
public class MyWindow : Window
{
private IStudentViewModel myStudentViewModel;
public MyWindow()
{
InitializeComponent();
this.myStudentViewModel = new StudentViewModel();
this.myStudentViewModel.NotifyToViewEvent += this.NotifyViewModelHandler;
this.DataContext = this.myStudentViewModel;
}
private void NotifyViewModelHandler(object sender, EventArgs e)
{
MessageBox.Show(e.ToString());
}
}
و در xaml :
<Button Content="Get Student" Command="{Binding Path = GetStudentCommand}"/>
اما همچین ساختاری را خیلی جای کمی دیدم و نمیدونم مرسوم هست یا کلا روال غیر عادی که نیست دیگه؟ به نظر خودم درسته .
برای بهتر کردن کد هم میشه از الگوی طراحی Factory Method یا Service Locator استفاده کرد .
رویداد NotifyToViewEvent هم در کد بالا ، تقریبا فقط میشه گفت همین یک رویداد کافی هست تا همه ی متدهایی که Command ها را اجرا میکنن ، این رویداد را فراخوانی کنن . یعنی همین رویداد ، به ازای همه ی Command کافی هست چون فقط وظیفه ی انتقال هر نوع پیغام از هر Command ای را داره .
=======
بنابراین حتی با رعایت اصول Solid در MVVM هم اشکالی نداره که در کد سی شارپ ، لایه ی View و ViewModel با هم ارتباط داشته باشند :
Simplifying the WPF TreeView by Using the ViewModel Pattern - CodeProject
البته در لینک بالا ، چون شیِ _familyTree اش در کلاس TextSearchDemoControl ، از نوع کلاس هست (و از نوع اینترفیس یا کلاس abstract نیست) ، اصل DIP در Solid را نقض میکنه .
======
بنابراین هیچ مشکلی نداره که یک Command مون را از توی یک رویداد عادی اجرا کنیم . یعنی بجای Command ، یک رویداد عادی برای کنترل مون درست کنیم و Command ای که در لایه ی ViewModel درست کردیم را از توی View ، فراخوانی کنیم .
این ، به درد جایی میخوره که فرضا بخشی از کدمون در لایه ی View و بخشی هم در لایه ی ViewModel وجود داشته باشه . یعنی مثلا بخوایم یه Dialog ای را برای انتخاب فایل باز کنیم (که این تیکه مربوط به وظایف لایه ی View هست) و بعد از اون ، یک متدی را از Model فراخوانی کنیم که باید برای این کار ، Command مون را در ViewModel اجرا کنیم .
بنابراین میتونیم یه هندلر Button_Click ای در View درست کنیم (و بعد از انجام کارهای مربوطه) ، در همون هندلر ، Command مربوطه در ViewModel (فرضا GetStudentCommand در کد بالا) را فراخوانی کنیم .
هر چند احتمالا این سناریو ، کم پیش میاد و بیشتر سناریوها ، مستقیما در کد xaml ، اون Command ای را که در ViewModel مون ساخته بودیم را مستقیما فراخوانی اش میکنیم .
===========
یک سناریوی دیگه هم اینه که وقتی که یک رویداد خاصی از یک المنت و کنترل مورد نظرمون ، اجرا شد ، در این صورت ، Command مون اجرا بشه که کتابخونه های زیادی برای این کار هست (از جمله کتابخونه ی پیش فرض Silverlight و MVVMLight و DevExpress و ...) اما کتابخانه ی Microsoft.Xaml.Behaviors.Wpf هم هست که خوبه و توسط nuget میشه ازش استفاده کرد :
<Window x:Class="MVVM_Practies.MainWindow"
xmlns:behavior ="http://schemas.microsoft.com/xaml/behaviors">
<Button Content="Mouse Move">
<behavior:Interaction.Triggers>
<behavior:EventTrigger EventName="MouseMove">
<behavior:InvokeCommandAction Command="{Binding Path=GetStudentCommand}"
PassEventArgsToCommand="True"/>
</behavior:EventTrigger>
</behavior:Interaction.Triggers>
</Button>
</Window>
که برای بیشترین سناریوها ، سناریو کد xaml بالا که برای رویداد خاص و کنترل خاصی استفاده میشه و همچنین مستقیما Command را در ViewModel فراخوانی میکنه .
پاورقی اینکه RelayCommand هم کلاس سبک شده ی DelegateCommand هست (که اغلب از این نوع ها استفاده میشه).
کلاس RoutedCommand هم مثل RoutedEvent ها ، اما این کلاسِ RoutedCommand ، اگه هر وقت اجرا بشه ، فقط استراتژی حبابی و به سمت بالا را طی میکنه تا به کنترلی که شی ای از نوع CommandBinding (که معمولا پروپرتیِ CommandBindings در المنت ها هستند) ای که شیِ Command هاشون یکی و هم خوان اش باشه (مثلا هر دو از نوع ApplicationCommands.Copy باشن ، برسه که در اون صورت ، مقداری که درون پروپرتیِ Command ای که در شیِ CommandBinding مشخص شده را اجرا میکنه .
و RelayCommand ، بیشتر به درد زمانی میخوره که در دو کنترل مختلف (مثلا هم در MenuItem و هم در کنترل دکمه) ، بخوایم ییک کار مشابه (مثل کپی کردن و ...) را انجام بدیم .
درست هست دیگه؟
تشکر .