MVVM – Part 10 PRISM real world Starter

5 בOctober 2013

אין תגובות
הגענו לפרק העשירי והאחרון במדריך שלי לMVVM. הפעם נדגים כיצד בונים תכנית מבוססת על ארכיטקטורת MVVM מורכבת, (מה שנקרא composite application) באמצעות Prism library.
 
נתחיל מזה שנוריד את הגרסה האחרונה של הספרייה מאתר הבית שלהם,(אני בוחר בגרסה המתאימה ל.NET 4.5) כי אני עובד עם VS2012 כדי להוריד קבצים מקודפלקס יכול להיות שתצטרכו חשבון וזה סתם מומלץ.
 
בכל מקרה כנסו לפה תורידו גרסה ונמשיך.
 
מה שצריך בנוסף זה dll של Unity כי כאמור באפליקציה מודולרית צריך IOC קונטיינר והתשתית של פריזם מגיעה עם תמיכה מובנית בMEF או UNITY (זו הסיבה שהקדשתי לכל שיטה פוסט שלם במדריך שלי לMVVM) אני בוחר להשתמש ב UNITY לצורך הדוגמא. וכאמור כבר הדגמתי בעבר איך עובדים עם unity
אז, מה נבנה? נניח שיש לי דרישה לבנות מרכז רישום מוצרים, ובנוסף מסך שמציג את כל המוצרים.
 
נכון שזה נשמע סטנדרטי רק שאנחנו נבנה כל חלק במודול נפרד עם לוגיקה משלו בלי קשר בין החלקים. בנוסיף נוסיף "בשביל הכיף" מודל כותרת של האפליקציה שיהיה ריק מתוכן מעשי ויהיה לו רק View . התוצאה הסופית אמורה לשאוף לדבר כזה:
 
image
אוקיי, אם הבנו את זה ועברנו על המבוא לפריזם צריך פשוט לכתוב קוד..
 
נפתח פרוייקט ונוסיף לSolution פרויקט מסוג WPF וקורא לפרוייקט MainClientApp הMainWindow מקבל את השם ShellView .
בשלב זה אני מתעלם מהדרך שבה מתחברים למודולים ש"מאכלסים" את האיזורים השונים אני רק מכין "תשתית זמליסטית" (וואלה המצאתי מושג) שנוכל תיכף להשתמש בה.
וכך הוא ייראה:
<Window x:Class="MainClientApp.ShellView"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

   Title="Shell" WindowState="Maximized">  

  <Grid>

       <Grid.ColumnDefinitions>

            <ColumnDefinition/>

            <ColumnDefinition/>

        </Grid.ColumnDefinitions> 

        

        <Grid.RowDefinitions>

            <RowDefinition/>

            <RowDefinition/>

            <RowDefinition/>

            <RowDefinition/>

        </Grid.RowDefinitions>

        <ContentControl Margin="5" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" x:Name="CaptionRegion"/>

        <ContentControl Margin="5" Grid.Column="0" Grid.Row="1" Grid.RowSpan="3" x:Name="LeftRegion"/>

        <ContentControl Margin="5" Grid.Column="1" Grid.Row="1" Grid.RowSpan="3" x:Name="RightRegion"/>

      

    </Grid>

</Window>

 

השלב הבא באותו הפרוייקט (MainclientApp) הוא הכנת BootStrapper. אני מוסיף לפרויקט את כל הdll הנחוצים. בסדר הבא.
Microsoft.Practices.Prism
Microsoft.Practices.Prism.Interactivity Microsoft.Practices.Prism.UnityExtensions
Microsoft.Practices.Unity
(לVS2012 תזדקקו ל unity 3.0 תוודאו שיש לכם אותו ואם לא תורידו כאן ותריצו את ההתקנה). כמו גם חלק מהכיף עם VS2012 הוא לנסות את הדבר הבא: מקש ימני על / Search for Unity / Manage NuGet Packages / Reference ולהריץ משם. הכלי הזה נותן לנו את הספריות המבוקשות ביותר ומגיעות ממקור חיצוני ישר לתוך הפרויקט בכל מקרה אני כותב את הדבר הבא:
 
using Microsoft.Practices.Prism.Modularity;

using Microsoft.Practices.Prism.Regions;

using Microsoft.Practices.Prism.      

 

namespace MainClientApp

{

    class BootStrapper:UnityBootstrapper

    {

        protected override System.Windows.DependencyObject CreateShell()

        {

            throw new NotImplementedException();

        }

    }

 
 
ושימו לב מה קיבלתי מעצם העובדה שאני יורש מbootstrapper של Unity, את ההכנה האוטומטית להרמת ה Shel. בסופו של דבר אני רוצה להגיע לתוצאה כזו:
class BootStrapper:UnityBootstrapper

    {

 

        public BootStrapper()

        {

            ;

        }

        protected override System.Windows.DependencyObject CreateShell()

        {

            return Container.Resolve<ShellView>();

        }

        protected override IModuleCatalog CreateModuleCatalog()

        {

            return new ConfigurationModuleCatalog();

        }

 

        protected override void InitializeShell()

        {

            base.InitializeShell();

            Application.Current.MainWindow = (ShellView)this.Shell;

            Application.Current.MainWindow.Show();

 

        }

    } 

עשיתי פה שלשה דברים חשובים:
א. יצרתי את הshell.
ב. ייצרתי את "קטלוג המודולים"
ג. הרמתי את ה shell לאוויר. (בערך, לא מדוייק)
ניגש רגע ל app.config של החלון הראשי ונעיף משם את startup uri.
אנו רוצים לשלוט על מסך הפתיחה מקובץ קונפיג ולא בצורה הרגילה. כך הוא נראה וכך הוא יישאר גם כשעובדים prism..
 
<Application x:Class="MainClientApp.App"

             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >

    <Application.Resources>

    </Application.Resources>

</Application>

אז מה הלאה? ניגש לקובץ ה configuration שהגיע עם פרויקט (app.config) ונשנה אותו שייראה כך:
 
 
<?xml version="1.0" encoding="utf-8" ?>

<configuration>

 

  <configSections>

    <section name="modules"

             type="Microsoft.Practices.Prism.Modularity.ModulesConfigurationSection, Microsoft.Practices.Prism"/>

  </configSections>

 

  <startup>

    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>

  </startup>

 

  <modules>

  </modules>

 

</configuration>

שוב בהתאמה שימו לב ל הבדלים שבין לרוץ על דוט נט 4 או 4.5
(אם אתם על 3.5 בכלל תעופו מכאן..)
 
    • הערה: ישנם דרכים נוספות לטיפול ב APP.config
 
בנוסף, שימו לב שלקובץ app.xaml יש code behind, נכון שבד"כ אנו לא מבקרים בו אבל זה הזמן לבקר. אנחנו נשכתב אותו שייראה כך:

 

namespace MainClientApp

{

    /// <summary>

    /// Interaction logic for App.xaml

    /// </summary>

    public partial class App : Application

    {

        BootStrapper m_bootstrapper;

        App()

        {

           m_bootstrapper = new BootStrapper();

           m_bootstrapper.Run();

        }

    }

} 

זאת הנקודה הקריטית שאנו מרימים את הBootStrapper לאוויר ומכאן הכל בעצם מתחיל.
בשלב הזה ניתן וחובה להריץ ולראות אם נפתח לנו חלון ראשי ריק מתוכן רק עם הכותרת shell כי הרי הגדרנו ב shellView את השורה Title="Shell",
 
בהנחה שפעלנו לפי ההוראות והכל תקין אני מתקדם..
 
ניצור את ספריית המודולים, כל מודול מכיל את המבנה הבא"
View , ViewModel , IModule , Model,
 
אני מוסיף פרוייקט dll וקורא לו ModuleDefenitions בתוכו ממקם שלשה מודולים קטנים ובסיסיים(בשביל הדוגמא).
 
נציג מודול אחד מפורט בשביל ההסבר. אני יוצר איזשהוא view שיהיה UserControl של WPF עם מה שבא לי בתוכו, ויוצר לו ViewModel בצורה הבאה:
 
public class CaptionViewModel : NotificationObject

    {

        private IEventAggregator _ieventAggrigator;

        public CaptionViewModel(IEventAggregator eventAggrigator)

        {

            _ieventAggrigator = eventAggrigator;

        }

 

        private string test;

 

        public string Test

        {

            get { return test; }

            set

            {

                test = value;

                RaisePropertyChanged();

            }

        }

 

    }

נשים לב שאני יורש מ NotificationObject של פריזם כמובן שבעתיד נמלא כאן נתונים אמיתיים של VM.
החיבור של Data Context מתבצע לרוב בצורה הבאה, (lazy loading) : נשים לב שאנו בcode behind של ה :View
public partial class CaptionView : UserControl

{

    public CaptionView(CaptionViewModel viewModel)

    {

        InitializeComponent();

        this.Loaded += (s, e) =>

            {

                this.DataContext = viewModel;

            };

    }

עכשיו מגיעה התוספת של הגדרת המודול וזה נעשה בצורה הזאת פחות או יותר לכל המודולים בתכנית.
 
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using Microsoft.Practices.Prism.Events;

using Microsoft.Practices.Prism.Modularity;

using Microsoft.Practices.Prism.Regions;

using Microsoft.Practices.Unity;

 

namespace ModuleDefenitions.CaptionModule

{

    public class Module : IModule

    {

        public Module(IRegionManager regionManager, IUnityContainer containter, IEventAggregator eventAggregator)

        {

            this.m_regionManager = regionManager;

            this.m_containter = containter;

            this.m_eventAggregator = eventAggregator;

        }

 

        IRegionManager m_regionManager;

        IUnityContainer m_containter;

        IEventAggregator m_eventAggregator;

 

 

        public void Initialize()

        {

            m_regionManager.RegisterViewWithRegion("CaptionRegion", typeof(CaptionView));

        }

 

    }

 

}

לכאורה יש לי מודול מוכן, כדי לטעון אותו לתוך הShell אנו חוזרים לקובץ הShell לשיפוץ קל. אני מוסיף את השורה הבאה:
xmlns:rgn="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"

 
שתיתן לי יכולות פריזם בXaml, פשוט נגדיר בmain View אזורים של regions לפי שם מפתח מזהה, בצורה הבאה.
<Window x:Class="MainClientApp.ShellView"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        xmlns:rgn="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"

   Title="Shell" WindowState="Maximized">

    <Grid>

        <Grid.ColumnDefinitions>

            <ColumnDefinition/>

            <ColumnDefinition/>

        </Grid.ColumnDefinitions>

 

        <Grid.RowDefinitions>

            <RowDefinition/>

            <RowDefinition/>

            <RowDefinition/>

            <RowDefinition/>

        </Grid.RowDefinitions>

        <ContentControl x:Name="CaptionRegion" Height="100" rgn:RegionManager.RegionName="CaptionRegion" Margin="5" Grid.Column="0" Grid.Row="0"  Grid.ColumnSpan="2"/>

        <ContentControl Margin="5" Grid.Column="0" Grid.Row="1" Grid.RowSpan="3" x:Name="LeftRegion"/>

        <ContentControl Margin="5" Grid.Column="1" Grid.Row="1" Grid.RowSpan="3" x:Name="RightRegion"/>

      

    </Grid>

</Window>

 

כדי שזה יעבוד צריך לרשום לקטלוג המודולים שלנו כל מודול בנפרד עם מזהה שם ייחודי. נעשה את זה בד"כ מקובץ קונפיגורציה נניח בצורה הבאה:
<modules>

   <module assemblyFile="PrismDemo.ModuleDefenitions.dll"

           moduleType="ModuleDefenitions.CaptionModule.Module, 

           CaptionModule" moduleName="CaptionModule"  startupLoaded="true"/>

  </modules>

וזהו. לפחות ברמה בסיסית. ניתן לרשום מודולים ישירות לתוך הקטלוג גם מקוד ולא מקובץ קונפיגורציה נניח עם הקוד הבא:
protected override void ConfigureModuleCatalog()

        {

            base.ConfigureModuleCatalog();

            var moduleCatalog = (ModuleCatalog)this.ModuleCatalog;

            moduleCatalog.AddModule(typeof(ModuleDefenitions.CaptionModule.Module));

        }

בהנחה שהקוד הזה מופיע ב BootStraprper שלנו.
הוסף תגובה
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *