‏نمایش پست‌ها با برچسب Prism. نمایش همه پست‌ها
‏نمایش پست‌ها با برچسب Prism. نمایش همه پست‌ها

۱۳۹۱ بهمن ۱۴, شنبه

نوشتن یک Module در Prism

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

قبل از هر چیز باید Shell برنامه را برای پذیرش View های جدید از Module ها، آماده کنیم. همینطور ممکن است بخواهیم در Shell برنامه یک Ribbon داشته باشیم تا Module ها بتوانند به آن RibbonTab های خود را اضافه کنند. پس از اطمینان از دانلود و نصب آخرین نسخه Ribbon Control Library برای دات نت 4، روی پروژه PrismApp.Shell راست کلیک کنید و پس از انتخاب گزینه Add Reference، در زبانه Extensions آیتم RibbonControlsLibrary را انتخاب کنید. اگر از Visual Studio 2010 استفاده می کنید احتمالا بتوانید RibbonControlsLibrary را در این مسیر پیدا کنید:

C:\Program Files (x86)\Microsoft Ribbon for WPF\V4.0

بسیار خوب. اکنون باید کد MainWindow.xaml را به این شکل تغییر دهید:

<ribbon:RibbonWindow x:Class="PrismApp.Shell.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:prism="http://www.codeplex.com/prism"
        xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
        Title="Prism App" Height="550" Width="725" Style="{StaticResource WindowStyle}">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="{Binding Height, ElementName=MainRibbon}" ></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <ribbon:Ribbon x:Name="MainRibbon" Grid.Row="0" prism:RegionManager.RegionName="RibbonRegion">
            <ribbon:Ribbon.ApplicationMenu>
                <ribbon:RibbonApplicationMenu Visibility="Collapsed">
                </ribbon:RibbonApplicationMenu>
            </ribbon:Ribbon.ApplicationMenu>
        </ribbon:Ribbon>
        <TabControl prism:RegionManager.RegionName="MainRegion" Margin="5" Grid.Row="2" />
    </Grid>
</ribbon:RibbonWindow>

پنجره اصلی برنامه از RibbonWindow ساخته شده است. فراموش نکنید در CodeBehind (فایل MainWindow.xaml.cs) نیز کلاس پدر را از Window به RibbonWindow تغییر بدهید. در قسمت ارجاعات ما دو ارجاع به ribbon و prism داریم تا بتوانیم از کلاس ها و extension های آن ها در این پنجره استفاده کنیم. پس از افزودن یک Ribbon، یک TabControl نیز در داخل Grid اصلی قرار دادیم. بدین صورت هر module می تواند View های خود را تحت یک TabItem نمایش دهد. ولی چطور؟
به کمک Region ها شما می توانید برخی کنترل ها را به عنوان میزبانی برای View های دیگر معرفی کنید. بدین صورت بعدا می توانید به Region ها دسترسی پیدا کنید و View های داخل آن ها را مطابق نیاز خود عوض کنید.
هر Region باید دارای یک نام منحصر به فرد باشد. ما در این پروژه، Region ی که قرار است TabItem ها را در خود نگه دارد را MainRegion نامیدیم. (به کد TabControl دقت کنید.)

<TabControl prism:RegionManager.RegionName="MainRegion" Margin="5" Grid.Row="2" />

همینطور در کد Ribbon، یک Region به نام RibbonRegion ایجاد کردیم تا module ها بتوانند RibbonTab های خود را به آن اضافه کنند. در همین پروژه یک پوشه به نام Themes بسازید و درون آن یک ResourceDictionary به نام Generic.xaml قرار دهید. سپس Style های زیر را در آن بنویسید:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon">
    <Style TargetType="Window" x:Key="WindowStyle">
        <Setter Property="FontFamily" Value="Tahoma" />
        <Setter Property="FontSize" Value="12" />
    </Style>
    
    <Style TargetType="ribbon:Ribbon">
        <Setter Property="FontFamily" Value="Tahoma" />
        <Setter Property="FontSize" Value="12" />
    </Style>

    <Style TargetType="TabControl">
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="VerticalAlignment" Value="Stretch" />
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        <Setter Property="VerticalContentAlignment" Value="Stretch" />
    </Style>

    <Style TargetType="TabItem">
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="VerticalAlignment" Value="Stretch" />
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        <Setter Property="VerticalContentAlignment" Value="Stretch" />
    </Style>
</ResourceDictionary>

سپس App.xaml را به این شکل تغییر دهید:

<Application x:Class="PrismApp.Shell.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Themes/Generic.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

با این کار، Style های تعریف شده در کل برنامه در دسترس خواهند بود. اگر همه چیز مرتب باشد باید بتوانید پروژه را کامپایل کرده و فرم خالی برنامه را ببینید. در صورتی که با خطای زیر مواجه شدید، پوشه Debug پروژه را پاک کنید و مجددا برنامه را اجرا کنید:
An unhandled exception of type 'System.StackOverflowException' occurred in System.Core.dll
حالا باید سراغ module برویم.
روی solution راست کلیک کرده و پروژه جدیدی از نوع WPF User Control با نام PrismApp.Modules.MyFirstModule ایجاد کنید. فراموش نکنید module ما باید به کتابخانه های RibbonControlLibrary ،Prism ،Unity و Prism.UnityExtensions ارجاع داشته باشد. پس reference های لازم را به این شکل در Package Manager Console اضافه کنید:

PM> install-package Prism -ProjectName PrismApp.Modules.MyFirstModule
PM> install-package Unity -ProjectName PrismApp.Modules.MyFirstModule
PM> install-package Prism.UnityExtensions -ProjectName PrismApp.Modules.MyFirstModule

نگران دانلود مجدد آن ها نباشید. nuget ابتدا برسی می کند که آیا قبلا این package ها را دانلود کرده است یا نه، سپس اقدام به نصب آن ها می کند. RibbonControlLibrary از طریق nuget در دسترس نیست. reference آن را بطور دستی به پروژه  ی module اضافه کنید. همچنین فایل پیش فرض xaml داخل پروژه module را پاک کنید. دو پوشه به نام های Views و ViewModels بسازید. سپس در پوشه Views دو WPF User Control با نامهای MyModuleMainView.xaml و MyModuleRibbonTab.xaml بسازید. 
فایل MyModuleMainView.xaml را به این شکل تغییر دهید:

<TabItem x:Class="PrismApp.Modules.MyFirstModule.Views.MyModuleMainView"
             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" 
             mc:Ignorable="d" Header="My First Module"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
            <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="{Binding Message}"></TextBlock>            
    </Grid>
</TabItem>

از آنجایی که در Shell اصلی برنامه، یک TabControl میزبان View های ماست، پس بهتر است View های ماژول ما TabItem باشند. فراموش نکنید در فایل MyModuleMainView.xaml.cs نیز کلاس پدر را از UserControl به TabItem تغییر دهید. حالا همین کار را با MyModuleRibbonTab.xaml و فایل Code Behind مربوطه انجام می دهیم، با این تفاوت که این بار آن را به کلاس RibbonTab تبدیل می کنیم. فایل نهایی باید چیزی شبیه این باشد:

<ribbon:RibbonTab x:Class="PrismApp.Modules.MyFirstModule.Views.MyModuleRibbonTab"
             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:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
xmlns:commands="clr-namespace:PrismApp.Modules.MyFirstModule.Commands"
             Header="My Module"
             mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
    <ribbon:RibbonGroup Header="My Module Items">
        <ribbon:RibbonButton Content="Save" Command="{x:Static commands:ModuleCommands.SaveCommand}"></ribbon:RibbonButton>
    </ribbon:RibbonGroup>
</ribbon:RibbonTab>
در این فایل یک ارجاع به فضای نامی ViewModels وجود دارد، همچنین در RibbonButton، مشخصه Command به یک مشخصه static در کلاس ModuleCommands متصل شده (bind) است. در ادامه این کلاس را ایجاد می کنیم:
پوشه ای به نام Commands بسازید و کلاس ModuleCommands را در آن ایجاد کنید:

using Microsoft.Practices.Prism.Commands;

namespace PrismApp.Modules.MyFirstModule.Commands
{
    public static class ModuleCommands
    {
        public static readonly CompositeCommand SaveCommand = new CompositeCommand();
    }
}

در این کلاس، فیلد عمومی به نام SaveCommand ایجاد کرده ایم تا از تمامی قسمت های ماژول به آن دسترسی داشته باشیم. نوع این فیلد CompositeCommand است که اجازه می دهد کلاس های ViewModel (یا هر کلاس دیگری) دیگر فرمان های مربوط به عمل Save خود را در آن ثبت کنند (Register) و با هر بار فراخوانی SaveCommand، تمامی Command های ثبت شده در آن اجرا (Execute) می شوند.
در پوشه ViewModels یک کلاس به نام MyModuleMainViewModel بسازید:

