65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Phone 7 的 Caliburn Micro

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (12投票s)

2011年4月1日

MIT

12分钟阅读

viewsIcon

52368

downloadIcon

1625

Windows Phone 7 世界中的约定优于配置 MVVM。

引言

Caliburn Micro 框架是首批可用于 Windows Phone 7 (WP7) 的 MVVM 框架之一,由于广泛使用了“约定优于配置”,因此它非常高效。

背景

创建 Windows Phone 7 的 Silverlight 应用程序时,会提供基本的 MVVM 实现,但大多数人会发现这些对于任何非简单的应用程序都不够。我确实建议学习 WP7 Silverlight 编程的人不要使用框架,因为这将使他们更多地接触平台,并让他们更欣赏他们正在使用的框架。

关于 Caliburn 框架

最初的 Caliburn 是作为一个用于 WPF 的开源 (MIT 许可证) MVVM 框架创建的,后来被采用于 Silverlight。在撰写本文时,该项目已达到 2.0 版本。在其项目页面上,您会找到以下消息:

"停止!如果您是新来的,我们建议使用一个具有 90% 功能和强大功能的更简单的框架!看看 Caliburn.Micro"。

此外,“Caliburn”是“Excalibur”的不同拼写/名称之一

进入 Windows Phone 7

现在,如果您正在为 Silverlight 和 WP7 进行开发,那么选择一个更轻量级的框架是很自然的,如果您也刚开始开发,为什么不选择一个更简单的呢?

以下是有关此框架的历史和创建的一些额外信息:链接

其他 WP7 MVVM 框架

市面上还有一些其他流行的 MVVM 框架

我们正在解决的问题

我将通过编写一个 WP7 客户端来监控 Cruise Control.Net 的状态,来演示使用 Caliburn Micro 框架的好处。

如果您熟悉 Cruise Control.Net (CC) 的作用,我建议您点击上面的链接,但简而言之,它是一个“自动持续集成服务器,监控您的源代码控制,运行构建、测试和代码分析,帮助您的产品保持成功构建和正确运行的状态”——它是免费的、开源的,并且是用 .NET 编写的。

使用 Caliburn Micro for Windows Phone 7

Caliburn Micro 项目的下载页面包含一个您需要下载和安装的包,该包将安装您所需的二进制文件、示例和 Visual Studio 模板。

在撰写本文时,此项目尚未在 NuGet 上,但我猜测您很快也可以通过这种方式获取它。

更新

2011 年 4 月 20 日:现在有了一个 Caliburn Micro NuGet 包(这里有一篇关于为 Caliburn Micro 添加 NuGet 支持的文章)。

如果您安装了 NuGet 1.2 或更高版本,请从包管理器控制台中输入

Install-Package Caliburn.Micro

空白项目

作为已安装的 Visual Studio 模板的一部分,您会找到这个项目模板

ccnetwp7_1a.png

创建项目后,readme.txt 文件将打开,指向 Caliburn Micro 项目页面,该页面目前是查找文档的最佳位置。

项目树看起来会是这样的

ccnetwp7_2.png

注意新增的“Framework”文件夹、“AppBootstrapper”类以及没有代码隐藏的主页。

Framework”文件夹包含其下的所有 Caliburn Micro 源代码(没有额外的程序集引用)。

ccnetwp7_3.png

应用程序和应用程序启动器

App.xaml 几乎是空的——它只在资源中引用了 AppBootstrapper,这是因为该类的所有逻辑都已重定向到 AppBoostrapper,它将为我们做一些额外的事情。

IoC/依赖注入

作为新项目的一部分,您还会得到一个基本的控制反转容器,但您可以将其替换为任何其他 WP7 容器。要设置容器,AppBootstrapper 在重写的 Configure 方法中包含基本代码。

protected override void Configure()
{
    container = new PhoneContainer(this);

    container.RegisterPerRequest(typeof(MainPageViewModel), 
              "MainPageViewModel", typeof(MainPageViewModel));

    container.RegisterInstance(typeof(INavigationService), 
              null, new FrameAdapter(RootFrame));
    container.RegisterInstance(typeof(IPhoneService), null, 
              new PhoneApplicationServiceAdapter(PhoneService));

    //container.Activator.InstallChooser<phonenumberchoosertask,>();
    //container.Activator.InstallLauncher<emailcomposetask>();

    AddCustomConventions();
}
约定优于配置

