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

MvvmCross TipCalc - 第 4 步:创建 Windows Phone UI

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.53/5 (5投票s)

2013 年 3 月 23 日

Ms-PL

6分钟阅读

viewsIcon

22009

TipCalc 教程 MvvmCross v3 - Hot Tuna 的第 4 步。

介绍  

本文是 MvvmCross v3 - Hot Tuna! 的 TipCalc 教程的第 4 步!

目前为止的故事...

我们开始的目标是创建一个应用程序,以帮助计算在餐厅应该留下多少小费。

我们计划基于此概念创建一个 UI

Sketch

为了实现这一点,我们构建了一个“Core”可移植类库项目,其中包含

  • 我们的“业务逻辑” - `ICalculation`
  • 我们的 `ViewModel` - `TipViewModel`
  • 我们的 App,它包含了应用的连接逻辑,包括启动指令

然后我们为 Xamarin.Android 添加了第一个用户界面

Android

然后我们为 Xamarin.iOS 添加了第二个用户界面

v1

对于我们下一个项目,让我们转向 Windows Phone。

要创建 Windows Phone MvvmCross UI,您可以使用 Visual Studio 项目模板向导,但在这里我们将像处理 Core、Android 和 iOS 项目一样,“从零开始”构建一个新项目。

显然,要使用 Windows Phone,我们需要切换回 PC 上的 Visual Studio 进行工作。

创建新的 Windows Phone 项目

向您的解决方案添加一个新项目 - 一个名为 TipCalc.UI.WP 的“Windows Phone App”应用程序。

对于目标操作系统,您可以选择 7.1 或 8.0 - 您的选择。

在此之中,您会发现 WP 常规应用程序结构

  • App.Xaml 的“应用程序”对象
  • 带有 AppManifest.xmlWMAppManifest.xml “配置文件”的“Properties”文件夹
  • 定义此应用程序默认页面的 MainPage.XamlMainPage.Xaml.cs 文件
  • 一些图标

删除 MainPage.xaml

没有人真正需要 MainPage笑脸 | <img src= " src="https://codeproject.org.cn/script/Forums/Images/smiley_smile.gif" />

添加引用

添加对 CoreCross 和 MvvmCross - PCL 版本的引用

为新项目添加对可移植库的引用

  • Cirrious.CrossCore.dll
    • 核心接口和概念,包括跟踪、IoC 和插件管理
  • Cirrious.MvvmCross.dll
    • Mvvm 类 - 包括视图和 ViewModel 的基类
  • Cirrious.MvvmCross.Plugins.Json.dll
    • 添加了一个 PCL Newtonsoft.JSON.Net 实现 - 我们的 Windows Phone UI 应用程序将使用它在页面之间导航

通常,这些文件会位于类似 {SolutionRoot}/Libs/Mvx/Portable/ 的文件夹路径中。

添加对 CoreCross 和 MvvmCross - Windows Phone 特定版本的引用

为 Windows Phone 特定库向新项目添加引用

  • Cirrious.CrossCore.WindowsPhone.dll
  • Cirrious.MvvmCross.WindowsPhone.dll

这些每个都通过 WP 特定的添加来扩展其 PCL 对等项的功能。

通常,这些可以在类似 {SolutionRoot}/Libs/Mvx/WindowsPhone/ 的文件夹路径中找到。

另外,在同一个文件夹中,您还需要添加

添加对 TipCalc.Core.csproj 的引用

添加对您的 TipCalc.Core 项目的引用 - 这是我们在上一步创建的项目,其中包含

  • 您的 Calculation 服务
  • 您的 TipViewModel
  • 您的 App 连接

添加 Setup 类

正如我们在 Android 和 iOS 构建过程中所说,每个 MvvmCross UI 项目都需要一个 Setup 类

