代码改变世界

[Silverlight]MVVM+MEF框架Jounce学习(2):标记和绑定

2012-11-29 10:09  slmk  阅读(1739)  评论(0编辑  收藏  举报

这一部分主要讲解如何绑定View和View Model。

 IApplicationService接口

Jounce实现了 IApplicationService接口,作为一个服务插入到Silverlight应用程序的生命周期。这样,Jounce就有机会在silverlight应用程序开始、结束或者出现未处理的异常时,接管程序的执行。使用Jounce框架的应用程序,App.xaml必须这样定义:

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:Services="clr-namespace:Jounce.Framework.Services;assembly=Jounce"
             x:Class="SilverlightApplication.App"
             >
    <Application.Resources>
        
    </Application.Resources>
    <Application.ApplicationLifetimeObjects>
        <Services:ApplicationService IgnoreUnhandledExceptions="False" LogSeverityLevel="Verbose"/>
    </Application.ApplicationLifetimeObjects>
</Application>

StartService方法

当应用程序启动时,自动调用IApplicationService接口的StartService方法:

public void StartService(ApplicationServiceContext context)
{
            var logLevel = LogSeverityLevel;

            if (context.ApplicationInitParams.ContainsKey(Constants.INIT_PARAM_LOGLEVEL))
            {
                logLevel =
                    (LogSeverity)
                    Enum.Parse(typeof (LogSeverity), context.ApplicationInitParams[Constants.INIT_PARAM_LOGLEVEL], true);
            }

            _mainCatalog = new AggregateCatalog(new DeploymentCatalog());

            _container = new CompositionContainer(_mainCatalog);
            
            CompositionHost.Initialize(_container);
            CompositionInitializer.SatisfyImports(this);

            if (Logger == null)
            {
                ILogger defaultLogger = new DefaultLogger(logLevel);
                _container.ComposeExportedValue(defaultLogger);
            }
            else
            {
                Logger.SetSeverity(logLevel);
            }

            DeploymentService.Catalog = _mainCatalog;
            DeploymentService.Container = _container;
            _mefDebugger = new MefDebugger(_container, Logger);
}

这里,Jounce主要做了这么几件事情:
设置默认的日志级别,你也可以通过以下方式制定某一日志级别:

<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
    <param name="initParams" value="Jounce.LogLevel=Verbose" />
</object>

Jounce为当前程序集定义了一个默认的部署类别DeploymentCatalog(Jounce可以为不同的XAP定义不同的部署类别)。

定义了一个ILogger对象的导入:

[Import(AllowDefault = true, AllowRecomposition = true)]
public ILogger Logger { get; set; }

AllowDefault告诉MEF这个导入不是一定要满足的,允许为null。如果你实现并导出了自己的ILogger对象,会满足这个导入条件;如果没有定义,Jounce使用一个默认的DefaultLogger。这个默认的logger会将日志写到调试窗口,MefDebugger使用此日志对象,输出一些Jounce日志,让我们可以跟踪Jounce做了什么。当View和ViewModel不能按我们预想的绑定时,查看这些日志会很有帮助。看一看MefDebugger的源代码,会对我们理解MEF的如何工作很有帮助。

MEF: Found part: SilverlightApplication.ViewModels.MainViewModel
2012/11/29 10:25:46 Verbose Jounce.Core.MefDebugger ::    With import: Jounce.Core.ViewModel.BaseViewModel.EventAggregator (ContractName="Jounce.Core.Event.IEventAggregator")
2012/11/29 10:25:46 Verbose Jounce.Core.MefDebugger ::    With import: Jounce.Core.ViewModel.BaseViewModel.Router (ContractName="Jounce.Core.ViewModel.IViewModelRouter")
2012/11/29 10:25:46 Verbose Jounce.Core.MefDebugger ::    With import: Jounce.Core.ViewModel.BaseViewModel.Logger (ContractName="Jounce.Core.Application.ILogger")
2012/11/29 10:25:46 Verbose Jounce.Core.MefDebugger ::    With export: SilverlightApplication.ViewModels.MainViewModel (ContractName="Jounce.Core.ViewModel.IViewModel")
2012/11/29 10:25:46 Verbose Jounce.Core.MefDebugger ::       With key: ViewModelType = SilverlightApplication.ViewModels.MainViewModel
2012/11/29 10:25:46 Verbose Jounce.Core.MefDebugger ::       With key: ExportTypeIdentity = Jounce.Core.ViewModel.IViewModel
2012/11/29 10:25:46 Verbose Jounce.Core.MefDebugger ::    With export: SilverlightApplication.ViewModels.MainViewModel (ContractName="Jounce.Core.ViewModel.IViewModel")
2012/11/29 10:25:46 Verbose Jounce.Core.MefDebugger ::       With key: ViewModelType = SilverlightApplication.ViewModels.MainViewModel
2012/11/29 10:25:46 Verbose Jounce.Core.MefDebugger ::       With key: ExportTypeIdentity = Jounce.Core.ViewModel.IViewModel

Starting方法

另一个重要的事件是Starting,在visual tree完全激活之前调用。

public void Starting()
{
            if (!IgnoreUnhandledExceptions)
            {
                Application.Current.UnhandledException += _CurrentUnhandledException;
            }

            var viewInfo = (from v in Views where v.Metadata.IsShell select v).FirstOrDefault();

            if (viewInfo == null)
            {
                var grid = new Grid();
                var tb = new TextBlock
                             {
                                 Text = Resources.ApplicationService_Starting_Jounce_Error_No_view
                             };
                grid.Children.Add(tb);
                Application.Current.RootVisual = grid;
                Logger.Log(LogSeverity.Critical, GetType().FullName, Resources.ApplicationService_Starting_Jounce_Error_No_view);
                return;
            }

            Application.Current.RootVisual = viewInfo.Value;
            Logger.LogFormat(LogSeverity.Information, GetType().FullName,Resources.ApplicationService_Starting_ShellResolved, MethodBase.GetCurrentMethod().Name,
                             viewInfo.Value.GetType().FullName);
            Logger.Log(LogSeverity.Information, GetType().FullName, MethodBase.GetCurrentMethod().Name);            

            EventAggregator.Publish(viewInfo.Metadata.ExportedViewType.AsViewNavigationArgs());
        }

Jounce捕捉未处理的异常,并用事件聚合发布异常,令我们的代码可以容易的订阅并处理他们。Jounce只可以标记一个View作为Shell,这个View将作为应用程序的root visual。如果找不到shell,Jounce会抛出异常。

所有的View元数据会被导入到ApplicationService对象中:

 [ImportMany(AllowRecomposition = true)]
 public Lazy<UserControl, IExportAsViewMetadata>[] Views { get; set; }

然后,Jounce记录一些消息并触发一个导航事件,Jounce导航到根视图,并绑定到视图模型。

标记View和View Model

如何标记Shell根视图:

[ExportAsView("Welcome",IsShell = true)]
public partial class Welcome
{
    public Welcome()
    {
        InitializeComponent();            
    }
}

标记View和ViewModel很灵活,可以像上面那样使用字符串(名称必须唯一),也可以使用强类型标记。ViewModel这样标记:

[ExportAsViewModel("Welcome")]
public class WelcomeViewModel : BaseViewModel 
{
    public WelcomeViewModel()
    {
        Title = "Welcome to Jounce!";            
    }

    private string _title;

    public string Title
    {
        get { return _title; }
        set
        {
            _title = value;
            RaisePropertyChanged(()=>Title);
        }
    }

    public override void _Activate(string viewName)
    {            
        GoToVisualState("WelcomeState",true);
    }
}

BaseViewModel定义了可以override的方法:

_Initialize在view model第一次创建时调用,_Activate当视图View绑定到ViewModel并激活时调用,

_Deactivate在view不再激活时调用。当导航Navigation发生时,Jounce拦截并绑定ViewModel,然后调用相应的这些方法。

如何绑定View和ViewModel呢?

[Export]
public ViewModelRoute Binding
{
    get
    {
        return ViewModelRoute.Create("Welcome", "Welcome");
    }
}

ViewRouter类

实现了IEventSink接口,

public class ViewRouter : IPartImportsSatisfiedNotification, IEventSink<ViewNavigationArgs>

可以接收全局导航消息并调用ViewModelRouter的ActivateView方法:

public bool ActivateView(string viewName)
{
    Logger.LogFormat(LogSeverity.Verbose, GetType().FullName, Resources.ViewModelRouter_ActivateView,MethodBase.GetCurrentMethod().Name,viewName);

    if (HasView(viewName))
    {
        var view = ViewQuery(viewName);

        var viewModelInfo = GetViewModelInfoForView(viewName);

        if (viewModelInfo != null)
        {
            var firstTime = !viewModelInfo.IsValueCreated;

            var viewModel = viewModelInfo.Value;

            if (firstTime)
            {
                viewModel.GoToVisualState =
                    (state, transitions) =>
                    JounceHelper.ExecuteOnUI(() => VisualStateManager.GoToState(view, state, transitions));
                _BindViewModel(view, viewModel);
                viewModel.Initialize();
            }
            viewModel.Activate(viewName);
        }

        return true;
    }
    return false;
}

viewModel的GoToVisualState可以在不引用view的情况下改变view的VisualState:

public override void _Activate(string viewName)
{            
    GoToVisualState("WelcomeState",true);
}