现在,关于 AddCustomConventions 方法:现代开发往往是一个非常重复且冗长的过程,我们学会了忍受它,这主要是由于 IDE 的巨大改进。XAML 平台 (WPF, WF, Silverlight, WP7) 也不例外。您经常会发现自己重复常见的模式——就像上面这样:

<Button x:Name="Insert" 
    Content="insert" 
    IsEnabled="{Binding CanInsert}"
    Click="Insert_OnClick"/>

这只是冰山一角。所有这一切都是因为您可以以多种方式设置所有这些属性和绑定,并且由于灵活性增加,我们增加了复杂性。为了应对复杂性,开发人员倾向于使用模式来简化他们对项目的心理图景(如上例——请注意,每行都有“Insert”文本)。为什么不更进一步说:我们将默认使用这些约定,也许我们只需要

<Button x:Name="Insert" 
        Content="insert"/>

Caliburn Micro 通过使用这些约定以及更多内容为您提供了这种便利。

在上面的示例中,我们必须在页面的代码隐藏中有一个方法,该方法然后会查找 ViewModel 并调用额外的“Insert”方法。有了 CM,您就可以免费获得所有这些。

  • 通过在 View Model 中拥有一个公共的 Insert 方法,CM 将确保“Insert”按钮的点击被传递到 ViewModel 的 Insert 方法。
  • 如果您有一个返回 bool 的公共方法或一个名为“CanInsert”的 bool 类型属性,CM 将将其绑定到按钮的 IsEnabled 属性。

这仅仅是个开始……

是的,但是!

但是,如果您不喜欢已经为您设置好的约定,或者在某些情况下需要覆盖它们怎么办?

  • 在 Caliburn Micro 附加到 ViewModel 之前设置的任何绑定和值都不会被更改。这允许您覆盖约定绑定。
  • 有一个 API 可以更改约定,您可以在此处查看相关文章:链接

视图和视图模型的配对

由于在 WP7 中您需要在页面之间导航,因此它们(作为视图)将需要以某种方式查找和初始化适当的视图模型。同样,您很可能会有“AddServerPage.xaml”和“AddServerPageViewModel.cs”作为一对,所以让我们让 Caliburn Micro 为我们配对这些。

container.RegisterPerRequest(typeof(AddServerPageViewModel), 
          "AddServerPageViewModel", typeof(AddServerPageViewModel));

仅通过这样做,当您导航到 AddServerPage 时,您的 ageView 模型将已准备好并在页面的 DataContext 中等待。

视图模型的基类

可以使用普通的 CLR 对象 (POCO) 作为视图模型。在大多数情况下,您将需要实现 INotifyPropertyChanged 接口。Caliburn Micro 提供了许多视图模型基类供您选择。

  • PropertyChangedBase - 是 INotifyPropertyChanged 的基本实现,带有一些额外功能。
  • Screen - 它提供了许多可以挂钩的事件,例如 OnActivate,以及访问已附加视图的选项。
  • Conductor - 特别是 Conductor<t>.Collection.OneActive,是在处理集合时的预组合视图模型套件。
使用 PropertyChangedBase

让我们深入研究 AddServerPageViewModel 并让它继承自这个基类。

public class AddServerPageViewModel : Framework.PropertyChangedBase
{
    public AddServerPageViewModel()
    {
        _fullAddress = new Uri("http://ccnet.wheelmud.net" + 
                               "/XmlServerReport.aspx");
    }

    Uri _fullAddress;
    
    public string ServerName
    {
        get { return _fullAddress.Host; }
        set
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentNullException();

            var ub = new UriBuilder(_fullAddress)
            {
                Host = value,
            };

            SetFullAddress(ub.Uri);
        }
    }

    public int PortNumber
    {
        get { return _fullAddress.Port; }
        set
        {
            var ub = new UriBuilder(_fullAddress)
            {
                Port = value,
            };

            SetFullAddress(ub.Uri);
        }
    }

    public string FullAddress
    {
        get { return _fullAddress.ToString(); }
        set
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentNullException();

            SetFullAddress(new Uri(value));
        }
    }

    void SetFullAddress(Uri uri)
    {
        _fullAddress = uri;

        CanAddServer = _fullAddress.IsAbsoluteUri;

        Refresh();
    }
}

