如何在Windows UWP (Xamarin,MvvmCross)中实现自定义Presenter

4

我在我的Android应用程序中有以下代码,它基本上使用一个页面(使用NavigationDrawer)并在中央视图中交换片段。这允许导航在一个页面上而不是许多页面上进行:

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应用程序中实现相同的行为?或者说,是否有任何关于在Windows MvvmCross应用程序中实现自定义Presenter的示例存在?这可能至少可以让我有一个开始来实现它。

谢谢!

更新:

最终我开始着手使用自定义Presenter解决此问题:

    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。我猜测在MvvmCross中使用OnNavigatedTo来初始化ViewModels?


尝试使用 ContentPresenter - Muster Station
我需要知道如何在MvvmCross中使用MvxWindowsStorePage管理导航。 - Maximus
2个回答

7
我遇到了同样的问题,为UWP构建了一个自定义Presenter。它借鉴了我在某个地方找到的一个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; }
    }
}

这是你需要的三个基础类。接下来,你需要在一个继承自MvxWindowsPage的类中实现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控件,并用在HomeView类的ShowView方法中传入的新视图替换Content属性。

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>

编辑:

扩展方法

我忘记发布了两个扩展方法,用于确定视图是否声明了 [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;
    }
}

希望这能帮到您。

哇,我认为这正是我在寻找的答案。我会尽快尝试并向您报告结果。谢谢! - Maximus
好的,现在我唯一得到的错误是“HasRegionAttribute”,在你的代码中似乎缺失了这个功能。我该如何添加这个函数? - Maximus
更新了原始答案以包括扩展方法。 - Stephanvs
一切都正常!!我喜欢你使用Region属性的方式 :)我的最后一个问题是,当您在splitview中显示内容时,您会显示页面还是用户控件?我之所以问是因为在Android中,它们有MvxFragment<ViewModel>,但在Windows中,我没有看到任何类似于MvxUserControl<ViewModel>的东西。感谢您的帮助!! :) 现在我可以从这里继续了。 - Maximus
刚刚完成了一个PullRequest,其中包含一个有点不同的Presenter,我认为它更加易于使用,请在这里查看:https://github.com/MvvmCross/MvvmCross/pull/1157/files,或者在我的博客上阅读更多信息http://stephanvs.com/implementing-a-multi-region-presenter-for-windows-10-uwp-and-mvvmcross/。 - Stephanvs
显示剩余2条评论

1

由于@Stephanvs博客的链接不再有效,我能够从Web Archive上获取内容,并在此发布,供需要的人使用:

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接