这个类位于我们 UI 项目的根命名空间(文件夹)中,负责初始化 `MvvmCross` 框架和你的应用程序,包括

  • 控制反转 (IoC) 系统
  • MvvmCross 数据绑定
  • 您的 App 及其 ViewModel 集合
  • 您的 UI 项目及其 View 集合

此功能大部分会自动为您提供。在您的 Windows Phone UI 项目中,您只需要提供

  • 您的 App - 您与业务逻辑和 ViewModel 内容的连接
  • 一些初始化
    • 用于 Json.Net 插件
    • 用于导航机制

对于 TipCalcSetup.cs 中需要的所有内容如下

using Cirrious.MvvmCross.ViewModels;
using Microsoft.Phone.Controls;
using Cirrious.MvvmCross.WindowsPhone.Platform;

namespace TipCalc.UI.WP
{
    public class Setup : MvxPhoneSetup
    {
        public Setup(PhoneApplicationFrame rootFrame)
            : base(rootFrame)
        {
        }

        protected override IMvxApplication CreateApp()
        {
            return new Core.App();
        }

        protected override IMvxNavigationSerializer CreateNavigationSerializer()
        {
            Cirrious.MvvmCross.Plugins.Json.PluginLoader.Instance.EnsureLoaded(true);
            return new MvxJsonNavigationSerializer();
        }
    }
}

修改 App.xaml.cs 以使用 Setup

您的 App.xaml.cs 提供了 Windows Phone 的“主应用程序”对象 - 该对象拥有用户界面,并在应用程序生命周期中的一些关键事件期间接收来自操作系统的某些回调。

要为 MvvmCross 修改此 App.xaml.cs,我们需要

  • 修改构造函数,以便它创建并启动“Setup

    var setup = new Setup(RootFrame);
    setup.Initialize();
  • 添加一个 private 字段 - 只是一个布尔标志,我们将在完成一次导航后设置它

    private bool _hasDoneFirstNavigation = false;
  • 修改 Application_Launching 回调,以便我们可以拦截第一次导航,可以取消它,并将初始导航委托给 IMvxAppStart

    // Code to execute when the application is launching (eg, from Start)
    // This code will not execute when the application is reactivated
    private void Application_Launching(object sender, LaunchingEventArgs e)
    {
        RootFrame.Navigating += (navigatingSender, navigatingArgs) =>
        {
            if (_hasDoneFirstNavigation)
                return;
    
            navigatingArgs.Cancel = true;
            _hasDoneFirstNavigation = true;
            var appStart = Mvx.Resolve<IMvxAppStart>();
            RootFrame.Dispatcher.BeginInvoke(() =>_appStart.Start());
        };
    }

完成此操作后,您的代码可能如下所示

using System.Windows;
using System.Windows.Navigation;
using Cirrious.CrossCore.IoC;
using Cirrious.MvvmCross.ViewModels;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;

namespace TipCalc.UI.WP
{
    public partial class App : Application
    {
        /// <summary>
        /// Provides easy access to the root frame of the Phone Application.
        /// </summary>
        /// <returns>The root frame of the Phone Application.</returns>
        public PhoneApplicationFrame RootFrame { get; private set; }

        /// <summary>
        /// Constructor for the Application object.
        /// </summary>
        public App()
        {
            // Global handler for uncaught exceptions.
            UnhandledException += Application_UnhandledException;

            // Standard Silverlight initialization
            InitializeComponent();

            // Phone-specific initialization
            InitializePhoneApplication();

            // Show graphics profiling information while debugging.
            if (System.Diagnostics.Debugger.IsAttached)
            {
                // Display the current frame rate counters.
                Application.Current.Host.Settings.EnableFrameRateCounter = true;

                // Show the areas of the app that are being redrawn in each frame.
                //Application.Current.Host.Settings.EnableRedrawRegions = true;

                // Enable non-production analysis visualization mode,
                // which shows areas of a page that are handed off to GPU with a colored overlay.
                //Application.Current.Host.Settings.EnableCacheVisualization = true;

                // Disable the application idle detection by setting the
                // UserIdleDetectionMode property of the
                // application's PhoneApplicationService object to Disabled.
                // Caution:- Use this under debug mode only.
                // Application that disables user idle detection will continue to run
                // and consume battery power when the user is not using the phone.
                PhoneApplicationService.Current.UserIdleDetectionMode = IdleDetectionMode.Disabled;
            }

            var setup = new Setup(RootFrame);
            setup.Initialize();
        }