我们有三个相互关联的属性,用于编辑服务器 URL。由于这三个属性很可能一起更改,所以我只是调用 PropertyChangedBaseRefresh 方法,该方法将通知视图所有这些属性都已更改。很方便。对于视图,我们只需要在 ContentPanel 中添加:

<StackPanel>
    <TextBlock><Run Text="Server DNS:"/></TextBlock>
    <TextBox x:Name="ServerName" InputScope="Url" />
    <TextBlock><Run Text="Server Port:"/></TextBlock>
    <TextBox x:Name="PortNumber" InputScope="Number"/>
    <TextBlock><Run Text="Full Uri:"/></TextBlock>
    <TextBox x:Name="FullAddress" InputScope="Url"/>
</StackPanel>

提示:在模拟器中测试此页面时,不要使用屏幕键盘用鼠标输入,而是可以按计算机键盘上的 Page Up 键来模拟手机的硬件键盘。

使用 Conductor<t>.Collection.OneActive

让您的视图模型继承自这个基类,可以轻松创建用于集合的视图模型,并且在这个变体中,它允许并只维护一个活动的子项。

这个基类有一个 Items 属性,在视图中它会与 List Box 或其他 Items 控件派生类以 x:Name="Items" 配对。活动的项会绑定到 SelectedItem

导航问题

您可能已经注意到页面和行为(如果使用 Blend)具有导航到其他页面的能力,但这并不理想,因为我们希望视图模型控制导航。导航到页面后,它可以访问传递的参数,方法是解析查询字符串参数,但同样——这是我们希望视图模型做的事情。

Caliburn Micro 通过公开丰富的 INavigationService 接口并利用依赖注入的强大功能使其易于在视图模型中使用,从而解决了这两个问题。要在视图模型中获取此服务的全部功能,您只需要一个构造函数,它将此接口作为参数。

public AddServerPageViewModel(INavigationService navigationService) {/*…*/}

问题的第二部分(解析传递的参数)会自动处理,这要归功于约定优于配置。如果您导航到的 URI 是“/ProjectPage.xaml?projectId=17”,并且该页面的视图模型中有一个 ProjectId 属性,Caliburn 将自动使用指定的参数值填充 ProjectId 属性。

如果您需要更多地控制视图模型中视图的生命周期,只需继承 Screen 基类或更专业的子类之一。

WP7 不可绑定的应用程序栏

WP7 UI 的一部分是应用程序栏,在其第一个版本中它是不可绑定的。事实上,您需要使用索引来操作按钮,通常直接从您的视图进行操作。

Caliburn Micro for WP7 在此领域进行了重大改进——允许您将应用程序栏绑定到您的视图模型。

您需要做的就是使用 CM 的 AppBarButtonAppBarMenuItem 在标准的应用程序栏中,然后通过指定“Message”属性来识别它需要绑定到哪个目标。

我喜欢在 Blend 中执行此操作,而且幸运的是,您也可以这样做。在 Visual Studio 中右键单击项目以打开您的项目,然后打开您要添加应用程序栏的页面,然后在对象列表中选择该页面。

ccnetwp7_4.png

然后在“属性”的“常用属性”部分。

ccnetwp7_5.png

按“新建”按钮创建应用程序栏。

ccnetwp7_6.png

注意“Buttons (Collection)”并按“...”按钮编辑此应用程序栏中的按钮集合。

ccnetwp7_7.png

现在,“添加另一项”有一个下拉按钮,如果您想要 WP7 框架的按钮,可以使用此下拉按钮,但要使用 Caliburn Micro 改进的按钮版本,只需按“添加另一项”按钮。选择将弹出的“对象”对话框。

ccnetwp7_8.png

我经常发现输入“AppBar”可以快速过滤掉不需要的类型。

ccnetwp7_9.png

从列表中选择 AppBarButton,然后按 OK 按钮。

添加的项目看起来很像标准按钮。

  • IconUri 下拉列表允许快速选择图像,包括库存图像,如果需要,这些图像也将添加到项目中。
  • 显示在按钮下方的文本。

