نحوه پیاده سازی الگوی MVVM

چهارشنبه 14 مرداد 1394

در این مقاله، MVVM Pattern را معرفی کرده، با مزایای آن آشنا می شویم و یک MVVM Pattern پیاده سازی می کنیم.

نحوه پیاده سازی الگوی MVVM

در این مقاله، یک پیاده سازی اولیه از MVVM Pattern خواهیم داشت. منظور از پیاده سازی اولیه MVVM pattern در این مقاله این است که، از ICommand Interface استفاده نخواهیم کرد و همچنین از هیچ کتابخانه خارجی مثل MVVMLight نیز استفاده نمی کنیم. تنها چیزی که ما نیاز خواهیم داشت، INotifyPropertyChanged Interface می باشد.

این مقاله آموزشی از دو بخش تشکیل شده است، بخش اول به صورت تئوری و معرفی MVVM Pattern و دلایل اهمیت آن می باشد. بخش دوم یک نمونه پروژه را با هم پیاده سازی خواهیم کرد. کدی که در ادامه می آید، در 4.5.2 Net. نوشته شده و در 4.6 Net. برای تضمین سازگاری با Visual Studio 2015 تست شده است، بنابراین، این مقاله تا جایی که ممکن بوده است، به روز می باشد.

بخش اول:

MVVM Pattern یک افزونه روی  MVC Pattern است که بیشتر در توسعه وب استفاده می شود. این افزونه، به جای Model View Controller از یک Model، View و ViewModel گرفته شده است. بنابراین، اگر در MVVM Pattern تازه کار باشید، بدون شک از اهمیت بیش از حد این الگوها تعجب خواهید کرد.دلیل این اهمیت چیزی جز جداسازی کد نیست. جداسازی کد به شما این امکان را می دهد که UI و کدهای خود را از هم جدا کنید.

مثال بسیار ساده آن، این است که شما روی کد Back end پروژه کار می کنید و برنامه نویس دیگری روی UI کار می کند، اگر از MVVM Pattern استفاده نکنید، کدی که شما می نویسید با هر تغییری که برنامه نویس UI اعمال می کند، تحت تاثیر قرار می گیرد. MVVM Pattern به حل این مشکل کمک می کند.

علاوه بر این، زمانی که از MVVM Pattern استفاده می کنید باید توجه داشته باشید که درحال حاضر در حالت اعلانی (declarative) برنامه نویسی می کنید.

مزایای MVVm Pattern:

1. UX designer و Backend programmer می توانند بدون تداخل و ایجاد مزاحمت برای هم، به طور همزمان روی یک پروژه کار کنند.

2. ساخت یک نمونه تست واحد برای viewModel و Model بدون استفاده از View آسان تر شده است.

3. طراحی دوباره UI بدون تاثیرگذاری روی کد  Back end به راحتی انجام می گیرد.

4. فرایند نگه داری و به روزرسانی آن ساده تر است.

5. در بلند مدت، نیاز به نوشتن کد کمتری است.

هر بخش از MVVM چیست و چه کاری انجام می دهد:

Model یک کلاس است که داده هایی را که می خواهید استفاده کنید، نمایش می دهد. علاوه بر این، Interface (رابط کاربری) INotifyPropertyChanged را نیز پیاده سازی می کند.

View همان Page یا usercontrol شماست، اما به صورتی که شما می خواهید از آن استفاده کنید.

این کلاس، نباید هیچ کد Back end ای داشته باشد اگرچه یک استثنا وجود دارد.

ViewModel یک کلاس است که همه کارهای پر حجم را انجام می دهد. این همان قسمتی است که دیتابیس را فراخوانی می کند، یک observablecollection می سازد، و هر کد Back endای که شما نیاز به پیاده سازی آن دارید.

نحوه کار MVVM:

کلاس Model، نقش یک پایه و اساس را برای داده های شما ایفا می کند. اطلاعاتی را که شما می خواهید در برنامه کاربری خود نمایش دهید، نگه می دارد. ViewModel رابطی است بین داده ها و UI شما و View همان UI است. 

دلیل اهمیت DataBinding با استفاده از MVVM:

