Xamarin.form页面导航在mvvm中

时间:2017-04-06 11:50:35

标签: c# xaml mvvm xamarin.forms cross-platform

我正在开发xamarin.form跨平台应用程序,我希望在按钮点击时从一个页面导航到另一个页面。因为我无法在ViewModel中执行Navigation.PushAsync(new Page2());因为它只能在Code-Behid文件中执行。请建议任何方式做到这一点?

这是我的观点:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Calculator.Views.SignIn"
             xmlns:ViewModels="clr-namespace:Calculator.ViewModels;assembly=Calculator">

    <ContentPage.BindingContext>
        <ViewModels:LocalAccountViewModel/>
    </ContentPage.BindingContext>

    <ContentPage.Content>

            <StackLayout>
                <Button Command="{Binding ContinueBtnClicked}"></Button>

            </StackLayout>


    </ContentPage.Content>
</ContentPage>

这是我的ViewModel:

 public class LocalAccountViewModel : INotifyPropertyChanged
        {




            public LocalAccountViewModel()
            {
                this.ContinueBtnClicked = new Command(GotoPage2);
            }


            public void GotoPage2()
            {
                 /////

            }


            public ICommand ContinueBtnClicked
            {

                protected set;
                get;
            }

           public event PropertyChangedEventHandler PropertyChanged;


        protected virtual void OnPropertyChanges([CallerMemberName] string PropertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
        }

        }

8 个答案:

答案 0 :(得分:15)

一种方法是您可以通过VM构造函数传递导航。由于页面继承自VisualElement,因此它们直接继承Navigation属性。

代码隐藏文件:

public class SignIn : ContentPage
{
    public SignIn(){
       InitializeComponent();
       // Note the VM constructor takes now a INavigation parameter
       BindingContext = new LocalAccountViewModel(Navigation);
    }
}

然后在您的VM中,添加INavigation属性并更改构造函数以接受INavigation。然后,您可以使用此属性进行导航:

public class LocalAccountViewModel : INotifyPropertyChanged
{

       public INavigation Navigation { get; set;}


        public LocalAccountViewModel(INavigation navigation)
        {
            this.Navigation = navigation;
            this.ContinueBtnClicked = new Command(async () => await GotoPage2());
        }


        public async Task GotoPage2()
        {
             /////
             await Navigation.PushAsync(new Page2());
        }


        ...

请注意您应修复的代码问题:必须设置GoToPage2()方法async并返回Task类型。此外,该命令将执行异步操作调用。这是因为你必须异步进行页面导航!

希望它有所帮助!

答案 1 :(得分:13)

一种简单的方法是

this.ContinueBtnClicked = new Command(async()=>{

    await Application.Current.MainPage.Navigation.PushAsync(new Page2());
});

答案 2 :(得分:2)

从您的虚拟机

cpan MIME::Base64

答案 3 :(得分:1)

我对此进行了调查,这实际上取决于您希望如何处理导航。您是希望视图模型处理导航还是想要您的视图?我发现最简单的方法是让我的视图处理我的导航,以便我可以选择针对不同的情况或应用程序使用不同的导航格式。在这种情况下,不要使用命令绑定模型,只需使用按钮单击事件,并将新页面添加到后面代码中的导航堆栈中。

将按钮更改为:

<StackLayout>
    <Button Clicked="Button_Clicked"></Button>
</StackLayout>

在你的代码中,实现方法并在那里进行导航。

public void Button_Clicked(object sender, EventArgs e)
{
    Navigation.PushAsync(new Page2());
}

如果您希望进行基于视图模型的导航,我相信有一种方法可以使用MvvmCross,但我不熟悉该工具。

答案 4 :(得分:1)

我的方法基于原则每个View只能导航到基于VM上下文的应用程序位置:

在ViewModel中,我声明INavigationHandler干涉:

props: ['myLoadFn'],
mounted() {
  this.myLoadFn()
}

将代码隐藏类分配为ViewModel的INavigationHandler:

public class ItemsViewModel : ViewModelBase
{
    public INavigationHandler NavigationHandler { private get; set; }


    // some VM code here where in some place i'm invoking
    RelayCommand<int> ItemSelectedCommand => 
        new RelayCommand<int>((itemID) => { NavigationHandler.NavigateToItemDetail(itemID); });


    public interface INavigationHandler
    {
        void NavigateToItemDetail(int itemID);
    }
}

答案 5 :(得分:1)

通过VM构造函数传递INavigation确实是一个很好的解决方案,但是如果您具有深层嵌套的VM架构,那么它也可能非常昂贵。

从任何视图模型都可以使用单例包装INavigation的方法是替代方法:

NavigationDispatcher Singleton:

 public class NavigationDispatcher
    {
        private static NavigationDispatcher _instance;

        private INavigation _navigation;

        public static NavigationDispatcher Instance =>
                      _instance ?? (_instance = new NavigationDispatcher());

