65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (7投票s)

2009年9月8日

CPOL

4分钟阅读

viewsIcon

71060

downloadIcon

2243

MVVM 应用中的导航。结合 DataTemplates 的导航,NavigationService 提供了更丰富的 WPF 开发工具包。我认为利用 NavigationService 会很有用,因为 WPF 应用的开发就会变得类似于 Web 开发。

mvvm.png

目录

引言

有很多文章讨论 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.xamlUserControl.xaml;也就是说,根元素是 System.Windows.Controls.PageSystem.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 内容的对象图。

baml_to_objects.png

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 可用于从各种位置加载文件,包括以下位置:

  • 当前程序集
  • 已引用的程序集
  • 相对于程序集的位置
  • 应用程序的站点来源

uri1.png

表 1
文件 绝对 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);
表 2
文件 相对 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 进行了扩展。
© . All rights reserved.