DataBinding، کاربر را قادر می سازد تا شی ها را Bind (متصل)کند، بنابراین هرجایی که اشیای دیگر تغییر کنند، شی اصلی نیز تاثیر می پذیرد. دلیل اصلی اهمیت MVVM این است که UI همیشه به طور خودکار  با ساختار داده های درون برنامه متقارن است.

نقش DataBinding:

این پاراگراف، تمام آنچه را که DataBinding می تواند انجام دهد، شرح نمی دهد و تنها یک مرور اجمالی است.

DataBinding، دو Property را به هم متصل می کند، اگر یکی تغییر کند دیگری هم به همان شکل تغییر می کند. راه های زیادی برای انجام این کار وجود دارد، مانند کاری که ما در بخش دوم انجام داده ایم. در بخش دوم از PropertyChanged event و Dependency property استفاده کرده ایم.

Dependency Property در مقایسه با PropertyChanged Event:

چندین تفاوت بین Dependency Property و PropertyChanged Event وجود دارد.

Dependency Objects به صورت serializable مشخص نشده اند و تنها در Thread ای که ساخته شده اند، قابل دسترسی هستند و متدهای ()Equals و ()GetHashCode را Seal و Override  می کنند.

البته در تست، Dependency Objectها بسیار بهتر از NotifyPropertyChanged اجرا می شوند.

تکنولوژی هایی که از  MVVM Pattern استفاده می کنند:

باید توجه داشته باشیدکه برای استفاده از MVVM Pattern در این تکنولوژی ها، هیچ پیش نیازی لازم نیست.

می توان MVVM را با تکنولوژی های وب مانند JavaScript و ASP.NET استفاده کرد، حتی اگر پیش نیازی لازم داشته باشد. اگر درحال ساختن یک برنامه کاربردی UI برای یک پروژه وب باشید، می توانید از MVVM استفاده کنید. پروژه های Windows Phone7، Windows Phone 8، Windows Phone 8.1، Windows Metro Applications (8 and 8.1) و Windows 10 Universal کاندیدهایی برای استفاده از MVVM Pattern هستند. اگر برنامه نویسی هستید که هنوز از Silverlight منسوخ شده استفاده می کنید، MVVM جایگزین فوق العاده ای برای آن است. نکته قابل توجه این که، اغلب افراد در پروژه های Windows Desktop، برنامه های کاربردی WPF شروع به یادگیری  MVVM Pattern می کنند. همچنین افرادی هستند که آن را با پروژه های Unity استفاده می کنند.

اهیمت یادگیری MVVM Pattern:

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

نحوه استفاده از DataBinding:

DataBinding در XAML بسیار ساده انجام می شود، کد زیر را در نظر بگیرید:

<TextBox x:Name="textBox" Height="34" TextWrapping="Wrap" Text="{Binding Genre}" IsReadOnly="True"/>

کد بالا به ما نشان می دهد که، متن Textbox روی Bound path مربوط به Genre تنظیم شده است.

حالا، به جای استفاده از XAML چه کدی را با استفاده از #C می خواهید، بنویسید؟ کد زیر را در نظر بگیرید.

