简言
书接上回,在 MvvmCross 跨平台应用开发——入门篇 中我们概述了MvvmCross框架的基础,并创建了基础开发模型包含Windows的WPF和Android的Xamarin.Android两个平台的应用程序,以及核心的Core程序集,构建一个跨平台的基础开发模板。这篇将通过细致的代码逻辑来讲解构建大型应程序的核心部件导航功能。
主窗体配置
MvvmCross框架下 WPF 默认的
MainWindow
窗体并不能完美的实现主窗体的功能并且需要继承MvxWindow
类才能够让框架识别。MainWindow
是没有对应的 VM 类的。这时我们就需要手动创建其他的窗体作为我们程序的主窗体。
首先我们将分别在 Core层 和 WPF层 创建
MainViewModel.cs
和MainView.xaml
。
- 先看 ViewModel 层,比较简单,首先
MainViewModel
类需要继承MvxNavigationViewModel
类。然后声明我们需要的属性和命令以及命令实现方法。
using Microsoft.Extensions.Logging;
using MvvmCross.Commands;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
using MvvmCrossCore.ViewModels.Navigations;
using System.Threading.Tasks;
namespace MvvmCrossCore.ViewModels
{
public class MainViewModel : MvxNavigationViewModel
{
public MainViewModel(ILoggerFactory logFactory, IMvxNavigationService navigationService) : base(logFactory, navigationService)
{
ShowToolsViewCommand = new MvxAsyncCommand(ShowToolsView);
ShowSecondViewCommand = new MvxAsyncCommand(ShowSecondView);
}
public MvxNotifyTask LoadTask { get; private set; }
public IMvxAsyncCommand ShowToolsViewCommand { get; private set; }
public IMvxAsyncCommand ShowSecondViewCommand { get; private set; }
public override void ViewAppeared()
{
base.ViewAppeared();
LoadTask = MvxNotifyTask.Create(Load);
}
private async Task Load()
{
await NavigationService.Navigate<ToolsViewModel>();
}
Task ShowToolsView()
{
return NavigationService.Navigate<ToolsViewModel>();
}
Task ShowSecondView()
{
return NavigationService.Navigate<SecondViewModel>();
}
}
}
再来看View层,也很好理解,view层包含xaml和cs两部分。
- 先看 cs 部分,不需要继承任何类删除原有的继承类,需要添加两个
Attribute
。MvxWindowPresentation
框架的窗口呈现器,字面意思就是用来显示窗体的。MvxViewFor(typeof(MainViewModel))
绑定创建的ViewModel。
using MvvmCross.Platforms.Wpf.Presenters.Attributes;
using MvvmCross.ViewModels;
using MvvmCrossCore.ViewModels;
namespace MvvmCrossWPF.Views
{
/// <summary>
/// MainView.xaml 的交互逻辑
/// </summary>
[MvxWindowPresentation]
[MvxViewFor(typeof(MainViewModel))]
public partial class MainView
{
public MainView()
{
InitializeComponent();
}
}
}
- UI布局,这里需要注意布局使用的
MvxWindow.ContentTemplate
标签 它是在clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf
命名空间下的,一定要引入。- 结构: ContentTemplate → DataTemplate → Grid
- 重点: Grid标签中的
DataContext
属性值DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
懂WPF的同学都知道这是将数据绑定到UI上的方式。 - 重中之重:
ContentPresenter
(内容呈现器) 标签中的Content
属性值Content="{Binding Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
这个关乎到我们的导航切换页面时,能否实现 “指哪打哪的效果”。 views:MvxWindow
用来创建窗体。
<views:MvxWindow
x:Class="MvvmCrossWPF.Views.MainView"
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="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
Title="XXX"
d:DesignHeight="350"
d:DesignWidth="600"
WindowState="Maximized"
mc:Ignorable="d">
<views:MvxWindow.ContentTemplate>
<DataTemplate>
<Grid DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Background="CornflowerBlue">
<Button
Margin="5"
Padding="5"
Command="{Binding ShowToolsViewCommand}"
CommandParameter="1"
Content="工具" />
<Button
Margin="5"
Padding="5"
Command="{Binding ShowSecondViewCommand}"
CommandParameter="1"
Content="Second 1st Child" />
</StackPanel>
<ContentPresenter
Grid.Column="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{Binding Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
</Grid>
</DataTemplate>
</views:MvxWindow.ContentTemplate>
</views:MvxWindow>
子页面的配置
还是一样的先看ViewModel层,再看View层。
- ViewModel,同样是需要继承
MvxNavigationViewModel
类 比较好理解
using Microsoft.Extensions.Logging;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
namespace MvvmCrossCore.ViewModels.Navigations
{
public class ToolsViewModel : MvxNavigationViewModel
{
public ToolsViewModel(ILoggerFactory logFactory, IMvxNavigationService navigationService) : base(logFactory, navigationService)
{
}
public string Title { get; set; } = "First View";
}
}
View层,这一层就和主窗体有些不一样了
- cs代码
WindowIdentifier
您可以通过该属性选择在哪个窗口中显示该视图。如果未提供标识符,则该视图将显示在最后打开的窗口中。StackNavigation
视图类可以决定是否要使用堆栈导航或非堆栈导航来显示。默认值为真。
using MvvmCross.Platforms.Wpf.Presenters.Attributes;
namespace MvvmCrossWPF.Views.Navigations
{
/// <summary>
/// FirstView.xaml 的交互逻辑
/// </summary>
[MvxContentPresentation(WindowIdentifier = nameof(MainWindow), StackNavigation = false)]
public partial class ToolsView
{
public ToolsView()
{
InitializeComponent();
}
}
}
- UI 布局唯一的不同点在于
views:MvxWpfView
用来创建显示页面 这里是唯一需要注意的点。
<views:MvxWpfView
x:Class="MvvmCrossWPF.Views.Navigations.ToolsView"
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="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<WrapPanel HorizontalAlignment="Center">
<Button Margin="10,5" Content="工具1" />
<Button Margin="10,5" Content="工具1" />
<Button Margin="10,5" Content="工具1" />
<Button Margin="10,5" Content="工具1" />
<Button Margin="10,5" Content="工具1" />
<Button Margin="10,5" Content="工具1" />
<Button Margin="10,5" Content="工具1" />
<Button Margin="10,5" Content="工具1" />
<Button Margin="10,5" Content="工具1" />
</WrapPanel>
</views:MvxWpfView>
- 效果图
详解
实现局部导航切换需要三个限定
1、VM类必须继承MvxNavigationViewModel
类。
2、主窗体cs代码添加[MvxWindowPresentation]
[MvxViewFor(typeof(MainViewModel))]
Attribute,xaml需要在views:MvxWindow
标签中进行布局。
3、子页面cs代码添加[MvxContentPresentation(WindowIdentifier = nameof(MainWindow), StackNavigation = false)]
Attribute,xaml需要在views:MvxWpfView
标签中进行布局。