Dans cet article nous allons voir comment interagir entre la Vue et la VueModèle en utilisant MVVM light toolkit.

Dans la 1e partie on verra comment utiliser la Commande et comment binder une ListBox à cette Commande.

Dans la 2e partie on verra comment utiliser la messagerie de MVVM light toolkit.

Navigation

Dans ce premier exemple nous allons voir comment naviguer dans l’application via un menu.

MenuViewModel

Le ViewModel de mon Menu contient un Identifiant, un titre et un Uri

?View Code CSHARP
    public class MenuViewModel : ViewModelBase
    {
        private int _id;
        /// <summary>
        /// Identifier
        /// </summary>
        public int Id
        {
            get
            {
                return _id;
            }
            set
            {
                _id = value;
                RaisePropertyChanged("Id");
            }
        }
 
        private string _title;
        /// <summary>
        /// Title
        /// </summary>
        public string Title
        {
            get
            {
                return _title;
            }
            set
            {
                _title = value;
                RaisePropertyChanged("Title");
            }
        }
 
        private Uri _uri;
        /// <summary>
        /// Uri
        /// </summary>
        public Uri Uri
        {
            get
            {
                return _uri;
            }
            set
            {
                _uri = value;
                RaisePropertyChanged("Uri");
            }
        }

MainViewModel

La VueModèle de mon Main contient un liste de Menus, une Commande et une fonction émettant un message en retour.

?View Code CSHARP
    public class MainViewModel : ViewModelBase
    {
        /// <summary>
        /// A collection for MenuViewModel objects.
        /// </summary>
        public ObservableCollection<MenuViewModel> Menus { get; private set; }
 
        /// <summary>
        /// Constructor
        /// </summary>
        public MainViewModel()
        {
            //Receiver
            MenuNavigation = new RelayCommand<MenuViewModel>((menu) => SendNavigationRequestMessage(menu));
 
            Menus = Helper.GetMenus(); //à vous de définir la fonction
        }
 
        public RelayCommand<MenuViewModel> MenuNavigation { get; private set; }
 
        /// <summary>
        /// A collection for MenuViewModel objects.
        /// </summary>
        protected void SendNavigationRequestMessage(MenuViewModel menu)
        {
            Messenger.Default.Send<MenuViewModel>(menu, "MenuNavigation");
        }
    }

MainPage

La Vue Main contiendra une ListBox affichant les menus.

<phone:PhoneApplicationPage x:Class="WindowsPhoneApplication.MainPage"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
		xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
		xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
		xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
		xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
		xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
		xmlns:MvvmLight="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
 
		FontFamily="{StaticResource PhoneFontFamilyNormal}"
		FontSize="{StaticResource PhoneFontSizeNormal}"
		Foreground="{StaticResource PhoneForegroundBrush}"
		SupportedOrientations="Portrait"
		Orientation="Portrait"
		mc:Ignorable="d"
		d:DesignWidth="480"
		d:DesignHeight="768"
		shell:SystemTray.IsVisible="True"
		DataContext="{Binding Main, Source={StaticResource Locator}}">
    <Grid x:Name="LayoutRoot" Background="Transparent">
                <StackPanel>
                    <ListBox Name="MenusListBox" Margin="12,0,-12,0" ItemsSource="{Binding Menus}">
                        <ListBox.ItemTemplate>
                            <DataTemplate>
                                    <TextBlock Text="{Binding Title}" />
                            </DataTemplate>
                        </ListBox.ItemTemplate>
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="Tap">
                                <MvvmLight:EventToCommand x:Name="SelectionCommand" 
									Command="{Binding NavigateToNews, Mode=OneWay}" 
									CommandParameter="{Binding SelectedItem, ElementName=MenusListBox}"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </ListBox>
                </StackPanel>
    </Grid>
</phone:PhoneApplicationPage>

Note: On pourra remarquer l’utilisation de MvvmLight:EventToCommand qui permet de binder l’évènement Tap à la commande MenuNavigation avec pour paramètre l’élément sélectionné.

MainPage, le code-behind

Dans le code-behind de la Vue MainPage, nous allons créer un récepteur de message qui réceptionnera un menu et redirigera vers une nouvelle page.

?View Code CSHARP
    public partial class MainPage : PhoneApplicationPage
    {
        // Constructor
        public MainPage()
        {
            InitializeComponent();
            Loaded += new System.Windows.RoutedEventHandler(MainPage_Loaded);
        }
 
        void MainPage_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            Messenger.Default.Register<MenuViewModel>(this, "MenuNavigation", (menu) => MenuNavigation(menu));
        }
 
        private void MenuNavigation(MenuViewModel menu)
        {
            if (MenusListBox.SelectedIndex != -1)
            {
                NavigationService.Navigate(new System.Uri(menu.Uri, System.UriKind.Relative));
                MenusListBox.SelectedIndex = -1;
            }
        }
    }

