如何在 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