C# mvvm中的Xamarin.form页面导航
我正在使用xamarin.form跨平台应用程序,我想通过单击按钮从一个页面导航到另一个页面。因为我不能C# mvvm中的Xamarin.form页面导航,c#,xaml,mvvm,xamarin.forms,cross-platform,C#,Xaml,Mvvm,Xamarin.forms,Cross Platform,我正在使用xamarin.form跨平台应用程序,我想通过单击按钮从一个页面导航到另一个页面。因为我不能Navigation.PushAsync(新的Page2())在ViewModel中,因为它只能在代码Behid文件中使用。请提出任何方法来做到这一点 以下是我的看法: <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
Navigation.PushAsync(新的Page2())代码>在ViewModel中,因为它只能在代码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>
一个简单的方法是
this.ContinueBtnClicked = new Command(async()=>{
await Application.Current.MainPage.Navigation.PushAsync(new Page2());
});
一种方法是可以通过VM构造函数传递导航。由于页面继承自VisualElement
,因此它们直接继承导航
属性
代码隐藏文件:
public class SignIn : ContentPage
{
public SignIn(){
InitializeComponent();
// Note the VM constructor takes now a INavigation parameter
BindingContext = new LocalAccountViewModel(Navigation);
}
}
然后在虚拟机中,添加一个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
并返回任务类型。此外,该命令将执行异步操作调用。这是因为你必须非同步地进行页面导航
希望有帮助 我对此进行了研究,这实际上取决于您希望如何处理导航。您希望视图模型处理导航还是希望视图。我发现让我的视图处理导航是最容易的,这样我就可以为不同的情况或应用选择不同的导航格式。在这种情况下,不使用命令绑定模型,只需使用按钮单击事件并将新页面添加到代码隐藏中的导航堆栈中即可
将按钮更改为类似以下内容:
<StackLayout>
<Button Clicked="Button_Clicked"></Button>
</StackLayout>
如果您希望进行基于viewmodel的导航,我相信有一种方法可以通过MvvmCross实现,但我不熟悉该工具。来自您的虚拟机
public Command RegisterCommand
{
get
{
return new Command(async () =>
{
await Application.Current.MainPage.Navigation.PushAsync(new RegisterNewUser());
});
}
}
我的方法基于原则每个视图只能导航到基于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);
}
}
public类ItemsViewModel:ViewModelBase
{
public INavigationHandler NavigationHandler{private get;set;}
//这里的一些VM代码,在我调用的某个地方
RelayCommand ItemSelectedCommand=>
新的RelayCommand((itemID)=>{NavigationHandler.NavigateToItemDetail(itemID);});
公共接口INavigationHandler
{
无效导航项详细信息(int itemID);
}
}
并将代码隐藏类指定为ViewModel的INavigationHandler:
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));
}
}
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));
}
}
...
private async void OnSomeCommand(object obj)
{
var page = new OtherPage();
await NavigationDispatcher.Instance.Navigation.PushAsync(page);
}
...
public类ItemsPage:ContentPage,ItemsViewModel.INavigationHandler
{
ItemsViewModel视图模型;
公共项目
{
viewModel=Container.Default.Get();
viewModel.NavigationHandler=this;
}
公共异步void NavigateToItemDetail(int-itemID)
{
等待Navigation.PushAsync(新的ItemDetailPage(itemID));
}
}
通过VM构造函数传递INavigation确实是一个很好的解决方案,但如果您有深度嵌套的VM体系结构,它的代码成本也会很高
使用可从任何视图模型访问的单例包装INavigation是另一种选择:
NavigationDispatcher单例:
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中使用:
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));
}
}
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));
}
}
...
private async void OnSomeCommand(object obj)
{
var page = new OtherPage();
await NavigationDispatcher.Instance.Navigation.PushAsync(page);
}
...
几天来,当我转向Xamarin开发时,我遇到了同样的障碍,为此绞尽脑汁
因此,我的答案是将页面的类型放入模型中,但不限制视图或ViewModel使用它(如果选择的话)。这使系统保持了灵活性,因为它不会通过视图中的硬接线或代码中的硬接线来限制导航,因此更便于携带。您可以跨项目重用您的模型,并且只需设置当这种情况到达另一个项目时它将导航到的页面的类型
为此,我生产了一台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);
}
}
}
以及它在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>
注意,虽然命令模式通常应在一次操作中使用一个实例,在这种情况下,我知道在所有控件中重复使用同一个实例是非常安全的,因为它是用于可穿戴设备的,所以我想让事情比正常情况更轻,因此在App.xaml中定义了NavigationCommand的单个实例。决定添加两种方法,将页面实例传递给viewmodel,以便以后用于导航,显示警报。关闭页面等等
1。如果可以使用命令参数传递它
在视图模型中:
public ICommand cmdAddRecord { get; set; }
视图模型构造函数
cmdAddRecord = new Command<ContentPage>(AddRecord);
XAML
标题
x:Name="thisPage"
用法
XAML
我们开始吧
<ContentPage.BindingContext>
<viewModels:cPetEventsListVm Parent="{Binding ., Source={x:Reference thisPage}}" />
</ContentPage.BindingContext>
现在,我们可以使用诸如Parent.DisplayAlert或Parent.Navigation.PushAsync等页面
我们甚至可以使用Parent.popsync()从视图模型关闭页面 它可以工作,但如果我们想在Xaml视图文件中绑定上下文呢。我们如何将参数传递给视图模型的构造函数?在Code Behid文件或Xaml中绑定上下文的最佳方法是什么?@WaleedArshad:在调用InitializeComponent()
之后创建DataContext
。因此,您可以稍后将其传递给ViewModel。我通常在构造函数中设置ViewModel,以防它需要额外的参数(这是我唯一尝试放入代码背后的代码)。我也发现它更容易阅读。如果您没有参数,这是一个选择的问题。很抱歉插嘴-但这绝对打破了所有MVVM/MVC/MVV规则。。。。您的ViewModel永远不应该创建视图,反之亦然。所有这些概念的目标都是将视图和模型分开,所以视图是可交换的,在您的示例中并非如此。想象一下为View、ViewModel和Model分别执行不同的项目(real*.csproj文件),您就会看到这个问题。来自WPF的背景,我来到这里,看看Xamarin的家伙们是如何做到这一点的,直到现在我对c不太满意
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);
}
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