Conclusion

Dans cette partie, nous avons vu comment simplement créer un Menu navigable avec MVVM Light toolkit. Cet exemple, relativement simple, atteint assez vite ses limites quand on passe à quelque chose de plus complexe.

Catalogue produit

Pour illustrer le cas d’un exemple un peu plus complexe, prenons le cas d’un catalogue produits contenant plusieurs categories contenant elles-mêmes plusieurs produits.

ProductViewModel

Le ViewModel de mon produit contient un identifiant, un nom et un identifiant de catégorie.

?View Code CSHARP
    public class ProductViewModel : ViewModelBase
    {
        private int _id;
        /// <summary>
        /// Identifier
        /// </summary>
        public int Id
        {
            get
            {
                return _id;
            }
            set
            {
                _id = value;
                RaisePropertyChanged("Id");
            }
        }
 
        private string _name;
        /// <summary>
        /// Name
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                RaisePropertyChanged("Name");
            }
        }
 
        private int _categoryId;
        /// <summary>
        /// Category Identifier
        /// </summary>
        public int CategoryId
        {
            get
            {
                return _categoryId;
            }
            set
            {
                _categoryId= value;
                RaisePropertyChanged("CategoryId");
            }
        }
    }

CategoryViewModel

Le ViewModel de la catégorie contient un identifiant, un nom et une liste de produits.

?View Code CSHARP
        private int _id;
        /// <summary>
        /// Identifier
        /// </summary>
        public int Id
        {
            get
            {
                return _id;
            }
            set
            {
                _id = value;
                RaisePropertyChanged("Id");
            }
        }
 
        private string _name;
        /// <summary>
        /// Name
        /// </summary>
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                _name = value;
                RaisePropertyChanged("Name");
            }
        }
 
        private ObservableCollection<ProductViewModel> _products;
 
        /// <summary>
        /// A collection for ProductViewModel objects.
        /// </summary>
        public ObservableCollection<ProductViewModel> Products
        {
            get
            {
                return _products;
            }
            set
            {
                _products= value;
                RaisePropertyChanged("Products");
            }
        }
    }

CatalogViewModel

Le ViewModel de la page Catalogue contient une liste de catégories, un récepteur de message et une fonction émettant un message en retour.

?View Code CSHARP
    public class CatalogViewModel : ViewModelBase
    {
        public ObservableCollection<CategoryViewModel> Categories { get; private set; }
 
        /// <summary>
        /// Initializes a new instance of the CatalogViewModel class.
        /// </summary>
        public CatalogViewModel()
        {
            Messenger.Default.Register<SubCategoryViewModel>(this, "ItemTapped", (subCategory) => ReceiveMessage(subCategory));
            Categories = Helpers.GetCategories(); //à vous de définir la fonction
        }
 
        public bool IsDataLoaded { get; private set; }
 
        private void ReceiveMessage(SubCategoryViewModel subCategory)
        {
            Messenger.Default.Send<SubCategoryViewModel>(subCategory, "NavigationRequest");
        }
    }

CatalogView

Ma Vue Catalogue contient un pivot contenant une ListBox.