using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.ViewModel;

namespace PrismApp.Modules.MyFirstModule.ViewModels
{
    public class MyModuleMainViewModel : NotificationObject
    {
        private readonly INameService _textService;
        public MyModuleMainViewModel(INameService textService)
        {
                   _textService = textService;
            this.SaveCommand = new DelegateCommand(Save, CanSave);
             ModuleCommands.SaveCommand.RegisterCommand(SaveCommand);
        }


        private string _message = "Hello from My Module!";

        public string Message
        {
            get { return _message; }
            set
            {
                if (_message != value)
                {
                    _message = value;
                    RaisePropertyChanged(() => Message);
                }
            }
        }

        public DelegateCommand SaveCommand { get; private set; }

        public void Save()
        {
            this.Message =  string.Format("Save Command Executed for {0}.", _textService.GetRandomName());
        }

        public bool CanSave()
        {
            return true;
        }
    }
}

اگر با الگوی MVVM آشنا باشید می دانید که در ViewModel ها باید واسط INotifyPropertyChanged پیاده سازی شود. در Prism ما کلاس NotificationObject را پیاده سازی می کنیم. این کلاس علاوه بر پیاده سازی واسط مذکور، تابع کمکی برای فراخوانی رویداد PropertyChanged را نیز دارد. تابع RaisePropertyChanged پارامتری به شکل عبارت Lambda می پذیرد (علاوه بر حالت رشته ای) که احتمال اشتباه را در نوشتن نام Property ناممکن می کند. همچنین در صورت تغییر نام Property در تعریف، این ارجاع بطور اتوماتیک تغییر می کند. 
در این ViewModel ما یک Command از نوع کلاس DelegateCommand ایجاد کردیم. این کلاس Prism، یک پیاده سازی از واسط ICommand است. هنگام نمونه سازی از آن در تابع سازنده، دو تابع Save و CanSave را به آن ارجاع دادیم. سپس آن را در ModuleCommands.SaveCommand ثبت کردیم تا هنگامی که کاربر بر روی RibbonButton کلیک می کند، این Command اجرا شود. هنگام اجرای دستور، متنی به کاربر نمایش داده می شود که قسمتی از آن توسط سرویس INameService فراهم می شود. هدفم از قرار دادن این سرویس، آشنایی با نحوه پیاده سازی و استفاده از سرویس ها در برنامه های MVVM است.
سرویس INameService بسیار ساده است. پوشه ای به نام Services ایجاد کنید و یک واسط به نام INameService و یک کلاس به نام NameService در آن ایجاد کنید:

// INameService.cs file
namespace PrismApp.Modules.MyFirstModule.Services
{
    public interface INameService
    {
        string GetRandomName();
    }
}

// NameService.cs file
namespace PrismApp.Modules.MyFirstModule.Services
{
    using System;
    public class NameService : INameService
    {
        private readonly string[] _texts = { "Jalal", "Majid", "Saeed", "Alireza", "Esmaeil" };
        private readonly Random _random = new Random();

        public string GetRandomName()
        {
            // return random name...
            return _texts[_random.Next(0, _texts.Length)];
        }
    }
}


واسط INameService فقط اعلام می کند که قابلیت ارائه یک نام تصادفی را می پذیرد، کلاس NamedService نیز این قابلیت را برای آن پیاده سازی می کند.
در کل، هدف از ایجاد واسط ها و سپس کلاس های پیاده سازی کننده آن ها، جدا کردن جریان کار از نحوه پیاده سازی آن است. در اینجا، ViewModel ما فقط یک نام تصادفی می خواهد، و نیازی نیست بداند این نام تصادفی از کجا (آرایه؟ وب سرویس؟ بانک اطلاعاتی؟ ...) آمده است. بدین صورت شما ممکن است چندین پیاده سازی متفاوت از INameService داشته باشید و وابسته به شرایط و یا تنظیمات نرم افزار، از یکی از آنها را انتخاب کنید.
اکنون نوبت پیاده سازی کلاس MyFirstModule است. این کلاس نقطه اجرای ماژول ما است:


using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Regions;
using Microsoft.Practices.Unity;
using PrismApp.Modules.MyFirstModule.Services;
using PrismApp.Modules.MyFirstModule.ViewModels;
using PrismApp.Modules.MyFirstModule.Views;
using System;

namespace PrismApp.Modules.MyFirstModule
{
    public class MyFirstModule : IModule
    {
        private readonly IUnityContainer _container;
        private readonly IRegionManager _regionManager;

        public MyFirstModule(IUnityContainer container, IRegionManager regionManager)
        {
            _container = container;
            _regionManager = regionManager;
        }

        public void Initialize()
        {
            // Register Services
            _container.RegisterType<INameService, NameService>();

            // Register ViewModels
            _container.RegisterType<MyModuleMainViewModel>();

            //Register Views
            _container.RegisterType<Object, MyModuleMainView>();
            _container.RegisterType<Object, MyModuleRibbonTab>();

            // Register views inside named regions
            _regionManager.RegisterViewWithRegion("RibbonRegion", ResolveRibbonTabView);
            _regionManager.RegisterViewWithRegion("MainRegion", ResolveMainView);
        }

        private object ResolveRibbonTabView()
        {
            return _container.Resolve<MyModuleRibbonTab>();
        }

        private object ResolveMainView()
        {
            return _container.Resolve<MyModuleMainView>();
        }
    }
}

اگر به خاطر داشته باشید، در Bootstrapper، تابعی به نام CreateModuleCatalog وجود داشت که وظیفه آن بارگذاری ماژول ها بود. به تابع سازنده این کلاس دقت کنید. دو پارامتر IUnityContainer و IRegionManager، هر دو توسط Unity در کلاس Bootstrapper مقدار دهی شده و هنگام بارگذاری ماژول، به تابع سازنده آن تزریق می شوند.
در ادامه تابع Initialize توسط Bootstrapper فراخوانی می شود که شروع به ثبت سرویس ها، View و ViewModel ها در Unity می کند. در ادامه، View های مربوطه را در Region های از پیش مشخص شده (نام گذاری شده) ثبت می کند. بدین صورت (در این مثال) هنگام اجرای ماژول، View ها ایجاد شده و بر اساس نام مشخص شده، در Region مربوط به خود قرار می گیرند.
در صورتی که همه چیز مرتب باشد باید بتوانید ماژول خود را compile کنید. سپس به پوشه Bin\Debug آن رفته و assembly ماژول را (که PrismApp.Modules.MyFirstModule.dll نام دارد) در مسیر فایل اجرای برنامه (که PrismApp.Shell.exe) کپی نمایید. البته ویژوال استودیو قابلیت اتوماتیک کردن این روال را برای هر بار Build ماژول دارد. پس برای کار آسان شود، روی پروژه ماژول راست کلیک کرده و Properties را انتخاب کنید. زبانه Build Events را انتخاب کنید، سپس در کادر متنی Post-build events command line فرمان زیر را کپی کنید:
xcopy "$(TargetDir)*.*" "$(SolutionDir)PrismApp.Shell\bin\$(ConfigurationName)\" /Y
در صورتی که هنگام Compile ماژول به خطای زیر برخوردید، احتمالا این فرمان را (شاید چون نام پروژه شما متفاوت است) اشتباه نوشته اید.

سورس کد این قسمت را تحت عنوان Step3 از github دانلود کنید.

۱۳۹۱ بهمن ۷, شنبه

آشنایی با Prism - Bootstrapper

در پست قبل اندکی با Bootstrapper، آشنا شدیم. اما این کلاس حرف های بسیاری برای گفتن دارد. در ادامه با برخی جزئیات این سلسله تنظیمات بیشتر آشنا می شویم.


شکل بالا گام های لازم برای مقدار دهی اولیه ی یک برنامه Prism را مشخص می کند. 

ایجاد یک ILoggerFacade: با override کردن تابع CreateLogger در Bootstrapper، شما این اختیار را دارید تا یک logger برای خود تهیه کنید. این logger در طول کل برنامه در دسترس شما خواهد بود. همچنین خود کتابخانه Prism، گزارش های خود را به این logger می فرستد تا شما در جریان روند امور باشید. برای طراحی یک logger کافیست یک کلاس تعریف کرده و واسط ILoggerFacade را در آن پیاده سازی کنید. ما برای سادگی کار گزارش ها را بر روی فایل ذخیره می کنیم. شما می توانید هر نوع پیاده سازی (ذخیره در بانک اطلاعاتی، ارسال ایمیل و ...) دلخواه خود را انجام دهید.
using System;
using System.IO;
using System.Text;
using Microsoft.Practices.Prism.Logging;

namespace PrismApp.Shell
{
    public class TextFileLogger : ILoggerFacade
    {
        private readonly string _fileName = Path.Combine(Environment.CurrentDirectory, "PrismApp.log");
        private readonly object _syncLock = new object();

        public TextFileLogger()
        {
        }

        public void Log(string message, Category category, Priority priority)
        {
            WriteToFile(category.ToString(), message);
        }


        private void WriteToFile(string type, string message)
        {
            lock (_syncLock)
            {
                using (var writer = new StreamWriter(_fileName, true, Encoding.UTF8))
                {
                    writer.WriteLine(string.Format("{0:G}\t{1}:{2}", DateTime.Now, type, message));
                }
            }
        }
    }
}

واسط ILoggerFacade فقط یک تابع Log دارد که در آن پیام، وضعیت و اولویت آن مشخص شده است. شما می توانید مطابق میل خود، اطلاعات لازم را دریافت و ذخیره کنید. در این مثال، فقط وضعیت و متن را ذخیره می شود. اکنون برای مقدار دهی اولیه logger در کلاس Bootstrapper به این شکل عمل می کنیم:

        protected override Microsoft.Practices.Prism.Logging.ILoggerFacade CreateLogger()
        {
            return new TextFileLogger();
        }
با ایجاد یک نمونه از کلاس ساخته شده و بازگرداندن آن، Prism، از این نمونه برای عملیات گزارش استفاده می کند.

ایجاد Module Catalog: در صورتی که برنامه ی شما ماژولار است، با پیاده سازی تابع CreateModuleCatalog می توانید نحوه بارگذاری ماژول های برنامه را مشخص کنید. برای مثال اگر می خواهید ماژول ها را از مسیر خاصی بارگذاری کنید می توانید یک نمونه از DirectoryModuleCatalog استفاده کنید.

        protected override IModuleCatalog CreateModuleCatalog()
        {
            return new DirectoryModuleCatalog()
            {
                ModulePath = Environment.CurrentDirectory;
            };
        }

بدین وسیله، هنگام اجرای برنامه، Prism در مسیر مشخص شده (در این مثال، مسیر اجرای برنامه) به دنبال ماژول ها می گردد و در صورت پیدا کردن، آن ها را بارگذاری می کند. در پست های آینده با نحوه ایجاد ماژول بیشتر آشنا خواهیم شد.

پیکربندی Container: تزریق وابستگی نقش کلیدی را در برنامه های مبتنی بر Prism ایفا می کند. در این مرحله، سرویس های اصلی (Core Services)، بطور پیشفرض توسط Prism در Container ثبت می شوند. اما می توان این کار را بطور دستی نیز انجام داد. اضافه بر این، سرویس های برنامه کاربردی نیز باید توسط برنامه نویس در Container ثبت شوند. از انجایی که فعلا فقط قصد تکمیل اسکلت کامل یک برنامه ماژولار Prism را داریم، سرویس خاصی در Container ثبت نمی کنیم. Prism بطور پیش فرض، بسیاری از سرویس های مورد نیاز را ثبت می کند. در پست های بعدی به این قسمت برمیگردیم. فعلا تابع ConfigureContainer ما در حد ثبت MainWindow باقی می ماند.

        protected override void ConfigureContainer()
        {
            // Register default core services
            base.ConfigureContainer();
            // Register Shell (main window)
            Container.RegisterType<MainWindow>();
        }
پیکربندی RegionAdapterMapping و RegionBehavior پیش فرض: در برنامه های مبتنی بر Prism، مدیریت View ها اندکی متفاوت از برنامه های WPF عادی است. در یک برنامه ماژولار ممکن است View خاصی در یک ماژول تعریف شده باشد و با اجرای فرمانی نیاز باشد آن View به کاربر نمایش داده شود.  بدین منظور از قبل باید در View اصلی برنامه (همان Shell که در اینجا منظور MainWindow است) قسمت هایی تعریف شوند که قادر به نمایش View ها باشند. به این قسمت ها Region گفته می شود. بدین صورت شما می توانید در Shell برنامه، یک یا چند Region با نام های منحصر به فرد طراحی کنید تا ماژول ها بتوانند در آن View های خود را نمایش دهند. در پست های بعدی بیشتر به این قسمت خواهیم پرداخت و فعلا توابع مربوط به آن را override نمی کنیم.

ثبت FrameworkExceptions: شما می توانید استثنا (Exception) های خاصی را در Prism ثبت کنید تا هنگام رخ دادن، استثنای اصلی توسط تابع GetRootException پیدا شده و نمایش داده شود. استثناهای ثبت شده، به سادگی در یک لیست Static ذخیره می شوند. در صورتی که یک Exception، در این لیست ثبت نشده باشد، پس از رخ دادن بدون جستجو برای InnerException اصلی، دوباره throw می شود. از این امکان بسیار به ندرت استفاده می شود.

CreateShell: همانطور که در پست قبل خواندید، از این تابع برای ایجاد View اصلی برنامه (که Shell نامیده می شود) استفاده می شود. در ادامه تابع دیگری به نام InitializeShell وجود دارد که پس از این تابع فراخوانی می شود و پیاده سازی آن نیز اختیاری است. در صورتی که برنامه شما WPF باشد می توانید در این تابع، کد مربوط به مقدار دهی اولیه و نمایش پنجره اصلی را بنویسید. بدین جهت، تغییرات زیر در کلاس Bootstrapper انجام می شود:

        protected override System.Windows.DependencyObject CreateShell()
        {
            return Container.Resolve<MainWindow>();
        }

        protected override void InitializeShell()
        {
            Application.Current.MainWindow = (Window)this.Shell;
            Application.Current.MainWindow.Show();
        }
بدین صورت، ما با override کردن تابع CreateShell، فقط یک نمونه سازی از کلاس MainWindow انجام می دهیم و سپس در تابع InitializeShell، پنجره اصلی برنامه WPF را مشخص کرده و آن را به کاربر نمایش می دهیم.

InitializeModules: همان طور که قبلا ذکر شد، در تابع ConfigureContainer، برخی سرویس های اصلی Prism با کلاس های پیش فرض ثبت می شوند. یکی از آن سرویس ها، IModuleManager است. این سرویس (واسط) وظیفه بارگذاری ماژول های برنامه را بر عهده دارد. در این سرویس، رویداد هایی به نام ModuleDownloadProgressChanged و LoadModuleCompleted وجود دارد که با subscribe شدن، می توان پیشرفت بارگذاری ماژول ها و زمان اتمام بارگذاری آن ها را مشاهده کرد. کتابخانه Prism یک پیاده سازی پیشفرض (کلاس) از این سرویس به عنوان ModuleManager دارد که در تابع ConfigureContainer، آن را ثبت کرده است. در صورتی که پس از override کردن این تابع، تابع base آن فراخوانی شده باشد، این سرویس بطور پیش فرض در Container ثبت شده است و نیازی نیست چیزی را تغییر بدهید.

خب، تا به این جا به اندازه کافی با Bootstrapper آشنا شده ایم. در پست بعدی به سراغ ماژول نویسی رفته و بخشی از مفاهیم Region ها را در کنار آن می آموزیم.

دریافت سورس کد کامل با تگ Step 2

۱۳۹۱ بهمن ۵, پنجشنبه

آشنایی با Prism - شروع به کار

Prism یک فریم ورک برای طراحی نرم افزارهای قوی، قابل انعطاف با نگه داری آسان در WPF، Silverlight و Windows Phone است.
به کمک Prism شما می توانید برنامه هایی با اجزای وابسته ضعیف طراحی کنید که می توانند مستقل باشند ولی به آسانی به برنامه اصلی مجتمع شوند. این گونه برنامه ها عموما برنامه های ترکیبی (Composite Application) گفته می شود.

برای شروع می توانید Prism را به همراه سورس آن دانلود کرده و بر روی سیستم خود extract کنید. کتابخانه Prism برای برنامه های Desktop (و WPF) در مسیر bin\Desktop قرار دارد. 
Prism جهت تزریق وابستگی ها، بطور پیشفرض با Unity کار می کند. ولی در صورتی که با فریم ورک های دیگر تزریق وابستگی کار می کنید، دست شما باز است. مثلا برای Ninject یک extension ویژه استفاده در Prism تهیه شده است. همینطور Structure Map هم دارای کلاس های ویژه Prism است.

در صورتی که با مفاهیم تزریق وابستگی آشنایی ندارید، می توانید از اینجا شروع کنید.

ابزار کار لازم
  • Visual Studio نسخه 2010 یا 2012 (البته نسخه های قدیمی تر هم قابل استفاده هستند ولی نسخه Prism 4.1 فقط با دات نت 4 کار می کند. در صورتی که از Visual Studio 2008 استفاده می کنید باید نسخه Prism 2.1 را نصب کنید.)
  • اگر تمایلی به دستکاری source code کتابخانه Prism ندارید می توانید براحتی آن را توسط Nuget نصب کنید. پس درصورتی که آن را ندارید، دانلود کرده و نصب کنید.


آماده سازی محیط کار
ویژوال استودیو را اجرا کنید و یک پروژه جدید از نوع WPF Application ایجاد کنید. دقت کنید، دات نت 4 در لیست Target Framework انتخاب شده باشد. حالا در پنجره Package Manager Console دستور زیر را وارد کنید:

Install-Package Prism

و در ادامه برای نصب Unity، دستور زیر را وارد کنید:

Install-Package Unity

حالا نوبت نصب Unity Extension برای Prism است:

Install-Package Prism.UnityExtensions

برنامه های WPF از فایلی به نام App.xaml شروع می شوند. بطور پیش فرض، در این فایل، مسیر پنجره اصلی برنامه در یک مشخصه به نام StartupUri تعیین شده است ولی برنامه های مبتنی بر Prism، راه حلی متفاوت برای شروع دارند.

کلاس Bootstrapper
Bootstrapper نام کلاسی است که تمامی پیکربندی اولیه شروع برنامه در آن مشخص می شود. در Solution Explorer بر روی پروژه راست کلیک کرده و کلاس جدید به آن اضافه کنید. آن را Bootstrapper بنامید و کد زیر را در آن بنویسید.

    public class Bootstrapper : UnityBootstrapper
    {
        protected override System.Windows.DependencyObject CreateShell()
        {
            var mainView = new MainWindow();
            mainView.Show();
            return mainView;
        }
    }

کلاس Bootstrapper ما از کلاس UnityBootstrapper ارث برده است. تنها تابع که پیاده سازی آن اجباری است، تابع CreateShell است. این تابع، مقداری از نوع DependencyObject برمیگرداند که نقطه شروع نمایش برنامه را مشخص می کند. از آنجایی که برنامه ما یک WPF Application است، و در آن، MainWindow.xaml قرار است فرم اصلی باشد، ما یک نمونه از ان را ایجاد کرده، نمایش داده و برمی گردانیم. البته این روش new کردن به صورت دستی چندان مناسب نیست، بهتر است به کمک Unity این نمونه سازی را انجام دهیم:

    using Microsoft.Practices.Unity;
    using Microsoft.Practices.Prism.UnityExtensions;

    public class Bootstrapper : UnityBootstrapper
    {
        protected override void ConfigureContainer()
        {
            base.ConfigureContainer();

            Container.RegisterType<MainWindow>();
        }

        protected override System.Windows.DependencyObject CreateShell()
        {
            var mainView = Container.Resolve<MainWindow>();
            mainView.Show();
            return mainView;
        }
    }

اکنون یک تابع دیگر از UnityBootstrapper را override کرده ایم و در آن علاوه بر فراخوانی ConfigureContainer کلاس والد، پنجره اصلی (کلاس MainWindow) را در Unity Container ثبت کردیم. از تابع ConfigureContainer جهت رجیستر کردن سرویس ها، اینترفیس ها و کلاس های مورد نیاز در برنامه استفاده می شود. (using ها فراموش نشود!)
به App.xaml برمیگردیم. ابتدا مشخصه StartupUri و مقدار آن را پاک کرده و سپس به code behind آن رفته و تغییرات زیر را ایجاد کنید:

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            var bootstrapper = new Bootstrapper();
            bootstrapper.Run();

        }
    }
ما تابع OnStartup را override کردیم. این تابع با شروع برنامه، اجرا می شود. در آن یک نمونه از Bootstrapper ساختیم و تابع Run آن را اجرا کردیم.
اکنون پروژه را کامپایل نموده و اجرا کنید. در صورتی که تمام مراحل را بدون خطا سپری کرده باشید، پنجره برنامه را مشاهده خواهید کرد.

سورس کامل این قسمت را تحت عنوان Step 1 از اینجا دانلود کنید.