MVVM 应用程序中的导航 (Onyx)






4.33/5 (7投票s)
MVVM 应用中的导航。结合 DataTemplates 的导航,NavigationService 提供了更丰富的 WPF 开发工具包。我认为利用 NavigationService 会很有用,因为 WPF 应用的开发就会变得类似于 Web 开发。
目录
引言
有很多文章讨论 MVVM。其中一种 MVVM 方法由 Josh Smith 和 Karl Shifflett 提出/介绍。在我的方法中,视图到视图的导航可以通过 DataTemplates 来实现,DataTemplates “告诉”如何显示指定的类型。由 Sacha Barber 编写的 Cinch 框架就采用了这种方法。另一种方法,我认为可以基于原生的 (.NET) NavigationService
。在阅读了 “WPF: FlipTile 3D” 文章,其中提到了 Onyx 框架后,我开始考虑 MVVM 的导航问题。在 “Future Onyx Work” 部分,Sacha Barber 写道:“我曾告诉 Bill,我认为缺少一些东西,那就是从 ViewModel 打开一个新的实际 WPF 窗口的能力,以及整个导航问题…”。在这篇文章中,我想介绍一种 MVVM 应用的导航方法,并可能为 Onyx 的开发做出一点贡献。
XAML 和 BAML
XAML 是 .NET 语言,用于定义对象图。XAML 类似于 C# 或 Visual Basic,可能没有那么灵活,但它在用 XML 描述对象图方面做得很好。而 BAML 类似于 IL。BAML 是 WPF 场景中编译后的 XAML 标记。
一个 XAML 文件(通常)与一个相关的首要代码文件一起工作,该文件通过 x:Class
属性指定。XAML 文件被处理后会生成一个 *.g.cs 文件和一个 BAML 文件。首要代码文件和 *.g.cs (g - generated) 文件合并成一个统一的类。BAML 被嵌入为程序集的资源。首次编译后,您可以在 obj/Debug 文件夹中找到这些文件。您也可以在 Reflector 中反射您的程序集时看到这些文件。
让我们考虑应用程序中的两种场景
StartupUri
属性等于 Page.xaml 或 UserControl.xaml;也就是说,根元素是System.Windows.Controls.Page
或System.Windows.Controls.UserControl
类型。StartupUri
属性等于 Window1.xaml;也就是说,根元素是System.Windows.Window
类型。
这两种情况下 WPF 应用程序的启动过程有什么区别?在第一种情况下,会创建一个 NavigationService
实例。考虑 System.Windows.Application::GetAppWindow()
方法。对于独立情况,该方法会创建并返回一个 NavigationWindow
。需要注意的是,在 NavigationWindow
初始化期间,会创建一个 NavigationService
。
public NavigationWindow()
{
this.Initialize();
}
private void Initialize()
{
Debug.Assert(_navigationService == null && _JNS == null);
_navigationService = new NavigationService(this);
_navigationService.BPReady += new BPReadyEventHandler(OnBPReady);
_JNS = new JournalNavigationScope(this);
_fFramelet = false;
}
NavigationWindow
的这个特性尤其有用,因为 NavigationService
的构造函数是 internal
的,我们无法直接创建它。
在第二种情况下,不会创建 NavigationService
,我们也无法进行导航。
URI
让我们看看 BAML 资源的内容是如何显示的。为此,使用了 System.Windows.Application::LoadComponent(Uri uri)
方法,该方法返回 XAML 内容的对象图。
internal void DoStartup()
{
…
object root = LoadComponent(StartupUri, false);
// If the root element is not a window, we need to create a window.
ConfigAppWindowAndRootElement(root, StartupUri);
…
}
为了访问 BAML 资源,我们必须使用统一资源标识符 (URI)。URI 可用于从各种位置加载文件,包括以下位置:
- 当前程序集
- 已引用的程序集
- 相对于程序集的位置
- 应用程序的站点来源
文件 | 绝对 Pack URI |
资源文件 - 本地程序集 | Uri uri = new Uri("pack://application:,,,/ResourceFile.xaml", UriKind.Absolute); |
子文件夹中的资源文件 - 本地程序集 | Uri uri = new Uri("pack://application:,,,/Subfolder/ResourceFile.xaml", UriKind.Absolute); |
资源文件 - 已引用的程序集 | Uri uri = new Uri("pack://application:,,,/ReferencedAssembly; component/ResourceFile.xaml", UriKind.Absolute); |
已引用程序集子文件夹中的资源文件 | Uri uri = new Uri("pack://application:,,,/ReferencedAssembly; component/Subfolder/ResourceFile.xaml", UriKind.Absolute); |
版本化的已引用程序集中的资源文件 | Uri uri = new Uri("pack://application:,,,/ReferencedAssembly; v1.0.0.0; component/ResourceFile.xaml", UriKind.Absolute); |
内容文件 | Uri uri = new Uri("pack://application:,,,/ContentFile.xaml", UriKind.Absolute); |
子文件夹中的内容文件 | Uri uri = new Uri("pack://application:,,,/Subfolder/ContentFile.xaml", UriKind.Absolute); |
站点来源文件 | Uri uri = new Uri("pack://siteoforigin:,,,/ SOOFile.xaml", UriKind.Absolute); |
子文件夹中的站点来源文件 | Uri uri = new Uri("pack://siteoforigin:,,,/Subfolder/ SOOFile.xaml", UriKind.Absolute); |
文件 | 相对 Pack URI |
资源文件 - 本地程序集 | Uri uri = new Uri("/ResourceFile.xaml", UriKind.Relative); |
子文件夹中的资源文件 - 本地程序集 | Uri uri = new Uri("/Subfolder/ResourceFile.xaml", UriKind.Relative); |
资源文件 - 已引用的程序集 | Uri uri = new Uri("/ReferencedAssembly;component/ResourceFile.xaml", UriKind.Relative); |
已引用程序集子文件夹中的资源文件 | Uri uri = new Uri("/ReferencedAssembly;component/Subfolder/ResourceFile.xaml", UriKind.Relative); |
内容文件 | Uri uri = new Uri("/ContentFile.xaml", UriKind.Relative); |
子文件夹中的内容文件 | Uri uri = new Uri("/Subfolder/ContentFile.xaml", UriKind.Relative); |
在演示应用程序中,我使用了绝对 URI 路径,因为我需要从本地和远程程序集进行导航。
private void NavCommandExecute(object sender, ExecutedRoutedEventArgs args)
{
NavigationService service =
NavigationService.GetNavigationService(this.View.ViewElement);
try
{
// We use absolute Uri path, since it should be valid
// in both cases when run from local or remote assemblies
Uri uri = new Uri("pack://application:,,,/NavigationDemo;component/" +
args.Parameter.ToString(), UriKind.Absolute);
service.Navigate(uri);
}
catch (Exception ex)
{
}
}
测试应用程序
让我们考虑如何在 WPF 应用程序中进行导航测试。一旦我们创建了 NavigationWindow
的实例,我们就获得了 NavigationService
的功能。因此,我们能够导航到可以位于本地和远程程序集中的资源。
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Load remote assembly
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
Assembly.LoadFile(path + @"\NavigationDemo.exe");
Application.Run(new TestForm());
}
public TestForm()
{
InitializeComponent();
nw = new NavigationWindow();
try
{
uri = new Uri("pack://application:,,,/NavigationDemo;component/Page1.xaml",
UriKind.Absolute);
nw.Navigate(uri);
nw.Show();
}
catch (Exception ex) {}
}
结论
本文介绍了一种 MVVM 应用的导航方法。结合 DataTemplates 的导航,NavigationService 提供了更丰富的 WPF 开发工具包。我认为利用 NavigationService 会很有用,因为 WPF 应用的开发就会变得类似于 Web 开发。
历史
- 2009/8/09:初始发布。
- 2009/12/11:
NavigationWindow
控件通过 Custom Navigator 进行了扩展。