额外的 Message 字段是 Caliburn Micro 的字段,它映射到视图模型中的 Command

ccnetwp7_10.png

这不仅会自动映射到“Message”命令(方法),还会映射到控制按钮启用或禁用的“CanMessage”(方法或属性)。

C# 5 还有很长的路要走

自 Silverlight 2 以来,进行 Web 服务调用的唯一方法是异步的。由于我们有一个多线程环境,并且 Microsoft 通过强制我们仅在 UI 线程上执行所有 UI 活动来阻止我们自断后路,因此在 WP7 中进行网络调用有点痛苦。C# 5 将引入“async”关键字,这将使这个过程更容易,但现在,我们必须在没有它的情况下进行编程。

这带来了一些重要问题

  • 如何尽可能多地保留在 MVVM 中,并保持其整洁?
  • 如何将所有可能的处理卸载到非 UI 线程,以保持应用程序的性能?
  • 如何向用户提供良好的加载/处理反馈,并可能提供取消选项?

对我来说,最棘手的问题之一是:

  • 如何从 ViewModel 控制动画以指示忙碌状态?

然后我发现了这篇博文:链接。Caliburn Micro 有一个很棒的功能,即调用以调用命令的方法也可以返回 IEnumerable<IResult>。Caliburn 将遍历返回的结果,逐个执行它们,直到全部完成、一个取消或抛出/返回错误。这本身就很棒,但还有更多!IResult 接口看起来像这样:

public interface IResult
{
    void Execute(ActionExecutionContext context);
    event EventHandler<resultcompletioneventargs> Completed;
}

public class ActionExecutionContext
{
    public ActionMessage Message;
    public FrameworkElement Source;
    public object EventArgs;
    public object Target;
    public DependencyObject View;
    public MethodInfo Method;
    public Func<bool> CanExecute;
    public object this[string key];
}

由于 ActionExecutionContext 中的 Source 属性,我们可以访问实际页面上的对象。这很好,除非您滥用它——所以尽量保持简单,不要过度耦合。我的选择是使用 Visual State Manager 来更改页面的状态。

public IEnumerable<iresult> AddServer()
{
    CanAddServer = false;
    Refresh();

    yield return new SetVisualState("Adding");

    var readServer = _readServerFactory();
    readServer.Server.Name = ServerName;
    readServer.Server.Uri = FullAddress;

    yield return readServer;

    _serverDataManager.AddServer(readServer.Server);

    yield return new SetVisualState("Default");

    CanAddServer = true;
    _navigationService.GoBack();
}

这个示例来自 AddServerPageViewModel,它将异步执行这些操作,但对我来说,最棒的部分是我可以轻松而清晰地操作 Visual State。

public class SetVisualState : IResult
{
    readonly string _state;
    public SetVisualState(string state)
    {
        _state = state;
    }

    public void Execute(ActionExecutionContext context)
    {
        VisualStateManager.GoToState((Control)context.View, _state, true);

        Completed(this, new ResultCompletionEventArgs());
    }

    public event EventHandler<resultcompletioneventargs> Completed = delegate { };
}

结论

Caliburn Micro 是 WP7 的一个很棒的框架,它有望带来巨大的生产力提升,但它仍然存在一些问题,这些问题会让很多人望而却步。

  • 它可能会以您可能不习惯的方式重构您的代码。
  • 有时您会浪费时间试图弄清楚如何执行一个看起来很直接的操作。
  • 与设计时数据和 Caliburn Micro 一起工作是可能的,但那些页面将不得不使用标准的 XAML 绑定。

注释

  • 我总是为文本框设置 Input Scope,如果 Visual Studio 没有提供智能感知,Blend 会提供。
  • 您的应用程序应该快速启动。除非您确定需要它,否则不要使用启动屏幕。
  • 您可以在此处找到一些出色的文章,它们充当 Caliburn Micro 文档:http://caliburnmicro.codeplex.com/documentation
  • 该项目将继续在 http://ccnetwp7.codeplex.com/ 上开发,以添加功能,例如:
    • 能够从手机监控和控制构建。
    • 实时磁贴 + 服务器通知。

历史

  1. 2011 年 5 月 31 日 - 初始版本。
  2. 2011 年 4 月 4 日 - 小幅度清理。
© . All rights reserved.