<phone:PhoneApplicationPage
    x:Class="WindowsPhoneApplication.Views.CatalogView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:MvvmLight="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
    mc:Ignorable="d"
    d:DesignWidth="480"
    d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait"
    Orientation="Portrait"
    shell:SystemTray.IsVisible="True"
    DataContext="{Binding Catalog, Source={StaticResource Locator}}">
 
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <controls:Pivot x:Name="MainPivot" Title="CATALOGUE" ItemsSource="{Binding Categories}">
            <controls:Pivot.TitleTemplate>
                <!-- This changes to look of the pivot overall title -->
                <DataTemplate>
                    <TextBlock Text="{Binding}" />
                </DataTemplate>
            </controls:Pivot.TitleTemplate>
            <controls:Pivot.HeaderTemplate>
                <!-- This changes to look of the items headers -->
                <DataTemplate>
                    <TextBlock Text="{Binding Name}" />
                </DataTemplate>
            </controls:Pivot.HeaderTemplate>
            <controls:Pivot.ItemTemplate>
                <DataTemplate>
                    <ScrollViewer>
                        <StackPanel>
                            <ListBox Name="ProductsListBox" ItemsSource="{Binding Products}" Tap="ProductsListBox_Tap">
                                <ListBox.ItemTemplate>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock Text="{Binding Name}" />
                                        </StackPanel>
                                    </DataTemplate>
                                </ListBox.ItemTemplate>
                            </ListBox>
                        </StackPanel>
                    </ScrollViewer>
                </DataTemplate>
            </controls:Pivot.ItemTemplate>
        </controls:Pivot>
    </Grid>
</phone:PhoneApplicationPage>

CatalogView, le code-behind

Comme vous avez pu le constater dans le paragraphe précédent, je n’ai pas utilisé EventToCommand. En effet la ListBox étant dynamique (pour chaque Catégorie, une nouvelle ListBox est instanciée), le EventToCommand n’arrive pas à se binder sur l’élément.

Pour contourner le problème, j’utilise la messagerie du MVVM Light Toolkit

?View Code CSHARP
    public partial class CatalogView : PhoneApplicationPage
    {
        /// <summary>
        /// Initializes a new instance of the CatalogView class.
        /// </summary>
        public CatalogView()
        {
            InitializeComponent();
            Loaded += new System.Windows.RoutedEventHandler(CatalogView_Loaded);
        }
 
        void CatalogView_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            Messenger.Default.Register<SubCategoryViewModel>(this, "NavigationRequest", (subCategory) => ReceiveMessage(subCategory));
        }
 
        private void ReceiveMessage(ProductViewModel product)
        {
            NavigationService.Navigate(new System.Uri(string.Format("/Views/ProductDetailView.xaml?Id={0}", product.Id), System.UriKind.Relative));
        }
 
        private void ProductsListBox_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            Messenger.Default.Send<ProductViewModel >((ProductViewModel )((ListBox)sender).SelectedItems[0], "ItemTapped");
        }
    }

Conclusion

Dans cette partie nous avons vu comment se passer de EventToCommand via l’utilisation d’envoi de messages.

La Messagerie MVVM Light Toolkit

Voici un récapitulatif rapide des éléments important de l’article.

Pour créer une commande:

?View Code CSHARP
        public RelayCommand<ProductViewModel> LaCommande{ get; private set; }
 
        protected void SendNavigationRequestMessage(ProductViewModel product)
        {
            Messenger.Default.Send<ProductViewModel>(product, "NavigationRequest");
        }

Pour se binder à la commande:

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:MvvmLight="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP71"
                                                <i:Interaction.Triggers>
                                                    <i:EventTrigger EventName="Tap">
                                                        <MvvmLight:EventToCommand x:Name="SelectionCommand" 
                                                            Command="{Binding LaCommande, Mode=OneWay}" 
                                                            CommandParameter="{Binding SelectedItem, ElementName=NomDeLaListBox}"/>
                                                    </i:EventTrigger>
                                                </i:Interaction.Triggers>

Pour envoyer un message:

?View Code CSHARP
            Messenger.Default.Send<TypeDeLObjet>(objetAEnvoyer, "NomDuMessage");

Pour réceptionner un message:

?View Code CSHARP
            Messenger.Default.Register<TypeDeLObjet>(this, "NomDuMessage", (objetReçu) => ProcedureAExcuter(objetReçu));

Conclusion

Concernant la 1e partie, l’utilisation de la commande et de son binding à la ListBox, il s’agit de l’utilisation standard du pattern MVVM que j’ai pu trouver sur le net.

Concernant la 2e partie, il s’agit de ma propre solution, alors si vous avez trouvé d’autres solutions ou si vous avez d’autres pistes, écrivez moi tout ça en commentaire, je suis ouvert aux propositions.

Be Sociable, Share!