        private bool _hasDoneFirstNavigation = false;

        // Code to execute when the application is launching (eg, from Start)
        // This code will not execute when the application is reactivated
        private void Application_Launching(object sender, LaunchingEventArgs e)
        {
            RootFrame.Navigating += (navigatingSender, navigatingArgs) =>
            {
                if (_hasDoneFirstNavigation)
                    return;

                navigatingArgs.Cancel = true;
                _hasDoneFirstNavigation = true;
                var appStart = Mvx.Resolve<IMvxAppStart>();
                RootFrame.Dispatcher.BeginInvoke(() => appStart.Start());
            };
        }

        // Code to execute when the application is activated (brought to foreground)
        // This code will not execute when the application is first launched
        private void Application_Activated(object sender, ActivatedEventArgs e)
        {
        }

        // Code to execute when the application is deactivated (sent to background)
        // This code will not execute when the application is closing
        private void Application_Deactivated(object sender, DeactivatedEventArgs e)
        {
        }

        // Code to execute when the application is closing (eg, user hit Back)
        // This code will not execute when the application is deactivated
        private void Application_Closing(object sender, ClosingEventArgs e)
        {
        }

        // Code to execute if a navigation fails
        private void RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
        {
            if (System.Diagnostics.Debugger.IsAttached)
            {
                // A navigation has failed; break into the debugger
                System.Diagnostics.Debugger.Break();
            }
        }

        // Code to execute on Unhandled Exceptions
        private void Application_UnhandledException
        	(object sender, ApplicationUnhandledExceptionEventArgs e)
        {
            if (System.Diagnostics.Debugger.IsAttached)
            {
                // An unhandled exception has occurred; break into the debugger
                System.Diagnostics.Debugger.Break();
            }
        }

        #region Phone application initialization

        // Avoid double-initialization
        private bool phoneApplicationInitialized = false;

        // Do not add any additional code to this method
        private void InitializePhoneApplication()
        {
            if (phoneApplicationInitialized)
                return;

            // Create the frame but don't set it as RootVisual yet; this allows the splash
            // screen to remain active until the application is ready to render.
            RootFrame = new PhoneApplicationFrame();
            RootFrame.Navigated += CompleteInitializePhoneApplication;

            // Handle navigation failures
            RootFrame.NavigationFailed += RootFrame_NavigationFailed;

            // Ensure we don't initialize again
            phoneApplicationInitialized = true;
        }

        // Do not add any additional code to this method
        private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
        {
            // Set the root visual to allow the application to render
            if (RootVisual != RootFrame)
                RootVisual = RootFrame;

            // Remove this handler since it is no longer needed
            RootFrame.Navigated -= CompleteInitializePhoneApplication;
        }

        #endregion
    }
}

添加您的 View

创建初始页面

创建一个 Views 文件夹。

在 Windows Phone 上,此文件夹称为 Views 非常重要 - MvvmCross 框架在 Windows Phone 上默认会查找此名称。

在此文件夹中,添加一个新的“Windows Phone Portrait Page”并将其命名为 TipView.xaml

这将生成

  • TipView.xaml
  • TipView.xaml.cs

将 TipView 变成 TipViewModel 的 MvvmCross 视图

打开 TipView.cs 文件。

要将 TipView PhonePage 更改为 MvvmCross 视图,请将其更改为继承自 MvxPhonePage

public partial class TipView : MvxPhonePage

