XAML 在Xamarin.Forms中使用MVVM进行页面导航

c86crjj0  于 2022-12-07  发布在  其他
关注(0)|答案(9)|浏览(195)

我正在处理xamarin。表单跨平台应用程序,我想通过单击按钮从一个页面导航到另一个页面。由于我无法在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}" />    
        </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));
    }
}
798qvoo8

798qvoo81#

一种方法是通过VM构造函数传递Navigation。由于页面继承自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类型。此外,该命令将执行异步操作调用。这是因为您必须异步进行页面导航!
希望能有所帮助!

qmb5sa22

qmb5sa222#

一个简单的方法是

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

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

nfzehxib3#

从虚拟机

public Command RegisterCommand
        {
            get
            {
                return new Command(async () =>
                {
                    await Application.Current.MainPage.Navigation.PushAsync(new RegisterNewUser());
                });

            }
        }
9o685dep

9o685dep4#

通过VM构造函数传递INavigation确实是一个很好的解决方案,但是如果您有深度嵌套的VM体系结构,它也会非常耗费代码。
用可从任何视图模型访问的单例来 Package INavigation是一种替代方法:
导航分派程序单例:

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);
        }
 ...
3qpi33ja

3qpi33ja5#

我研究了这个问题,这实际上取决于您希望如何处理导航。您希望视图模型处理导航还是希望视图处理导航。我发现让视图处理导航是最简单的,这样我就可以为不同的情况或应用程序选择不同的导航格式。在这种情况下,与其使用命令绑定模型,只需使用按钮单击事件并将新页面添加到后面代码中的导航堆栈。
将按钮更改为类似以下内容:

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

在您的程式码后置中,实作方法并在那里进行巡览。

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

如果您正在寻找基于视图模型的导航,我相信有一种方法可以使用MvvmCross来实现,但我对该工具并不熟悉。

ac1kyiln

ac1kyiln6#

我的方法基于每个视图只能导航到基于VM上下文的应用程序位置的原则:
在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);
    }
}

并将代码隐藏类指定为ViewModel的INavigationHandler:

public class ItemsPage : ContentPage, ItemsViewModel.INavigationHandler
{
    ItemsViewModel viewModel;

    public ItemsPage()
    {
        viewModel = Container.Default.Get<ItemsViewModel>();
        viewModel.NavigationHandler = this;
    }

    public async void NavigateToItemDetail(int itemID)
    {
        await Navigation.PushAsync(new ItemDetailPage(itemID));
    }
}
y1aodyip

y1aodyip7#

我决定添加两种方法将Page示例传递给视图模型,您可以在以后使用视图模型进行导航、显示警报、关闭页面等操作。

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

在视图模型中:

public ICommand cmdAddRecord { get; set; }

视图模型构造器

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等页面,我们甚至可以使用Parent.PopAsync()从视图模型关闭页面;

vs3odd8k

vs3odd8k8#

当我转向Xamarin开发时,我在这几天里绞尽脑汁,遇到了同样的障碍。
所以我的答案是把页面的Type放在Model中,但如果你选择这样做的话,不要限制View或ViewModel与它一起工作。这使系统保持灵活性,因为它不会通过视图或代码隐藏中的硬连线来束缚Navigation,因此它更容易移植。您可以跨项目重用您的模型,并且只需要设置在其他项目中出现这种情况时将导航到的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.虽然命令模式通常应该为一个操作使用一个示例,但在本例中,我知道在所有控件中重用同一个示例是非常安全的,因为它是针对可穿戴设备的,所以我希望保持比正常情况更轻的东西,因此在App.xaml中定义了NavigationCommand的单个示例。

jbose2ul

jbose2ul9#

我认为导航应该在View中而不是ViewModel中
这是我的解决方案

记录页.xaml

<Button x:Name="btnAddRecord"  Text="Add record" Command="{Binding AddRecordCommand}"  />

记录页. xaml.cs

[XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class RecordViewPage : ContentPage
    {
      public RecordsPage()
      { 
        InitializeComponent();
        var view_model =(RecordsPageViewModel) BindingContext;
        viewModel.RecordAdded+=OnRecordAdded;
      }
      public void OnRecordAdded(object sender, EventArgs e)
      { 
       await Shell.Current.GoToAsync($"{nameof(Page2)}");

  
      }
    }

记录页面视图模型.cs

public class RecordsPageViewModel: INotifyPropertyChanged
{   
    //it's better to create own EventHandler
    public event EventHandler RecordAdded;

    public ICommand AddRecordCommand=> new Command(async () =>
        { 
         //some code
         //if it's ok
          RecordAdded(this,new EventArgs())
        }

}

相关问题