MvvmCross TipCalc - 第 4 步:创建 Windows Phone UI
TipCalc 教程 MvvmCross v3 - Hot Tuna 的第 4 步。
介绍
本文是 MvvmCross
v3 - Hot Tuna! 的 TipCalc
教程的第 4 步!
目前为止的故事...
我们开始的目标是创建一个应用程序,以帮助计算在餐厅应该留下多少小费。
我们计划基于此概念创建一个 UI

为了实现这一点,我们构建了一个“Core
”可移植类库项目,其中包含
- 我们的“业务逻辑” - `ICalculation`
- 我们的 `ViewModel` - `TipViewModel`
- 我们的 App,它包含了应用的连接逻辑,包括启动指令
然后我们为 Xamarin.Android
添加了第一个用户界面

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

对于我们下一个项目,让我们转向 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.xml 和 WMAppManifest.xml “配置文件”的“Properties”文件夹
- 定义此应用程序默认页面的 MainPage.Xaml 和 MainPage.Xaml.cs 文件
- 一些图标
删除 MainPage.xaml
没有人真正需要 MainPage
。 " 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 应用程序将使用它在页面之间导航
- 添加了一个 PCL
通常,这些文件会位于类似 {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 插件
- 用于导航机制
对于 TipCalc
,Setup.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}"
。
在设计器中,这将显示为

WP UI 完成!
此时,您应该能够运行您的应用程序。
当它启动时...您应该看到

这似乎工作得很完美,尽管您可能会注意到,如果您在 SubTotal TextBox
中编辑值,则其余的显示不会正确更新。
这是一个视图方面的问题 - 这是一个 UI 问题。所以我们可以在 Windows Phone UI 代码 - 在这个视图中解决它。例如,要在此处解决此问题,您可以从 Nuget 添加“Coding4Fun”工具包,然后使用他们的 UpdateSourceOnChange
附加属性来解决此问题
coding4fun:TextBinding.UpdateSourceOnChange="True"
继续...
我们还可以做更多的事情来让这个用户界面更美观,让应用更丰富……但对于这第一个应用程序,我们暂时就到这里。
让我们继续进行更多的 Windows!
文章
- MvvmCross TipCalc - 步骤 1:创建核心可移植应用程序
- MvvmCross TipCalc - 第 2 步:创建 Android UI
- MvvmCross TipCalc - 第 3 步:创建 iOS UI
- MvvmCross TipCalc - 第 4 步:创建 Windows Phone UI
- MvvmCross TipCalc - 第 5 步:创建 Windows Store UI
- MvvmCross TipCalc - 步骤 6:创建 WPF UI
- MvvmCross TipCalc - 回顾
历史
- 2013 年 3 月 22 日 - 首次提交