Revitalize Tipcalc tutorial (#2835)
* Fix TipCalc order of documents * Update TipCalc intro and Core files * Update TipCalc Android & iOS files * Removed WindowsCommon tutorial for TipCalc * Added Xamarin.Forms version of TipCalc and bump screenshots to more recent layouts * WIP UWP and WPF docs for TipCalc * TipCalc Docs: Update UWP and small addition to iOS tutorial * Update TipCalc WPF tutorial * TipCalc tutorial: Update final articles * Rename docs for TipCalc
|
@ -1,45 +1,18 @@
|
|||
---
|
||||
layout: documentation
|
||||
title: A note about Views and ViewModels
|
||||
title: Extra - Views and ViewModels
|
||||
category: Tutorials
|
||||
order: 10
|
||||
order: 9
|
||||
---
|
||||
This tutorial used MvvmCross's support for naming conventions to associate the View with the ViewModel. With a view named `TipView`, MvvmCross can locate it's ViewModel as long as it's named `TipViewModel`. The base classes we used for views (e.g. `MvxViewController`) have `MvxViewModel` properties named `ViewModel`.
|
||||
|
||||
We can however link Views with ViewModels that have different names and have the Views's `ViewModel` property and have the `ViewModel` property be typed with the actual ViewModel's type rather than the `MvxViewModel` base class. There are actually two approaches to this...
|
||||
## View <-> ViewModel
|
||||
|
||||
During this tutorial we have used generic parameters to link our Views / ViewModels. This is the recommended approach and it provides you with many advantages.
|
||||
|
||||
## Generic base classes
|
||||
There is an alternative however. If you don't use generic parameters, then MvvmCross will make use of some built in "Convention over Configuration" to figure out how to link classes.
|
||||
|
||||
Instead of using the normal view base classes, there are generic versions available for use. For example, we could have used `MvxViewController<TipViewModel>` instead of `MvxViewController`. Unfortunately on Windows you can't use generic types within XAML. You therefore need an extra class such as this:
|
||||
MvvmCross can locate a ViewModel for a View as long as both members have the same root and suffixes `View` & `ViewModel`. In our case, MvvmCross will look for a `TipViewModel` for `TipView`.
|
||||
|
||||
```c#
|
||||
public class TipViewBase : MvxWindowsPage<TipViewModel>
|
||||
{
|
||||
}
|
||||
```
|
||||
The `TipView` class can then derive from this class and the following XAML can be used:
|
||||
|
||||
```xml
|
||||
<views:TipViewBase
|
||||
xmlns:views="using:MvvmCross.WindowsUWP.Views"
|
||||
...
|
||||
</views:TipViewBase>
|
||||
```
|
||||
|
||||
## Explicit ViewModel properties
|
||||
|
||||
The `ViewModel` property of a View can be explicitly defined. In the case of `TipView`, you would use the following code:
|
||||
|
||||
```c#
|
||||
public new TipViewModel ViewModel
|
||||
{
|
||||
get {
|
||||
return (TipViewModel)base.ViewModel;
|
||||
}
|
||||
set {
|
||||
base.ViewModel = value;
|
||||
}
|
||||
}
|
||||
```
|
||||
More notes on this docs to be added! :)
|
||||
|
||||
[Next!](https://www.mvvmcross.com/documentation/tutorials/tipcalc/the-tip-calc-navigation)
|
|
@ -1,637 +0,0 @@
|
|||
---
|
||||
layout: documentation
|
||||
title: A Universal Windows App UI Project
|
||||
category: Tutorials
|
||||
order: 7
|
||||
---
|
||||
We started with the goal of creating an app to help calculate what tip to leave in a restaurant
|
||||
|
||||
We had a plan to produce a UI based on this concept:
|
||||
|
||||
![Sketch](../../assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
|
||||
To satisfy this we built a 'Core' Portable Class Library project which contained:
|
||||
|
||||
* our 'business logic' - `ICalculation`
|
||||
* our ViewModel - `TipViewModel`
|
||||
* our `App` which contains the application wiring, including the start instructions.
|
||||
|
||||
We then added User Interfaces for Xamarin.Android, Xamarin.iOS and Windows UWP.
|
||||
|
||||
While UWP is the recommended approach for Windows and Windows Mobile development, you can also target Windows and Windows Phone using Windows 8.1 Universal Windows Apps. Feel free to skip this section if you don't need to work with Windows 8.1 Universal Windows Apps.
|
||||
|
||||
A Universal Windows App is actually a set of three projects. A Windows Phone 8.1 WinRT project, A Windows 8.1 project and a Shared project. Unlike normal projects, the Shared project does not build to an assembly. It's actually a set of files which are accessible within the Windows Phone and Windows projects as if they existed within those projects. If you are familiar with file linking, it's pretty much the same thing with an improved UI.
|
||||
|
||||
You could follow all the steps below creating two separate Windows 8.1 and Windows Phone 8.1 RT projects but the code which gets placed in the Universal Shared project gets placed in both the Windows 8.1 and the Windows Phone 8.1 RT projects instead.
|
||||
|
||||
Obviously, to work with Windows and Windows Phone, we will need to switch back to working on the PC with Visual Studio.
|
||||
|
||||
## Create the new Universal Windows Apps Projects
|
||||
|
||||
Add a new project to your solution - a 'Blank App (Universal Apps)' application with name `TipCalc.UI.WindowsCommon`
|
||||
|
||||
Note that three projects have been added to your solution. TipCalc.UI.WindowsCommon.Windows, TipCalc.UI.WindowsCommon.WindowsPhone and TipCalc.UI.WindowsCommon.Shared.
|
||||
|
||||
Within the WindowsCommon.Windows and WindowsCommon.WindowsPhone projects, you'll find the normal Universal Windows App UI application constructs:
|
||||
|
||||
* the 'Assets' folder, which contains the default images
|
||||
* the MainPage.Xaml and MainPage.Xaml.cs files that define the default Page for this app
|
||||
* the 'Package.appxmanifest' configuration file
|
||||
* the debug private key for your development (WindowsCommon.Windows project only)
|
||||
|
||||
Within the Shared project, you'll find the normal Universal Windows App shared application constructs:
|
||||
|
||||
* the App.Xaml 'application' object
|
||||
|
||||
## Delete MainPage.xaml
|
||||
|
||||
No-one really needs a `MainPage` :)
|
||||
|
||||
This needs to be removed from both the WindowsCommon.Windows and WindowsCommon.WindowsPhone projects.
|
||||
|
||||
## Install MvvmCross
|
||||
|
||||
In the Package Manager Console, enter...
|
||||
|
||||
Install-Package MvvmCross.Core
|
||||
|
||||
...for both the WindowsCommon.Windows and WindowsCommon.WindowsPhone projects.
|
||||
|
||||
## Add a reference to TipCalc.Core.csproj
|
||||
|
||||
Add a reference to your `TipCalc.Core` project - the project we created in the last step which included:
|
||||
|
||||
* your `Calculation` service,
|
||||
* your `TipViewModel`
|
||||
* your `App` wiring.
|
||||
|
||||
This needs to be added to both the WindowsCommon.Windows and WindowsCommon.WindowsPhone projects
|
||||
|
||||
## Add a Setup class
|
||||
|
||||
Just as we said during the construction of the other UI projects, *Every MvvmCross UI project requires a `Setup` class*
|
||||
|
||||
This class sits in the root namespace (folder) of our UI project and performs the initialization of the MvvmCross framework and your application, including:
|
||||
|
||||
* the Inversion of Control (IoC) system
|
||||
* the MvvmCross data-binding
|
||||
* your `App` and its collection of `ViewModel`s
|
||||
* your UI project and its collection of `View`s
|
||||
|
||||
Most of this functionality is provided for you automatically. Within your WindowsCommon.Shared UI project all you have to supply are:
|
||||
|
||||
- your `App` - your link to the business logic and `ViewModel` content
|
||||
|
||||
For `TipCalc` here's all that is needed in Setup.cs:
|
||||
|
||||
```cs
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using MvvmCross.Core.ViewModels;
|
||||
using MvvmCross.WindowsCommon.Platform;
|
||||
|
||||
namespace TipCalc.UI.WindowsCommon
|
||||
{
|
||||
public class Setup : MvxWindowsSetup
|
||||
{
|
||||
public Setup(Frame rootFrame) : base(rootFrame)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IMvxApplication CreateApp()
|
||||
{
|
||||
return new Core.App();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Modify the App.xaml.cs to use Setup
|
||||
|
||||
Your `App.xaml.cs` provides the Universal Windows App 'main application' object - an object which owns the User Interface and receives some callbacks from the operating system during some key events in your application's lifecycle.
|
||||
|
||||
To modify this `App.xaml.cs` for MvvmCross, we need to:
|
||||
|
||||
* modify the `OnLaunched` callback
|
||||
|
||||
* remove these lines
|
||||
|
||||
```cs
|
||||
// When the navigation stack isn't restored navigate to the first page,
|
||||
// configuring the new page by passing required information as a navigation
|
||||
// parameter
|
||||
if (!rootFrame.Navigate(typeof(MainPage), e.Arguments))
|
||||
{
|
||||
throw new Exception("Failed to create initial page");
|
||||
}
|
||||
```
|
||||
|
||||
* add these lines to allow it to create `Setup`, and to then initiate the `IMvxAppStart` `Start` navigation
|
||||
|
||||
```cs
|
||||
var setup = new Setup(rootFrame);
|
||||
setup.Initialize();
|
||||
|
||||
var start = Mvx.Resolve<IMvxAppStart>();
|
||||
start.Start();
|
||||
```
|
||||
|
||||
To do this, you will need to add these `using` lines:
|
||||
|
||||
```cs
|
||||
using MvvmCross.Core.ViewModels;
|
||||
using MvvmCross.Platform;
|
||||
```
|
||||
|
||||
## Add your View
|
||||
|
||||
### Create an initial Page for the WindowsCommon.Windows project
|
||||
|
||||
Create a Views folder in the WindowsCommon.Windows project
|
||||
|
||||
Within this folder, add a new 'Basic Page' and call it `TipView.xaml`
|
||||
|
||||
You will be asked if you want to add the missing 'Common' files automatically in order to support this 'Basic Page' - answer **Yes**
|
||||
|
||||
The page will generate:
|
||||
|
||||
* TipView.xaml
|
||||
* TipView.xaml.cs
|
||||
|
||||
A Common folder will be added containing:
|
||||
|
||||
* NavigationHelper.cs
|
||||
* ObservableDictionary.cs
|
||||
* RelayCommand.cs
|
||||
* SuspensionManager.cs
|
||||
|
||||
### Convert TipView into an MvvmCross base view
|
||||
|
||||
Change `TipView` so that it inherits from `MvxWindowsPage`
|
||||
|
||||
Change:
|
||||
|
||||
```cs
|
||||
public class TipView : Page
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```cs
|
||||
public class TipView : MvxWindowsPage
|
||||
```
|
||||
|
||||
This requires the addition of:
|
||||
|
||||
```cs
|
||||
using MvvmCross.WindowsCommon.Views;
|
||||
```
|
||||
|
||||
### Persuade TipView to cooperate more reasonably with the `MvxWindowsPage` base class
|
||||
|
||||
Change the `OnNavigatedTo` and `OnNavigatedFrom` methods so that they call their base class implementations:
|
||||
|
||||
```cs
|
||||
base.OnNavigatedTo(e);
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```cs
|
||||
base.OnNavigatedFrom(e);
|
||||
```
|
||||
|
||||
Altogether this looks like:
|
||||
|
||||
```cs
|
||||
using TipCalc.UI.WindowsCommon.Common;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using MvvmCross.WindowsCommon.Views;
|
||||
|
||||
// The Basic Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234237
|
||||
|
||||
namespace TipCalc.UI.WindowsCommon.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// A basic page that provides characteristics common to most applications.
|
||||
/// </summary>
|
||||
public sealed partial class TipView : MvxWindowsPage
|
||||
{
|
||||
|
||||
private NavigationHelper navigationHelper;
|
||||
private ObservableDictionary defaultViewModel = new ObservableDictionary();
|
||||
|
||||
/// <summary>
|
||||
/// This can be changed to a strongly typed view model.
|
||||
/// </summary>
|
||||
public ObservableDictionary DefaultViewModel
|
||||
{
|
||||
get { return this.defaultViewModel; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// NavigationHelper is used on each page to aid in navigation and
|
||||
/// process lifetime management
|
||||
/// </summary>
|
||||
public NavigationHelper NavigationHelper
|
||||
{
|
||||
get { return this.navigationHelper; }
|
||||
}
|
||||
|
||||
|
||||
public TipView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
this.navigationHelper = new NavigationHelper(this);
|
||||
this.navigationHelper.LoadState += navigationHelper_LoadState;
|
||||
this.navigationHelper.SaveState += navigationHelper_SaveState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the page with content passed during navigation. Any saved state is also
|
||||
/// provided when recreating a page from a prior session.
|
||||
/// </summary>
|
||||
/// <param name="sender">
|
||||
/// The source of the event; typically <see cref="Common.NavigationHelper"/>
|
||||
/// </param>
|
||||
/// <param name="e">Event data that provides both the navigation parameter passed to
|
||||
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested and
|
||||
/// a dictionary of state preserved by this page during an earlier
|
||||
/// session. The state will be null the first time a page is visited.</param>
|
||||
private void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preserves state associated with this page in case the application is suspended or the
|
||||
/// page is discarded from the navigation cache. Values must conform to the serialization
|
||||
/// requirements of <see cref="Common.SuspensionManager.SessionState"/>.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event; typically <see cref="Common.NavigationHelper"/></param>
|
||||
/// <param name="e">Event data that provides an empty dictionary to be populated with
|
||||
/// serializable state.</param>
|
||||
private void navigationHelper_SaveState(object sender, SaveStateEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
#region NavigationHelper registration
|
||||
|
||||
/// The methods provided in this section are simply used to allow
|
||||
/// NavigationHelper to respond to the page's navigation methods.
|
||||
///
|
||||
/// Page specific logic should be placed in event handlers for the
|
||||
/// <see cref="Common.NavigationHelper.LoadState"/>
|
||||
/// and <see cref="Common.NavigationHelper.SaveState"/>.
|
||||
/// The navigation parameter is available in the LoadState method
|
||||
/// in addition to page state preserved during an earlier session.
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
navigationHelper.OnNavigatedTo(e);
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedFrom(e);
|
||||
navigationHelper.OnNavigatedFrom(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Edit the XAML layout
|
||||
|
||||
Double click on the XAML file
|
||||
|
||||
This will open the XAML editor within Visual Studio.
|
||||
|
||||
I won't go into much depth at all here about how to use the XAML or do the Windows data-binding. I'm assuming most readers are already coming from at least a little XAML background.
|
||||
|
||||
To make the XAML inheritance match the `MvxWindowsPage` inheritance, change the outer root node of the Xaml file from:
|
||||
|
||||
```xml
|
||||
<Page
|
||||
... >
|
||||
<!-- content -->
|
||||
</Page>
|
||||
```
|
||||
to:
|
||||
|
||||
```xml
|
||||
<views:MvxWindowsPage
|
||||
xmlns:views="using:MvvmCross.WindowsCommon.Views"
|
||||
... >
|
||||
<!-- content -->
|
||||
</views:MvxWindowsPage>
|
||||
```
|
||||
|
||||
To add the XAML user interface for our tip calculator, we will add a `ContentPanel` Grid just above the final `</Grid>` in the existing XAML. This will contain:
|
||||
|
||||
* a `StackPanel` container, into which we add:
|
||||
* some `TextBlock` static text
|
||||
* a bound `TextBox` for the `SubTotal`
|
||||
* a bound `Slider` for the `Generosity`
|
||||
* a bound `TextBlock` for the `Tip`
|
||||
|
||||
This will produce XAML like:
|
||||
|
||||
```xml
|
||||
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="SubTotal" />
|
||||
<TextBox
|
||||
Text="{Binding SubTotal, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock
|
||||
Text="Generosity" />
|
||||
<Slider
|
||||
Value="{Binding Generosity,Mode=TwoWay}"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
Minimum="0"
|
||||
Maximum="100" />
|
||||
<TextBlock
|
||||
Text="Tip" />
|
||||
<TextBlock
|
||||
Text="{Binding Tip}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
```
|
||||
|
||||
**Note** that in XAML, `OneWay` binding is generally the default. To provide TwoWay binding we explicitly add `Mode` to our binding expressions: e.g. `Value="{Binding Generosity, Mode=TwoWay}"`
|
||||
|
||||
**Note** the binding for the TextBox uses `UpdateSourceTrigger=PropertyChanged` so that the `SubTotal` property of `TipViewModel` is updated immediately rather than when the TextBox loses focus.
|
||||
|
||||
In the designer, this will look like:
|
||||
|
||||
![Designer](../../assets/img/tutorials/tipcalc/TipCalc_WindowsCommonWindows_Designer.png)
|
||||
|
||||
### Create an initial Page for the WindowsCommon.WindowsPhone project
|
||||
|
||||
Create a Views folder in the WindowsCommon.WindowsPhone project
|
||||
|
||||
Within this folder, add a new 'Basic Page' and call it `TipView.xaml`
|
||||
|
||||
You will be asked if you want to add the missing 'Common' files automatically in order to support this 'Basic Page' - answer **Yes**
|
||||
|
||||
The page will generate:
|
||||
|
||||
* TipView.xaml
|
||||
* TipView.xaml.cs
|
||||
|
||||
A Common folder will be added containing:
|
||||
|
||||
* NavigationHelper.cs
|
||||
* ObservableDictionary.cs
|
||||
* RelayCommand.cs
|
||||
* SuspensionManager.cs
|
||||
|
||||
### Convert TipView into an MvvmCross base view
|
||||
|
||||
Change `TipView` so that it inherits from `MvxWindowsPage`
|
||||
|
||||
Change:
|
||||
|
||||
```cs
|
||||
public class TipView : Page
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```cs
|
||||
public class TipView : MvxWindowsPage
|
||||
```
|
||||
|
||||
This requires the addition of:
|
||||
|
||||
```cs
|
||||
using MvvmCross.WindowsCommon.Views;
|
||||
```
|
||||
|
||||
### Persuade TipCalc to cooperate more reasonably with the `MvxWindowsPage` base class
|
||||
|
||||
Change the `OnNavigatedTo` and `OnNavigatedFrom` methods so that they call their base class implementations:
|
||||
|
||||
```cs
|
||||
base.OnNavigatedTo(e);
|
||||
```
|
||||
|
||||
and
|
||||
|
||||
```cs
|
||||
base.OnNavigatedFrom(e);
|
||||
```
|
||||
|
||||
Altogether this looks like:
|
||||
|
||||
```cs
|
||||
using TipCalc.UI.WindowsCommon.Common;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using MvvmCross.WindowsCommon.Views;
|
||||
|
||||
// The Basic Page item template is documented at http://go.microsoft.com/fwlink/?LinkID=390556
|
||||
|
||||
namespace TipCalc.UI.WindowsCommon.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class TipView : MvxWindowsPage
|
||||
{
|
||||
private NavigationHelper navigationHelper;
|
||||
private ObservableDictionary defaultViewModel = new ObservableDictionary();
|
||||
|
||||
public TipView()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
this.navigationHelper = new NavigationHelper(this);
|
||||
this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
|
||||
this.navigationHelper.SaveState += this.NavigationHelper_SaveState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
|
||||
/// </summary>
|
||||
public NavigationHelper NavigationHelper
|
||||
{
|
||||
get { return this.navigationHelper; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the view model for this <see cref="Page"/>.
|
||||
/// This can be changed to a strongly typed view model.
|
||||
/// </summary>
|
||||
public ObservableDictionary DefaultViewModel
|
||||
{
|
||||
get { return this.defaultViewModel; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the page with content passed during navigation. Any saved state is also
|
||||
/// provided when recreating a page from a prior session.
|
||||
/// </summary>
|
||||
/// <param name="sender">
|
||||
/// The source of the event; typically <see cref="NavigationHelper"/>
|
||||
/// </param>
|
||||
/// <param name="e">Event data that provides both the navigation parameter passed to
|
||||
/// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested and
|
||||
/// a dictionary of state preserved by this page during an earlier
|
||||
/// session. The state will be null the first time a page is visited.</param>
|
||||
private void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preserves state associated with this page in case the application is suspended or the
|
||||
/// page is discarded from the navigation cache. Values must conform to the serialization
|
||||
/// requirements of <see cref="SuspensionManager.SessionState"/>.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
|
||||
/// <param name="e">Event data that provides an empty dictionary to be populated with
|
||||
/// serializable state.</param>
|
||||
private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
|
||||
{
|
||||
}
|
||||
|
||||
#region NavigationHelper registration
|
||||
|
||||
/// <summary>
|
||||
/// The methods provided in this section are simply used to allow
|
||||
/// NavigationHelper to respond to the page's navigation methods.
|
||||
/// <para>
|
||||
/// Page specific logic should be placed in event handlers for the
|
||||
/// <see cref="NavigationHelper.LoadState"/>
|
||||
/// and <see cref="NavigationHelper.SaveState"/>.
|
||||
/// The navigation parameter is available in the LoadState method
|
||||
/// in addition to page state preserved during an earlier session.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="e">Provides data for navigation methods and event
|
||||
/// handlers that cannot cancel the navigation request.</param>
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
this.navigationHelper.OnNavigatedTo(e);
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
base.OnNavigatedFrom(e);
|
||||
this.navigationHelper.OnNavigatedFrom(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Edit the XAML layout
|
||||
|
||||
Double click on the XAML file
|
||||
|
||||
This will open the XAML editor within Visual Studio.
|
||||
|
||||
Again, I won't go into much depth at all here about how to use the XAML or do the Windows data-binding. I'm assuming most readers are already coming from at least a little XAML background.
|
||||
|
||||
To make the XAML inheritance match the `MvxWindowsPage` inheritance, change the outer root node of the Xaml file from:
|
||||
|
||||
```xml
|
||||
<Page
|
||||
... >
|
||||
<!-- content -->
|
||||
</Page>
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```xml
|
||||
<views:MvxWindowsPage
|
||||
xmlns:views="using:MvvmCross.WindowsCommon.Views"
|
||||
... >
|
||||
<!-- content -->
|
||||
</views:MvxWindowsPage>
|
||||
```
|
||||
|
||||
To add the XAML user interface for our tip calculator, we will add a `ContentPanel` Grid in place of the `ContentRoot` Grid in the existing XAML.
|
||||
|
||||
This `Content Panel` will include exactly the same XAML as we added to the WindowsCommon.WindowsPhone project except for the margins:
|
||||
|
||||
* a `StackPanel` container, into which we add:
|
||||
* some `TextBlock` static text
|
||||
* a bound `TextBox` for the `SubTotal`
|
||||
* a bound `Slider` for the `Generosity`
|
||||
* a bound `TextBlock` for the `Tip`
|
||||
|
||||
This will produce XAML like:
|
||||
|
||||
```xml
|
||||
<Grid Grid.Row="1" x:Name="ContentRoot" Margin="19,9.5,19,0">
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="SubTotal" />
|
||||
<TextBox
|
||||
Text="{Binding SubTotal, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock
|
||||
Text="Generosity" />
|
||||
<Slider
|
||||
Value="{Binding Generosity,Mode=TwoWay}"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
Minimum="0"
|
||||
Maximum="100" />
|
||||
<TextBlock
|
||||
Text="Tip" />
|
||||
<TextBlock
|
||||
Text="{Binding Tip}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
```
|
||||
|
||||
**Note** that in XAML, `OneWay` binding is generally the default. To provide TwoWay binding we explicitly add `Mode` to our binding expressions: e.g. `Value="{Binding Generosity, Mode=TwoWay}"`
|
||||
|
||||
|
||||
**Note** the binding for the TextBox uses `UpdateSourceTrigger=PropertyChanged` so that the `SubTotal` property of `TipViewModel` is updated immediately rather than when the TextBox loses focus.
|
||||
|
||||
In the designer, this will look like:
|
||||
|
||||
![Designer](../../assets/img/tutorials/tipcalc/TipCalc_WindowsCommonWindowsPhone_Designer.png)
|
||||
|
||||
### The navigation cache
|
||||
|
||||
Universal Windows Phone apps seem to differ from Silverlight Windows Phone apps in that the default page navigation cache mechanism has changed. While Silverlight Windows Phone apps have built-in support for caching pages in forward/backward navigation, universal Windows Phone apps do not and need the NavigationHelper class and setting the NavigationCacheMode property of a Page to "Required" to provide this. This tutorial already showed you how to make use of the NavigationHelper and it is recommended that you do this even if you plan not to cache a view.
|
||||
|
||||
If you do enable caching by setting the NavigationCacheMode property of a Page to "Required" and navigate backwards or forwards, the view is retrieved from the cache. This includes the ViewModel property of the view. While this doesn't create a problem when navigating backwards, it does when you navigate forward! If you already have cached a view with a particular state (loading from the init parameters into the viewmodel), that state is also retrieved from the cache and you'll end up with a view with an 'old' viewmodel state.
|
||||
|
||||
To counter this, you must set the viewmodel to null when navigating to a page:
|
||||
|
||||
```cs
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
if (e.NavigationMode == NavigationMode.New)
|
||||
ViewModel = null;
|
||||
|
||||
base.OnNavigatedTo(e);
|
||||
this.navigationHelper.OnNavigatedTo(e);
|
||||
}
|
||||
```
|
||||
|
||||
## The Universal Windows App is complete!
|
||||
|
||||
At this point you should be able to run your application.
|
||||
|
||||
When you run the WindowsCommon.Windows project... you should see:
|
||||
|
||||
![v1](../../assets/img/tutorials/tipcalc/TipCalc_WindowsCommonWindows_Emu.png)
|
||||
|
||||
When you run the WindowsCommon.WindowsPhone project... you should see:
|
||||
|
||||
![v1](../../assets/img/tutorials/tipcalc/TipCalc_WindowsCommonWindowsPhone_Emu.png)
|
||||
|
||||
## Moving on...
|
||||
|
||||
There's more we could do to make this User Interface nicer and to make the app richer... but for this first application, we will leave it here for now.
|
||||
|
||||
But there are other ways of building Windows apps...
|
|
@ -1,367 +1,253 @@
|
|||
---
|
||||
layout: documentation
|
||||
title: A Windows Universal App Platform Project
|
||||
title: TipCalc UWP Project
|
||||
category: Tutorials
|
||||
order: 6
|
||||
order: 5
|
||||
---
|
||||
We started with the goal of creating an app to help calculate what tip to leave in a restaurant
|
||||
|
||||
We started with the goal of creating an app to help calculate what tip to leave in a restaurant.
|
||||
|
||||
We had a plan to produce a UI based on this concept:
|
||||
|
||||
![TipCalc](../../assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
![TipCalc]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
|
||||
To satisfy this we built a 'Core' Portable Class Library project which contained:
|
||||
To satisfy this we built a 'Core' .NET Standard project which contained:
|
||||
|
||||
* our 'business logic' - `ICalculation`
|
||||
* our ViewModel - `TipViewModel`
|
||||
* our `App` which contains the application wiring, including the start instructions.
|
||||
- Our 'business logic' - `ICalculationService`
|
||||
- Our ViewModel - `TipViewModel`
|
||||
- Our `App` - which contains some bootstrapping code.
|
||||
|
||||
We then added User Interfaces for Xamarin.Android and Xamarin.iOS
|
||||
We even added User Interfaces for Xamarin.Android and Xamarin.iOS so far:
|
||||
|
||||
![Android](../../assets/img/tutorials/tipcalc/TipCalc_Android_Styled.png) ![iOS](../../assets/img/tutorials/tipcalc/TipCalc_Touch_Sim.png)
|
||||
![Android]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Android_Styled.png)
|
||||
![iOS]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Touch_Sim.png)
|
||||
|
||||
For our next project, let's look at Windows, specifically Universal Windows Platform (UWP) Apps which run on Windows 10 and Windows 10 Mobile.
|
||||
For our next project, let's look at Windows! More specifically, let's build a Universal Windows Platform (UWP) App.
|
||||
|
||||
To create a Windows UWP MvvmCross UI, you can use the Visual Studio project template wizards, but here we'll instead build up a new project 'from empty', just as we did for the Core and other UI projects.
|
||||
Same as we did with the _Core_ project, we will use a standard template to create the UWP project - although you can of course use a project template wizard.
|
||||
|
||||
## Create a new Windows UWP Project
|
||||
|
||||
Add a new project to your solution - a 'Blank App (Universal Windows)' application with name `TipCalc.UI.UWP`
|
||||
Add a new project to your solution - a 'Blank App (Universal Windows)' application with name `TipCalc.UWP`. Set it's minimum version to `Windows 10 Fall Creators Update` as that is the minimum version which supports .NET Standard 2.0.
|
||||
|
||||
Within this, you'll find the normal WindowsStore application constructs:
|
||||
Within this new project, you'll find the normal UWP application constructs:
|
||||
|
||||
* the 'Properties' folder with just the 'AssemblyInfo' file
|
||||
* the 'Assets' folder
|
||||
* the 'Common' folder
|
||||
* the App.Xaml 'application' object
|
||||
* the MainPage.Xaml and MainPage.Xaml.cs files that define the default Page for this app
|
||||
* the 'Package.appxmanifest' configuration file
|
||||
* the 'project.json'
|
||||
* the debug private key for your development
|
||||
- The `Properties` folder with just the `AssemblyInfo` file
|
||||
- The `Assets` folder
|
||||
- An `App.xaml` and the `App.xaml.cs` files. The 'application' class
|
||||
- A `MainPage.xaml` and `MainPage.xaml.cs` files that define the default Page for this app
|
||||
- The `Package.appxmanifest` configuration file
|
||||
- The debug private key for your development
|
||||
|
||||
## Delete MainPage.xaml
|
||||
|
||||
No-one really needs a `MainPage` :)
|
||||
No-one really needs a default `MainPage` :)
|
||||
|
||||
## Install MvvmCross
|
||||
|
||||
In the Package Manager Console, enter...
|
||||
Open the Nuget Package Manager and search for the package `MvvmCross`.
|
||||
|
||||
Install-Package MvvmCross.Core
|
||||
If you don't really enjoy the NuGet UI experience, then you can alternatively open the Package Manager Console, and type:
|
||||
|
||||
## Add a reference to TipCalc.Core.csproj
|
||||
Install-Package MvvmCross
|
||||
|
||||
Add a reference to your `TipCalc.Core` project - the project we created in the last step which included:
|
||||
## Add a reference to TipCalc.Core project
|
||||
|
||||
* your `Calculation` service,
|
||||
* your `TipViewModel`
|
||||
* your `App` wiring.
|
||||
Add a reference to your `TipCalc.Core` project - the project we created in the first step.
|
||||
|
||||
## Add a Setup class
|
||||
## Edit App.xaml.cs
|
||||
|
||||
Just as we said during the Android, iOS and WO construction *Every MvvmCross UI project requires a `Setup` class*
|
||||
The `App` class plays a very important role on UWP apps, as it provides a set of callback that the OS uses to inform you about events in your application's lifecycle. We won't dig further into it's responsibilities, but you may want to read about it in the official documentation for the platform.
|
||||
|
||||
This class sits in the root namespace (folder) of our UI project and performs the initialization of the MvvmCross framework and your application, including:
|
||||
|
||||
* the Inversion of Control (IoC) system
|
||||
* the MvvmCross data-binding
|
||||
* your `App` and its collection of `ViewModel`s
|
||||
* your UI project and its collection of `View`s
|
||||
|
||||
Most of this functionality is provided for you automatically. Within your Windows UWP UI project all you have to supply is:
|
||||
|
||||
- your `App` - your link to the business logic and `ViewModel` content.
|
||||
|
||||
For `TipCalc` here's all that is needed in Setup.cs:
|
||||
Open the `App.xaml.cs` and delete all the class content. Leave only the default constructor in place:
|
||||
|
||||
```c#
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using MvvmCross.Core.ViewModels;
|
||||
using MvvmCross.WindowsUWP.Platform;
|
||||
|
||||
namespace TipCalc.UI.UWP
|
||||
public sealed partial class App
|
||||
{
|
||||
public class Setup : MvxWindowsSetup
|
||||
{
|
||||
public Setup(Frame rootFrame) : base(rootFrame)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IMvxApplication CreateApp()
|
||||
{
|
||||
return new Core.App();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Modify the App.xaml.cs to use Setup
|
||||
|
||||
Your `App.xaml.cs` provides the Windows UWP 'main application' object - an object which owns the User Interface and receives some callbacks from the operating system during some key events in your application's lifecycle.
|
||||
|
||||
To modify this `App.xaml.cs` for MvvmCross, we need to:
|
||||
|
||||
* modify the `OnLaunched` callback
|
||||
|
||||
* replace this lines
|
||||
|
||||
```c#
|
||||
// When the navigation stack isn't restored navigate to the first page,
|
||||
// configuring the new page by passing required information as a navigation
|
||||
// parameter
|
||||
rootFrame.Navigate(typeof(MainPage), e.Arguments);
|
||||
```
|
||||
|
||||
* with these lines to allow it to create `Setup`, and to then initiate the `IMvxAppStart` `Start` navigation
|
||||
|
||||
```c#
|
||||
var setup = new Setup(rootFrame);
|
||||
setup.Initialize();
|
||||
|
||||
var start = Mvx.Resolve<IMvxAppStart>();
|
||||
start.Start();
|
||||
```
|
||||
|
||||
After you've done this your code might look like:
|
||||
|
||||
```c#
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Controls.Primitives;
|
||||
using Windows.UI.Xaml.Data;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using Windows.UI.Xaml.Media;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using MvvmCross.Core.ViewModels;
|
||||
using MvvmCross.Platform;
|
||||
|
||||
namespace TipCalc.UI.UWP
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides application-specific behavior to supplement the default Application class.
|
||||
/// </summary>
|
||||
sealed partial class App : Application
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
/// executed, and as such is the logical equivalent of main() or WinMain().
|
||||
/// </summary>
|
||||
public App()
|
||||
{
|
||||
Microsoft.ApplicationInsights.WindowsAppInitializer.InitializeAsync(
|
||||
Microsoft.ApplicationInsights.WindowsCollectors.Metadata |
|
||||
Microsoft.ApplicationInsights.WindowsCollectors.Session);
|
||||
this.InitializeComponent();
|
||||
this.Suspending += OnSuspending;
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the application is launched normally by the end user. Other entry points
|
||||
/// will be used such as when the application is launched to open a specific file.
|
||||
/// </summary>
|
||||
/// <param name="e">Details about the launch request and process.</param>
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs e)
|
||||
{
|
||||
|
||||
#if DEBUG
|
||||
if (System.Diagnostics.Debugger.IsAttached)
|
||||
{
|
||||
this.DebugSettings.EnableFrameRateCounter = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
Frame rootFrame = Window.Current.Content as Frame;
|
||||
|
||||
// Do not repeat app initialization when the Window already has content,
|
||||
// just ensure that the window is active
|
||||
if (rootFrame == null)
|
||||
{
|
||||
// Create a Frame to act as the navigation context and navigate to the first page
|
||||
rootFrame = new Frame();
|
||||
|
||||
rootFrame.NavigationFailed += OnNavigationFailed;
|
||||
|
||||
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
|
||||
{
|
||||
//TODO: Load state from previously suspended application
|
||||
}
|
||||
|
||||
// Place the frame in the current Window
|
||||
Window.Current.Content = rootFrame;
|
||||
}
|
||||
|
||||
if (rootFrame.Content == null)
|
||||
{
|
||||
//// When the navigation stack isn't restored navigate to the first page,
|
||||
//// configuring the new page by passing required information as a navigation
|
||||
//// parameter
|
||||
//rootFrame.Navigate(typeof(MainPage), e.Arguments);
|
||||
var setup = new Setup(rootFrame);
|
||||
setup.Initialize();
|
||||
|
||||
var start = Mvx.Resolve<IMvxAppStart>();
|
||||
start.Start();
|
||||
}
|
||||
// Ensure the current window is active
|
||||
Window.Current.Activate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when Navigation to a certain page fails
|
||||
/// </summary>
|
||||
/// <param name="sender">The Frame which failed navigation</param>
|
||||
/// <param name="e">Details about the navigation failure</param>
|
||||
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when application execution is being suspended. Application state is saved
|
||||
/// without knowing whether the application will be terminated or resumed with the contents
|
||||
/// of memory still intact.
|
||||
/// </summary>
|
||||
/// <param name="sender">The source of the suspend request.</param>
|
||||
/// <param name="e">Details about the suspend request.</param>
|
||||
private void OnSuspending(object sender, SuspendingEventArgs e)
|
||||
{
|
||||
var deferral = e.SuspendingOperation.GetDeferral();
|
||||
//TODO: Save application state and stop any background activity
|
||||
deferral.Complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
On the same file add another class, which will contain the necessary information to get MvvmCross up and running:
|
||||
|
||||
```c#
|
||||
public abstract class TipCalcApp : MvxApplication<MvxWindowsSetup<Core.App>, Core.App>
|
||||
{
|
||||
}
|
||||
```
|
||||
|
||||
We will use this class in Xaml. It extends `MvxApplication` and providers two generic parameters:
|
||||
- `MvxWindowsSetup<Core.App>` tells MvvmCross we want to use the default Setup class for UWP.
|
||||
- `Core.App` is our _Core_ application class.
|
||||
|
||||
Wrapping everything together, this is what the App.xaml.cs class should look like at the end:
|
||||
|
||||
```c#
|
||||
using MvvmCross.Platforms.Uap.Core;
|
||||
using MvvmCross.Platforms.Uap.Views;
|
||||
|
||||
namespace TipCalc.UWP
|
||||
{
|
||||
public sealed partial class App
|
||||
{
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TipCalcApp : MvxApplication<MvxWindowsSetup<Core.App>, Core.App>
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Edit App.xaml
|
||||
|
||||
Now it's time to edit the xaml part of our UWP `App` class. Open the file and replace all the content for this code:
|
||||
|
||||
```xml
|
||||
<local:TipCalcApp x:Class="TipCalc.UWP.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:TipCalc.UWP"
|
||||
RequestedTheme="Light">
|
||||
<Application.Resources>
|
||||
<x:String x:Key="WelcomeText">Hello World!</x:String>
|
||||
</Application.Resources>
|
||||
</local:TipCalcApp>
|
||||
```
|
||||
|
||||
What this code snippet does, is to set the App base class type to `TipCalcApp`.
|
||||
|
||||
### Some more details about the Setup class
|
||||
|
||||
Every MvvmCross UI project requires a `Setup` class, but if your app is fairly simple, like the TipCalc is, then you can safely use the default one, provided by the framework.
|
||||
|
||||
The `Setup` class is responsible for performing the initialization of the MvvmCross framework, including:
|
||||
|
||||
- The IoC Container and DI engine
|
||||
- The Data-Binding engine
|
||||
- The ViewModel / View lookups
|
||||
- The whole navigation system
|
||||
- Plugins
|
||||
|
||||
Finally, the `Setup` class is also responsible for initializing your `App` class.
|
||||
|
||||
Luckily for us, all this functionality is provided for you automatically, unless you want / need to use a custom `Setup` class (since it is an excellent place to register your own services / plugins, it is often the case).
|
||||
|
||||
## Add your View
|
||||
|
||||
### Create an initial Page
|
||||
|
||||
Create a Views folder
|
||||
Create a folder named `Views`.
|
||||
|
||||
Within this folder, add a new 'Blank Page' and call it `TipView.xaml`
|
||||
Within this folder, add a new Xaml 'Blank Page' and call it `TipView.xaml`.
|
||||
|
||||
You will be asked if you want to add the missing 'Common' files automatically in order to support this 'Basic Page' - answer **Yes**
|
||||
This will generate two files:
|
||||
|
||||
The page will generate:
|
||||
|
||||
* TipView.xaml
|
||||
* TipView.xaml.cs
|
||||
- `TipView.xaml`
|
||||
- `TipView.xaml.cs`
|
||||
|
||||
### Turn TipView into the MvvmCross View for TipViewModel
|
||||
|
||||
Change:
|
||||
Change the class inheritance to this:
|
||||
|
||||
```public class TipView : Page```
|
||||
|
||||
To:
|
||||
```c#
|
||||
public sealed partial class TipView : MvxWindowsPage
|
||||
```
|
||||
|
||||
```public class TipView : MvxWindowsPage```
|
||||
And also add a `MvxViewForAttribute` over the class, to let MvvmCross know which ViewModel should be attached to it:
|
||||
|
||||
This requires the addition of:
|
||||
|
||||
```using MvvmCross.Uwp.Views;``
|
||||
```c#
|
||||
[MvxViewFor(typeof(TipViewModel))]
|
||||
public sealed partial class TipView : MvxWindowsPage
|
||||
```
|
||||
|
||||
Altogether this looks like:
|
||||
|
||||
```c#
|
||||
using MvvmCross.Uwp.Views;
|
||||
using MvvmCross.Platforms.Uap.Views;
|
||||
using MvvmCross.ViewModels;
|
||||
using TipCalc.Core.ViewModels;
|
||||
|
||||
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace TipCalc.UI.UWP.Views
|
||||
namespace TipCalc.UWP.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class TipView : MvxWindowsPage
|
||||
{
|
||||
public TipView()
|
||||
[MvxViewFor(typeof(TipViewModel))]
|
||||
public sealed partial class TipView : MvxWindowsPage
|
||||
{
|
||||
this.InitializeComponent();
|
||||
public TipView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Edit the XAML layout
|
||||
|
||||
Double click on the XAML file
|
||||
Open the xaml file for our UWP `TipView`.
|
||||
|
||||
This will open the XAML editor within Visual Studio.
|
||||
Same as we did for the other platforms, we won't do into much depth at all here about using Xaml. We will focus on everything which is MvvmCross related instead.
|
||||
|
||||
I won't go into much depth at all here about how to use the XAML or do the Windows data-binding. I'm assuming most readers are already coming from at least a little XAML background.
|
||||
In order to complete our UI, please add the following elements:
|
||||
|
||||
To add the XAML user interface for our tip calculator, we will add a StackPanel within the existing Grid.
|
||||
- A `StackPanel` host, which will contain:
|
||||
- A `TextBlock` which text should be `SubTotal`
|
||||
- A bound `TextBox` for the `SubTotal` property of `TipViewModel`
|
||||
- A `TextBlock` which text should be `Generosity`
|
||||
- A bound `Slider` for the `Generosity` property of `TipViewModel`
|
||||
- A `TextBlock` which text should be `Tip to leave`
|
||||
- A bound `TextBlock` for the `Tip` property of `TipViewModel`
|
||||
|
||||
* a `StackPanel` container, into which we add:
|
||||
* some `TextBlock` static text
|
||||
* a bound `TextBox` for the `SubTotal`
|
||||
* a bound `Slider` for the `Generosity`
|
||||
* a bound `TextBlock` for the `Tip`
|
||||
|
||||
The full page will look like:
|
||||
The full page will look like this:
|
||||
|
||||
```xml
|
||||
<views:MvxWindowsPage
|
||||
x:Class="TipCalc.UI.UWP.Views.TipView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:TipCalc.UI.UWP.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="using MvvmCross.Uwp.Views"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<StackPanel Margin="12,0,12,0">
|
||||
<TextBlock Text="SubTotal" />
|
||||
<TextBox Text="{Binding SubTotal, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Text="Generosity" />
|
||||
<Slider Value="{Binding Generosity,Mode=TwoWay}"
|
||||
<views:MvxWindowsPage x:Class="TipCalc.UWP.Views.TipView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:views="using:MvvmCross.Platforms.Uap.Views"
|
||||
mc:Ignorable="d">
|
||||
<StackPanel Margin="12,0,12,0">
|
||||
<TextBlock Text="SubTotal" />
|
||||
<TextBox Text="{Binding SubTotal, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Text="Generosity" />
|
||||
<Slider Value="{Binding Generosity,Mode=TwoWay}"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
Minimum="0"
|
||||
Maximum="100" />
|
||||
<TextBlock Text="Tip" />
|
||||
<TextBlock Text="{Binding Tip}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<TextBlock Text="Tip to leave" />
|
||||
<TextBlock Text="{Binding Tip}" />
|
||||
</StackPanel>
|
||||
</views:MvxWindowsPage>
|
||||
```
|
||||
|
||||
**Note** that in XAML, `OneWay` binding is generally the default. To provide TwoWay binding we explicitly add `Mode` to our binding expressions: e.g. `Value="{Binding Generosity,Mode=TwoWay}"`
|
||||
|
||||
**Note** the binding for the TextBox uses `UpdateSourceTrigger=PropertyChanged` so that the `SubTotal` property of `TipViewModel` is updated immediately rather than when the TextBox loses focus.
|
||||
**Second note** the binding for the TextBox uses `UpdateSourceTrigger=PropertyChanged` so that the `SubTotal` property of `TipViewModel` is updated immediately rather than when the TextBox loses focus.
|
||||
|
||||
In the designer, this will look like:
|
||||
Although this sample only shows simple bindings, the infrastructure built within MvvmCross is really powerful! Our data-binding engine supports ValueConverters, ValueCombiners, FallbackValues, different modes of bindings and a super straight forward mechanism to add your own custom bindings.
|
||||
|
||||
![TipCalc UWP](../../assets/img/tutorials/tipcalc/TipCalc_UWP_designer.png)
|
||||
## The UWP UI is complete!
|
||||
|
||||
## The Store UI is complete!
|
||||
|
||||
At this point you should be able to run your application either on the Local Machine or in a Mobile emulator.
|
||||
At this point you should be able to run your application either on the Local Machine.
|
||||
|
||||
When it starts... you should see this for the local machine:
|
||||
|
||||
![TipCalc UWP](../../assets/img/tutorials/tipcalc/TipCalc_UWP_landscape.png)
|
||||
|
||||
and in the mobile emulator:
|
||||
|
||||
![TipCalc UWP mobile](../../assets/img/tutorials/tipcalc/TipCalc_UWP.png)
|
||||
![TipCalc UWP]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_UWP.png)
|
||||
|
||||
## Moving on...
|
||||
|
||||
There's more we could do to make this User Interface nicer and to make the app richer... but for this first application, we will leave it here for now.
|
||||
|
||||
Let's move on to the next piece of Windows!
|
||||
Let's move on to the next piece of Windows, WPF!
|
||||
|
||||
[Next!](https://www.mvvmcross.com/documentation/tutorials/tipcalc/a-wpf-ui-project)
|
||||
|
||||
|
|
|
@ -1,198 +1,180 @@
|
|||
---
|
||||
layout: documentation
|
||||
title: A Wpf UI Project
|
||||
title: TipCalc WPF Project
|
||||
category: Tutorials
|
||||
order: 8
|
||||
order: 6
|
||||
---
|
||||
We started with the goal of creating an app to help calculate what tip to leave in a restaurant
|
||||
|
||||
We started with the goal of creating an app to help calculate what tip to leave in a restaurant.
|
||||
|
||||
We had a plan to produce a UI based on this concept:
|
||||
|
||||
![Sketch](../../assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
![TipCalc]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
|
||||
To satisfy this we built a 'Core' Portable Class Library project which contained:
|
||||
To satisfy this we built a 'Core' .NET Standard project which contained:
|
||||
|
||||
* our 'business logic' - `ICalculation`
|
||||
* our ViewModel - `TipViewModel`
|
||||
* our `App` which contains the application wiring, including the start instructions.
|
||||
- Our 'business logic' - `ICalculationService`
|
||||
- Our ViewModel - `TipViewModel`
|
||||
- Our `App` - which contains some bootstrapping code.
|
||||
|
||||
We then added User Interfaces for Xamarin.Android, Xamarin.iOS, Windows UWP, Windows 8.1 Univeral, Windows 8.1 and Windows Phone Silverlight apps.
|
||||
We even added User Interfaces for Xamarin.Android, Xamarin.iOS and UWP so far.
|
||||
|
||||
For our next project, let's shift to Windows Desktop - via WPF - Windows Presentation Framework.
|
||||
Now let's build a UI for WPF (Windows Presentation Foundation)!
|
||||
|
||||
Here we'll build up a new project 'from empty', just as we did for the Core, Android, iOS, Windows Universal Apps, Windows Phone Silverlight and Windows Store projects.
|
||||
|
||||
Obviously, to work with WPF, you will need to be working on the PC with Visual Studio
|
||||
Once again, we will build up a new project 'from empty', just as we did before.
|
||||
|
||||
## Create a new WPF Project
|
||||
|
||||
Add a new project to your solution - a 'WPF Application' with name `TipCalc.UI.Wpf`
|
||||
Add a new project to your solution - a 'WPF App (.NET Framework)' named `TipCalc.WPF`.
|
||||
|
||||
**Important** - make sure this is at least **.Net4.5** - as this is the version required for PCL Profile259 - *goodbye, XP!*
|
||||
Within this, you will find the normal WPF application constructs:
|
||||
|
||||
Within this, you'll find the normal Wpf application constructs:
|
||||
- The `Properties` folder with the `AssemblyInfo` file, some resources and a settings file
|
||||
- The `App.config` configuration file
|
||||
- The `App.xaml` file, which we will edit shortly
|
||||
- A `MainWindow.xaml` and `MainWindow.xaml.cs` files that define a default Window for this app
|
||||
|
||||
* the 'Properties' folder with the 'AssemblyInfo' file, some resources and a settings file
|
||||
* the 'App.Config' configuration file
|
||||
* the App.Xaml 'application' object
|
||||
* the MainWindow.Xaml and MainWindows.Xaml.cs files that define the default Window for this app
|
||||
|
||||
## Keep MainWindow.xaml
|
||||
## Keep(!) MainWindow.xaml
|
||||
|
||||
We do actually want a `MainWindow` for this app :)
|
||||
|
||||
## Install MvvmCross
|
||||
|
||||
In the Package Manager Console, enter...
|
||||
Open the Nuget Package Manager and search for the package `MvvmCross`.
|
||||
|
||||
If you don't really enjoy the NuGet UI experience, then you can alternatively open the Package Manager Console, and type:
|
||||
|
||||
Install-Package MvvmCross
|
||||
|
||||
## Install MvvmCross.Platforms.Wpf
|
||||
|
||||
Same as you did with the `MvvmCross` package, install the specific one for `Wpf`.
|
||||
|
||||
## Add a reference to TipCalc.Core project
|
||||
|
||||
Add a reference to your `TipCalc.Core` project - the project we created in the first step.
|
||||
|
||||
## Edit App.xaml.cs
|
||||
|
||||
WPF will be an easy addition if you have followed the article for UWP. On this platform, the `App` class also plays a very important role, as it provides a set of callback that the OS uses to inform you about events in your application's lifecycle. We won't dig further into it's responsibilities, but you may want to read about it in the official documentation.
|
||||
|
||||
Open the `App.xaml.cs` and delete all the class content. Leave only the default class and make it extend `MvxApplication`:
|
||||
|
||||
```c#
|
||||
Install-Package MvvmCross.Core
|
||||
public partial class App : MvxApplication
|
||||
```
|
||||
|
||||
## Add a reference to TipCalc.Core.csproj
|
||||
|
||||
Add a reference to your `TipCalc.Core` project - the project we created in the last step which included:
|
||||
|
||||
* your `Calculation` service,
|
||||
* your `TipViewModel`
|
||||
* your `App` wiring.
|
||||
|
||||
## Add a Setup class
|
||||
|
||||
Just as we said during the construction of the other UI projects *Every MvvmCross UI project requires a `Setup` class*
|
||||
|
||||
This class sits in the root namespace (folder) of our UI project and performs the initialization of the MvvmCross framework and your application, including:
|
||||
|
||||
* the Inversion of Control (IoC) system
|
||||
* the MvvmCross data-binding
|
||||
* your `App` and its collection of `ViewModel`s
|
||||
* your UI project and its collection of `View`s
|
||||
|
||||
Most of this functionality is provided for you automatically. Within your Wpf UI project all you have to supply is:
|
||||
|
||||
- your `App` - your link to the business logic and `ViewModel` content
|
||||
|
||||
For `TipCalc` here's all that is needed in Setup.cs:
|
||||
Now override the method `RegisterSetup` and use the object extension method `RegisterSetupType`:
|
||||
|
||||
```c#
|
||||
using System.Windows.Threading;
|
||||
using MvvmCross.Core.ViewModels;
|
||||
using MvvmCross.Wpf.Platform;
|
||||
using MvvmCross.Wpf.Views;
|
||||
this.RegisterSetupType<MvxWpfSetup<Core.App>>();
|
||||
```
|
||||
|
||||
namespace TipCalc.UI.Wpf
|
||||
{
|
||||
public class Setup : MvxWpfSetup
|
||||
{
|
||||
public Setup(Dispatcher uiThreadDispatcher, IMvxWpfViewPresenter presenter) : base(uiThreadDispatcher, presenter)
|
||||
{
|
||||
}
|
||||
This line of code tells MvvmCross we want to use the default provided Setup class, and also that our _Core_ application is `Core.App`.
|
||||
|
||||
protected override IMvxApplication CreateApp()
|
||||
Altogether this is what your App.xaml.cs should loook like:
|
||||
|
||||
```c#
|
||||
using MvvmCross.Core;
|
||||
using MvvmCross.Platforms.Wpf.Core;
|
||||
using MvvmCross.Platforms.Wpf.Views;
|
||||
|
||||
namespace TipCalc.WPF
|
||||
{
|
||||
public partial class App : MvxApplication
|
||||
{
|
||||
return new Core.App();
|
||||
protected override void RegisterSetup()
|
||||
{
|
||||
this.RegisterSetupType<MvxWpfSetup<Core.App>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Modify the App.xaml.cs to use Setup
|
||||
## Edit App.xaml
|
||||
|
||||
There are other lifecycles and display techniques that you can use to write Wpf apps.
|
||||
Now it's time to edit the xaml part of our WPF `App` class. Open the file and replace all the content for this code:
|
||||
|
||||
However, here we will just use a 'one window with one view' approach.
|
||||
|
||||
To achieve this, add some lines to the WPF `App` class that:
|
||||
|
||||
* provide a private flag to determine if setup has already been done
|
||||
|
||||
```c#
|
||||
bool _setupComplete;
|
||||
```xml
|
||||
<views:MvxApplication
|
||||
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
|
||||
x:Class="TipCalc.WPF.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
</views:MvxApplication>
|
||||
```
|
||||
|
||||
* perform the setup - using a `Simple` presenter based on `MainWindow`
|
||||
That's it! We now only need to build the UI.
|
||||
|
||||
### Some more details about the Setup class
|
||||
|
||||
Every MvvmCross UI project requires a `Setup` class, but if your app is fairly simple, like the TipCalc is, then you can safely use the default one, provided by the framework.
|
||||
|
||||
The `Setup` class is responsible for performing the initialization of the MvvmCross framework, including:
|
||||
|
||||
- The IoC Container and DI engine
|
||||
- The Data-Binding engine
|
||||
- The ViewModel / View lookups
|
||||
- The whole navigation system
|
||||
- Plugins
|
||||
|
||||
Finally, the `Setup` class is also responsible for initializing your `App` class.
|
||||
|
||||
Luckily for us, all this functionality is provided for you automatically, unless you want / need to use a custom `Setup` class (since it is an excellent place to register your own services / plugins, it is often the case).
|
||||
|
||||
## Make MainWindow be a MvvmCross window
|
||||
|
||||
Open up `MainWindow.xaml.cs` and change the base class to `MvxWindow`:
|
||||
|
||||
```c#
|
||||
void DoSetup()
|
||||
using MvvmCross.Platforms.Wpf.Views;
|
||||
|
||||
namespace TipCalc.WPF
|
||||
{
|
||||
var presenter = new MvxSimpleWpfViewPresenter(MainWindow);
|
||||
|
||||
var setup = new Setup(Dispatcher, presenter);
|
||||
setup.Initialize();
|
||||
|
||||
var start = Mvx.Resolve<IMvxAppStart>();
|
||||
start.Start();
|
||||
|
||||
_setupComplete = true;
|
||||
}
|
||||
```
|
||||
|
||||
* override the `OnActivated` event to perform this startup
|
||||
|
||||
```c#
|
||||
protected override void OnActivated(System.EventArgs e)
|
||||
{
|
||||
if (!_setupComplete)
|
||||
DoSetup();
|
||||
|
||||
base.OnActivated(e);
|
||||
}
|
||||
```
|
||||
|
||||
After you've done this your `App.xaml.cs` might look like:
|
||||
|
||||
```c#
|
||||
using System.Windows;
|
||||
using MvvmCross.Core.ViewModels;
|
||||
using MvvmCross.Platform;
|
||||
using MvvmCross.Wpf.Views;
|
||||
|
||||
namespace TipCalc.UI.Wpf
|
||||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
bool _setupComplete;
|
||||
|
||||
void DoSetup()
|
||||
public partial class MainWindow : MvxWindow
|
||||
{
|
||||
var presenter = new MvxSimpleWpfViewPresenter(MainWindow);
|
||||
|
||||
var setup = new Setup(Dispatcher, presenter);
|
||||
setup.Initialize();
|
||||
|
||||
var start = Mvx.Resolve<IMvxAppStart>();
|
||||
start.Start();
|
||||
|
||||
_setupComplete = true;
|
||||
}
|
||||
|
||||
protected override void OnActivated(System.EventArgs e)
|
||||
{
|
||||
if (!_setupComplete)
|
||||
DoSetup();
|
||||
|
||||
base.OnActivated(e);
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now open the XAML document and apply the same change:
|
||||
|
||||
```xml
|
||||
<views:MvxWindow x:Class="TipCalc.WPF.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:TipCalc.WPF"
|
||||
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
|
||||
mc:Ignorable="d"
|
||||
Title="MainWindow" Height="450" Width="800">
|
||||
<Grid>
|
||||
</Grid>
|
||||
</views:MvxWindow>
|
||||
```
|
||||
|
||||
## Add your View
|
||||
|
||||
### Create the UserControl
|
||||
|
||||
Create a Views folder
|
||||
Create a folder named `Views`.
|
||||
|
||||
Within this folder, add a new 'User Control (WPF)' and call it `TipView.xaml`
|
||||
|
||||
The page will generate:
|
||||
This will generate two files:
|
||||
|
||||
* TipView.xaml
|
||||
* TipView.xaml.cs
|
||||
- `TipView.xaml`
|
||||
- `TipView.xaml.cs`
|
||||
|
||||
### Turn TipView into the MvvmCross View for TipViewModel
|
||||
|
||||
Open the TipView.xaml.cs file.
|
||||
Open the `TipView.xaml.cs` file.
|
||||
|
||||
Change the class to inherit from `MvxWpfView`
|
||||
|
||||
|
@ -203,106 +185,99 @@ public partial class TipView : MvxWpfView
|
|||
Altogether this looks like:
|
||||
|
||||
```c#
|
||||
using MvvmCross.Wpf.Views;
|
||||
using MvvmCross.Platforms.Wpf.Views;
|
||||
|
||||
namespace TipCalc.UI.Wpf.Views
|
||||
namespace TipCalc.WPF.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for TipView.xaml
|
||||
/// </summary>
|
||||
public partial class TipView : MvxWpfView
|
||||
{
|
||||
public TipView()
|
||||
public partial class TipView : MvxWpfView
|
||||
{
|
||||
InitializeComponent();
|
||||
public TipView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Edit the XAML layout
|
||||
|
||||
Double click on the XAML file
|
||||
|
||||
This will open the XAML editor within Visual Studio.
|
||||
|
||||
Just as with Windows Uwp, I won't go into much depth at all here about how to use the XAML or do the Windows data-binding. I'm assuming most readers are already coming from at least a little XAML background.
|
||||
Double click on the XAML file. At this point we will just assume you understand the basics of XAML editing. If you need further explanations on that, please take a read at the WPF official documentation. We will concentrate on the MvvmCross specific bits only.
|
||||
|
||||
Change the root node from:
|
||||
|
||||
```xml
|
||||
<UserControl
|
||||
...
|
||||
...>
|
||||
</UserControl>
|
||||
```
|
||||
|
||||
to:
|
||||
To:
|
||||
|
||||
```xml
|
||||
<views:MvxWpfView
|
||||
xmlns:views="clr-namespace:MvvmCross.Wpf.Views;assembly=MvvmCross.Wpf"
|
||||
...
|
||||
<views:MvxWpfView
|
||||
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
|
||||
...>
|
||||
</views:MvxWpfView>
|
||||
```
|
||||
|
||||
To add the XAML user interface for our tip calculator, we will add exactly the same XAML as we added inside the `ContentPanel` grid for the Windows UWP example. This includes:
|
||||
We now need to fill the content of the view with some widgets:
|
||||
|
||||
- Add a `StackPanel` container. Then inside of it, add:
|
||||
- A `TextBlock` which text should be `SubTotal`
|
||||
- A bound `TextBox` for the `SubTotal` property of `TipViewModel`
|
||||
- A `TextBlock` which text should be `Generosity`
|
||||
- A bound `Slider` for the `Generosity` property of `TipViewModel`
|
||||
- A `TextBlock` which text should be `Tip to leave`
|
||||
- A bound `TextBlock` for the `Tip` property of `TipViewModel`
|
||||
|
||||
* a `StackPanel` container, into which we add:
|
||||
* some `TextBlock` static text
|
||||
* a bound `TextBox` for the `SubTotal`
|
||||
* a bound `Slider` for the `Generosity`
|
||||
* a bound `TextBlock` for the `Tip`
|
||||
|
||||
This will produce finished XAML like:
|
||||
|
||||
```xml
|
||||
<views:MvxWpfView
|
||||
x:Class="TipCalc.UI.Wpf.Views.TipView"
|
||||
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:local="clr-namespace:TipCalc.UI.Wpf.Views"
|
||||
xmlns:views="clr-namespace:MvvmCross.Wpf.Views;assembly=MvvmCross.Wpf"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="300">
|
||||
<views:MvxWpfView x:Class="TipCalc.WPF.Views.TipView"
|
||||
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:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<StackPanel>
|
||||
<StackPanel Margin="12,0,12,0">
|
||||
<TextBlock Text="SubTotal" />
|
||||
<TextBox Text="{Binding SubTotal, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<TextBlock Text="Generosity" />
|
||||
<Slider
|
||||
Value="{Binding Generosity, Mode=TwoWay}"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
Minimum="0"
|
||||
Maximum="100" />
|
||||
<TextBlock Text="Tip" />
|
||||
<Slider Value="{Binding Generosity,Mode=TwoWay}"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
Minimum="0"
|
||||
Maximum="100" />
|
||||
<TextBlock Text="Tip to leave" />
|
||||
<TextBlock Text="{Binding Tip}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</views:MvxWpfView>
|
||||
|
||||
```
|
||||
|
||||
**Note** that in XAML, `OneWay` binding is generally the default. To provide TwoWay binding we explicitly add `Mode` to our binding expressions: e.g. `Value="{Binding Generosity,Mode=TwoWay}"`
|
||||
|
||||
In the designer, this will look like:
|
||||
**Second note** the binding for the TextBox uses `UpdateSourceTrigger=PropertyChanged` so that the `SubTotal` property of `TipViewModel` is updated immediately rather than when the TextBox loses focus.
|
||||
|
||||
![TipCalc WPF Design](../../assets/img/tutorials/tipcalc/TipCalc_Wpf_Designer.png)
|
||||
Although this sample only shows simple bindings, the infrastructure built within MvvmCross is really powerful! Our data-binding engine supports ValueConverters, ValueCombiners, FallbackValues, different modes of bindings and a super straight forward mechanism to add your own custom bindings.
|
||||
|
||||
## The Wpf UI is complete!
|
||||
|
||||
At this point you should be able to run your application.
|
||||
|
||||
When it starts... you should see:
|
||||
When it starts... you should see something like this:
|
||||
|
||||
![TipCalc WPF Run](../../assets/img/tutorials/tipcalc/TipCalc_Wpf_Run.png)
|
||||
![TipCalc WPF Run]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_WPF.png)
|
||||
|
||||
## Moving on...
|
||||
|
||||
There's more we could do to make this User Interface nicer and to make the app richer... but for this first application, we will leave it here for now.
|
||||
|
||||
And actually... that's the end of our journey through this first app.
|
||||
|
||||
One day soon there will be more steps - a step for Mac desktop, a step for XBox, a step for TV, etc - but for now this is the end.
|
||||
Stay tuned, because for the next step, we will completely change the approach and build a Xamarin.Forms version.
|
||||
|
||||
[Next!](https://www.mvvmcross.com/documentation/tutorials/tipcalc/a-xamarin-forms-version)
|
|
@ -0,0 +1,460 @@
|
|||
---
|
||||
layout: documentation
|
||||
title: TipCalc Xamarin.Forms
|
||||
category: Tutorials
|
||||
order: 7
|
||||
---
|
||||
|
||||
We started with the goal of creating an app to help calculate what tip to leave in a restaurant
|
||||
|
||||
We had a plan to produce a UI based on this concept:
|
||||
|
||||
![Sketch]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
|
||||
To satisfy this we built a 'Core' .NET Standard project which contained:
|
||||
|
||||
- Our 'business logic' - `ICalculationService`
|
||||
- Our ViewModel - `TipViewModel`
|
||||
- Our `App` - which contains some bootstrapping code.
|
||||
|
||||
We then added User Interfaces for Xamarin.Android, Xamarin.iOS, UWP, and WPF.
|
||||
|
||||
For our next project, let's shift the approach of the app from Xamarin traditional to Xamarin.Forms!
|
||||
|
||||
Our Xamarin.Forms app will support iOS and Android for now.
|
||||
|
||||
## Create a new .NET Standard 2 library project
|
||||
|
||||
Using Visual Studio, create a new `.NET Standard 2 Library` project using the File|New Project wizard.
|
||||
|
||||
This project will host all the common UI files (xaml) for our Xamarin.Forms app.
|
||||
|
||||
Call this project `TipCalc.Forms.UI`.
|
||||
|
||||
## Install MvvmCross.Forms and Xamarin.Forms packages
|
||||
|
||||
Open the Nuget Package Manager and search for the packages `MvvmCross.Forms`.
|
||||
|
||||
If you don't really enjoy the NuGet UI experience, then you can alternatively open the Package Manager Console, and type:
|
||||
|
||||
Install-Package MvvmCross.Forms
|
||||
|
||||
Installing that package should also install `Xamarin.Forms` and `MvvmCross` packages to your project.
|
||||
|
||||
## Add a reference to TipCalc.Core project
|
||||
|
||||
One of the main advantages of using MvvmCross with Xamarin is the flexibility it gives you in terms of approaches. You can start small with Xamarin.Forms, and when your app becomes big and complex enough, you can migrate to Xamarin traditional reusing 100% of your _Core_ layer.
|
||||
|
||||
Add a reference to your `TipCalc.Core` project - the project we created in the first step.
|
||||
|
||||
## Add a FormsApp.xaml class
|
||||
|
||||
Add a 'Forms ContentPage Xaml' file to the root of your `TipCalc.Forms.UI` project named `FormsApp`. This will add two files:
|
||||
|
||||
- `FormsApp.xaml`
|
||||
- `FormsApp.xaml.cs`
|
||||
|
||||
Xamarin.Forms requests you to define an App class to initialize correctly. We won't go deeper into the details, for now let's assume it is a necessary class, which also provides some callbacks for the different states of your app.
|
||||
|
||||
## Edit FormsApp.xaml
|
||||
|
||||
Replace the content of the file with the following lines of code:
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Application xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="TipCalc.Forms.UI.FormsApp">
|
||||
<Application.Resources>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
```
|
||||
|
||||
## Edit FormsApp.xaml.cs
|
||||
|
||||
Replace the content of the file with the following code:
|
||||
|
||||
```c#
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace TipCalc.Forms.UI
|
||||
{
|
||||
public partial class FormsApp : Application
|
||||
{
|
||||
public FormsApp()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Add your View
|
||||
|
||||
Create a folder named `Pages` on the root of the project.
|
||||
|
||||
Within this folder, add a new 'Forms Content Page Xaml' file and call it `TipView`.
|
||||
|
||||
This will generate:
|
||||
|
||||
- `TipView.xaml`
|
||||
- `TipView.xaml.cs`
|
||||
|
||||
### Turn TipView into the MvvmCross View for TipViewModel
|
||||
|
||||
Open the `TipView.xaml.cs` file and change the class to inherit from `MvxContentPage<TipViewModel>`:
|
||||
|
||||
```c#
|
||||
public partial class TipView : MvxContentPage<TipViewModel>
|
||||
```
|
||||
|
||||
Altogether this looks like:
|
||||
|
||||
```c#
|
||||
using MvvmCross.Forms.Views;
|
||||
using TipCalc.Core.ViewModels;
|
||||
|
||||
namespace TipCalc.Forms.UI.Pages
|
||||
{
|
||||
public partial class TipView : MvxContentPage<TipViewModel>
|
||||
{
|
||||
public TipView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Edit the XAML layout
|
||||
|
||||
Double click on the XAML file to edit it's code.
|
||||
|
||||
Same as we did for other platforms, we won't go into much depth at all here about how to use XAML. We will only highlight some parts of the code which are important to MvvmCross.
|
||||
|
||||
Change the root node from:
|
||||
|
||||
```xml
|
||||
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
x:Class="TipCalc.Forms.UI.Pages.TipView">
|
||||
...
|
||||
</ContentPage>
|
||||
```
|
||||
|
||||
To:
|
||||
|
||||
```xml
|
||||
<views:MvxContentPage x:TypeArguments="viewModels:TipViewModel"
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
|
||||
xmlns:mvx="clr-namespace:MvvmCross.Forms.Bindings;assembly=MvvmCross.Forms"
|
||||
xmlns:viewModels="clr-namespace:TipCalc.Core.ViewModels;assembly=TipCalc.Core"
|
||||
x:Class="TipCalc.Forms.UI.Pages.TipView">
|
||||
...
|
||||
</views:MvxContentPage>
|
||||
```
|
||||
|
||||
This makes your View use the MvvmCross base class. And we have also added some namespaces for `views`, `mvx` and `viewmodels`.
|
||||
|
||||
We are now ready to start adding content to our page!
|
||||
|
||||
We will start by adding a StackLayout:
|
||||
|
||||
```xml
|
||||
<views:MvxContentPage ...>
|
||||
<ContentPage.Content>
|
||||
<StackLayout Margin="10">
|
||||
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</views:MvxContentPage>
|
||||
```
|
||||
|
||||
And then we just need to add:
|
||||
|
||||
- A `Label` which text should be `SubTotal` and an `Entry` named `SubTotalEntry`, for editing the value
|
||||
- A `Label` which text should be `Generosity` and a `Slider` named `GenerositySlider`, for editing the "generosity"
|
||||
- A `Label` which text should be `Tip to leave` and a `Label` named `TipLabel,` for showing the `Tip` result
|
||||
|
||||
This will produce the following XAML:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<views:MvxContentPage x:TypeArguments="viewModels:TipViewModel"
|
||||
xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:views="clr-namespace:MvvmCross.Forms.Views;assembly=MvvmCross.Forms"
|
||||
xmlns:mvx="clr-namespace:MvvmCross.Forms.Bindings;assembly=MvvmCross.Forms"
|
||||
xmlns:viewModels="clr-namespace:TipCalc.Core.ViewModels;assembly=TipCalc.Core"
|
||||
x:Class="TipCalc.Forms.UI.Pages.TipView">
|
||||
<ContentPage.Content>
|
||||
<StackLayout Margin="10">
|
||||
<Label Text="Subtotal" />
|
||||
<Entry x:Name="SubTotalEntry"
|
||||
mvx:Bi.nd="Text SubTotal, Mode=TwoWay"/>
|
||||
<Label Text="Generosity" />
|
||||
<Slider x:Name="GenerositySlider"
|
||||
Maximum="100"
|
||||
mvx:Bi.nd="Value Generosity, Mode=TwoWay"/>
|
||||
<Label Text="Tip to leave" />
|
||||
<Label x:Name="TipLabel"
|
||||
mvx:Bi.nd="Text Tip"/>
|
||||
</StackLayout>
|
||||
</ContentPage.Content>
|
||||
</views:MvxContentPage>
|
||||
```
|
||||
|
||||
In the above code, we are using the MvvmCross data-binding engine in XAML directly.
|
||||
|
||||
On each of the `mvx:Bi.nd` calls:
|
||||
- The first element corresponds to a View property, or in the MvvmCross context, the 'binding target'.
|
||||
- The second element corresponds to a ViewModel property, or in the MvvmCross context, the 'binding source'.
|
||||
- By default most bindings are `OneWay`, so in order to make our View work as we want to, we are defining a different binding mode.
|
||||
|
||||
Although this sample only shows simple bindings, the infrastructure built within MvvmCross is really powerful! Our data-binding engine supports ValueConverters, ValueCombiners, FallbackValues, different modes of bindings and a super straight forward mechanism to add your own custom bindings.
|
||||
|
||||
## The Xamarin.Forms UI is complete!
|
||||
|
||||
At this point we have finished working on the shared code parts of our app. We will now add an Android and an iOS projects to our solution.
|
||||
|
||||
## Add a Xamarin.Android project
|
||||
|
||||
Xamarin typically provides you with very nice and easy to use templates for creating cross-platform Xamarin.Forms apps. But in this case, we will add a blank application and do all the steps by ourselves.
|
||||
|
||||
Add a new project to your solution - a 'Blank App (Android)' application with name `TipCalc.Forms.Droid`
|
||||
|
||||
### Install MvvmCross.Forms package
|
||||
|
||||
Open the Nuget Package Manager and search for the package `MvvmCross.Forms`.
|
||||
|
||||
If you don't really enjoy the NuGet UI experience, then you can alternatively open the Package Manager Console, and type:
|
||||
|
||||
Install-Package MvvmCross.Forms
|
||||
|
||||
Installing this package will also install Xamarin.Forms, MvvmCross and a few Android.Support packages (so it may take a while).
|
||||
|
||||
### Add references to TipCalc.Core and TipCalc.Forms.UI
|
||||
|
||||
Add a reference to both `TipCalc.Core` and `TipCalc.Forms.UI` projects.
|
||||
|
||||
### Delete Resources/Layout/Main.axml
|
||||
|
||||
We won't use that file in any case.
|
||||
|
||||
### Add Tabbar and Toolbar layouts:
|
||||
|
||||
Add an axml file called `Tabbar.axml` within the Resources/Layout folder:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.TabLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/sliding_tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
app:tabIndicatorColor="@android:color/white"
|
||||
app:tabGravity="fill"
|
||||
app:tabMode="fixed" />
|
||||
```
|
||||
|
||||
Now do the same with an axml file called `Toolbar.axml`:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
android:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
|
||||
```
|
||||
|
||||
### Add a default style for your Android app:
|
||||
|
||||
If it doesn't exist, add a folder named `values` inside the `Resources` folder.
|
||||
|
||||
Within that folder, add a file named `styles.xml` and add this content to it:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<resources>
|
||||
<style name="MyTheme"
|
||||
parent="MyTheme.Base">
|
||||
</style>
|
||||
<!-- Base theme applied no matter what API -->
|
||||
<style name="MyTheme.Base"
|
||||
parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!--If you are using revision 22.1 please use just windowNoTitle. Without android:-->
|
||||
<item name="windowNoTitle">true</item>
|
||||
<!--We will be using the toolbar so no need to show ActionBar-->
|
||||
<item name="windowActionBar">false</item>
|
||||
<!-- Set theme colors from http://www.google.com/design/spec/style/color.html#color-color-palette-->
|
||||
<!-- colorPrimary is used for the default action bar background -->
|
||||
<item name="colorPrimary">#2196F3</item>
|
||||
<!-- colorPrimaryDark is used for the status bar -->
|
||||
<item name="colorPrimaryDark">#1976D2</item>
|
||||
<!-- colorAccent is used as the default value for colorControlActivated
|
||||
which is used to tint widgets -->
|
||||
<item name="colorAccent">#FF4081</item>
|
||||
<!-- You can also set colorControlNormal, colorControlActivated
|
||||
colorControlHighlight and colorSwitchThumbNormal. -->
|
||||
<item name="windowActionModeOverlay">true</item>
|
||||
<item name="android:datePickerDialogTheme">@style/AppCompatDialogStyle</item>
|
||||
</style>
|
||||
<style name="AppCompatDialogStyle"
|
||||
parent="Theme.AppCompat.Light.Dialog">
|
||||
<item name="colorAccent">#FF4081</item>
|
||||
</style>
|
||||
</resources>
|
||||
```
|
||||
|
||||
It will be the Android Theme we will use for the app. You can also customize some values in there.
|
||||
|
||||
### Edit MainActivity.cs
|
||||
|
||||
As our app uses Xamarin.Forms, we don't need to add any piece of UI to our Android project, but we do need to hook up some code to perform the framework initialization.
|
||||
|
||||
First of all, change the inheritance of MainActivity to be:
|
||||
|
||||
```c#
|
||||
public class MainActivity : MvxFormsAppCompatActivity<MvxFormsAndroidSetup<App, FormsApp>, App, FormsApp>
|
||||
```
|
||||
|
||||
Here we are letting MvvmCross know that:
|
||||
- We want to use the default provided `Setup`
|
||||
- Our _Core_ application class is `App`
|
||||
- Our _Xamarin.Forms_ application class is `FormsApp`
|
||||
|
||||
Now override the method `OnCreate(Bundle bundle)` and let Xamarin.Forms know which are the Resource IDs of the tabbar & toolbar we want to use (before calling base):
|
||||
|
||||
```c#
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
TabLayoutResource = Resource.Layout.Tabbar;
|
||||
ToolbarResource = Resource.Layout.Toolbar;
|
||||
base.OnCreate(bundle);
|
||||
}
|
||||
```
|
||||
|
||||
We are almost there! The only missing piece is the `ActivityAttribute` which Xamarin uses to fill the AndroidManifest file.
|
||||
|
||||
This is what the `MainActivity` class should look like in the end:
|
||||
|
||||
```c#
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using MvvmCross.Forms.Platforms.Android.Core;
|
||||
using MvvmCross.Forms.Platforms.Android.Views;
|
||||
using TipCalc.Core;
|
||||
using TipCalc.Forms.UI;
|
||||
|
||||
namespace TipCalc.Forms.Droid
|
||||
{
|
||||
[Activity(
|
||||
Label = "TipCalc.Forms.Droid",
|
||||
Icon = "@drawable/icon",
|
||||
Theme = "@style/MyTheme",
|
||||
MainLauncher = true,
|
||||
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
|
||||
LaunchMode = LaunchMode.SingleTask)]
|
||||
public class MainActivity : MvxFormsAppCompatActivity<MvxFormsAndroidSetup<App, FormsApp>, App, FormsApp>
|
||||
{
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
TabLayoutResource = Resource.Layout.Tabbar;
|
||||
ToolbarResource = Resource.Layout.Toolbar;
|
||||
base.OnCreate(bundle);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
At this point you should be able to set the `TipCalc.Forms.Droid` as the startup project and finally give the app a run! This is what you should see:
|
||||
|
||||
![TipCalc Android Forms]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Forms_Android.png)
|
||||
|
||||
## Add a Xamarin.iOS project
|
||||
|
||||
Xamarin typically provides you with very nice and easy to use templates for creating cross-platform Xamarin.Forms apps. But in this case, same as we did with Android, we will add a blank application and do all the steps by ourselves.
|
||||
|
||||
Add a new project to your solution - a 'Blank App (iOS)' application with name `TipCalc.Forms.iOS`
|
||||
|
||||
### Install MvvmCross.Forms package
|
||||
|
||||
Open the Nuget Package Manager and search for the package `MvvmCross.Forms`.
|
||||
|
||||
If you don't really enjoy the NuGet UI experience, then you can alternatively open the Package Manager Console, and type:
|
||||
|
||||
Install-Package MvvmCross.Forms
|
||||
|
||||
Installing this package will also install Xamarin.Forms and the MvvmCross package.
|
||||
|
||||
### Add references to TipCalc.Core and TipCalc.Forms.UI
|
||||
|
||||
Add a reference to both `TipCalc.Core` and `TipCalc.Forms.UI` projects.
|
||||
|
||||
## Delete ViewController.cs and Main.storyboard
|
||||
|
||||
Because we will add our own view.
|
||||
|
||||
After you have deleted those, open the `Info.plist` file and under the 'Deployment Info' section, remove the text in the 'Main Interface' input (leave it blank).
|
||||
|
||||
### Edit AppDelegate.cs
|
||||
|
||||
As our app uses Xamarin.Forms, we don't need to add any piece of UI to our iOS project, but we do need to hook up some code to perform the framework initialization.
|
||||
|
||||
The `AppDelegate` class plays a very important role on iOS apps, as it provides a set of callback that the OS uses to inform you about events in your application's lifecycle.
|
||||
|
||||
First of all, change the inheritance of AppDelegate to be:
|
||||
|
||||
```c#
|
||||
public partial class AppDelegate : MvxFormsApplicationDelegate<MvxFormsIosSetup<App, FormsApp>, App, FormsApp>
|
||||
```
|
||||
|
||||
Here we are letting MvvmCross know that:
|
||||
- We want to use the default provided `Setup`
|
||||
- Our _Core_ application class is `App`
|
||||
- Our _Xamarin.Forms_ application class is `FormsApp`
|
||||
|
||||
This is what the `AppDelegate` class should look like in the end:
|
||||
|
||||
```c#
|
||||
using Foundation;
|
||||
using MvvmCross.Forms.Platforms.Ios.Core;
|
||||
using TipCalc.Core;
|
||||
using TipCalc.Forms.UI;
|
||||
using UIKit;
|
||||
|
||||
namespace TipCalc.Forms.iOS
|
||||
{
|
||||
[Register(nameof(AppDelegate))]
|
||||
public partial class AppDelegate : MvxFormsApplicationDelegate<MvxFormsIosSetup<App, FormsApp>, App, FormsApp>
|
||||
{
|
||||
public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary launchOptions)
|
||||
{
|
||||
return base.FinishedLaunching(uiApplication, launchOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
At this point you should be able to set the `TipCalc.Forms.Droid` as the startup project and finally give the app a run! This is what you should see:
|
||||
|
||||
![TipCalc Android Forms]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Forms_iOS.png)
|
||||
|
||||
## Moving on...
|
||||
|
||||
There's more we could do to make the Forms User Interface nicer and to make the app richer... but for this first application, we will leave it here for now.
|
||||
|
||||
Of course you can go ahead and add some more platforms to the Xamarin.Forms version of TipCalc (UWP, macOS).
|
||||
|
||||
For now we will stop here and review some more key concepts like navigation.
|
||||
|
||||
[Next!](https://www.mvvmcross.com/documentation/tutorials/tipcalc/tip-calculator-a-recap)
|
||||
|
|
@ -1,111 +1,114 @@
|
|||
---
|
||||
layout: documentation
|
||||
title: A Xamarin.Android UI Project
|
||||
title: TipCalc Android Project
|
||||
category: Tutorials
|
||||
order: 4
|
||||
order: 3
|
||||
---
|
||||
|
||||
We started with the goal of creating an app to help calculate what tip to leave in a restaurant.
|
||||
|
||||
We had a plan to produce a UI based on this concept:
|
||||
|
||||
![Sketch](../../assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
![Sketch]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
|
||||
To satisfy this we built a 'Core' Portable Class Library project which contained:
|
||||
To satisfy this we built a 'Core' .NET Standard project which contained:
|
||||
|
||||
- Our 'business logic' - `ICalculation`
|
||||
- Our 'business logic' - `ICalculationService`
|
||||
- Our ViewModel - `TipViewModel`
|
||||
- Our `App` which contains the application wiring, including the start instructions.
|
||||
- Our `App` - which contains some bootstrapping code.
|
||||
|
||||
We're now ready to add out first User Interface.
|
||||
We're now ready to add out first User Interface!
|
||||
|
||||
So... let's start with Android.
|
||||
|
||||
To create an Android MvvmCross UI, you can use the Visual Studio project template wizards, but here we'll instead build up a new project 'from empty', just as we did for the Core project.
|
||||
Same as we did with the _Core_ project, we will use a standard template to create the Android project.
|
||||
|
||||
## Create a new Android UI Project
|
||||
## Create a new Android project
|
||||
|
||||
Add a new project to your solution - a 'Blank App (Android)' application with name `TipCalc.UI.Droid`
|
||||
Add a new project to your solution - a 'Blank App (Android)' application with name `TipCalc.Droid`
|
||||
|
||||
Within this, you'll find the normal Android application constructs:
|
||||
|
||||
- the Assets folder
|
||||
- the Resources folder
|
||||
- the MainActivity.cs
|
||||
- The Assets folder
|
||||
- The Resources folder
|
||||
- The MainActivity.cs
|
||||
|
||||
## Delete MainActivity.cs
|
||||
|
||||
No-one really needs a `MainActivity` :)
|
||||
No-one really needs that `MainActivity` :)
|
||||
|
||||
Also, delete `Main.axml` in the /resources/Layout folder.
|
||||
Also, make sure you delete `Main.axml` in the /resources/Layout folder.
|
||||
|
||||
## Install MvvmCross
|
||||
|
||||
In the Package Manager Console, enter...
|
||||
Open the Nuget Package Manager and search for the package `MvvmCross`.
|
||||
|
||||
Install-Package MvvmCross.Binding
|
||||
If you don't really enjoy the NuGet UI experience, then you can alternatively open the Package Manager Console, and type:
|
||||
|
||||
## Add a reference to TipCalc.Core.csproj
|
||||
Install-Package MvvmCross
|
||||
|
||||
Add a reference to your `TipCalc.Core` project - the project we created in the last step which included:
|
||||
## Add a reference to TipCalc.Core project
|
||||
|
||||
- Your `Calculation` service,
|
||||
- Your `TipViewModel`
|
||||
- Your `App` wiring.
|
||||
Add a reference to your `TipCalc.Core` project - the project we created in the first step.
|
||||
|
||||
## Add an Android Application class
|
||||
|
||||
## Add a Setup class
|
||||
|
||||
Every MvvmCross UI project requires a `Setup` class.
|
||||
|
||||
This class sits in the root namespace (folder) of our UI project and performs the initialization of the MvvmCross framework and your application, including:
|
||||
|
||||
- The Inversion of Control (IoC) system
|
||||
- The MvvmCross data-binding
|
||||
- Your `App` and its collection of `ViewModel`s
|
||||
- Your UI project and its collection of `View`s
|
||||
|
||||
Most of this functionality is provided for you automatically. Within your Droid UI project all you have to supply are:
|
||||
|
||||
- your `App` - your link to the business logic and `ViewModel` content
|
||||
|
||||
For `TipCalc` here's all that is needed in Setup.cs:
|
||||
The Android Application class will allow us to specify the MvvmCross framework some key classes to be used for initialization:
|
||||
|
||||
```c#
|
||||
using Android.Content;
|
||||
using MvvmCross.Core.ViewModels;
|
||||
using MvvmCross.Droid.Platform;
|
||||
using System;
|
||||
using Android.App;
|
||||
using Android.Runtime;
|
||||
using MvvmCross.Platforms.Android.Core;
|
||||
using MvvmCross.Platforms.Android.Views;
|
||||
using TipCalc.Core;
|
||||
|
||||
namespace TipCalc.UI.Droid
|
||||
namespace TipCalc.Droid
|
||||
{
|
||||
public class Setup : MvxAndroidSetup
|
||||
{
|
||||
public Setup(Context applicationContext)
|
||||
: base(applicationContext)
|
||||
[Application]
|
||||
public class MainApplication : MvxAndroidApplication<MvxAndroidSetup<App>, App>
|
||||
{
|
||||
public MainApplication(IntPtr javaReference, JniHandleOwnership transfer)
|
||||
: base(javaReference, transfer)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
protected override IMvxApplication CreateApp()
|
||||
{
|
||||
return new App();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We won't go deeper into what role does MainApplication have on the Android platform, but let's talk a bit about the MvvmCross bits:
|
||||
|
||||
- `MvxAndroidApplication` provides some behavior for initializing the framework in runtime - although it isn't really the only way to configure the Android project.
|
||||
- `MvxAndroidSetup` is the default setup MvvmCross contains. But you can use your own customized setup - sometimes it's necessary and it's a good place to initialize some 3rd party libraries on it.
|
||||
- `App` here is a reference to our `TipCalc.Core.App` class.
|
||||
|
||||
### Some more details about the Setup class
|
||||
|
||||
Every MvvmCross UI project requires a `Setup` class, but if your app is fairly simple, like the TipCalc is, then you can safely use the default one, provided by the framework.
|
||||
|
||||
The `Setup` class is responsible for performing the initialization of the MvvmCross framework, including:
|
||||
|
||||
- The IoC Container and DI engine
|
||||
- The Data-Binding engine
|
||||
- The ViewModel / View lookups
|
||||
- The whole navigation system
|
||||
- Plugins
|
||||
|
||||
Finally, the `Setup` class is also responsible for initializing your `App` class.
|
||||
|
||||
Luckily for us, all this functionality is provided for you automatically, unless you want / need to use a custom `Setup` class (since it is an excellent place to register your own services / plugins, it is often the case).
|
||||
|
||||
## Add your View
|
||||
|
||||
### Add the Android Layout XML (AXML)
|
||||
|
||||
This tutorial doesn't attempt to give an introduction to Android XML layout.
|
||||
This tutorial doesn't attempt to give an introduction to Android XML layout, but any knowledge is actually really necessary at this point. If you are very new to Android, you can read more about Android XML on the [official documentation](http://developer.android.com/guide/topics/ui/declaring-layout.html).
|
||||
|
||||
Instead all I'll say here is the bare minimum. If you are new to Android, then you can find out more about Android XML from lots of places including the official documentation at [this site](http://developer.android.com/guide/topics/ui/declaring-layout.html). If you are coming from a XAML background - you are a *XAMLite* - then I'll include some simple XAML-AXML comparisons to help you out.
|
||||
To achieve the basic layout that we need:
|
||||
|
||||
To achieve the basic layout:
|
||||
- We will add a new .axml file - called `TipView.axml` into the `/Resources/Layout` folder.
|
||||
|
||||
- We'll add a new AXML file - `View_Tip.axml` in the `/Resources/Layout` folder
|
||||
|
||||
- We'll edit this using either the Xamarin Android designer or the Visual Studio XML editor - the designer gives us a visual display, while the VS editor **sometimes** gives us XML Intellisense.
|
||||
- We will edit this file using the XML editor - the designer gives us a visual display, while the VS editor **sometimes** gives us XML Intellisense. Open the file, go to the "Source" tab and replace the file content with the following code:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
@ -115,7 +118,7 @@ To achieve the basic layout:
|
|||
android:layout_height="match_parent" />
|
||||
```
|
||||
|
||||
- We'll add a local app namespace - `http://schemas.android.com/apk/res-auto` - this is just like adding a namespace in XAML.
|
||||
- We will now add a local app namespace - `http://schemas.android.com/apk/res-auto` - which is just like adding a namespace in XAML:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
@ -128,7 +131,7 @@ To achieve the basic layout:
|
|||
|
||||
- Notice that the `LinearLayout` has by default a **horizontal** orientation - for XAMLites, this layout is just like a `StackPanel` except that it is **very important** to specify the **vertical** orientation
|
||||
|
||||
- Within this layout we'll add some `TextView`s to provide some static text labels - for XAMLites, these are like `TextBlock`s
|
||||
- Within this layout we will add some `TextView`s to provide some static text labels:
|
||||
|
||||
```xml
|
||||
<TextView
|
||||
|
@ -145,7 +148,7 @@ To achieve the basic layout:
|
|||
android:text="Tip to leave" />
|
||||
```
|
||||
|
||||
- We'll also add a short, wide `View` with a yellow background to provide a small amount of chrome:
|
||||
- We will also add a short, wide `View` with a yellow background to provide a small amount of chrome after the last `TextView`:
|
||||
|
||||
```xml
|
||||
<View
|
||||
|
@ -154,9 +157,9 @@ To achieve the basic layout:
|
|||
android:background="#ffff00" />
|
||||
```
|
||||
|
||||
- We'll add some `View`s for data display and entry, and we'll **databind** these `View`s to properties in our `TipViewModel`
|
||||
- Now it's time to add some `View`s for data display and data entry, which we will also **databind** to properties in our `TipViewModel`:
|
||||
|
||||
- An `EditText` for text data entry of the `SubTotal` - for XAMLites, this is a `TextBox`
|
||||
- Add an `EditText` for text data entry of the `SubTotal`:
|
||||
|
||||
```xml
|
||||
<EditText
|
||||
|
@ -165,7 +168,7 @@ To achieve the basic layout:
|
|||
local:MvxBind="Text SubTotal" />
|
||||
```
|
||||
|
||||
- A `SeekBar` for touch/slide entry of the `Generosity` - for XAMLites, this is like a `ProgressBar`:
|
||||
- Add a `SeekBar` for touch/slide entry of the `Generosity`:
|
||||
|
||||
```xml
|
||||
<SeekBar
|
||||
|
@ -175,7 +178,7 @@ To achieve the basic layout:
|
|||
local:MvxBind="Progress Generosity" />
|
||||
```
|
||||
|
||||
- We'll add a `TextView` to display the `Tip` that results from the calculation:
|
||||
- Now for a last step, add a `TextView` to display the final `Tip` result:
|
||||
|
||||
```xml
|
||||
<TextView
|
||||
|
@ -184,7 +187,7 @@ To achieve the basic layout:
|
|||
local:MvxBind="Text Tip" />
|
||||
```
|
||||
|
||||
Put together, this looks like:
|
||||
Putting everything together, your .axml file should look like this:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
@ -227,57 +230,53 @@ Put together, this looks like:
|
|||
|
||||
### About the data-binding syntax
|
||||
|
||||
Each of the data-binding blocks within our first sample looks similar:
|
||||
You may have noticed that each of the data-binding blocks within our first sample look very similar, for example:
|
||||
|
||||
`local:MvxBind="Text SubTotal"`
|
||||
```xml
|
||||
local:MvxBind="Text SubTotal"
|
||||
```
|
||||
|
||||
What this line means is:
|
||||
|
||||
- Data-bind:
|
||||
- The property `Text` on the `View`
|
||||
- To the property `SubTotal` on the `DataContext` - which in this case will be a `TipViewModel`
|
||||
- So:
|
||||
- Whenever the `TipViewModel` calls `RaisePropertyChanged` on `SubTotal` then the `View` should update
|
||||
- And whenever the user enters text into the `View` then the `SubTotal` value should be `set` on the `TipViewModel`
|
||||
Data-Binding:
|
||||
- The property `Text` of the `TextView`
|
||||
- To the property `SubTotal` on the `DataContext` - which in this case will be a `TipViewModel`.
|
||||
|
||||
This means:
|
||||
- Whenever the `TipViewModel` calls `RaisePropertyChanged` on `SubTotal` then the `View` should update its content
|
||||
- And whenever the user enters text into the `View`, the `SubTotal` value should be `set` on the `TipViewModel`
|
||||
|
||||
Note that this `TwoWay` binding is **different** to XAML where generally the default `BindingMode` is only `OneWay`.
|
||||
|
||||
In later topics, we'll return to show you more options for data-binding, including how to use `ValueConverter`s, but for now all our binding uses this simple `ViewProperty ViewModelProperty` syntax.
|
||||
|
||||
|
||||
### Add the View class
|
||||
|
||||
With our AXML layout complete, we can now add the C# `Activity` which is used to display this content. For developers coming from XAML backgrounds, these `Activity` classes are roughly equivalent to `Page` objects in WindowsPhone on WindowsStore applications - they own the 'whole screen' and have a lifecycle which means that only one of them is shown at any one time.
|
||||
With our .axml layout complete, we can now move back to C# and add an `Activity`, which is used to display the content. These `Activity` classes are very special objects on Android, which provide a `context` to your app and a placeholder to display widgets on the UI.
|
||||
|
||||
To create our `Activity` - which will also be our MVVM `View`:
|
||||
This `Activity` will act as our MVVM `View`. Please follow these steps:
|
||||
|
||||
- Create a Views folder within your TipCalc.UI.Droid project
|
||||
- Create a `Views` folder within your TipCalc.Droid project.
|
||||
|
||||
- Within this folder create a new C# class - `TipView`
|
||||
|
||||
Not that the name of this class **MUST** match the name of the viewmodel. As our viewmodel is called TipViewModel our class must be named TipView).
|
||||
|
||||
- This class will:
|
||||
|
||||
- Inherit from `MvxActivity`:
|
||||
- Within this folder create a new C# class called `TipView`.
|
||||
|
||||
- This class should inherit from `MvxActivity<TipViewModel>`:
|
||||
|
||||
```c#
|
||||
public class TipCalcView : MvxActivity"
|
||||
public class TipView : MvxActivity<TipViewModel>"
|
||||
```
|
||||
|
||||
- Be marked with the Xamarin.Android `Activity` attribute, marking it as the `MainLauncher` for the project:
|
||||
- Add an `Activity` attribute over the class and set the `MainLauncher` property to `true`. This attribute lets Xamarin.Android add it automatically to your AndroidManifest file:
|
||||
|
||||
```c#
|
||||
[Activity(Label = "Tip", MainLauncher=true)]
|
||||
[Activity(Label = "Tip Calculator", MainLauncher = true)]
|
||||
```
|
||||
|
||||
- Use `OnViewModelSet` to inflate its `ContentView` from the AXML - this will use a resource identifier generated by the Android and Xamarin tools.
|
||||
- Override the method `OnCreate` and call `SetContentView()` right after the call to base:
|
||||
|
||||
```c#
|
||||
protected override void OnViewModelSet()
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
SetContentView(Resource.Layout.View_Tip);
|
||||
base.OnCreate(bundle);
|
||||
SetContentView(Resource.Layout.TipView);
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -285,47 +284,48 @@ As a result this completed class is very simple:
|
|||
|
||||
```c#
|
||||
using Android.App;
|
||||
using MvvmCross.Droid.Views;
|
||||
using Android.OS;
|
||||
using MvvmCross.Platforms.Android.Views;
|
||||
using TipCalc.Core.ViewModels;
|
||||
|
||||
namespace TipCalc.UI.Droid.Views
|
||||
namespace TipCalc.Droid.Views
|
||||
{
|
||||
[Activity(Label = "Tip", MainLauncher = true)]
|
||||
public class TipView : MvxActivity
|
||||
{
|
||||
protected override void OnViewModelSet()
|
||||
[Activity(Label = "Tip Calculator", MainLauncher = true)]
|
||||
public class TipView : MvxActivity<TipViewModel>
|
||||
{
|
||||
SetContentView(Resource.Layout.View_Tip);
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
base.OnCreate(bundle);
|
||||
SetContentView(Resource.Layout.TipView);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## The Android UI is complete!
|
||||
## The Android project is complete!
|
||||
|
||||
At this point you should be able to run your application.
|
||||
|
||||
When it starts... you should see:
|
||||
When it starts... you should see something like this:
|
||||
|
||||
![Android TipCalc](../../assets/img/tutorials/tipcalc/TipCalc_Android.png)
|
||||
![Android TipCalc]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Android.png)
|
||||
|
||||
If you then want to make it 'more beautiful', then try adding a few attributes to some of your AXML - things like:
|
||||
If you then want to make it 'more beautiful', then try adding a few attributes to some of your .axml - things like:
|
||||
|
||||
android:background="#00007f"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="24dp"
|
||||
android:layout_margin="30dp"
|
||||
android:padding="20dp"
|
||||
android:layout_marginTop="10dp"
|
||||
|
||||
Within a very short time, you should hopefully be able to create something 'styled'...
|
||||
|
||||
![Android TipCalc Styled](../../assets/img/tutorials/tipcalc/TipCalc_Android_Styled.png)
|
||||
|
||||
... but actually making it look 'nice' might take some design skills!
|
||||
```xml
|
||||
android:background="#00007f"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="24dp"
|
||||
android:layout_margin="30dp"
|
||||
android:padding="20dp"
|
||||
android:layout_marginTop="10dp"
|
||||
```
|
||||
|
||||
## Moving on
|
||||
|
||||
There's more we could do to make this User Interface nicer and to make the app richer... but for this first application, we will leave it here for now.
|
||||
|
||||
Let's move on to Xamarin.iOS and to Windows!
|
||||
Let's move on to Xamarin.iOS!
|
||||
|
||||
[Next!](https://www.mvvmcross.com/documentation/tutorials/tipcalc/a-xamarinios-ui-project)
|
||||
|
||||
|
|
|
@ -1,204 +1,147 @@
|
|||
---
|
||||
layout: documentation
|
||||
title: A Xamarin.iOS UI Project
|
||||
title: TipCalc iOS Project
|
||||
category: Tutorials
|
||||
order: 5
|
||||
order: 4
|
||||
---
|
||||
We started with the goal of creating an app to help calculate what tip to leave in a restaurant
|
||||
|
||||
We started with the goal of creating an app to help calculate what tip to leave in a restaurant.
|
||||
|
||||
We had a plan to produce a UI based on this concept:
|
||||
|
||||
![Sketch](../../assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
![Sketch]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
|
||||
To satisfy this we built a 'Core' Portable Class Library project which contained:
|
||||
To satisfy this we built a 'Core' .NET Standard project which contained:
|
||||
|
||||
* our 'business logic' - `ICalculation`
|
||||
* our ViewModel - `TipViewModel`
|
||||
* our `App` which contains the application wiring, including the start instructions.
|
||||
- Our 'business logic' - `ICalculationService`
|
||||
- Our ViewModel - `TipViewModel`
|
||||
- Our `App` - which contains some bootstrapping code.
|
||||
|
||||
We then added our first User Interface - for Xamarin.Android:
|
||||
We even added our first User Interface - for Xamarin.Android:
|
||||
|
||||
![Android TipCalc Styled](../../assets/img/tutorials/tipcalc/TipCalc_Android_Styled.png)
|
||||
![Android TipCalc]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Android.png)
|
||||
|
||||
For our next project, let's shift to Xamarin.iOS.
|
||||
Now let's move forward to Xamarin.iOS.
|
||||
|
||||
To create an iPhone MvvmCross UI, you can use the Visual Studio project template wizards, but here we'll instead build up a new project 'from empty', just as we did for the Core and Android projects.
|
||||
Same as we did with the _Core_ project, we will use a standard template to create the iOS project.
|
||||
|
||||
Also, to work with iPhone, for now we will switch to working on the Mac with Xamarin Studio
|
||||
## Create a new iOS Project
|
||||
|
||||
## Create a new iOS UI Project
|
||||
|
||||
Add a new project to your solution - a 'iOS' -> 'Single View App' application with name `TipCalc.UI.iOS`
|
||||
Add a new project to your solution - a 'Blank App (iOS)' application with name `TipCalc.iOS`.
|
||||
|
||||
Within this, you'll find the normal iOS application constructs:
|
||||
|
||||
* the Resources folder
|
||||
* the AppDelegate.cs class
|
||||
* the Entitlements.plist 'configuration' information
|
||||
* the Info.plist 'configuration' information
|
||||
* the Main.cs class
|
||||
* the Main.storyboard view
|
||||
- The `Resources` folder
|
||||
- The `AppDelegate.cs` class
|
||||
- The `Entitlements.plist`, which contains certain configurations - we won't touch it for now.
|
||||
- The `Info.plist` file, which is the equivalent for an Android ApplicationManifest.
|
||||
- The `Main.cs` class
|
||||
- The `Main.storyboard` view
|
||||
- A default `ViewController` xib and class
|
||||
|
||||
## Install MvvmCross
|
||||
|
||||
Click the `Add NuGet Packages...` item in the `Project` menu. Install `MvvmCross.Binding`.
|
||||
Open the Nuget Package Manager and search for the package `MvvmCross`.
|
||||
|
||||
## Add a reference to TipCalc.Core.csproj
|
||||
If you don't really enjoy the NuGet UI experience, then you can alternatively open the Package Manager Console, and type:
|
||||
|
||||
Add a reference to your `TipCalc.Core` project - the project we created in the last step which included:
|
||||
Install-Package MvvmCross
|
||||
|
||||
* your `Calculation` service,
|
||||
* your `TipViewModel`
|
||||
* your `App` wiring.
|
||||
## Delete ViewController.cs and Main.storyboard
|
||||
|
||||
## Add a Setup class
|
||||
Because we will add our own view.
|
||||
|
||||
Just as we said during the Android construction *Every MvvmCross UI project requires a `Setup` class*
|
||||
After you have deleted those, open the `Info.plist` file and under the 'Deployment Info' section, remove the text in the 'Main Interface' input (leave it blank).
|
||||
|
||||
This class sits in the root namespace (folder) of our UI project and performs the initialization of the MvvmCross framework and your application, including:
|
||||
## Add a reference to TipCalc.Core project
|
||||
|
||||
* the Inversion of Control (IoC) system
|
||||
* the MvvmCross data-binding
|
||||
* your `App` and its collection of `ViewModel`s
|
||||
* your UI project and its collection of `View`s
|
||||
Add a reference to your `TipCalc.Core` project - the project we created in the first step.
|
||||
|
||||
Most of this functionality is provided for you automatically. Within your iOS UI project all you have to supply are:
|
||||
## Edit the AppDelegate.cs file
|
||||
|
||||
- your `App` - your link to the business logic and `ViewModel` content
|
||||
The `AppDelegate` class plays a very important role on iOS apps, as it provides a set of callback that the OS uses to inform you about events in your application's lifecycle.
|
||||
|
||||
For `TipCalc` here's all that is needed in Setup.cs:
|
||||
|
||||
```c#
|
||||
using MvvmCross.iOS.Platform;
|
||||
using MvvmCross.iOS.Views.Presenters;
|
||||
using MvvmCross.Core.ViewModels;
|
||||
using TipCalc.Core;
|
||||
|
||||
namespace TipCalc.UI.iOS
|
||||
{
|
||||
public class Setup : MvxIosSetup
|
||||
{
|
||||
public Setup(MvxApplicationDelegate appDelegate, IMvxIosViewPresenter presenter)
|
||||
: base(appDelegate, presenter)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IMvxApplication CreateApp ()
|
||||
{
|
||||
return new App();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Modify the AppDelegate to use Setup
|
||||
|
||||
Your `AppDelegate` provides a set of callback that iOS uses to inform you about events in your application's lifecycle.
|
||||
|
||||
To use this `AppDelegate` within MvvmCross, we need to:
|
||||
|
||||
* modify it so that it inherits from `MvxApplicationDelegate` instead of `UIApplicationDelegate`
|
||||
|
||||
public partial class AppDelegate : MvxApplicationDelegate
|
||||
|
||||
* modify it so that the method that is called on startup (FinishedLaunching) does some UI application setup:
|
||||
|
||||
* create a new presenter - this is the class that will determine how Views are shown - for this sample, we choose a 'standard' one:
|
||||
|
||||
```c#
|
||||
var presenter = new MvxIosViewPresenter(this, Window);
|
||||
```
|
||||
|
||||
* create and call Initialize on a `Setup`:
|
||||
|
||||
```c#
|
||||
var setup = new Setup(this, presenter);
|
||||
setup.Initialize();
|
||||
```
|
||||
|
||||
* with `Setup` completed, use the `Mvx` Inversion of Control container in order to find and `Start` the `IMvxAppStart` object:
|
||||
|
||||
```c#
|
||||
var startup = Mvx.Resolve<IMvxAppStart>();
|
||||
startup.Start();
|
||||
```
|
||||
|
||||
Together, this looks like:
|
||||
It will also allow us to specify the MvvmCross framework some key classes to be used for initialization, the same way the Android MainApplication did. This is what your AppDelegate class should look like:
|
||||
|
||||
```c#
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
using MvvmCross.iOS.Platform;
|
||||
using MvvmCross.iOS.Views.Presenters;
|
||||
using MvvmCross.Platform;
|
||||
using MvvmCross.Core.ViewModels;
|
||||
using TipCalc.Core;
|
||||
using MvvmCross.Platforms.Ios.Core;
|
||||
|
||||
namespace TipCalc.UI.iOS
|
||||
namespace TipCalc.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : MvxApplicationDelegate
|
||||
{
|
||||
public override UIWindow Window {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
|
||||
[Register(nameof(AppDelegate))]
|
||||
public class AppDelegate : MvxApplicationDelegate<MvxIosSetup<App>, App>
|
||||
{
|
||||
Window = new UIWindow(UIScreen.MainScreen.Bounds);
|
||||
public override UIWindow Window { get; set; }
|
||||
|
||||
var presenter = new MvxIosViewPresenter(this, Window);
|
||||
// FinishedLaunching is the very first code to be executed in your app. Don't forget to call base!
|
||||
public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
|
||||
{
|
||||
var result = base.FinishedLaunching(application, launchOptions);
|
||||
|
||||
var setup = new Setup(this, presenter);
|
||||
setup.Initialize();
|
||||
|
||||
var startup = Mvx.Resolve<IMvxAppStart>();
|
||||
startup.Start();
|
||||
|
||||
Window.MakeKeyAndVisible();
|
||||
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the code snippet above, we're extending the app's `AppDelegate` from `MvxApplicationDelegate`, and that class contains two generics: one for a setup class, and one for a core app class.
|
||||
|
||||
In our case, we want to use the default `Setup` class MvvmCross provides for iOS applications. In your own app, you may want to use a custom one, which extends `MvxIosSetup<App>`, to add some customization.
|
||||
|
||||
That's it! MvvmCross is up and running after this small addition.
|
||||
|
||||
### Some more details about the Setup class
|
||||
|
||||
Every MvvmCross UI project requires a `Setup` class, but if your app is fairly simple, like the TipCalc is, then you can safely use the default one, provided by the framework.
|
||||
|
||||
The `Setup` class is responsible for performing the initialization of the MvvmCross framework, including:
|
||||
|
||||
- The IoC Container and DI engine
|
||||
- The Data-Binding engine
|
||||
- The ViewModel / View lookups
|
||||
- The whole navigation system
|
||||
- Plugins
|
||||
|
||||
Finally, the `Setup` class is also responsible for initializing your `App` class.
|
||||
|
||||
Luckily for us, all this functionality is provided for you automatically, unless you want / need to use a custom `Setup` class (since it is an excellent place to register your own services / plugins, it is often the case).
|
||||
|
||||
## Add your View
|
||||
|
||||
### Create an initial UIViewController
|
||||
|
||||
Create a Views folder
|
||||
Create a folder called `Views`.
|
||||
|
||||
Within this, add a new 'View Controller' and call it `TipView`
|
||||
Within this, add a new 'View Controller' and call it `TipView`.
|
||||
|
||||
This will generate:
|
||||
This will generate three classes:
|
||||
|
||||
* TipView.cs
|
||||
* TipView.designer.cs
|
||||
* TipView.xib
|
||||
- TipView.cs
|
||||
- TipView.designer.cs
|
||||
- TipView.xib
|
||||
|
||||
### Edit the XIB layout
|
||||
|
||||
Double click on the XIB file to edit it.
|
||||
|
||||
Just as we did with Android, I won't go into depth here about how to use the XIB iOS editor - instead I'll just cover the bare basics, and I'll also try to provide some comparisons for those familiar with XAML.
|
||||
Just as we did with Android, we won't go into depth here about how to use the XIB iOS editor - instead we will just cover the bare basics.
|
||||
|
||||
Drag/drop from the 'Toolbox' to add:
|
||||
Drag/Drop from the 'Toolbox' to add:
|
||||
|
||||
* some `Label`s for showing static text - these are like `TextBlock`s
|
||||
* a `Text Field` named `SubTotalTextField` for editing the `SubTotal` - this is like a `TextBox`
|
||||
* a `Slider` named `GenerositySlider` for editing the `Generosity` - this is like a `ProgressBar`
|
||||
* a `Label` named `TipLabel` for showing the `Tip` result - this is like a `TextBlock`
|
||||
- A `Label` which text should be `SubTotal` and a `TextField` named `SubTotalTextField`, for editing the value
|
||||
- A `Label` which text should be `Generosity` and a `Slider` named `GenerositySlider`, for editing the "generosity"
|
||||
- a `Label` named `TipLabel,` for showing the `Tip` result
|
||||
|
||||
Set the Maximum Value of the `Slider` to '100'.
|
||||
|
||||
Using drag and drop, you should be able to quite quickly generate a design similar to:
|
||||
|
||||
![iOS design](../../assets/img/tutorials/tipcalc/TipCalc_Touch_Design.png)
|
||||
Using drag and drop, you should be able to quite quickly generate a design similar what we accomplished on Android.
|
||||
|
||||
### Edit TipView.cs
|
||||
|
||||
Because we want our `TipView` to be not only a `UIViewController` but also an Mvvm `View`, then change the inheritance of `TipView` so that it inherits from `MvxViewController`.
|
||||
Because we want our `TipView` to be not only a `UIViewController` but also an MVVM `View`, then change the inheritance of `TipView` so that it inherits from `MvxViewController`.
|
||||
|
||||
```c#
|
||||
public class TipView : MvxViewController<TipViewModel>
|
||||
|
@ -215,87 +158,89 @@ public override void ViewDidLoad()
|
|||
{
|
||||
base.ViewDidLoad();
|
||||
|
||||
this.CreateBinding(TipLabel).To((TipViewModel vm) => vm.Tip).Apply();
|
||||
this.CreateBinding(SubTotalTextField).To((TipViewModel vm) => vm.SubTotal).Apply();
|
||||
this.CreateBinding(GenerositySlider).To((TipViewModel vm) => vm.Generosity).Apply();
|
||||
var set = this.CreateBindingSet<TipView, TipViewModel>();
|
||||
set.Bind(TipLabel).To(vm => vm.Tip);
|
||||
set.Bind(SubTotalTextField).To(vm => vm.SubTotal);
|
||||
set.Bind(GenerositySlider).To(vm => vm.Generosity);
|
||||
set.Apply();
|
||||
}
|
||||
```
|
||||
|
||||
What this code does is to generate 'in code' exactly the same type of data-binding information as we generated 'in XML' in Android.
|
||||
What this code does is to generate 'in code' exactly the same type of data-binding information as we generated 'in XML' in Android. This binding syntax is called _fluent_.
|
||||
|
||||
**Note** that before the calls to `this.Bind` are made, then we first call `base.ViewDidLoad()`. This is important because `base.ViewDidLoad()` is where MvvmCross locates the `TipViewModel` that this `TipView` will bind to.
|
||||
**Note** You need to add all the bindings **after** `base.ViewDidLoad()` runs, because that's where MvvmCross locates the `TipViewModel`.
|
||||
|
||||
**Also note** that after you specify all your bindings, you must call `set.Apply()` for them to be added.
|
||||
|
||||
Altogether this looks like:
|
||||
|
||||
```c#
|
||||
using MvvmCross.Binding.BindingContext;
|
||||
using MvvmCross.iOS.Views;
|
||||
using MvvmCross.Platforms.Ios.Views;
|
||||
using TipCalc.Core.ViewModels;
|
||||
using UIKit;
|
||||
|
||||
namespace TipCalc.UI.iOS
|
||||
namespace TipCalc.iOS
|
||||
{
|
||||
public partial class TipView : MvxViewController<TipViewModel>
|
||||
{
|
||||
public TipView() : base("TipView", null)
|
||||
public partial class TipView : MvxViewController<TipViewModel>
|
||||
{
|
||||
}
|
||||
public TipView() : base(nameof(TipView), null)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ViewDidLoad()
|
||||
{
|
||||
base.ViewDidLoad();
|
||||
public override void ViewDidLoad()
|
||||
{
|
||||
base.ViewDidLoad();
|
||||
|
||||
this.CreateBinding(TipLabel).To((TipViewModel vm) => vm.Tip).Apply();
|
||||
this.CreateBinding(SubTotalTextField).To((TipViewModel vm) => vm.SubTotal).Apply();
|
||||
this.CreateBinding(GenerositySlider).To((TipViewModel vm) => vm.Generosity).Apply();
|
||||
var set = this.CreateBindingSet<TipView, TipViewModel>();
|
||||
set.Bind(TipLabel).To(vm => vm.Tip);
|
||||
set.Bind(SubTotalTextField).To(vm => vm.SubTotal);
|
||||
set.Bind(GenerositySlider).To(vm => vm.Generosity);
|
||||
set.Apply();
|
||||
|
||||
// this is optional. What this code does is to close the keyboard whenever you
|
||||
// tap on the screen, outside the bounds of the TextField
|
||||
View.AddGestureRecognizer(new UITapGestureRecognizer(() =>
|
||||
{
|
||||
this.SubTotalTextField.ResignFirstResponder();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Binding in Xamarin.iOS
|
||||
|
||||
You will no doubt have noticed that data-binding in iOS looks very different to the way it looked in Android - and to what you may have expected from XAML.
|
||||
You may have noticed that data-binding in iOS looks very different to the way it looked in Android.
|
||||
|
||||
This is because the XIB format used in iOS is a lot less human manipulable and extensible than the XML formats used in Android AXML and Windows XAML - so it makes more sense to use C# rather than the XIB to register our bindings.
|
||||
This is because the XIB format used in iOS is a lot less human manipulable and extensible than the XML formats used in Android AXML and or Xamarin.Forms / Windows XAML - so it makes more sense to use C# rather than the XIB to register our bindings.
|
||||
|
||||
Within this section of the tutorial all of our iOS bindings look like:
|
||||
|
||||
```c#
|
||||
this.CreateBinding(TipLabel).To((TipViewModel vm) => vm.Tip).Apply();
|
||||
set.Bind(TipLabel).To(vm => vm.Tip);
|
||||
```
|
||||
|
||||
what this line means is:
|
||||
|
||||
* bind the `TipLabel`'s default binding property - which happens to be a property called `Text`
|
||||
* to the `ViewModel`'s Tip property
|
||||
|
||||
As with Android, this will be a `TwoWay` binding by default - which is different to what XAML developers may expect to see.
|
||||
- Bind the `TipLabel`'s default binding property - which happens to be the `Text` property (for labels)
|
||||
- To the `ViewModel`'s Tip property
|
||||
|
||||
If you had wanted to specify the `TipLabel` property to use instead of relying on the default, then you could have done this with:
|
||||
|
||||
```c#
|
||||
this.CreateBinding(TipLabel).For(label => label.Text).To((TipViewModel vm) => vm.Tip).Apply();
|
||||
set.Bind(TipLabel).For(v => v.Text).To(vm => vm.Tip);
|
||||
```
|
||||
|
||||
In later topics we'll cover more on binding in iOS, including more on binding to non-default fields; other code-based binding code mechanisms; custom bindings; using `ValueConverter`s; and creating bound sub-views.
|
||||
Although this sample only shows simple bindings, the infrastructure built within MvvmCross is really powerful! Our data-binding engine supports ValueConverters, ValueCombiners, FallbackValues, different modes of bindings and a super straight forward mechanism to add your own custom bindings.
|
||||
|
||||
## The iOS UI is complete!
|
||||
|
||||
At this point you should be able to run your application.
|
||||
At this point you should be able to run your application and see some magic.
|
||||
|
||||
When it starts... you should see:
|
||||
|
||||
![ios ui](../../assets/img/tutorials/tipcalc/TipCalc_Touch_Sim.png)
|
||||
|
||||
This seems to work perfectly, although you may notice that if you tap on the `SubTotal` property and start entering text, then you cannot afterwards close the keyboard.
|
||||
|
||||
This is a View concern - it is a UI problem. So we can fix it just in the iOS UI code - in this View. For example, to fix this here, you can add a gesture recognizer to the end of the `ViewDidLoad` method like:
|
||||
|
||||
```c#
|
||||
View.AddGestureRecognizer(new UITapGestureRecognizer(() => {
|
||||
this.SubTotalTextField.ResignFirstResponder();
|
||||
}));
|
||||
```
|
||||
![ios ui]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Touch_Sim.png)
|
||||
|
||||
## Moving on...
|
||||
|
||||
|
@ -303,3 +248,4 @@ There's more we could do to make this User Interface nicer and to make the app r
|
|||
|
||||
Let's move on to Windows!
|
||||
|
||||
[Next!](https://www.mvvmcross.com/documentation/tutorials/tipcalc/a-universal-windows-app-ui-project)
|
|
@ -4,86 +4,55 @@ title: The Core Project
|
|||
category: Tutorials
|
||||
order: 2
|
||||
---
|
||||
MvvmCross application's are normally structured with:
|
||||
MvvmCross applications normally consist on:
|
||||
|
||||
* one shared 'core' Portable Class Library (PCL) project
|
||||
* containing as much code as possible: models, view models, services, converters, etc
|
||||
* one UI project per platform
|
||||
* each containing the bootstrap and view-specific code for that platform
|
||||
- A 'Core' project in the form of a .NET Standard library, which will contain all the shared code (so you want to maximize the amount of code placed in this project). The _Core_ will contain Models, ViewModels, Services, Converters, ...
|
||||
- One 'Platform' project per targeted platform. These projects will contain some framework initialization code, Views and SDK dependant code.
|
||||
|
||||
Normally, you start development from the core project - and that's exactly what we'll do here.
|
||||
Normally, you start development from the _Core_ project - and that's exactly what we'll do here.
|
||||
|
||||
To create the core, you can use the Visual Studio project template wizards, but here we'll instead build up a new project 'from empty'.
|
||||
Although it is recommended that you install any of the community made solution templates, we'll use a blank solution on this tutorial.
|
||||
|
||||
## Create the new Portable Class Library
|
||||
## Create the new .NET Standard library
|
||||
|
||||
Using Visual Studio, create your new `Class Library (Portable)` project using the File|New Project wizard.
|
||||
Using Visual Studio, create your new `.NET Standard 2 Library` project using the File|New Project wizard.
|
||||
|
||||
Call it something like TipCalc.Core.csproj and name the solution TipCalc.
|
||||
Call it something like `TipCalc.Core` and name the solution `TipCalc`.
|
||||
|
||||
When asked to choose platforms, select .NET Framework 4.5, Windows 8, Windows Phone Silverlight 8, Windows Phone 8.1, Xamarin.Android and Xamarin.iOS - this will ensure that the PCL is in **Profile259**. If Visual Studio stops you selecting these targets with the error 'The selection does not match any portable APIs' then use the workaround described here: http://danrigby.com/2014/04/10/windowsphone81-pcl-xamarin-fix/
|
||||
## Delete any auto-generated class
|
||||
|
||||
Profile259 defines a small subset of .Net including:
|
||||
No-one really needs something like `Class1.cs` :)
|
||||
|
||||
* Microsoft.CSharp
|
||||
* mscorelib
|
||||
* System.Collections
|
||||
* System.ComponentModel
|
||||
* System.Core
|
||||
* System.Diagnostics
|
||||
* System
|
||||
* System.Globalization
|
||||
* System.IO
|
||||
* System.Linq
|
||||
* System.Net
|
||||
* System.ObjectModel
|
||||
* System.Reflection
|
||||
* System.Resources.ResourceManager
|
||||
* System.Runtime
|
||||
* System.Security.Principal
|
||||
* System.ServiceModel.Web
|
||||
* System.Text.Encoding
|
||||
* System.Text.RegularExpressions
|
||||
* System.Threading
|
||||
* System.Windows
|
||||
* System.Xml
|
||||
## Install MvvmCross. Yey!
|
||||
|
||||
To see the full list of assemblies, look in `C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETPortable\v4.5\Profile\Profile259`
|
||||
Open the Nuget Package Manager and search for the package `MvvmCross`.
|
||||
|
||||
Importantly for us this Profile259 includes everything we need to build our Mvvm applications.
|
||||
If you don't really enjoy the NuGet UI experience, then you can alternatively open the Package Manager Console, and type:
|
||||
|
||||
## Delete Class1.cs
|
||||
Install-Package MvvmCross
|
||||
|
||||
No-one really needs a `Class1` :)
|
||||
## Add the Tip Calculation Service
|
||||
|
||||
## Install MvvmCross
|
||||
Create a folder called `Services`.
|
||||
|
||||
In the Package Manager Console, enter...
|
||||
|
||||
Install-Package MvvmCross.Core
|
||||
|
||||
## Add the Tip Calculation service
|
||||
|
||||
Create a folder called 'Services'
|
||||
|
||||
Within this folder create a new Interface which will be used for calculating tips:
|
||||
Within this folder create a new interface, which will be used for calculating tips:
|
||||
|
||||
```c#
|
||||
namespace TipCalc.Core.Services
|
||||
{
|
||||
public interface ICalculation
|
||||
public interface ICalculationService
|
||||
{
|
||||
double TipAmount(double subTotal, int generosity);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Within this folder create an implementation of this interface:
|
||||
Within the `Services` folder now create an implementation for the interface:
|
||||
|
||||
```c#
|
||||
namespace TipCalc.Core.Services
|
||||
{
|
||||
public class Calculation : ICalculation
|
||||
public class CalculationService : ICalculationService
|
||||
{
|
||||
public double TipAmount(double subTotal, int generosity)
|
||||
{
|
||||
|
@ -93,25 +62,23 @@ namespace TipCalc.Core.Services
|
|||
}
|
||||
```
|
||||
|
||||
This provides us with some simple business logic for our app
|
||||
This provides us with some simple business logic for our app.
|
||||
|
||||
## Add the ViewModel
|
||||
|
||||
At a sketch level, we want a user interface that:
|
||||
|
||||
* uses:
|
||||
* our calculation service to calculate the tip
|
||||
* has inputs of:
|
||||
* the current bill (the subTotal)
|
||||
* a feeling for how much tip we'd like to leave (the generosity)
|
||||
* has output displays of:
|
||||
* the calculated tip to leave
|
||||
- Uses our calculation service to calculate the tip
|
||||
- Has inputs of:
|
||||
- The current bill (the SubTotal)
|
||||
- A feeling for how much tip we'd like to leave (the generosity)
|
||||
- Has an output for the calculated tip to leave
|
||||
|
||||
To represent this user interface we need to build a 'model' for the user interface - which is, of course, a 'ViewModel'
|
||||
To represent this user interface we need to build a 'model' for it. In other words, we need a `ViewModel`.
|
||||
|
||||
Within MvvmCross, all ViewModels should inherit from `MvxViewModel`.
|
||||
Within MvvmCross, all ViewModels _should_ inherit from `MvxViewModel`.
|
||||
|
||||
So now create a ViewModels folder in our project, and in this folder add a new `TipViewModel` class like:
|
||||
So now let's create a folder called `ViewModels`, and inside of it a new class named `TipViewModel`. This is what it should look like:
|
||||
|
||||
```c#
|
||||
using MvvmCross.Core.ViewModels;
|
||||
|
@ -121,58 +88,53 @@ namespace TipCalc.Core.ViewModels
|
|||
{
|
||||
public class TipViewModel : MvxViewModel
|
||||
{
|
||||
readonly ICalculation _calculation;
|
||||
readonly ICalculationService _calculationService;
|
||||
|
||||
public TipViewModel(ICalculation calculation)
|
||||
public TipViewModel(ICalculationService calculationService)
|
||||
{
|
||||
_calculation = calculation;
|
||||
_calculationService = calculationService;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
public override async Task Initialize()
|
||||
{
|
||||
await base.Initialize();
|
||||
|
||||
_subTotal = 100;
|
||||
_generosity = 10;
|
||||
|
||||
Recalculate();
|
||||
base.Start();
|
||||
}
|
||||
|
||||
double _subTotal;
|
||||
|
||||
private double _subTotal;
|
||||
public double SubTotal
|
||||
{
|
||||
get {
|
||||
return _subTotal;
|
||||
}
|
||||
get => _subTotal;
|
||||
set
|
||||
{
|
||||
_subTotal = value;
|
||||
RaisePropertyChanged(() => SubTotal);
|
||||
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
int _generosity;
|
||||
|
||||
private int _generosity;
|
||||
public int Generosity
|
||||
{
|
||||
get {
|
||||
return _generosity;
|
||||
}
|
||||
get => _generosity;
|
||||
set
|
||||
{
|
||||
_generosity = value;
|
||||
RaisePropertyChanged(() => Generosity);
|
||||
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
double _tip;
|
||||
|
||||
private double _tip;
|
||||
public double Tip
|
||||
{
|
||||
get {
|
||||
return _tip;
|
||||
}
|
||||
get => _tip;
|
||||
set
|
||||
{
|
||||
_tip = value;
|
||||
|
@ -180,85 +142,78 @@ namespace TipCalc.Core.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
void Recalculate()
|
||||
private void Recalculate()
|
||||
{
|
||||
Tip = _calculation.TipAmount(SubTotal, Generosity);
|
||||
Tip = _calculationService.TipAmount(SubTotal, Generosity);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For many of you, this `TipViewModel` will already make sense to you. If it does then **skip ahead** to 'Add the App(lication)'. If not, then here are some simple explanations:
|
||||
It is possible that this `TipViewModel` will already make sense to you. If it does, then **skip ahead** to 'Add the App(lication)'. If not, then here are some further explanations:
|
||||
|
||||
* the `TipViewModel` is constructed with an `ICalculation` service
|
||||
- the `TipViewModel` is constructed with an `ICalculationService` service, which is injected using the MvvmCross Dependency Injection engine.
|
||||
|
||||
```c#
|
||||
readonly ICalculation _calculation;
|
||||
readonly ICalculationService _calculationService;
|
||||
|
||||
public TipViewModel(ICalculation calculation)
|
||||
public TipViewModel(ICalculationService calculationService)
|
||||
{
|
||||
_calculation = calculation;
|
||||
_calculationService = calculationService;
|
||||
}
|
||||
```
|
||||
|
||||
* after construction, the `TipViewModel` will be started - during this it sets some initial values.
|
||||
- After construction, the `TipViewModel` runs the `Initialize` method, which is part of the [ViewModel lifecycle](https://www.mvvmcross.com/documentation/fundamentals/viewmodel-lifecycle) - during this it sets some initial values.
|
||||
|
||||
```c#
|
||||
public override void Start()
|
||||
public override async Task Initialize()
|
||||
{
|
||||
// set some start values
|
||||
SubTotal = 100.0;
|
||||
Generosity = 10;
|
||||
await base.Initialize();
|
||||
|
||||
_subTotal = 100;
|
||||
_generosity = 10;
|
||||
|
||||
Recalculate();
|
||||
base.Start();
|
||||
}
|
||||
```
|
||||
|
||||
* the view data held within the `TipViewModel` is exposed through properties.
|
||||
* Each of these properties is backed by a private member variable
|
||||
* Each of these properties has a get and a set
|
||||
* The set accessor for `Tip` is marked private
|
||||
* All of the set accessors call `RaisePropertyChanged` to tell the base `MvxViewModel` that the data has changed
|
||||
* The `SubTotal` and `Generosity` set accessors also call `Recalculate()`
|
||||
- The view data held within the `TipViewModel` is exposed through properties, where:
|
||||
- Each of these properties is backed by a private member variable
|
||||
- Each of these properties has a getter and a setter
|
||||
- All of the set accessors call `RaisePropertyChanged` to tell the base `MvxViewModel` that the data has changed
|
||||
- The `SubTotal` and `Generosity` set accessors also call `Recalculate()`
|
||||
|
||||
```c#
|
||||
double _subTotal;
|
||||
|
||||
private double _subTotal;
|
||||
public double SubTotal
|
||||
{
|
||||
get {
|
||||
return _subTotal;
|
||||
}
|
||||
get => _subTotal;
|
||||
set
|
||||
{
|
||||
_subTotal = value;
|
||||
RaisePropertyChanged(() => SubTotal);
|
||||
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
int _generosity;
|
||||
|
||||
private int _generosity;
|
||||
public int Generosity
|
||||
{
|
||||
get {
|
||||
return _generosity;
|
||||
}
|
||||
get => _generosity;
|
||||
set
|
||||
{
|
||||
_generosity = value;
|
||||
RaisePropertyChanged(() => Generosity);
|
||||
|
||||
Recalculate();
|
||||
}
|
||||
}
|
||||
|
||||
double _tip;
|
||||
|
||||
private double _tip;
|
||||
public double Tip
|
||||
{
|
||||
get {
|
||||
return _tip;
|
||||
}
|
||||
get => _tip;
|
||||
set
|
||||
{
|
||||
_tip = value;
|
||||
|
@ -267,55 +222,51 @@ public double Tip
|
|||
}
|
||||
```
|
||||
|
||||
* The `Recalculate` method uses the `_calculation` service to update `Tip` from the current values in `SubTotal` and `Generosity`
|
||||
- The private `Recalculate` method uses the `_calculationService` to update `Tip` from the current values of `SubTotal` and `Generosity`.
|
||||
|
||||
```c#
|
||||
void Recalculate()
|
||||
private void Recalculate()
|
||||
{
|
||||
Tip = _calculation.TipAmount(SubTotal, Generosity);
|
||||
Tip = _calculationService.TipAmount(SubTotal, Generosity);
|
||||
}
|
||||
```
|
||||
|
||||
## Add the App(lication)
|
||||
## Add the App(lication) class
|
||||
|
||||
With our `Calculation` service and `TipViewModel` defined, we now just need to add the main `App` code.
|
||||
With our `CalculationService` and our `TipViewModel` defined, we now just need to add the main `App` code. This code:
|
||||
|
||||
This code;
|
||||
- Will sit in a single class within the root folder of our .NET Standard project.
|
||||
- Will inherit from the `MvxApplication` class
|
||||
- Is normally just called `App`
|
||||
- Is responsible for providing:
|
||||
- Registration of which interfaces and implementations the app uses
|
||||
- Registration of which `ViewModel` the `App` will show when it starts
|
||||
|
||||
* will sit in a single class within the root folder of our PCL core project.
|
||||
* this class will inherits from the `MvxApplication` class
|
||||
* this class is normally just called `App`
|
||||
* this class is responsible for providing:
|
||||
* registration of which interfaces and implementations the app uses
|
||||
* registration of which `ViewModel` the `App` will show when it starts
|
||||
* control of how `ViewModel`s are located - although most applications normally just use the default implementation of this supplied by the base `MvxApplication` class.
|
||||
'Registration' here means creating an entry on the 'Inversion of Control' Container - IoC -. This record tells the IoC Container what to do when anything asks for an instance of the registered interface.
|
||||
|
||||
'Registration' here means creating an 'Inversion of Control' - IoC - record for an interface. This IoC record tells the MvvmCross framework what to do when anything asks for an instance of that interface.
|
||||
|
||||
For our Tip Calculation app:
|
||||
|
||||
* we register the `Calculation` class to implement the `ICalculation` service
|
||||
Our "Tip Calculation" App class will register the `ICalculationService` as a dynamic service:
|
||||
|
||||
```c#
|
||||
Mvx.RegisterType<ICalculation, Calculation>();
|
||||
Mvx.RegisterType<ICalculationService, CalculationService>();
|
||||
```
|
||||
|
||||
this line tells the MvvmCross framework that whenever any code requests an `ICalculation` reference, then the framework should create a new instance of `Calculation`. Note the single static class `Mvx` which acts as a single place for both registering and resolving interfaces and their implementations.
|
||||
The previous line tells the IoC Container that whenever any code requests an `ICalculationService` reference, an object of type `CalculationService` should be created and returned.
|
||||
|
||||
* we want the app to start with the `TipViewModel`
|
||||
Also note that the single static class `Mvx` acts as a single place for both registering and resolving interfaces and their implementations.
|
||||
|
||||
Within the App class we also decide that we want the app to start with the `TipViewModel`:
|
||||
|
||||
```c#
|
||||
var appStart = new MvxAppStart<TipViewModel>();
|
||||
Mvx.RegisterSingleton<IMvxAppStart>(appStart);
|
||||
RegisterAppStart<TipViewModel>();
|
||||
```
|
||||
|
||||
this line tells the MvvmCross framework that whenever any code requests an `IMvxAppStart` reference, then the framework should return that same `appStart` instance.
|
||||
The previous line tells the MvvmCross framework that `TipViewModel` should be the first ViewModel / View pair that should appear on foreground when the app starts.
|
||||
|
||||
So here's what App.cs looks like:
|
||||
In summary, this is what App.cs should look like:
|
||||
|
||||
```c#
|
||||
using MvvmCross.Core.ViewModels;
|
||||
using MvvmCross.Platform;
|
||||
using MvvmCross;
|
||||
using MvvmCross.ViewModels;
|
||||
using TipCalc.Core.Services;
|
||||
using TipCalc.Core.ViewModels;
|
||||
|
||||
|
@ -323,57 +274,57 @@ namespace TipCalc.Core
|
|||
{
|
||||
public class App : MvxApplication
|
||||
{
|
||||
public App()
|
||||
public override void Initialize()
|
||||
{
|
||||
Mvx.RegisterType<ICalculation, Calculation>();
|
||||
Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<TipViewModel>());
|
||||
Mvx.RegisterType<ICalculationService, CalculationService>();
|
||||
|
||||
RegisterAppStart<TipViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Note: What is 'Inversion of Control'?
|
||||
## Our Core project is complete :)
|
||||
|
||||
Just to recap the steps we've followed:
|
||||
|
||||
1. We created a new .NET Standard project
|
||||
2. We added the MvvmCross libraries via NuGet
|
||||
3. We added a `ICalculationService` interface and implementation pair
|
||||
4. We added a `TipViewModel` which:
|
||||
- Inherits from `MvxViewModel`
|
||||
- Declares a dependency on `ICalculationService` on its constructor
|
||||
- Presents a number of public properties each of which called `RaisePropertyChanged` internally
|
||||
5. We added an `App` which:
|
||||
- Inherits from `MvxApplication`
|
||||
- Registers the `ICalculationService`/`CalculationService` pair
|
||||
- Registers a ViewModel to use for when the app starts
|
||||
|
||||
These are the same steps that you need to go through for every new MvvmCross application.
|
||||
|
||||
The next step is about building a first UI for our MvvmCross application.
|
||||
|
||||
[Next!](https://www.mvvmcross.com/documentation/tutorials/tipcalc/the-tip-calc-navigation)
|
||||
|
||||
|
||||
## Side note: What is 'Inversion of Control'?
|
||||
|
||||
We won't go into depth here about what IoC - Inversion of Control - is.
|
||||
|
||||
Instead, we will just say that:
|
||||
|
||||
* Within each MvvmCross application, there is a single special object - a `singleton`
|
||||
* This `singleton` lives within the `Mvx` static class.
|
||||
* The application startup code can use the `Mvx.Register` methods in order to specify what will implement `interface`s during the lifetime of the app.
|
||||
* After this has been done, then later in the life when any code needs an `interface` implementation, then it can request one using the `Mvx.Resolve` methods.
|
||||
- Within each MvvmCross application, there is a very special object, whici is a `singleton`.
|
||||
- This `singleton` lives within the `Mvx` static class.
|
||||
- The application startup code can use the `Mvx.Register...` methods to let `Mvx` know how to resolve certain requests during the lifetime of the app.
|
||||
- After the registration has been done, then when any code asks for an `interface` implementation, it can do that by using the `Mvx.Resolve` methods.
|
||||
|
||||
One common pattern that is seen is 'constructor injection':
|
||||
One common pattern the app is also using is 'constructor injection':
|
||||
|
||||
* Our `TipViewModel` uses this pattern.
|
||||
* It presents a constructor like: `public TipViewModel(ICalculation calculation)`.
|
||||
* When the app is running a part of the MvvmCross framework called the `ViewModelLocator` is used to find and create `ViewModel`s
|
||||
* when a `TipViewModel` is needed, the `ViewModelLocator` uses a call to `Mvx.IocConstruct` to create one.
|
||||
* This `Mvx.IocConstruct` call creates the `TipViewModel` using the `ICalculation` implementation that it finds using `Mvx.Resolve`
|
||||
- Our `TipViewModel` uses this pattern.
|
||||
- It presents a constructor like: `public TipViewModel(ICalculationService calculationService)`.
|
||||
- When the app is running a part of the MvvmCross framework called the `ViewModelLocator` is used to find and create ViewModels.
|
||||
- when a `TipViewModel` is needed, the `ViewModelLocator` uses a call to `Mvx.IocConstruct` to create one.
|
||||
- This `Mvx.IocConstruct` call creates the `TipViewModel` using the `ICalculationService` implementation that it finds using `Mvx.Resolve`
|
||||
|
||||
This is obviously only a very brief introduction.
|
||||
|
||||
If you would like to know more, please see look up some of the excellent tutorials out there on the Internet - like http://joelabrahamsson.com/inversion-of-control-an-introduction-with-examples-in-net/
|
||||
|
||||
## The Core project is complete :)
|
||||
|
||||
Just to recap the steps we've followed:
|
||||
|
||||
1. We created a new PCL project using Profile259
|
||||
2. We added the MvvmCross libraries
|
||||
3. We added a `ICalculation` interface and implementation pair
|
||||
4. We added a `TipViewModel` which:
|
||||
* inherited from `MvxViewModel`
|
||||
* used `ICalculation`
|
||||
* presented a number of public properties each of which called `RaisePropertyChanged`
|
||||
5. We added an `App` which:
|
||||
* inherited from `MvxApplication`
|
||||
* registered the `ICalculation`/`Calculation` pair
|
||||
* registered a special start object for `IMvxAppStart`
|
||||
|
||||
These are the same steps that you need to go through for every new MvvmCross application.
|
||||
|
||||
## Moving on
|
||||
|
||||
Next we'll start looking at how to add a first UI to this MvvmCross application.
|
||||
|
||||
|
|
|
@ -1,40 +1,43 @@
|
|||
---
|
||||
layout: documentation
|
||||
title: The TipCalc navigation
|
||||
title: Extra - Navigation
|
||||
category: Tutorials
|
||||
order: 3
|
||||
order: 10
|
||||
---
|
||||
|
||||
This article will cover some of the techniques available within MvvmCross for navigating between Page-level **ViewModels**.
|
||||
|
||||
### What do we mean by Page?
|
||||
|
||||
MvvmCross was born for making modern Mobile apps - for building apps for iPhone, Android, and Windows Phone.
|
||||
MvvmCross was born for making modern Mobile apps - for building apps for iPhone, Android, and windows. Over the years the community expanded the framework to make it usable on many more platforms like macOS, watchOS or even Xamarin.Forms!
|
||||
|
||||
These apps are generally Page-based - that is they generally involve User-Interfaces which show a single page at a time and which often involve the user experience moving forwards and backwards through the application workflow.
|
||||
Since usually Apps have more than one screen - pages - there is normally a need for navigation between them, which often involves the user experience moving forwards and backwards through the application workflow.
|
||||
|
||||
There are variations on this, especially for Tabbed or Pivoting user interfaces; for Dialogs; and for Split displays. We'll introduce some of these briefly at the end of this article.
|
||||
The most common scenario for UIs consist on a single page at a time. But there are variations on this, especially for tabbed or splitted user interfaces. There are also dialogs and other several approaches.
|
||||
|
||||
## The initial navigation
|
||||
|
||||
In the TipCalc walkthough, we built most of our initial MvvmCross applications to use the `IMvxAppStart` interface as a starting mechanism.
|
||||
On the TipCalc tutorial, we added a very special line to our `App.cs`, which indicates which `ViewModel` should be the very first to appear on the screen:
|
||||
|
||||
An implementation of this interface was registered by the core `App` like:
|
||||
```c#
|
||||
RegisterAppStart<TipViewModel>();
|
||||
```
|
||||
|
||||
Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<TipViewModel>());
|
||||
That line can be also replaced by this code, since that's what it does internally:
|
||||
|
||||
This implementation was then used in the `AppDelegate` and `App.Xaml` start sequences within the UI projects.
|
||||
```c#
|
||||
Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<TipViewModel>());
|
||||
```
|
||||
|
||||
The exception was Android - where we explicitly specified one of our Activities/Views with the `MainLauncher=true` property value within the `[Activity]` attribute.
|
||||
The only exception to the rule is, in terms of AppStart, Android. This is because Android requires the application to indicate which Activity will be the first to show up. We are doing so through the `MainLauncher=true` property value within the `[Activity]` attribute.
|
||||
|
||||
To use the `IMvxAppStart` instruction in Android as well, the easiest way is to add a splashscreen which will be displayed briefly while the application starts.
|
||||
### How to add a splashscreen to the Android app
|
||||
|
||||
### Adding a splashscreen to an Android app
|
||||
|
||||
To add a splashscreen:
|
||||
In case you want to display something fancy while MvvmCross and your app is loaded, you can follow these steps:
|
||||
|
||||
1. Remove the `MainLauncher=true` property from any existing `Activity` attributes.
|
||||
|
||||
2. Add some simple AXML for a splashscreen. For example, a very simple screen might be:
|
||||
2. Add some simple axml for a splashscreen. For example, a very simple screen might be:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
@ -49,7 +52,7 @@ To add a splashscreen:
|
|||
</FrameLayout>
|
||||
```
|
||||
|
||||
Note that this splashscreen will be displayed before the MvvmCross system is fully booted - so you **cannot** use data-binding within the splashscreen AXML.
|
||||
Note that this splashscreen will be displayed before the MvvmCross system is fully booted - so you **cannot** use data-binding within the splashscreen axml.
|
||||
|
||||
3. Add a simple Activity for the splashscreen. This will contain C# like:
|
||||
|
||||
|
@ -59,135 +62,138 @@ using MvvmCross.Droid.Views;
|
|||
|
||||
namespace CalcApp.UI.Droid
|
||||
{
|
||||
[Activity(Label = "My App", MainLauncher = true, NoHistory = true, Icon = "@drawable/icon")]
|
||||
public class SplashScreenActivity
|
||||
: MvxSplashScreenActivity
|
||||
{
|
||||
public SplashScreenActivity()
|
||||
: base(Resource.Layout.SplashScreen)
|
||||
[Activity(Label = "My App", MainLauncher = true, NoHistory = true, Icon = "@drawable/icon")]
|
||||
public class SplashScreenActivity : MvxSplashScreenActivity
|
||||
{
|
||||
public SplashScreenActivity()
|
||||
: base(Resource.Layout.SplashScreen)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This `SplashScreenActivity` uses the base `MvxSplashScreenActivity` which will start the MvvmCross framework and, when initialization is complete, will then use the `IMvxAppStart` interface.
|
||||
This `SplashScreenActivity` uses the base `MvxSplashScreenActivity` which implements `IMvxSetupMonitor` to start the MvvmCross framework and call `IMvxAppStart` when initialization is complete.
|
||||
|
||||
### Supporting more advanced startup
|
||||
### Supporting a more advanced startup
|
||||
|
||||
The `TipCalc` app has a very simple startup instruction:
|
||||
The `TipCalc` app has a very simple startup instruction, which doesn't handle any logic.
|
||||
|
||||
`Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<TipViewModel>()); `
|
||||
This was an instruction to: **always start the app with a `TipViewModel`**.
|
||||
|
||||
This was an instruction to: **always start the app with a `TipViewModel`**
|
||||
|
||||
If you wanted instead to start with a different `ViewModel` - e.g. with `LoginViewModel` then you'd have to replace this with:
|
||||
|
||||
`Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<LoginViewModel>());`
|
||||
|
||||
If you wanted instead to start with some logic, then you can do this by providing a custom `IMvxAppStart` implementation - e.g.:
|
||||
If you wanted to start with some logic - suppose you need to display a `LoginViewModel` or a `MainViewModel` in different situations - then you can do this by providing a custom `IMvxAppStart` implementation - e.g.:
|
||||
|
||||
```c#
|
||||
public class CustomAppStart
|
||||
: MvxNavigatingObject
|
||||
, IMvxAppStart
|
||||
public class CustomAppStart : MvxAppStart
|
||||
{
|
||||
private readonly ILoginService _service;
|
||||
private readonly IMvxNavigationService _mvxNavigationService;
|
||||
private readonly ILoginService _loginService;
|
||||
|
||||
public CustomAppStart(ILoginService service)
|
||||
public CustomAppStart(IMvxApplication app,
|
||||
IMvxNavigationService mvxNavigationService,
|
||||
ILoginService loginService)
|
||||
: base(app)
|
||||
{
|
||||
_service = service;
|
||||
_mvxNavigationService = mvxNavigationService;
|
||||
_loginService = loginService;
|
||||
}
|
||||
|
||||
public void Start(object hint = null)
|
||||
protected override void Startup(object hint = null)
|
||||
{
|
||||
if (!_service.IsLoggedIn)
|
||||
if(_loginService.IsLoggedIn)
|
||||
{
|
||||
ShowViewModel<LoginViewModel>();
|
||||
_mvxNavigationService.Navigate<MainViewModel>();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowViewModel<TipViewModel>();
|
||||
}
|
||||
_mvxNavigationService.Navigate<LoginViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Notice that to request this initial navigation, the `CustomAppStart` uses the `ShowViewModel<TViewModel>` method on the `MvxNavigatingObject` base class. We'll see this method used throughout this article - it is the core of the MvvmCross navigation mechanism.
|
||||
Notice that to request this initial navigation, the `CustomAppStart` uses the `IMvxNavigationService` to navigate through the pages. We will see more about this throughout this article - `IMvxNavigationService` is the core of the MvvmCross navigation mechanism.
|
||||
|
||||
If you wanted to do even more here, e.g. if you wanted to use parameters passed to the app from the operating system, then this is also possible - these can be passed from the platform-specific startup code to the `IMvxAppStart.Start(object hint)` method using the `hint` and can then also be passed on to the displayed `ViewModel`.
|
||||
If you wanted to do even more here, e.g. if you wanted to use parameters passed to the app from the operating system, then this is also possible - these can be passed from the platform-specific startup code to the `IMvxAppStart.Startup(object hint)`.
|
||||
|
||||
_Hint: There is a method called `GetAppStartHint(object hint = null)` on every platform specific 'App' class which can be used to set the hint parameter.
|
||||
|
||||
## Simple Navigation between Page-level ViewModels
|
||||
|
||||
When your app is displaying a `ViewModel` page, say `FirstViewModel`, then that first page can request that the display is moved forwards to a new `ViewModel` page, say `SecondViewModel` by using a call like:
|
||||
|
||||
ShowViewModel<SecondViewModel>();
|
||||
```c#
|
||||
_mvxNavigationService.Navigate<SecondViewModel>();
|
||||
```
|
||||
|
||||
When the `FirstViewModel` makes this request, then the MvvmCross framework will:
|
||||
|
||||
- locate a View to use as a page for `SecondViewModel` within the app - normally this will be `SecondView`
|
||||
- create a new instance of this `SecondView`
|
||||
- create a `SecondViewModel` and provide it as the `DataContext` for the new `SecondView`
|
||||
- ask the operating system to display the `SecondView`
|
||||
- Locate a `View` to use as a page for `SecondViewModel` within the app - normally this will be `SecondView`.
|
||||
- Create a new instance of this `SecondView`.
|
||||
- Create a `SecondViewModel` and provide it as the `DataContext` for the new `SecondView`.
|
||||
- Ask the platform ViewPresenter to display the `SecondView` in the most appropiate way.
|
||||
- Make the ViewPresenter do the actual navigation.
|
||||
|
||||
### In action - an Android app
|
||||
|
||||
To see an example of this, let's set up a simple Android application.
|
||||
|
||||
1. Create a Core Portable Class Library application - exactly as we did in the `TipCalc` example.
|
||||
1. Create a new _Core_, .NET Standard Library - exactly as we did in the `TipCalc` example.
|
||||
|
||||
2. Within this Core application add two `ViewModel`s:
|
||||
2. Within this _Core_ application add two `ViewModel`s:
|
||||
|
||||
```c#
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
using MvvmCross.Platform;
|
||||
using MvvmCross.Commands;
|
||||
using MvvmCross.Navigation;
|
||||
using MvvmCross.ViewModels;
|
||||
|
||||
namespace MyApp.Core
|
||||
namespace TipCalc.Core.ViewModels
|
||||
{
|
||||
public class FirstViewModel : MvxViewModel
|
||||
{
|
||||
public ICommand GoCommand
|
||||
public class FirstViewModel : MvxViewModel
|
||||
{
|
||||
get
|
||||
{
|
||||
return new MvxCommand(() => ShowViewModel<SecondViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
private readonly IMvxNavigationService _navigationService;
|
||||
|
||||
public class SecondViewModel : MvxViewModel
|
||||
{
|
||||
}
|
||||
public FirstViewModel(IMvxNavigationService navigationService)
|
||||
{
|
||||
_navigationService = navigationService;
|
||||
|
||||
NavigateCommand = new MvxAsyncCommand(() => _navigationService.Navigate<SecondViewModel>());
|
||||
}
|
||||
|
||||
public IMvxAsyncCommand NavigateCommand { get; private set; }
|
||||
}
|
||||
|
||||
public class SecondViewModel : MvxViewModel
|
||||
{
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. For `IMvxAppStart` choose to always show the `FirstViewModel` using:
|
||||
3. For `IMvxAppStart` choose to always show the `FirstViewModel` adding this line on the `App` class:
|
||||
|
||||
`Mvx.RegisterSingleton<IMvxAppStart>(new MvxAppStart<FirstViewModel>());`
|
||||
```c#
|
||||
RegisterAppStart<FirstViewModel>();
|
||||
```
|
||||
|
||||
4. Create an Android UI for this app - just as we did in the `TipCalc` sample
|
||||
4. Create an Android UI for this app - just as we did in the `TipCalc` sample.
|
||||
|
||||
5. Add simple Android views for both `FirstView` and `SecondView`.
|
||||
|
||||
6. For `FirstView` include a button - and bind it's `Click` event to the `GoCommand` within the `FirstViewModel`
|
||||
6. For `FirstView` include a button - and bind it's `Click` event to the `NavigateCommand` within the `FirstViewModel`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:local="http://schemas.android.com/apk/res/MyApp.UI.Droid"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
>
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<Button
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Go"
|
||||
android:textSize="40dp"
|
||||
local:MvxBind="Click GoCommand"
|
||||
/>
|
||||
local:MvxBind="Click NavigateCommand"/>
|
||||
</LinearLayout>
|
||||
```
|
||||
|
||||
|
@ -198,21 +204,19 @@ public class SecondViewModel : MvxViewModel
|
|||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:local="http://schemas.android.com/apk/res/MyApp.UI.Droid"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
>
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="This is the Second View"
|
||||
android:textSize="40dp"
|
||||
/>
|
||||
android:textSize="40dp"/>
|
||||
</LinearLayout>
|
||||
```
|
||||
|
||||
8. As discussed above in 'The initial navigation' add a SplashScreen for this Droid app.
|
||||
|
||||
When this application runs, you should see a simple UI for `FirstView` with a `FirstViewModel` data context, and when you press the 'Go' button, you should see the display shift to a `SecondView` with a `SecondViewModel` data context
|
||||
When this application runs, you should see a simple UI for `FirstView` with a `FirstViewModel` data context, and when you press the 'Go' button, you should see the display shift to a `SecondView` with a `SecondViewModel` data context.
|
||||
|
||||
## Navigation with parameters - using a parameter object
|
||||
|
||||
|
@ -222,87 +226,40 @@ For example, you may encounter List-Detail situations - where:
|
|||
|
||||
- The Master view shows a list of items.
|
||||
- When the user selects one of these, then the app will navigate to a Detail view
|
||||
- The Detail view will then shows that specific selected item.
|
||||
- The Detail view will then show that specific selected item.
|
||||
|
||||
To achieve this, the navigation from `MasterViewModel` to `DetailViewModel` will normally be achieved by:
|
||||
|
||||
- we declare a class `DetailParameters` for the navigation:
|
||||
- We declare a class named `DetailNavigationArgs` for the navigation:
|
||||
|
||||
```c#
|
||||
public class DetailParameters
|
||||
public class DetailNavigationArgs
|
||||
{
|
||||
public int Index {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public int Index { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
- the `MasterViewModel` makes `ShowViewModel` a call like:
|
||||
|
||||
`ShowViewModel<DetailViewModel>(new DetailParameters() { Index = 2 });`
|
||||
|
||||
- the `DetailViewModel` declares an `Init` method in order to receive this `DetailParameters`:
|
||||
- the `MasterViewModel` performs a navigaton with a call like this:
|
||||
|
||||
```c#
|
||||
public void Init(DetailParameters parameters)
|
||||
_navigationService.Navigate<DetailViewModel, DetailNavigationArgs>(new DetailNavigationArgs { Index = 2});
|
||||
```
|
||||
|
||||
- The `DetailViewModel` class declaration should then be:
|
||||
|
||||
```c#
|
||||
public class DetailViewModel : MvxViewModel<DetailNavigationArgs>
|
||||
```
|
||||
|
||||
And you should override a method named `Prepare` to receive the parameter:
|
||||
|
||||
```c#
|
||||
public void Prepare(DetailNavigationArgs parameter)
|
||||
{
|
||||
// use the parameters here
|
||||
// use the parameters here!
|
||||
}
|
||||
```
|
||||
|
||||
**Note** that the `DetailParameters` class used here must be a 'simple' class used only for these navigations:
|
||||
## Navigating for result and more advanced scenarios
|
||||
|
||||
- it must contain a parameterless constructor
|
||||
- it should contain only public properties with both `get` and `set` access
|
||||
- these properties should be only of types:
|
||||
- `int`
|
||||
- `long`
|
||||
- `double`
|
||||
- `string`
|
||||
- `Guid`
|
||||
- enumeration values
|
||||
|
||||
The reason for this limitations are that the navigation object itself needs to be serialized - it needs to be passed through mechanisms like Xaml urls on WindowsPhone, and like Intent parameter bundles on Android.
|
||||
|
||||
If you do ever want to pass more complex objects between ViewModels during navigation, then you will need to find an alternative mechanism - e.g. perhaps caching the object in SQLite and using an index to identify the object.
|
||||
|
||||
## In action - an iOS example
|
||||
|
||||
TODO
|
||||
|
||||
## Navigation with parameters - using an anonymous parameter object
|
||||
|
||||
For simple navigations, declaring a formal `Parameters` object can feel like 'overkill' - like 'hard work'.
|
||||
|
||||
In these situations you can instead use anonymous classes and named method arguments.
|
||||
|
||||
For example, you can:
|
||||
|
||||
- use a call to `ShowViewModel` like:
|
||||
|
||||
`ShowViewModel<DetailViewModel>(new { index = 2 });`
|
||||
|
||||
- in the `DetailViewModel` declare an `Init` method in order to receive this `index` as:
|
||||
|
||||
```c#
|
||||
public void Init(int index)
|
||||
{
|
||||
// use the index here
|
||||
}
|
||||
```
|
||||
|
||||
**Note** that due to serialization requirements, the only available parameter types used within this technique are only:
|
||||
|
||||
- `int`
|
||||
- `long`
|
||||
- `double`
|
||||
- `string`
|
||||
- `Guid`
|
||||
- enumeration values
|
||||
|
||||
**Note** that in order to use this technique on Windows platforms, you will need to add a `InternalsVisibleTo` line within the `AssemblyInfo.cs` file for the Core project.
|
||||
|
||||
`[assembly: InternalsVisibleTo("MvvmCross")]`
|
||||
|
||||
This is because anonymous classes within C# are `internal` by default - so MvvmCross can only use reflection on them if `InternalsVisibleTo` is specified.
|
||||
If you want to learn more about how navigation in MvvmCross works, we highly recommend you to read the [official article](https://www.mvvmcross.com/documentation/fundamentals/navigation) which covers the main aspects (async/await, navigating for result, url navigation, ...).
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
---
|
||||
layout: documentation
|
||||
title: The Tip Calc tutorial
|
||||
title: The TipCalc tutorial
|
||||
category: Tutorials
|
||||
order: 1
|
||||
---
|
||||
In this first Model-View-ViewModel project, we'll create a simple single screen cross-platform application for calculating the tip at a restaurant.
|
||||
|
||||
Welcome to the TipCalc Tutorial! In the following guide we will create a simple Cross-Platform app to introduce some key concepts and showcase some MvvmCross features.
|
||||
|
||||
The application consists on a single screen for calculating the tip at a restaurant.
|
||||
|
||||
Here's a sketch of where we hope to end up:
|
||||
|
||||
![Sketch](../../assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
![Sketch]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
|
||||
The goals of this first application are all about **introducing**:
|
||||
|
||||
1. the general structure of MvvmCross applications
|
||||
2. some of the code elements required in all MvvmCross applications
|
||||
3. how data-binding support on Xamarin.Android and Xamarin.iOS (the products formerly known as Mono for Android and MonoTouch)
|
||||
1. The general structure of MvvmCross applications
|
||||
2. Some of the code elements required in all MvvmCross applications
|
||||
3. Data-Binding
|
||||
|
||||
Within this guide, we won't attempt to provide any introduction to the Apple, Google, Microsoft, and Xamarin products and platforms - there are plenty of guides available already for all of those. Instead, we'll focus instead on pure, unadulterated MvvmCross Nirvana.
|
||||
Within this guide, we won't attempt to provide any introduction to the Apple, Google, Microsoft, or Xamarin products. We will focus on pure, unadulterated MvvmCross framework.
|
||||
|
||||
[Next!](https://www.mvvmcross.com/documentation/tutorials/tipcalc/the-core-project)
|
||||
|
|
|
@ -1,43 +1,41 @@
|
|||
---
|
||||
layout: documentation
|
||||
title: Tip Calculator - A recap
|
||||
title: TipCalc - Summary
|
||||
category: Tutorials
|
||||
order: 9
|
||||
order: 8
|
||||
---
|
||||
Over the course of these articles, we've covered the complete `Tip Calc` app on 5 platforms from one shared PCL code library using Mvvm.
|
||||
|
||||
![Summary](../../assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
Over the course of these articles, we've covered the complete `Tip Calc` app on many platforms, used a .NET Standard library to share code and took advantage of the MVVM pattern to structure our solution.
|
||||
|
||||
I hope this was simple and easy to follow...
|
||||
![Summary]({{ site.url }}/assets/img/tutorials/tipcalc/TipCalc_Sketch.png)
|
||||
|
||||
We really hope this tutorial was simple and easy to follow.
|
||||
|
||||
Just to recap what we did:
|
||||
|
||||
* For the core PCL library, we:
|
||||
* created a Profile 259 library
|
||||
* added some `MvvmCross` PCL libraries
|
||||
* added our services - the `Calculator`
|
||||
* added our `TipViewModel` which exposed properties
|
||||
* added our `App` which wired the services together and defined an `IMvxAppStart`
|
||||
* For each platform, we generally went through a process like:
|
||||
* created a platform specific project
|
||||
* added some `MvvmCross` PCL libraries
|
||||
* added some `MvvmCross` platform specific libraries
|
||||
* added a `Setup` class which would initialize everything
|
||||
* modified the platform-specific Application to call `Setup`
|
||||
* created a `Views` folder
|
||||
* added a platform specific `View`
|
||||
* changed that `View` base class to something beginning with `Mvx`
|
||||
* added a `public new TipViewModel ViewModel` to link the `View` to the `ViewModel`
|
||||
* modified the XML for that `View` to add the UI fields
|
||||
* modified those UI fields to add databinding to the `ViewModel` properties
|
||||
* pressed 'Run' :)
|
||||
* considered some ways that the UI could be improved using platform-specific UI techniques
|
||||
For the _Core_, we:
|
||||
- Created a .NET Standard 2 library
|
||||
- Added the `MvvmCross` package
|
||||
- Added a service: - `ICalculatorService`
|
||||
- Added our `TipViewModel` which exposed several properties to be consumed
|
||||
- Added our `App` class, which wired the services together and defined an `IMvxAppStart`
|
||||
|
||||
For each platform, we generally went through a process like:
|
||||
- Created a platform specific project
|
||||
- Added the `MvvmCross` package
|
||||
- Modified the platform-specific 'Application' to let MvvmCross be initialized
|
||||
- Used the default provided `Setup` class for that platform
|
||||
- Created a `Views` folder
|
||||
- Added a platform specific `TipView`, which inherited from something beginning with `Mvx`
|
||||
- Modified the layout for that View and added some widgets
|
||||
- Added data-binding to the platform view, targeting the `TipViewModel` properties
|
||||
- Pressed 'Run' :)
|
||||
|
||||
Generally, these same steps are what you'll follow for each MvvmCross application you want to make.
|
||||
|
||||
We'll cover more advanced topics in future articles.
|
||||
This is the final article for the TipCalc app, we hope you have enjoyed the lectures and we hope you have a great time developing apps with MvvmCross.
|
||||
|
||||
Thanks for reading
|
||||
In order to provide some follow-up content, we will talk about ViewModels and Navigation in the final two articles.
|
||||
|
||||
Stuart
|
||||
[Next!](https://www.mvvmcross.com/documentation/tutorials/tipcalc/a-note-about-views-and-viewmodels)
|
||||
|
||||
|
|
Двоичные данные
docs/assets/img/tutorials/tipcalc/TipCalc_Android_Styled.png
До Ширина: | Высота: | Размер: 11 KiB После Ширина: | Высота: | Размер: 59 KiB |
После Ширина: | Высота: | Размер: 42 KiB |
После Ширина: | Высота: | Размер: 70 KiB |
Двоичные данные
docs/assets/img/tutorials/tipcalc/TipCalc_Touch_Design.png
До Ширина: | Высота: | Размер: 21 KiB |
Двоичные данные
docs/assets/img/tutorials/tipcalc/TipCalc_Touch_Sim.png
До Ширина: | Высота: | Размер: 13 KiB После Ширина: | Высота: | Размер: 65 KiB |
Двоичные данные
docs/assets/img/tutorials/tipcalc/TipCalc_UWP.png
До Ширина: | Высота: | Размер: 148 KiB После Ширина: | Высота: | Размер: 5.5 KiB |
Двоичные данные
docs/assets/img/tutorials/tipcalc/TipCalc_UWP_designer.png
До Ширина: | Высота: | Размер: 23 KiB |
Двоичные данные
docs/assets/img/tutorials/tipcalc/TipCalc_UWP_landscape.png
До Ширина: | Высота: | Размер: 136 KiB |
После Ширина: | Высота: | Размер: 6.1 KiB |
Двоичные данные
docs/assets/img/tutorials/tipcalc/TipCalc_WP_Designer.png
До Ширина: | Высота: | Размер: 5.2 KiB |
Двоичные данные
docs/assets/img/tutorials/tipcalc/TipCalc_WP_Emu.png
До Ширина: | Высота: | Размер: 108 KiB |
До Ширина: | Высота: | Размер: 21 KiB |
До Ширина: | Высота: | Размер: 13 KiB |
До Ширина: | Высота: | Размер: 17 KiB |
До Ширина: | Высота: | Размер: 4.1 KiB |
Двоичные данные
docs/assets/img/tutorials/tipcalc/TipCalc_Wpf_Designer.png
До Ширина: | Высота: | Размер: 38 KiB |
Двоичные данные
docs/assets/img/tutorials/tipcalc/TipCalc_Wpf_Run.png
До Ширина: | Высота: | Размер: 3.3 KiB |