۱۳۹۰/۱۰/۱۰

MVVM و فراخواني متدهاي اشياء View از طريق ViewModel


ما در ViewModel دسترسي مستقيمي به هيچ يك از اشياء موجود در View نداريم (و درستش هم همين است). الان فرض كنيد كه مي‌خواهيم از طريق ViewModel يك View را ببنديم؛ مثلا متد Close آن پنجره را فراخواني كنيم. به عبارتي در حالت كلي مي‌خواهيم يكي از متدهاي تعريف شده يكي از عناصر بصري موجود در View را از طريق ViewModel فراخواني نمائيم.
براي حل اين مساله از فايل‌هاي همان SDK‌ مرتبط با Expression blend استفاده خواهيم كرد.

ابتدا ارجاعاتي را به اسمبلي‌هاي System.Windows.Interactivity.dll و Microsoft.Expression.Interactions.dll اضافه مي‌كنيم.
سپس دو فضاي نام مرتبط هم بايد اضافه شوند:

  xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"  
  xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"

يك مثال عملي:
قصد داريم از طريق ViewModel ، پنجره‌اي را ببنديم. كدهاي XAML اين مثال را در ادامه مشاهده خواهيد كرد:

<Window x:Class="WpfCallMethodActionSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:vm="clr-namespace:WpfCallMethodActionSample.ViewModels" 
        Name="ThisWindow"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <vm:MainWindowViewModel x:Key="vmMainWindowViewModel" />
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource vmMainWindowViewModel}}">

        <Button Content="Save &amp; Close" VerticalAlignment="Top" Margin="5">            
            <i:Interaction.Triggers>
                <!--فراخواني متدي در ويوو مدل-->
                <i:EventTrigger EventName="Click">
                    <ei:CallMethodAction 
                            TargetObject="{Binding}"
                            MethodName="SaveButtonClicked" />
                </i:EventTrigger>

                <!--فراخواني متدي در شيء جاري از طريق ويوو مدل-->
                <i:EventTrigger SourceObject="{Binding}"  EventName="CloseMainWindow">
                    <ei:CallMethodAction
                            TargetObject="{Binding ElementName=ThisWindow}"
                            MethodName="Close"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>            
        </Button>
    </Grid>
</Window>

همچنين ViewModel تعريف شده نيز همين چند سطر زير است:

using System;

namespace WpfCallMethodActionSample.ViewModels
{
    public class MainWindowViewModel
    {
        public void SaveButtonClicked()
        {
            close();
        }

        public event EventHandler CloseMainWindow;
        private void close()
        {
            if (CloseMainWindow != null) CloseMainWindow(this, EventArgs.Empty);
        }
    }
}

توضيحات:
اگر به ViewModel دقت كنيد خبري از DelegateCommand در آن نيست. بله، به كمك تركيبي از EventTrigger و CallMethodAction مي‌توان جايگزيني را جهت DelegateCommand معرفي شده در قسمت‌هاي قبل اين سري مباحث MVVM ارائه داد.
EventTrigger در اينجا به اين معنا است كه اگر EventName ذكر شده رخ داد، آنگاه اين اعمال را انجام بده. مثلا در اينجا CallMethodAction را فراخواني كن.
CallMethodAction در اسمبلي Microsoft.Expression.Interactions.dll تعريف شده است و تنها متدي از نوع void و بدون پارامتر را مي‌تواند به صورت خودكار فراخواني كند (محدوديت مهم آن است).
اينكه اين متد كجا قرار دارد، توسط TargetObject آن مشخص مي‌شود. اگر TargetObject را مساوي Binding قرار داديم، يعني به دنبال متدي كه در DataContext گريد وجود دارد بگرد. به عبارتي به صورت خودكار به SaveButtonClicked تعريف شده در ViewModel ما متصل خواهد شد و آن‌را فراخواني مي‌كند.

تا اينجا رخداد Click دكمه تعريف شده را به متد SaveButtonClicked موجود در ViewModel سيم كشي كرديم.

در مرحله بعد مي‌خواهيم از طريق ViewModel ، متدي را در View فراخواني كنيم. نكته آن هم پيشتر ذكر شد؛ TargetObject صحيحي را بايد انتخاب كرد. در اينجا براي پنجره جاري نام ThisWindow تعريف شده است و از طريق تعريف:

TargetObject="{Binding ElementName=ThisWindow}"

به CallMethodAction خواهيم گفت كه قرار است متد Close را در شيء ThisWindow فراخواني كني.
همچنين نحوه تعريف EventTrigger ما هم در اينجا برعكس شده است:

<i:EventTrigger SourceObject="{Binding}"  EventName="CloseMainWindow">

قبلا به دنبال مثلا رخداد Click يك دكمه بوديم، اكنون با توجه به SourceObject تعريف شده، در ViewModel به دنبال اين رخداد كه براي نمونه در اينجا CloseMainWindow نام گرفته خواهيم گشت.

بنابراين View اينبار به رخداد CloseMainWindow تعريف شده در ViewModel سيم كشي خواهد شد. اكنون اگر اين رخداد در ViewModel فراخواني شود، CallMethodAction متناظر فعال شده و متد Close پنجره را فراخواني مي‌كند.