如何在 Windows UWP(Xamarin、MvvmCross)中实现自定义演示器
How to implement a custom presenter in a Windows UWP (Xamarin, MvvmCross)
我的 Android 应用程序中有以下代码,它基本上使用一个页面(使用 NavigationDrawer)并交换中央视图的片段 in/out。这允许在一页而不是多页上进行导航:
Setup.cs:
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
var customPresenter = new MvxFragmentsPresenter();
Mvx.RegisterSingleton<IMvxFragmentsPresenter>(customPresenter);
return customPresenter;
}
ShellPage.cs
public class ShellPage : MvxCachingFragmentCompatActivity<ShellPageViewModel>, IMvxFragmentHost
{
.
.
.
public bool Show(MvxViewModelRequest request, Bundle bundle)
{
if (request.ViewModelType == typeof(MenuContentViewModel))
{
ShowFragment(request.ViewModelType.Name, Resource.Id.navigation_frame, bundle);
return true;
}
else
{
ShowFragment(request.ViewModelType.Name, Resource.Id.content_frame, bundle, true);
return true;
}
}
public bool Close(IMvxViewModel viewModel)
{
CloseFragment(viewModel.GetType().Name, Resource.Id.content_frame);
return true;
}
.
.
.
}
如何在 Windows UWP 应用程序中实现相同的行为?或者更确切地说,是否存在任何实施 CustomPresenter 的 Windows MvvmCross 应用程序的示例?这至少可以让我开始了解如何实施它。
谢谢!
更新:
我终于开始弄清楚如何与客户演示者一起解决这个问题了:
public class CustomPresenter : IMvxWindowsViewPresenter
{
IMvxWindowsFrame _rootFrame;
public CustomPresenter(IMvxWindowsFrame rootFrame)
{
_rootFrame = rootFrame;
}
public void AddPresentationHintHandler<THint>(Func<THint, bool> action) where THint : MvxPresentationHint
{
throw new NotImplementedException();
}
public void ChangePresentation(MvxPresentationHint hint)
{
throw new NotImplementedException();
}
public void Show(MvxViewModelRequest request)
{
if (request.ViewModelType == typeof(ShellPageViewModel))
{
//_rootFrame?.Navigate(typeof(ShellPage), null); // throws an exception
((Frame)_rootFrame.UnderlyingControl).Content = new ShellPage();
}
}
}
当我尝试导航到 ShellPage 时,它失败了。因此,当我将 Content 设置为 ShellPage 时,它可以工作,但是当我这样做时,ShellPage 的 ViewModel 不会自动初始化。我猜 ViewModels 是在 MvvmCross 中使用 OnNavigatedTo 初始化的 ???
我 运行 遇到了同样的问题,并为 UWP 构建了一个自定义演示器。它借用了我在某处找到的 Android 示例中的一些想法,该示例使用片段。思路如下
我有一个容器视图,它可以包含多个具有自己的 ViewModel 的子视图。所以我希望能够在容器中呈现多个视图。
注:我用的是MvvmCross 4.0.0-beta3
主持人
using System;
using Cirrious.CrossCore;
using Cirrious.CrossCore.Exceptions;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Views;
using Cirrious.MvvmCross.WindowsUWP.Views;
using xxxxx.WinUniversal.Extensions;
namespace xxxxx.WinUniversal.Presenters
{
public class MvxWindowsMultiRegionViewPresenter
: MvxWindowsViewPresenter
{
private readonly IMvxWindowsFrame _rootFrame;
public MvxWindowsMultiRegionViewPresenter(IMvxWindowsFrame rootFrame)
: base(rootFrame)
{
_rootFrame = rootFrame;
}
public override async void Show(MvxViewModelRequest request)
{
var host = _rootFrame.Content as IMvxMultiRegionHost;
var view = CreateView(request);
if (host != null && view.HasRegionAttribute())
{
host.Show(view as MvxWindowsPage);
}
else
{
base.Show(request);
}
}
private static IMvxWindowsView CreateView(MvxViewModelRequest request)
{
var viewFinder = Mvx.Resolve<IMvxViewsContainer>();
var viewType = viewFinder.GetViewType(request.ViewModelType);
if (viewType == null)
throw new MvxException("View Type not found for " + request.ViewModelType);
// Create instance of view
var viewObject = Activator.CreateInstance(viewType);
if (viewObject == null)
throw new MvxException("View not loaded for " + viewType);
var view = viewObject as IMvxWindowsView;
if (view == null)
throw new MvxException("Loaded View is not a IMvxWindowsView " + viewType);
view.ViewModel = LoadViewModel(request);
return view;
}
private static IMvxViewModel LoadViewModel(MvxViewModelRequest request)
{
// Load the viewModel
var viewModelLoader = Mvx.Resolve<IMvxViewModelLoader>();
return viewModelLoader.LoadViewModel(request, null);
}
}
}
IMvxMultiRegionHost
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.WindowsUWP.Views;
namespace xxxxx.WinUniversal.Presenters
{
public interface IMvxMultiRegionHost
{
void Show(MvxWindowsPage view);
void CloseViewModel(IMvxViewModel viewModel);
void CloseAll();
}
}
RegionAttribute
using System;
namespace xxxxx.WinUniversal.Presenters
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class RegionAttribute
: Attribute
{
public RegionAttribute(string regionName)
{
Name = regionName;
}
public string Name { get; private set; }
}
}
这些是您需要的三个基础 classes。接下来,您需要在 MvxWindowsPage
派生的 class.
中实现 IMvxMultiRegionHost
这是我正在使用的:
HomeView.xaml.cs
using System;
using System.Diagnostics;
using System.Linq;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.WindowsUWP.Views;
using xxxxx.Shared.Controls;
using xxxxx.WinUniversal.Extensions;
using xxxxx.WinUniversal.Presenters;
using xxxxx.Core.ViewModels;
namespace xxxxx.WinUniversal.Views
{
public partial class HomeView
: MvxWindowsPage
, IMvxMultiRegionHost
{
public HomeView()
{
InitializeComponent();
}
// ...
public void Show(MvxWindowsPage view)
{
if (!view.HasRegionAttribute())
throw new InvalidOperationException(
"View was expected to have a RegionAttribute, but none was specified.");
var regionName = view.GetRegionName();
RootSplitView.Content = view;
}
public void CloseViewModel(IMvxViewModel viewModel)
{
throw new NotImplementedException();
}
public void CloseAll()
{
throw new NotImplementedException();
}
}
}
完成这项工作的最后一部分是视图中实际 xaml 的设置方式。您会注意到我正在使用 SplitView
控件,并且我将内容 属性 替换为 [=20 上的 ShowView
方法中的新视图=] class.
HomeView.xaml
<SplitView x:Name="RootSplitView"
DisplayMode="CompactInline"
IsPaneOpen="false"
CompactPaneLength="48"
OpenPaneLength="200">
<SplitView.Pane>
// Some ListView with menu items.
</SplitView.Pane>
<SplitView.Content>
// Initial content..
</SplitView.Content>
</SplitView>
编辑:
扩展方法
我忘记了 post 两个扩展方法来确定视图是否声明了 [Region]
属性。
public static class RegionAttributeExtentionMethods
{
public static bool HasRegionAttribute(this IMvxWindowsView view)
{
var attributes = view
.GetType()
.GetCustomAttributes(typeof(RegionAttribute), true);
return attributes.Any();
}
public static string GetRegionName(this IMvxWindowsView view)
{
var attributes = view
.GetType()
.GetCustomAttributes(typeof(RegionAttribute), true);
if (!attributes.Any())
throw new InvalidOperationException("The IMvxView has no region attribute.");
return ((RegionAttribute)attributes.First()).Name;
}
}
希望对您有所帮助。
由于@Stephanvs 博客的 link 不再活跃,我能够从 Web Archive 中提取内容,我将 post 放在这里以供需要的人使用它:
Implementing a Multi Region Presenter for Windows 10 UWP and MvvmCross
18 October 2015 on MvvmCross, Xamarin, UWP, Windows 10, Presenter > Universal Windows Platform
I'm upgrading a Windows Store app to the new Windows 10 Universal
Windows Platform. MvvmCross has added support for UWP in v4.0-beta2.
A new control in the UWP is the SplitView control. Basically it
functions as a container view which consist of two sub views, shown
side-by-side. Mostly it's used to implement the (in)famous hamburger
menu.
By default MvvmCross doesn't know how to deal with the SplitView, and
just replaces the entire screen contents with a new View when
navigating between ViewModels. If however we want to lay-out our views
differently and show multiple views within one window, we need a
different solution. Luckily we can plug-in a custom presenter, which
will take care of handling the lay-out per platform.
Registering the MultiRegionPresenter
In the Setup.cs file in your UWP project, you can override the
CreateViewPresenter method with the following implementation.
protected override IMvxWindowsViewPresenter CreateViewPresenter(IMvxWindowsFrame rootFrame)
{
return new MvxWindowsMultiRegionViewPresenter(rootFrame);
}
Using Regions
We can define a region by declaring a
element. At this point it has to be a Frame type because then we can
also show a nice transition animation when switching views.
<mvx:MvxWindowsPage ...>
<Grid>
<!-- ... -->
<SplitView>
<SplitView.Pane>
<!-- Menu Content as ListView or something similar -->
</SplitView.Pane>
<SplitView.Content>
<Frame x:Name="MainContent" />
</SplitView.Content>
</SplitView>
</Grid>
</mvx:MvxWindowsPage>
Now we want to be able when a ShowViewModel(...) occurs to swap out
the current view presented in the MainContent frame.
Showing Views in a Region
In the code-behind for a View we can now declare a MvxRegionAttribute,
defining in which region we want this View to be rendered. This name
has to match a Frame element in the view.
[MvxRegion("MainContent")]
public partial class PersonView
{
// ...
}
It's also possible to declare multiple regions within the same view.
This would allow you to split up your UI in more re-usable pieces.
Animating the Transition between Content Views
If you want a nice animation when transitioning between views in the
Frame, you can add the following snippet to the Frame declaration.
<Frame x:Name="MainContent">
<Frame.ContentTransitions>
<TransitionCollection>
<NavigationThemeTransition>
<NavigationThemeTransition.DefaultNavigationTransitionInfo>
<EntranceNavigationTransitionInfo />
</NavigationThemeTransition.DefaultNavigationTransitionInfo>
</NavigationThemeTransition>
</TransitionCollection>
</Frame.ContentTransitions>
</Frame>
The contents will now be nicely animated when navigating.
Hope this helps, Stephanvs
我的 Android 应用程序中有以下代码,它基本上使用一个页面(使用 NavigationDrawer)并交换中央视图的片段 in/out。这允许在一页而不是多页上进行导航:
Setup.cs:
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
var customPresenter = new MvxFragmentsPresenter();
Mvx.RegisterSingleton<IMvxFragmentsPresenter>(customPresenter);
return customPresenter;
}
ShellPage.cs
public class ShellPage : MvxCachingFragmentCompatActivity<ShellPageViewModel>, IMvxFragmentHost
{
.
.
.
public bool Show(MvxViewModelRequest request, Bundle bundle)
{
if (request.ViewModelType == typeof(MenuContentViewModel))
{
ShowFragment(request.ViewModelType.Name, Resource.Id.navigation_frame, bundle);
return true;
}
else
{
ShowFragment(request.ViewModelType.Name, Resource.Id.content_frame, bundle, true);
return true;
}
}
public bool Close(IMvxViewModel viewModel)
{
CloseFragment(viewModel.GetType().Name, Resource.Id.content_frame);
return true;
}
.
.
.
}
如何在 Windows UWP 应用程序中实现相同的行为?或者更确切地说,是否存在任何实施 CustomPresenter 的 Windows MvvmCross 应用程序的示例?这至少可以让我开始了解如何实施它。
谢谢!
更新:
我终于开始弄清楚如何与客户演示者一起解决这个问题了:
public class CustomPresenter : IMvxWindowsViewPresenter
{
IMvxWindowsFrame _rootFrame;
public CustomPresenter(IMvxWindowsFrame rootFrame)
{
_rootFrame = rootFrame;
}
public void AddPresentationHintHandler<THint>(Func<THint, bool> action) where THint : MvxPresentationHint
{
throw new NotImplementedException();
}
public void ChangePresentation(MvxPresentationHint hint)
{
throw new NotImplementedException();
}
public void Show(MvxViewModelRequest request)
{
if (request.ViewModelType == typeof(ShellPageViewModel))
{
//_rootFrame?.Navigate(typeof(ShellPage), null); // throws an exception
((Frame)_rootFrame.UnderlyingControl).Content = new ShellPage();
}
}
}
当我尝试导航到 ShellPage 时,它失败了。因此,当我将 Content 设置为 ShellPage 时,它可以工作,但是当我这样做时,ShellPage 的 ViewModel 不会自动初始化。我猜 ViewModels 是在 MvvmCross 中使用 OnNavigatedTo 初始化的 ???
我 运行 遇到了同样的问题,并为 UWP 构建了一个自定义演示器。它借用了我在某处找到的 Android 示例中的一些想法,该示例使用片段。思路如下
我有一个容器视图,它可以包含多个具有自己的 ViewModel 的子视图。所以我希望能够在容器中呈现多个视图。
注:我用的是MvvmCross 4.0.0-beta3
主持人
using System;
using Cirrious.CrossCore;
using Cirrious.CrossCore.Exceptions;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Views;
using Cirrious.MvvmCross.WindowsUWP.Views;
using xxxxx.WinUniversal.Extensions;
namespace xxxxx.WinUniversal.Presenters
{
public class MvxWindowsMultiRegionViewPresenter
: MvxWindowsViewPresenter
{
private readonly IMvxWindowsFrame _rootFrame;
public MvxWindowsMultiRegionViewPresenter(IMvxWindowsFrame rootFrame)
: base(rootFrame)
{
_rootFrame = rootFrame;
}
public override async void Show(MvxViewModelRequest request)
{
var host = _rootFrame.Content as IMvxMultiRegionHost;
var view = CreateView(request);
if (host != null && view.HasRegionAttribute())
{
host.Show(view as MvxWindowsPage);
}
else
{
base.Show(request);
}
}
private static IMvxWindowsView CreateView(MvxViewModelRequest request)
{
var viewFinder = Mvx.Resolve<IMvxViewsContainer>();
var viewType = viewFinder.GetViewType(request.ViewModelType);
if (viewType == null)
throw new MvxException("View Type not found for " + request.ViewModelType);
// Create instance of view
var viewObject = Activator.CreateInstance(viewType);
if (viewObject == null)
throw new MvxException("View not loaded for " + viewType);
var view = viewObject as IMvxWindowsView;
if (view == null)
throw new MvxException("Loaded View is not a IMvxWindowsView " + viewType);
view.ViewModel = LoadViewModel(request);
return view;
}
private static IMvxViewModel LoadViewModel(MvxViewModelRequest request)
{
// Load the viewModel
var viewModelLoader = Mvx.Resolve<IMvxViewModelLoader>();
return viewModelLoader.LoadViewModel(request, null);
}
}
}
IMvxMultiRegionHost
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.WindowsUWP.Views;
namespace xxxxx.WinUniversal.Presenters
{
public interface IMvxMultiRegionHost
{
void Show(MvxWindowsPage view);
void CloseViewModel(IMvxViewModel viewModel);
void CloseAll();
}
}
RegionAttribute
using System;
namespace xxxxx.WinUniversal.Presenters
{
[AttributeUsage(AttributeTargets.Class)]
public sealed class RegionAttribute
: Attribute
{
public RegionAttribute(string regionName)
{
Name = regionName;
}
public string Name { get; private set; }
}
}
这些是您需要的三个基础 classes。接下来,您需要在 MvxWindowsPage
派生的 class.
IMvxMultiRegionHost
这是我正在使用的:
HomeView.xaml.cs
using System;
using System.Diagnostics;
using System.Linq;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.WindowsUWP.Views;
using xxxxx.Shared.Controls;
using xxxxx.WinUniversal.Extensions;
using xxxxx.WinUniversal.Presenters;
using xxxxx.Core.ViewModels;
namespace xxxxx.WinUniversal.Views
{
public partial class HomeView
: MvxWindowsPage
, IMvxMultiRegionHost
{
public HomeView()
{
InitializeComponent();
}
// ...
public void Show(MvxWindowsPage view)
{
if (!view.HasRegionAttribute())
throw new InvalidOperationException(
"View was expected to have a RegionAttribute, but none was specified.");
var regionName = view.GetRegionName();
RootSplitView.Content = view;
}
public void CloseViewModel(IMvxViewModel viewModel)
{
throw new NotImplementedException();
}
public void CloseAll()
{
throw new NotImplementedException();
}
}
}
完成这项工作的最后一部分是视图中实际 xaml 的设置方式。您会注意到我正在使用 SplitView
控件,并且我将内容 属性 替换为 [=20 上的 ShowView
方法中的新视图=] class.
HomeView.xaml
<SplitView x:Name="RootSplitView"
DisplayMode="CompactInline"
IsPaneOpen="false"
CompactPaneLength="48"
OpenPaneLength="200">
<SplitView.Pane>
// Some ListView with menu items.
</SplitView.Pane>
<SplitView.Content>
// Initial content..
</SplitView.Content>
</SplitView>
编辑:
扩展方法
我忘记了 post 两个扩展方法来确定视图是否声明了 [Region]
属性。
public static class RegionAttributeExtentionMethods
{
public static bool HasRegionAttribute(this IMvxWindowsView view)
{
var attributes = view
.GetType()
.GetCustomAttributes(typeof(RegionAttribute), true);
return attributes.Any();
}
public static string GetRegionName(this IMvxWindowsView view)
{
var attributes = view
.GetType()
.GetCustomAttributes(typeof(RegionAttribute), true);
if (!attributes.Any())
throw new InvalidOperationException("The IMvxView has no region attribute.");
return ((RegionAttribute)attributes.First()).Name;
}
}
希望对您有所帮助。
由于@Stephanvs 博客的 link 不再活跃,我能够从 Web Archive 中提取内容,我将 post 放在这里以供需要的人使用它:
Implementing a Multi Region Presenter for Windows 10 UWP and MvvmCross 18 October 2015 on MvvmCross, Xamarin, UWP, Windows 10, Presenter > Universal Windows Platform
I'm upgrading a Windows Store app to the new Windows 10 Universal Windows Platform. MvvmCross has added support for UWP in v4.0-beta2.
A new control in the UWP is the SplitView control. Basically it functions as a container view which consist of two sub views, shown side-by-side. Mostly it's used to implement the (in)famous hamburger menu.
By default MvvmCross doesn't know how to deal with the SplitView, and just replaces the entire screen contents with a new View when navigating between ViewModels. If however we want to lay-out our views differently and show multiple views within one window, we need a different solution. Luckily we can plug-in a custom presenter, which will take care of handling the lay-out per platform.
Registering the MultiRegionPresenter
In the Setup.cs file in your UWP project, you can override the CreateViewPresenter method with the following implementation.
protected override IMvxWindowsViewPresenter CreateViewPresenter(IMvxWindowsFrame rootFrame) { return new MvxWindowsMultiRegionViewPresenter(rootFrame); }
Using Regions
We can define a region by declaring a element. At this point it has to be a Frame type because then we can also show a nice transition animation when switching views.
<mvx:MvxWindowsPage ...> <Grid> <!-- ... --> <SplitView> <SplitView.Pane> <!-- Menu Content as ListView or something similar --> </SplitView.Pane> <SplitView.Content> <Frame x:Name="MainContent" /> </SplitView.Content> </SplitView> </Grid> </mvx:MvxWindowsPage>
Now we want to be able when a ShowViewModel(...) occurs to swap out the current view presented in the MainContent frame.
Showing Views in a Region
In the code-behind for a View we can now declare a MvxRegionAttribute, defining in which region we want this View to be rendered. This name has to match a Frame element in the view.
[MvxRegion("MainContent")] public partial class PersonView { // ... }
It's also possible to declare multiple regions within the same view. This would allow you to split up your UI in more re-usable pieces.
Animating the Transition between Content Views
If you want a nice animation when transitioning between views in the Frame, you can add the following snippet to the Frame declaration.
<Frame x:Name="MainContent"> <Frame.ContentTransitions> <TransitionCollection> <NavigationThemeTransition> <NavigationThemeTransition.DefaultNavigationTransitionInfo> <EntranceNavigationTransitionInfo /> </NavigationThemeTransition.DefaultNavigationTransitionInfo> </NavigationThemeTransition> </TransitionCollection> </Frame.ContentTransitions> </Frame>
The contents will now be nicely animated when navigating.
Hope this helps, Stephanvs