BindingSource binding = new BindingSource();
binding.Add(new Models.ModelClass("Action");
textbox.text = binding;

حال که مقدمات کار انجام شد، به بخش دوم می رویم.

بخش دوم:

در ابتدا، کلاس Model را معرفی می کنیم.

در این مثال، می خواهیم ReleaseDate، Name و Genre را نمایش دهیم. همچنین می خواهیم interfaceای به نام INotifyPropertyChanged بسازیم و در متد  set هر public property یک event را فراخوانی کنیم.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace PracticeProject.Models
{
    public class ModelClass : INotifyPropertyChanged
    {
        private string name;
        public string Name
        {
            get { return name; }
            set
            {
                name = value;
                NotifyPropertyChanged();
            }
        }
        private string releaseDate;
        public string ReleaseDate
        {
            get { return releaseDate; }
            set
            {
                releaseDate = value;
                NotifyPropertyChanged();
            }
        }
        private string genre;
        public string Genre
        {
            get { return genre; }
            set
            {
                genre = value;
                NotifyPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

مرحله بعد، کلاس ViewModel. در این مثال، از مزایای استفاده از ObservableCollection بهره می گیریم و پارامتر ها را برای کلاس Model ارسال می کنیم. از Linq و Lambda استفاده می کنیم تا ObservableCollection را بشماریم. فایل XML، داخل برنامه کاربردی قرار گرفته است، بنابراین از کلاس های reflection و StreamReader استفاده می کنیم تا این داده ها را فراخوانی کنیم وآن ها را در ObservableCollection قرار دهیم.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Xml.Linq;
using System.IO;
using System.Reflection;
using System.Windows;
namespace PracticeProject.ViewModels
{
    public class ViewModelClass
    {
        public ObservableCollection<Models.ModelClass> Movies { get; set; }
        StreamReader _textStreamReader;
        public ViewModelClass()
        {
           LoadEmbeddedResource("PracticeProject.DataSources.Movies.xml");
            XDocument xdoc = XDocument.Load(_textStreamReader);
            var movies = xdoc.Descendants("Movies")
                .Select(x =>
                new Models.ModelClass
                {
                    Name = (string)x.Element("Name"),
                    Genre = (string)x.Element("Genre").Value,
                    ReleaseDate = (string)x.Element("ReleaseDate").Value
                }
                ).ToList();
            Movies = new ObservableCollection<Models.ModelClass>(movies);
        }
        private void LoadEmbeddedResource(string resource)
        {
            try
            {
                Assembly _assembly;
                _assembly = Assembly.GetExecutingAssembly();
                _textStreamReader = new StreamReader(_assembly.GetManifestResourceStream(resource));
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error Loading Embedded Resource: " + ex, "Error!", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
    }
}

مرحله بعدی، CodeBehind مربوط به View می باشد. این مثال، نشان می دهد که شما نباید هیچ Back endای برای آن داشته باشید، اگرچه طبق مثال زیر، یک خط استثنا وجود دارد.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace PracticeProject.Views
{
    /// <summary>
    /// Interaction logic for MainView.xaml
    /// </summary>
    public partial class MainView : UserControl
    {
        public MainView()
        {
            InitializeComponent();
            DataContext = new ViewModels.ViewModelClass();
        }
    }
}

و درنهایت، کد XAML مربوط به View. این قسمت، به راستی سخت ترین قسمت استفاده از MVVM Pattern است. در ابتدا، ViewModel را در فضای نام فایل XAML تعریف می کنیم. یک Listbox ساخته و اعضای ObservableCollection را به آن متصل (Bind) می کنیم. سپس UpdateSourceTrigger را روی PropertyChanged تنظیم می کنیم. و در آخر، DisplayMemberPath را روی Name تنظیم می  کنیم. توجه داشته باشید که IsSynchronizedWithCurrentItem روی True تنظیم شده باشد. 

در ادامه، یک stackPanel می سازیم و DataContext آن را روی مسیری که به عنوان selected item در Listbox تنظیم شده، قرار می دهیم. این کار را انجام می دهیم تا مطمئن شویم، هر آیتمی که در Listbox انتخاب شد، متناظر با آن Bound Object نیز update شود.

حال، یک Lable و یک Textbox در stackpanel می سازیم و متن Textbox را به Genre و محتوای Lable را به ReleaseDate متصل می کنیم.

<UserControl x:Class="PracticeProject.Views.MainView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:local="clr-namespace:PracticeProject.Views"
             mc:Ignorable="d"
             xmlns:vm="clr-namespace:PracticeProject.ViewModels"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <ListBox x:Name="listBox" ItemsSource="{Binding Movies, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" Height="100" IsSynchronizedWithCurrentItem="True"/>
        <StackPanel Margin="10" VerticalAlignment="Bottom" DataContext="{Binding ElementName=listBox, Path=SelectedItem}">
            <Label x:Name="label" Content="{Binding ReleaseDate}" Height="36"/>
            <TextBox x:Name="textBox" Height="34" TextWrapping="Wrap" Text="{Binding Genre}" IsReadOnly="True"/>
        </StackPanel>

حال، می توانید برنامه را اجرا کنید و هر آیتمی که شما در Listbox انتخاب کنید، مقدار متناظر آن نیز، در  label و TextBox به روزرسانی (update) می شود.

اگر مفاهیم MVVM pattern را تا حدودی یاد بگیرید، می توانید آن را با یادگیری ICommand Interface توسعه دهید.

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید