我对这个问题感到非常头疼。我真的不喜欢商店应用程序,但在这种情况下我被迫使用它。我已经与XAML合作了几个星期。
我的问题是:
如何在RelayCommand
(从我的课程视图中)调用ViewModel
来改变我视图中的页面?更好的是,使用URI更改它,以便我可以将命令参数传递给文件。
我完全迷失了。目前,我在后面的“查看代码”中使用this.Frame.Navigate(type type)
来浏览网页。
我真的,我的意思是真的很感谢从a到z的描述,在这种情况下该做什么。
我认为我可以做一些事情,比如在我的View上构建一个framecontainer并将其发送到我的ViewModel,并从那里导航当前帧到另一个。但我不确定它在Store应用程序中是如何工作的。
我真的很抱歉缺少好问题,但是我在截止日期前需要以适当的方式将我的View连接到我的ViewModel ..我不喜欢这两个视图代码隐藏以及ViewModel代码。
答案 0 :(得分:12)
斯科特说你可以使用NavigationService。 我首先创建一个这个示例中不需要的接口,但是如果你将来使用依赖注入(带有视图模型和服务的好解决方案),它将非常有用:)
INavigationService:
public interface INavigationService
{
void Navigate(Type sourcePage);
void Navigate(Type sourcePage, object parameter);
void GoBack();
}
NavigationService.cs将继承INavigationService 您将需要以下命名空间
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
public sealed class NavigationService : INavigationService
{
public void Navigate(Type sourcePage)
{
var frame = (Frame)Window.Current.Content;
frame.Navigate(sourcePage);
}
public void Navigate(Type sourcePage, object parameter)
{
var frame = (Frame)Window.Current.Content;
frame.Navigate(sourcePage, parameter);
}
public void GoBack()
{
var frame = (Frame)Window.Current.Content;
frame.GoBack();
}
}
用于显示RelayCommand示例的简单ViewModel。注意我使用DoSomething RelayCommand导航到另一个页面(Page2.xaml)。
MyViewModel.cs
public class MyViewModel : INotifyPropertyChanged
{
private INavigationService _navigationService;
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public MyViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
private ICommand _doSomething;
public ICommand DoSomething
{
get
{
return _doSomething ??
new RelayCommand(() =>
{
_navigationService.Navigate(typeof(Page2));
});
}
}}
在简单示例中,我在MainPage.cs中创建了viewmodel并添加了NavigationService 但你可以在其他地方这样做,具体取决于你的MVVM设置。
MainPage.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var vm = new MyViewModel(new NavigationService());
this.DataContext = vm;
}
}
MainPage.xaml(绑定命令DoSomething)
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Width="200" Height="50" Content="Go to Page 2"
Command="{Binding DoSomething}"/>
</Grid>
希望有所帮助。
答案 1 :(得分:11)
有两种方法可以做到这一点,一种简单的方法是将视图中的中继命令操作传递给视图模型。
public MainPage()
{
var vm = new MyViewModel();
vm.GotoPage2Command = new RelayCommand(()=>{ Frame.Navigate(typeof(Page2)) });
this.DataContext = vm;
}
<Button Command={Binding GoToPage2Command}>Go to Page 2</Button>
另一种方法是使用IocContainer和DependencyInjection。这是一种更加失败的耦合方法。
我们需要一个导航页面界面,这样我们就不需要引用或了解有关PageX或任何UI元素的任何内容,假设您的viewmodel位于一个不了解UI的单独项目中。
ViewModel项目:
public interface INavigationPage
{
Type PageType { get; set; }
}
public interface INavigationService
{
void Navigate(INavigationPage page) { get; set; }
}
public class MyViewModel : ViewModelBase
{
public MyViewModel(INavigationService navigationService, INavigationPage page)
{
GotoPage2Command = new RelayCommand(() => { navigationService.Navigate(page.PageType); })
}
private ICommand GotoPage2Command { get; private set; }
}
UI项目:
public class NavigationService : INavigationService
{
//Assuming that you only navigate in the root frame
Frame navigationFrame = Window.Current.Content as Frame;
public void Navigate(INavigationPage page)
{
navigationFrame.Navigate(page.PageType);
}
}
public abstract class NavigationPage<T> : INavigationPage
{
public NavigationPage()
{
this.PageType = typeof(T);
}
}
public class NavigationPage1 : NavigationPage<Page1> { }
public class MainPage : Page
{
public MainPage()
{
//I'll just place the container logic here, but you can place it in a bootstrapper or in app.xaml.cs if you want.
var container = new UnityContainer();
container.RegisterType<INavigationPage, NavigationPage1>();
container.RegisterType<INavigationService, NavigationService>();
container.RegisterType<MyViewModel>();
this.DataContext = container.Resolve<MyViewModel>();
}
}
答案 2 :(得分:3)
当ViewModel引用要导航到的视图时,我真的不喜欢。所以我更喜欢ViewModel-first方法。通过使用ContentControls,DataTemplates for ViewModel类型&amp;我的ViewModels中有某种导航模式。
我的导航如下:
[ImplementPropertyChanged]
public class MainNavigatableViewModel : NavigatableViewModel
{
public ICommand LoadProfileCommand { get; private set; }
public ICommand OpenPostCommand { get; private set; }
public MainNavigatableViewModel ()
{
LoadProfileCommand = new RelayCommand(() => Navigator.Navigate(new ProfileNavigatableViewModel()));
OpenPostCommand = new RelayCommand(() => Navigator.Navigate(new PostEditViewModel { Post = SelectedPost }), () => SelectedPost != null);
}
}
我的NavigatableViewModel看起来像:
[ImplementPropertyChanged]
public class NavigatableViewModel
{
public NavigatorViewModel Navigator { get; set; }
public NavigatableViewModel PreviousViewModel { get; set; }
public NavigatableViewModel NextViewModel { get; set; }
}
我的导航器:
[ImplementPropertyChanged]
public class NavigatorViewModel
{
public NavigatableViewModel CurrentViewModel { get; set; }
public ICommand BackCommand { get; private set; }
public ICommand ForwardCommand { get; private set; }
public NavigatorViewModel()
{
BackCommand = new RelayCommand(() =>
{
// Set current control to previous control
CurrentViewModel = CurrentViewModel.PreviousViewModel;
}, () => CurrentViewModel != null && CurrentViewModel.PreviousViewModel != null);
ForwardCommand = new RelayCommand(() =>
{
// Set current control to next control
CurrentViewModel = CurrentViewModel.NextViewModel;
}, () => CurrentViewModel != null && CurrentViewModel.NextViewModel != null);
}
public void Navigate(NavigatableViewModel newViewModel)
{
if (newViewModel.Navigator != null && newViewModel.Navigator != this)
throw new Exception("Viewmodel can't be added to two different navigators");
newViewModel.Navigator = this;
if (CurrentViewModel != null)
{
CurrentViewModel.NextViewModel = newViewModel;
}
newViewModel.PreviousViewModel = CurrentViewModel;
CurrentViewModel = newViewModel;
}
}
My MainWindows.xaml:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Windows.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="389" Width="573"
d:DataContext="{d:DesignInstance {x:Type viewmodels:MyAppViewModel}, IsDesignTimeCreatable=True}">
<Grid>
<!-- Show data according to data templates as defined in App.xaml -->
<ContentControl Content="{Binding Navigator.CurrentViewModel}" Margin="0,32,0,0" />
<Button Content="Previous" Command="{Binding Navigator.BackCommand}" Style="{DynamicResource ButtonStyle}" HorizontalAlignment="Left" Margin="10,5,0,0" VerticalAlignment="Top" Width="75" />
<Button Content="Next" Command="{Binding Navigator.ForwardCommand}" Style="{DynamicResource ButtonStyle}" HorizontalAlignment="Left" Margin="90,5,0,0" VerticalAlignment="Top" Width="75" />
</Grid>
</Window>
App.xaml.cs:
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
new MainWindow {DataContext = new MyAppViewModel()}.Show();
}
}
MyAppViewModel:
[ImplementPropertyChanged]
public class MyAppViewModel
{
public NavigatorViewModel Navigator { get; set; }
public MyAppViewModel()
{
Navigator = new NavigatorViewModel();
Navigator.Navigate(new MainNavigatableViewModel());
}
}
的App.xaml:
<DataTemplate DataType="{x:Type viewmodels:MainNavigatableViewModel}">
<controls:MainControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:PostEditViewModel}">
<controls:PostEditControl/>
</DataTemplate>
缺点是你有更多的ViewModel代码来管理你正在看的状态。但显然这在可测试性方面也是一个巨大的优势。当然,您的ViewModel不需要依赖于您的视图。
另外我使用Fody / PropertyChanged,这是[ImplementPropertyChanged]的内容。让我不要编写OnPropertyChanged代码。
答案 3 :(得分:1)
这是实现NavigationService的另一种方法,不使用抽象类,也不在视图模型中引用视图类型。
假设目标页面的视图模型是这样的:
public interface IDestinationViewModel { /* Interface of destination vm here */ }
class MyDestinationViewModel : IDestinationViewModel { /* Implementation of vm here */ }
然后您的NavigationService可以简单地实现以下界面:
public interface IPageNavigationService
{
void NavigateToDestinationPage(IDestinationViewModel dataContext);
}
在主窗口ViewModel中,您需要注入导航器和目标页面的视图模型:
class MyViewModel1 : IMyViewModel
{
public MyViewModel1(IPageNavigationService navigator, IDestinationViewModel destination)
{
GoToPageCommand = new RelayCommand(() =>
navigator.NavigateToDestinationPage(destination));
}
public ICommand GoToPageCommand { get; }
}
NavigationService的实现封装了视图类型(Page2)和对通过构造函数注入的帧的引用:
class PageNavigationService : IPageNavigationService
{
private readonly Frame _navigationFrame;
public PageNavigationService(Frame navigationFrame)
{
_navigationFrame = navigationFrame;
}
void Navigate(Type type, object dataContext)
{
_navigationFrame.Navigate(type);
_navigationFrame.DataContext = dataContext;
}
public void NavigateToDestinationPage(IDestinationViewModel dataContext)
{
// Page2 is the corresponding view of the destination view model
Navigate(typeof(Page2), dataContext);
}
}
要获取框架,只需在MainPage xaml中命名:
<Frame x:Name="RootFrame"/>
在MainPage的代码中,通过传递根框架来初始化你的引导程序:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var bootstrapper = new Bootstrapper(RootFrame);
DataContext = bootstrapper.GetMainScreenViewModel();
}
}
最后这里是完整性的bootstrapper实现;)
class Bootstrapper
{
private Container _container = new Container();
public Bootstrapper(Frame frame)
{
_container.RegisterSingleton(frame);
_container.RegisterSingleton<IPageNavigationService, PageNavigationService>();
_container.Register<IMyViewModel, MyViewModel1>();
_container.Register<IDestinationViewModel, IDestinationViewModel>();
#if DEBUG
_container.Verify();
#endif
}
public IMyViewModel GetMainScreenViewModel()
{
return _container.GetInstance<IMyViewModel>();
}
}
答案 4 :(得分:0)
这简直让我感到困扰,没有人在架构级别上解决过这个问题。因此,这是使用内置的基于框架的导航将视图,视图模型及其之间的映射完全解耦的代码。该实现将Autofact用作DI容器,但可以轻松移植到其他IoC解决方案。
核心VM逻辑(它们应该在同一程序集中):
// I would not get into how the ViewModel or property change notification is implemented
public abstract class PageViewModel : ViewModel
{
protected internal INavigationService Navigation { get; internal set; }
internal void NavigationCompleted()
{
OnNavigationCompleted();
}
protected virtual void OnNavigationCompleted()
{
}
}
public interface INavigationService
{
void Navigate<TModel>() where TModel : PageViewModel;
}
public abstract class NavigationServiceBase : INavigationService
{
public abstract void Navigate<TModel>() where TModel : PageViewModel;
protected void CompleteNavigation(PageViewModel model)
{
model.Navigation = this;
model.NavigationCompleted();
}
}
此代码应在UWP类库或可执行文件中:
public interface INavigationMap<TModel>
where TModel: PageViewModel
{
Type ViewType { get; }
}
internal class NavigationMap<TModel, TView> : INavigationMap<TModel>
where TModel: PageViewModel
where TView: Page
{
public Type ViewType => typeof(TView);
}
public class NavigationService : NavigationServiceBase
{
private readonly Frame NavigationFrame;
private readonly ILifetimeScope Resolver;
public NavigationService(ILifetimeScope scope)
{
Resolver = scope;
NavigationFrame = Window.Current.Content as Frame;
NavigationFrame.Navigated += NavigationFrame_Navigated;
}
private void NavigationFrame_Navigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
if(e.Content is FrameworkElement element)
{
element.DataContext = e.Parameter;
if(e.Parameter is PageViewModel page)
{
CompleteNavigation(page);
}
}
}
public override void Navigate<TModel>()
{
var model = Resolver.Resolve<TModel>();
var map = Resolver.Resolve<INavigationMap<TModel>>();
NavigationFrame.Navigate(map.ViewType, model);
}
}
其余只是用于在DI和用法示例中注册的便捷代码:
public static class NavigationMap
{
public static void RegisterNavigation<TModel, TView>(this ContainerBuilder builder)
where TModel : PageViewModel
where TView : Page
{
builder.RegisterInstance(new NavigationMap<TModel, TView>())
.As<INavigationMap<TModel>>()
.SingleInstance();
}
}
builder.RegisterNavigation<MyViewModel, MyView>();
public class UserAuthenticationModel : PageViewModel
{
protected override void OnNavigationCompleted()
{
// UI is visible and ready
// navigate to somewhere else
Navigation.Navigate<MyNextViewModel>();
}
}