最近在看Laurent Bugnion的Deep Dive MVVM,大神传授了很多技巧,本文只关注一点,如何从ViewModel发起页面导航。
本文的Demo也很简单,MasterPage使用ListBox加载一些信息,单击其中任意项,页面导航到DetailsPage并显示选择项的详情,如下图。
MasterPage绑定至MasterViewModel,DetailsPage绑定至DetailsViewModel,代码在此MvvmNavigationDemo.zip,以下将只介绍关键技术点。
一、声明INavigationService接口,并实现一个继承NavigationService类.
public interface INavigationService
{
event NavigatingCancelEventHandler Navigating;
void NavigateTo(Uri uri);
void GoBack();
}
public class NavigationService : INavigationService
{
private PhoneApplicationFrame _mainFrame;
public event System.Windows.Navigation.NavigatingCancelEventHandler Navigating;
public void NavigateTo(Uri uri)
{
if (EnsureMainFrame())
{
_mainFrame.Navigate(uri);
}
}
public void GoBack()
{
if (EnsureMainFrame() && _mainFrame.CanGoBack)
{
_mainFrame.GoBack();
}
}
private bool EnsureMainFrame()
{
if(_mainFrame!=null)
{
return true;
}
_mainFrame = Application.Current.RootVisual as PhoneApplicationFrame;
if (_mainFrame != null)
{
_mainFrame.Navigating += (s, e) =>
{
if (Navigating != null)
{
Navigating(s, e);
}
};
}
return false;
}
}
二、 在MasterViewModel类中声明INavigationService字段及SelectedItem属性.
public class MasterViewModel : ViewModelBase
{
public INavigationService NavigationService { get; set; }
public ObservableCollection<FriendViewModel> Friends
{
get;
private set;
}
public const string SelectedFriendPropertyName = "SelectedItem";
private FriendViewModel _selectedItem = null;
public FriendViewModel SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem == value)
return;
var oldValue = _selectedItem;
_selectedItem = value;
RaisePropertyChanged(SelectedFriendPropertyName, oldValue, _selectedItem, true);
if(NavigationService !=null)
{
// 调用NavigationService实例方法,进行页面导航
NavigationService.NavigateTo(ViewModelLocator.DetailsPageUri);
}
}
}
}
注意,本处变更通知,使用的是ViewModelBase的重载方法,发起PropertyChanged事件并广播一个PropertyChangedMessage消息。本处另一重要技巧便是使用INavigationService接口调用导航方法。
三、DetailsViewModel中加入SelectedItem属性,在默认构造中注册接受PropertyChangedMessage消息,并在收到消息时,执行传入的Action。
public partial class DetailsViewModel : ViewModelBase
{
public const string SelectedFriendPropertyName = "SelectedItem";
private FriendViewModel _selectedItem = null;
public FriendViewModel SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem == value)
{
return;
}
_selectedItem = value;
RaisePropertyChanged(SelectedFriendPropertyName);
}
}
public DetailsViewModel(IFriendsService friendsService)
{
……
Messenger.Default.Register<PropertyChangedMessage<FriendViewModel>>(
this,
message =>
{
SelectedItem = null;
SelectedItem = message.NewValue;
});
……
}
四、ViewModelLocator中分别向SimpleIoc注册MasterViewModel/DetailsViewModel,并加入Master/Detail属性。同时将Master的NavigationService字段赋值为NavigationService的实例。
public class ViewModelLocator
{
public static readonly Uri DetailsPageUri = new Uri("/DetailsPage.xaml", UriKind.Relative);
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IFriendsService, Design.DesignFriendsService>();
}
else
{
SimpleIoc.Default.Register<IFriendsService, FriendsService>();
}
SimpleIoc.Default.Register<MasterViewModel>();
// Ensure VM
var master = SimpleIoc.Default.GetInstance<MasterViewModel>();
SimpleIoc.Default.Register<DetailsViewModel>();
// Ensure VM
SimpleIoc.Default.GetInstance<DetailsViewModel>();
master.NavigationService = new NavigationService();
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MasterViewModel Master
{
get
{
return ServiceLocator.Current.GetInstance<MasterViewModel>();
}
}
public DetailsViewModel Details
{
get
{
return SimpleIoc.Default.GetInstance<DetailsViewModel>();
}
}
}
Demo源代码:MvvmNavigationDemo.zip