        public INavigation Navigation => 
                     _navigation ?? throw new Exception("NavigationDispatcher is not initialized");

        public void Initialize(INavigation navigation)
        {
            _navigation = navigation;
        }
    }

在App.xaml.cs中初始化:

       public App()
       {
          InitializeComponent();
          MainPage = new NavigationPage(new MainPage());
          NavigationDispatcher.Instance.Initialize(MainPage.Navigation);
       }

在任何ViewModel中使用:

 ...
 private async void OnSomeCommand(object obj)
        {
            var page = new OtherPage();
            await NavigationDispatcher.Instance.Navigation.PushAsync(page);
        }
 ...

答案 6 :(得分:0)

这几天我全神贯注,当我转向Xamarin开发时遇到了同样的障碍。

所以我的答案是将页面的类型放在模型中,但不限制View或ViewModel也可以使用它。这使系统具有灵活性,因为它不会通过在视图或代码后方进行硬接线来捆绑导航,因此它的可移植性要强得多。您可以在项目中重复使用模型,只需设置在这种情况下其他项目中将导航到的Page的类型。

为此,我生成了一个IValueConverter

    public class PageConverter : IValueConverter
    {
        internal static readonly Type PageType = typeof(Page);

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Page rv = null;

            var type = (Type)value;

            if (PageConverter.PageType.IsAssignableFrom(type))
            {
                var instance = (Page)Activator.CreateInstance(type);
                rv = instance;
            }

            return rv;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var page = (Page)value;
            return page.GetType();
        }
    }

还有一个ICommand

public class NavigateCommand : ICommand
{
    private static Lazy<PageConverter> PageConverterInstance = new Lazy<PageConverter>(true);

    public event EventHandler CanExecuteChanged;

    public bool CanExecute(object parameter)
    {
        return true;
    }

    public void Execute(object parameter)
    {
        var page = PageConverterInstance.Value.Convert(parameter, null, null, null) as Page;

        if(page != null)
        {
            Application.Current.MainPage.Navigation.PushAsync(page);
        }
    }
}

现在,模型的页面可能具有可分配的类型,因此可以更改,并且该页面在您的设备类型(例如手机,手表,Android,iOS)之间可以不同。示例:

        [Bindable(BindableSupport.Yes)]
        public Type HelpPageType
        {
            get
            {
                return _helpPageType;
            }
            set
            {
                SetProperty(ref _helpPageType, value);
            }
        }

然后在Xaml中使用它的示例。

<Button x:Name="helpButton" Text="{Binding HelpButtonText}" Command="{StaticResource ApplicationNavigate}" CommandParameter="{Binding HelpPageType}"></Button>

为了完整起见,App.xaml中定义的资源

<Application.Resources>
    <ResourceDictionary>   
        <xcmd:NavigateCommand x:Key="ApplicationNavigate" />             
    </ResourceDictionary>
</Application.Resources>

P.S。虽然Command模式通常应该对一个操作使用一个实例,但是在这种情况下,我知道在所有控件中重用同一实例是非常安全的,并且由于它是可穿戴设备,因此我想使事情比平时更轻巧,因此定义了一个实例App.xaml中的NavigationCommand。

答案 7 :(得分:0)

决定添加两种方法将Page实例传递给viewmodel,您可以稍后将其用于导航,显示警报。关闭页面等等。

1。如果可以使用命令参数传递它

在视图模型中:

public ICommand cmdAddRecord { get; set; }

viewmodel构造函数

cmdAddRecord = new Command<ContentPage>(AddRecord);

viewmodel中的某个地方

    void AddRecord(ContentPage parent)
    {
        parent.Navigation.Whatever
    }

XAML

标题

            x:Name="thisPage"

用法

 <ToolbarItem IconImageSource="{StaticResource icAdd}"  Command="{Binding cmdAddRecord}"  CommandParameter="{Binding ., Source={x:Reference thisPage}}" />

2。开始在我的基类中将其用于视图模型

viewmodel

public class cMyBaseVm : BindableObject

...

public static BindableProperty ParentProperty = BindableProperty.Create("Parent", typeof(ContentPage), typeof(cMyBaseVm), null, BindingMode.OneWay);

...

   public ContentPage Parent
    {
        get => (ContentPage)GetValue(ParentProperty);
        set => SetValue(ParentProperty, value);
    }

XAML

        xmlns:viewModels="clr-namespace:yournamespace.ViewModels"
        x:Name="thisPage"

然后我们去

<ContentPage.BindingContext>
    <viewModels:cPetEventsListVm Parent="{Binding ., Source={x:Reference thisPage}}" />
</ContentPage.BindingContext>

子视图模型

public class cPetEventsListVm : cMyBaseVm

现在,围绕子视图模型,我们可以使用诸如Parent.DisplayAlert或Parent.Navigation.PushAsync等的Page 我们甚至可以使用Parent.PopAsync();从视图模型关闭页面。