要将 TipView 链接到 TipViewModel,请创建一个 public new TipViewModel ViewModel 属性 - 就像您在 Xamarin.Android Xamarin.iOS 中所做的那样

public new TipViewModel ViewModel
{
    get { return (TipViewModel) base.ViewModel; }
    set { base.ViewModel = value; }
}

总而言之,这看起来是这样的

using Cirrious.MvvmCross.WindowsPhone.Views;
using TipCalc.Core.ViewModels;

namespace TipCalc.UI.WP.Views
{
    public partial class TipView : MvxPhonePage
    {
        public new TipViewModel ViewModel
        {
            get { return (TipViewModel) base.ViewModel; }
            set { base.ViewModel = value; }
        }

        public TipView()
        {
            InitializeComponent();
        }
    }
}

编辑 XAML 布局

双击 XAML 文件。

这将在 Visual Studio 中打开 XAML 编辑器。

我在这里不会深入介绍如何使用 XAML 或进行 Windows 数据绑定。我假设大多数读者至少都有一些 XAML 背景。

为了使 XAML 继承与 MvxPhonePage 继承匹配,请将 XAML 文件的最外层根节点从

<phone:PhoneApplicationPage
    ... >
    <!-- content -->
</phone:PhoneApplicationPage>

to

<views:MvxPhonePage
    xmlns:views="clr-namespace:Cirrious.MvvmCross.WindowsPhone.Views;
    assembly=Cirrious.MvvmCross.WindowsPhone"
    ... >
    <!-- content -->
</views:MvxPhonePage>

然后,要为我们的账单计算器添加 XAML 用户界面,我们将编辑 ContentPanel 以包含

  • 一个 StackPanel 容器,我们将在其中添加
    • 一些 TextBlock 静态文本
    • 用于 SubTotal 的绑定 TextBox
    • 用于 Generosity 的绑定 Slider
    • 用于 Tip 的绑定 TextBlock

这将生成如下 XAML

    <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        <StackPanel>
            <TextBlock
                Text="SubTotal"
                Style="{StaticResource PhoneTextSubtleStyle}"
                />
            <TextBox
                Text="{Binding SubTotal, Mode=TwoWay}"
                />

            <TextBlock
                Text="Generosity"
                Style="{StaticResource PhoneTextSubtleStyle}"
                />
            <Slider
                Value="{Binding Generosity,Mode=TwoWay}"
                SmallChange="1"
                LargeChange="10"
                Minimum="0"
                Maximum="100" />

            <TextBlock
                Text="Tip"
                Style="{StaticResource PhoneTextSubtleStyle}"
                />
            <TextBlock
                Text="{Binding Tip}"
                />
        </StackPanel>
    </Grid>

注意:在 XAML 中,OneWay 绑定通常是默认值。要提供 TwoWay 绑定,我们显式地将 Mode 添加到我们的绑定表达式中:例如 Value="{Binding Generosity,Mode=TwoWay}"

在设计器中,这将显示为

Designer

WP UI 完成!

此时,您应该能够运行您的应用程序。

当它启动时...您应该看到

v1

这似乎工作得很完美,尽管您可能会注意到,如果您在 SubTotal TextBox 中编辑值,则其余的显示不会正确更新。

这是一个视图方面的问题 - 这是一个 UI 问题。所以我们可以在 Windows Phone UI 代码 - 在这个视图中解决它。例如,要在此处解决此问题,您可以从 Nuget 添加“Coding4Fun”工具包,然后使用他们的 UpdateSourceOnChange 附加属性来解决此问题

 coding4fun:TextBinding.UpdateSourceOnChange="True"

继续...

我们还可以做更多的事情来让这个用户界面更美观,让应用更丰富……但对于这第一个应用程序,我们暂时就到这里。

让我们继续进行更多的 Windows!

文章

历史 

  • 2013 年 3 月 22 日 - 首次提交  
© . All rights reserved.