我正在开发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));
}
}
答案 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();从视图模型关闭页面。