WPF SDK研究 之 AppModel
本章共计27个示例,全都在VS2008下.NET3.5测试通过,点击这里下载:AppModel.rar
1.ActivationSample
This sample demonstrates how to handle both the Activated and Deactivated events to monitor the activation status of an application.
这个示例是在模拟一个mail系统。当窗体未被激活时,状态栏会显示代表新邮件的icon;反之,这个icon就会消失。如果熟悉事件机制,逻辑就很简单了。再有就是timer在MailDispatcher中,而主逻辑和事件都在App.xaml中;MainWindow只是显示添加的新数据。这其实就是一个Observer模式。
还有,就是WPF支持的在App应用程序级别声明的4个事件:
Startup="MyApp_Startup" //应用程序开始,不是窗体开始
Activated="MyApp_Activated" //激活
Deactivated="MyApp_Deactivated" //未激活
Exit="MyApp_Exit" //应用程序结束,不是窗体结束
示例图如下:
激活时的截图:
未激活时的截图,注意状态栏中红色勾选的icon,表明这是一封新mail:
2.ApplicationShutdownSample
This sample demonstrates the implicit and explicit mechanisms for calling Shutdown and Shutdown.
这个例子是在讲WPF中应用程序关闭的3种方式(注意,不是窗体关闭),也就是ShutdownMode的3个枚举。
l OnLastWindowClose
应用程序在最后一个窗体关闭时,或调用Shutdown方法时才会关闭
l OnMainWindowClose
An application shuts down when either the main window closes, or Shutdown is called.
应用程序在主窗体关闭时,或调用Shutdown方法时才会关闭
l OnExplicitShutdown
应用程序只有在调用Shutdown方法时才会关闭。
*注:这里的Shutdown方法指的是:Application.Current.Shutdown();
对于应用程序,可以这么使用:
Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
或者是,这里的exitCode为枚举对应的数值。
Application.Current.Shutdown(exitCode);
3.CookieSample
This sample demonstrates how to create and retrieve cookies from a Windows Presentation Foundation (WPF) application using SetCookie and GetCookie.
主要是两个方法:
获取Cookie
this.getCookieValueTextBox.Text = Application.GetCookie(cookieUri);
设置Cookie
Application.SetCookie(cookieUri, this.setCookieValueTextBox.Text);
4.CustomWindow
This sample shows how to implement a window that contains a custom chrome area at the top and a custom status bar at the bottom.
通过在StackPanel中指定事件:
MouseLeftButtonDown="DragAttempt"
后台代码相应为:
{
this.DragMove();
e.Handled = true;
}
从而鼠标在图中蓝色区域单击左键,然后可以拖动到任意地方。
5.DispatcherUnhandledExceptionSample
This sample demonstrates how to handle DispatcherUnhandledException to process recoverable and unrecoverable unhandled application exceptions.
这个示例讲的是如何实现DispatcherUnhandledException接口,从而处理应用程序中的可恢复和不可恢复异常。
第一个按钮,使用DivideByZeroException来模拟可恢复异常
throw new DivideByZeroException("Recoverable Exception");
第二个按钮,使用ArgumentNullException来模拟不可恢复异常
throw new ArgumentNullException("Unrecoverable Exception");
由于在App.xaml中设置了DispatcherUnhandledException事件:
DispatcherUnhandledException="MyApp_DispatcherUnhandledException"
后台的MyApp_DispatcherUnhandledException方法,会根据具体是哪种异常,来决定是否显示自定义对话框并关闭。逻辑很简单。关键是在窗体抛出异常的时候,在应用程序级别App.xaml通过DispatcherUnhandledException机制可以捕获到。
点击第二个按钮,弹出对话框如下:
6.WindowAPI
This sample shows how to view and assign values to a variety of window properties.
要使用NavigationWindow,必须引用System.Windows.Navigation命名空间。
只有Page可以使用NavigationWindow。
其它都是一些Page的属性,很简单。
7.FragmentNavigationSample
This sample demonstrates how to navigate to an embedded XAML Page fragment. It also shows how to handle the FragmentNavigation event to detect when the desired fragment is not found, and override the default behavior by navigating to an error XAML Page, FragmentNotFoundPage.
这个例子的逻辑是,在文本框里输入URL,可以是外部的一个URL,也可以是DocumentPage.xaml#Fragment5这样的相对路径,其中#Fragment5指定了显示DocumentPage.xaml中Name="Fragment5"的TextBlock。在找不到#Fragment5这样的节点时,就跳转到FragmentNotFoundPage.xaml。
按下Go按钮的逻辑,执行goButton_Click方法:
{
// Navigate to Uri, with or with out fragment
// NOTE - Uri with fragment looks like "DocumentPage.xaml#Fragment5"
Uri uri = new Uri(this.addressTextBox.Text, UriKind.RelativeOrAbsolute);
this.browserFrame.Navigate(uri);
}
这里browserFrame的Navigate方法,会激发FragmentNavigation事件,从而执行相应的browserFrame_FragmentNavigation方法。
{
object content = ((ContentControl)e.Navigator).Content;
FrameworkElement fragmentElement = LogicalTreeHelper.FindLogicalNode((DependencyObject)content, e.Fragment) as FrameworkElement;
if (fragmentElement != null)
{
// Go to fragment if found
fragmentElement.BringIntoView();
}
else
{
// Redirect to error page
this.browserFrame.Navigate(new FragmentNotFoundPage());
}
e.Handled = true;
}
仍然是使用DispatcherUnhandledException捕获异常,在访问不到URL的时候,也就是WebException异常。
8.GetSet
This sample illustrates how to get and set named application object properties.
这个例子演示了获取和设置应用程序级命名对象属性,这就像ASP中的Application级变量。
先看一下点击按钮的方法,设置了App.Current.Properties["TextFromPage1"]变量:
{
NavigationWindow navWindow = (NavigationWindow)App.Current.MainWindow;
App.Current.Properties["TextFromPage1"] = txtBox.Text;
navWindow.Navigate(new Uri("Page2.xaml", UriKind.RelativeOrAbsolute));
}
这里设置了App.Current.Properties["TextFromPage1"],同时创建了NavigationWindow对象。
创建Uri对象的语法如下:
new Uri("Page2.xaml", UriKind.RelativeOrAbsolute)
第二个页面在初始化中,从App.Current.Properties["TextFromPage1"]变量中获取值。
{
string appPropertyValue;
appPropertyValue = (String)App.Current.Properties["TextFromPage1"];
txtFromPage1.Text += appPropertyValue;
}
点击“Go To Page2”按钮后,跳转到第二个页面:
9.ThemedApplicationSample
This sample illustrates how to implement an application that supports dynamically-swappable themes.
这个例子的逻辑是,点击NewChildWindow按钮,新打开的子窗体会使用当前选择的Theme作为背景色。选择不同的Theme,主窗体背景色改变的同时,子窗体也会跟着改变。
这里更多的技术是WPF资源ResourceDictionary。然后辅助于一些小技巧。
从BlueTheme类说起:
{
public BlueTheme()
{
this["background"] = new SolidColorBrush(Colors.Blue);
}
}
这个类中this["background"]是比较费解的,因为BlueTheme派生于ResourceDictionary这个字典类,所以this["background"]等价于BlueTheme["background"],"background"是key,而它的值是new SolidColorBrush(Colors.Blue)这个对象,从而我们可以这样使用BlueTheme类:
Object obj = blue["background"];
Application.Current.Resources = obj;
这时obj是一个可以使用的SolidColorBrush类型的资源。
在App中,建立应用程序级的themes变量,用来存储BlueTheme这些资源对象:
static Dictionary<string, ResourceDictionary> themes;
themes["Blue"] = new BlueTheme();
此时的themes["Blue"]等价于下列语句的rd对象:
ResourceDictionary rd = new ResourceDictionary();
rd.Add("background", new SolidColorBrush(Colors.Blue));
之后就可以在SetTheme方法中设置资源了。
Application.Current.Resources = themes[theme];
这就添加了一个key为"background",值为一个SolidColorBrush对象的资源。
主窗体和子窗体都使用如下语句来设置资源,因为在此之前App已经建立了"background"资源:
this.SetResourceReference(Window.BackgroundProperty, "background");
这是一个动态资源,ResourceDictionary["background"]的改变,会使得应用了该资源的属性也跟着改变。
10.LaunchDialogBox
This sample shows how to launch modal dialog boxes and message boxes.
这个例子讲的是主页面如何与对话框(dialog box)和消息框(message box)通信。
对于左边的对话框,并没有借助于Observer模式实现主从窗体建的通信,而是通过读写App.Current.Properties["UserData"]来传递值。
而右边的消息框则与传统方式一样,没有什么特别之处,只是详细的配置了MessagBox的各个属性。
点击左边按钮:
点击右边按钮:
注意到ReturnEventArgs<T>派生自EventArgs:
{
public ReturnEventArgs();
public ReturnEventArgs(T result);
public T Result { get; set; }
}
默认构造函数使用于不需要传递参数的情形。
11.NavigationServiceSample
This sample demonstrates using NavigationService to navigate to embedded and loose XAML content.
这个示例讲的是导航条NavigationService的使用。注意到其5个事件的使用(在Window加载的时候绑定方法)
Navigating
Navigated
NavigationProgress
NavigationStopped
LoadCompleted
其中只要发生导航动作(向前,向后,刷新,Go按钮),都会先触发Navigating;然后,只要每加载完毕,就会一直调用NavigationProgress;加载结束,会调用Navigated;最后是LoadCompleted。
12.NavWindowEvents
This sample shows how to handle the NavigationWindow events (C#, Extensible Application Markup Language (XAML)). It also displays the text equivalents of the arguments that are passed to the event handlers.
这个例子演示了NavigationWindow各种事件的用法。
LoadCompleted
Navigating
Navigated
NavigationProgress
NavigationStopped
在执行Navigate方法到一个新的Uri的时候,一般都会先后经历以下几个事件:Navigating——Navigated——LoadCompleted。
对于导航到有若干Frame的页面,则是还要而外加上几套Navigated——LoadCompleted事件。
对于NavigationProgress方法,是在加载中不停地询问,加载了多少个字节,每次1024个字节,直到全部加载完成。这个方法在大块文字时才会激发,对于小于1024字节的文本,不会激发。
NavigationStopped触发于以下语句,navWindow为导航窗口:
navWindow.StopLoading();
注意,Hyperlink也有RequestNavigate事件——在设置了NavigateUri="Page1.xaml"的同时。在点击超链接时,也就激发了这个事件。
还有就是如何在StackPanel中添加子控件,要使用它的Children属性:
eventText.Children.Add(new TextBlock);//这里eventText是一个StackPanel
这个例子中左边显示的参数,是e的各个参数,如Content、Navigator和Uri,这个e为NavigationEventArgs类型,携带着导航信息。
13.NonRectangularWindowSample
This sample demonstrates how to create a non-rectangular window, which is configured using the Background, AllowsTransparency, and WindowStyle properties of the Window type.
这个例子是我目前见到的最酷的一个例子。
为了将Window的背景色设置为透明,需要配置以下三个属性,缺一不可:
WindowStyle="None"
AllowsTransparency="True"
Background="Transparent"
14.OnStartUp
This sample shows how to override the application object's OnStartingUp method in the application definition file to implement a custom startup procedure.
OnStartingUp是Application基类的虚方法,同样的方法还有:
OnActivated(EventArgs e);
OnDeactivated(EventArgs e);
OnExit(ExitEventArgs e);
OnFragmentNavigation(FragmentNavigationEventArgs e);
OnLoadCompleted(NavigationEventArgs e);
OnNavigated(NavigationEventArgs e);
OnNavigating(NavigatingCancelEventArgs e);
OnNavigationFailed(NavigationFailedEventArgs e);
OnNavigationProgress(NavigationProgressEventArgs e);
OnNavigationStopped(NavigationEventArgs e);
OnSessionEnding(SessionEndingCancelEventArgs e);
15.OpenWindow
This example shows how to open a new window.
打开一个新Window的命令是 window1.Open();
16.PageAPI
Use the Page tag to view and assign values to a variety of window properties.
在Page中设定大小和Title可没有Window那么简单,WindowHeight、WindowWidth,这两个属性在.NET2.0中仅用于设定Console窗体的大小,对WinForm无效。这里容易与WPF混淆的。在.NET3.0中,这两个属性仅用于设定Page的大小——Window中没有,此时,在xaml中指定Height和Width是没有用的。
<Page Title="Page1" WindowHeight="300" WindowWidth="300">
再说Title,要显示这个属性有点麻烦。可以直接在xaml中设置,但是不会显示。只有通过编程的方式,将其放在一个NavigationWindow中,并手动设置这个属性,才可以显示。
myWindow = new NavigationWindow();
myWindow.Navigate(new Uri("Page1.xaml", UriKind.RelativeOrAbsolute));
myWindow.Title = "123";
myWindow.Show();
通过在App中指定StartupUri="Page1.xaml"打开初始页面;关闭Page时,也就是关闭整个App,所以Page页面的关闭方法为:
{
App.Current.Shutdown();
}
这是与Window的关闭方法不同的:
this.Close(); //Window通过这个方法就可以直接关闭
17.PersistApplication
This example uses isolated storage to persist the application state on shutdown and restores it when the application is restarted.
这个例子演示了IsolatedStorageFile这个流的用法,又名“独立存储区”,可以把一些信息存这个存储区,而不会随着应用程序的关闭而失效,使用最广的地方是SmartClient中。唯一的清除方法是使用命令行storeadm,其中比较常用的是:
storeadm/remove可以清空存储区
storeadm/list 是存储区的列表
一般存储在“C:"Documents and Settings"Administrator"Local Settings"Application Data”中。
18.ReusableCustomApplicationSample
This sample demonstrates how to create a reusable Application class, deployed in a library (dll) assembly.
这个例子讲的是如何重用控件库中的dll。
控件库中,有一个派生于Application的CustomApplication子类,重写了OnStartup方法。
{
base.OnStartup(e);
MessageBox.Show("Hello, reusable custom application!");
}
在我们的WPF程序中,添加对这个dll的引用,使App.xaml派生于CustomApplication,相应的XAML要改为:
x:Class="CustomApplication.App"
xmlns:local="clr-namespace:CustomApplicationLibrary"
StartupUri="MainWindow.xaml"
>
</local:CustomApplication>
于是,在MainWindow窗口打开前,先弹出下面这个对话框:
19.SingleInstanceDetectionSample
This sample demonstrates how to leverage the Microsoft .NET Framework to incorporate single-instance detection into a Windows Presentation Foundation application.
本例导入了Microsoft.VisualBasic.ApplicationServices以使用其WindowsFormsApplicationBase基类。这个基类提供了IsSingleInstance属性,构造函数中设置为true,启动应用程序时,会根据这个值判断:如果为true则激活OnStartup方法,先Run起来这个Window,然后将其值改为false;如果为false则激活OnStartupNextInstance方法,将焦点设置在原先那个Window上。SingleInstanceManager会使用时创建唯一的SingleInstanceApplication对象,而Main函数仅对SingleInstanceManager进行操作:
{
SingleInstanceManager manager = new SingleInstanceManager();
manager.Run(args);
}
结果很简单,就是显示一个Window,但是如果不关闭当前应用程序,再次执行exe可执行文件,不会产生第二个窗体。
20.SimpleNav
This sample shows several ways to navigate to a new Extensible Application Markup Language (XAML) page.
这个例子演示了页面导航的各种方式。
URL的方式,直接跳转到Page2.xaml:
<Hyperlink NavigateUri="Page2.xaml">Go To Page 2</Hyperlink>
这时,为Page2页面的myWindow.CurrentSource为SimpleNav;component/Page2.xaml
第1个按钮,使用Navigate方法:
navWindow.Navigate(new Uri("Page2.xaml", UriKind.RelativeOrAbsolute));
这时Page2页面的myWindow.CurrentSource为Page2.xaml
第2个按钮,使用Source属性达到和Navigate方法同样的效果:
navWindow.Source = new Uri("Page2.xaml", UriKind.RelativeOrAbsolute);
第3个按钮,使用Content属性:
Page2 nextPage = new Page2();
nextPage.InitializeComponent();
navWindow.Content = nextPage;
但是这样不会Page2页面的myWindow.CurrentSource不会有值
第4个按钮:暂缺,这个功能我现在还没调试出来。
第5个按钮,这里打开的是一个Window,所以是在一个新的窗口显示,这就区别于以上所有按钮——都是打开一个Page,所以是在Page间进行跳转。
NewWindow newWindow = new NewWindow();
newWindow.InitializeComponent();
newWindow.Show();
观察Page2页面,由于页面间有跳转功能,所以Page类提供了CanGoBack属性和GoBack方法,进行导航。
21.SimpleProcedural
This sample shows how to implement a simple Windows Presentation Foundation application entirely in procedural code.
本例演示如何在代码中现一个WPF应用程序,而不是基于XAML。也就是写一个继承于Application的类MyApp,让Main函数调用它。
{
MyApp app = new MyApp();
app.Run();
}
而MyApp要重写OnStartup方法,手动建立一个Window实例,并Show()出来:
{
win = new System.Windows.Window();
rootPanel = new StackPanel();
txtElement = new TextBlock();
txtElement.Text = "Some Text";
win.Content = rootPanel;
rootPanel.Children.Add(txtElement);
win.Show();
}
22.StructuredNavigationSample
这个示例是PageFuction的基础。
结构化导航,如下图:
由于Page并未实现结构化导航,所以我们引进了派生自Page的PageFunction<T>,并使用结构化导航所需的基本构造来对它进行扩展。
在本例中,调用页为CallingPage.xaml,提供一个超链接;调用的页为CalledPageFunction.xaml。
不必向被调用页传递参数。可以执行以下操作:
·从调用页:
1. 使用默认构造函数实例化被调用的 PageFunction<T>。
2. 将参数存储在 Properties 中,这个参数这里就是字符串"Initial Data Item Value"。
3. 导航到被调用的 PageFunction<T>。
代码如下:
CalledPageFunction CalledPageFunction = new CalledPageFunction("Initial Data Item Value");
CalledPageFunction.Return += pageFunction_Return;
this.NavigationService.Navigate(CalledPageFunction);
以及相应的处理return的方法:
{
this.pageFunctionResultsTextBlock.Visibility = Visibility.Visible;
// Display result
this.pageFunctionResultsTextBlock.Text = (e != null ? "Accepted" : "Canceled");
// If page function returned, display result and date
if (e != null)
{
this.pageFunctionResultsTextBlock.Text += ""n" + e.Result
}
}
·从被调用的 PageFunction<T>: 检索并使用存储在 Properties 中的参数。
代码如下。其中所谓的参数存储在dataItem1TextBox文本框的Text中:
{
// Accept when Ok button is clicked
OnReturn(new ReturnEventArgs<string>(this.dataItem1TextBox.Text));
}
void cancelButton_Click(object sender, RoutedEventArgs e)
{
// Cancel
OnReturn(null);
}
这里OnReturn事件是PageFunction<T>的,负责触发前面的Return方法。
注意到PageFunction<T>的XAML这么写:
<PageFunction ……
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:TypeArguments="sys:String">
String就是我们在PageFunction<T>中的T。
23.SimpleIUI2
This sample shows how to create a simple Extensible Application Markup Language (XAML)-based PageFunction application with a fixed linear topology. It behaves much like a conventional wizard.
这个貌似简单的例子,真要讲明白还要多花些力气。
PageFunction<T>的JournalEntry.KeepAlive属性的作用:
获取或设置一个值,该值指示在导航到日记条目内容时,在导航历史记录中是保留(true)还是重新创建内容(false)。
这个PageFunction<T>,将T换成了自定义类型:TaskData
为此要修改XAML的声明方式:
<PageFunction ……
xmlns:sys="clr-namespace:SimpleIUI2"
x:TypeArguments="sys:TaskData">
流程:
1.初始化页面
2.点击Start按钮后:
以下有两条路:
3.1点击Cancel按钮后,回到初始页面:
3.2点击Go To Task1按钮后:
4.沿着3.2分支走下来:
5.点击Cancel后回到初始页面,点击Done按钮后如图示:
注意一下ReturnEventArgs,这个派生于EventArgs的类:
{
public ReturnEventArgs();
public ReturnEventArgs(T result);
public T Result { get; set; }
}
对于PageFunction<T>,如果T为简单类型,如String,那么使用ReturnEventArgs的默认构造函数就够了,因为不需要传递对象,通常只是简单的字符串或数字等;如果T为自定义类型,那么要使用ReturnEventArgs带参数的构造函数,这样创建实例的同时,就得到了它的Result属性,从而传递了返回值。
还有就是RemoveFromJournal的使用——当任务完成时移除任务页。
当被调用页返回,并且用户未取消被调用页时,调用页将处理由用户提供并且从被调用页返回的数据。这种方式的数据获取通常是一个独立的活动;当被调用页返回时,调用页需要创建并导航到新的调用页来捕获更多数据。
但是,除非从日记中移除了被调用页,否则,用户将能够重新导航到调用页的上一个实例。是否在日记中保留 PageFunction<T> 由 RemoveFromJournal 属性决定。默认情况下,调用 OnReturn 时会自动移除页面函数,因为 RemoveFromJournal 设置为 true。若要在调用 OnReturn 后在导航历史记录中保留页面函数,请将 RemoveFromJournal 设置为 false。
24.IUIFrame
This sample shows how to display a PageFunction application in a frame.
这个例子是对示例-24的包装——将PageFunction放在Frame控件content中,让Frame来进行导航:
content.Navigate(nextPage);
25.DynamicIUI
This sample shows how to create an Extensible Application Markup Language (XAML)-Based PageFunction application with a topology that is determined dynamically at run time.
调用顺序:
步骤1:
由StartPage进入,通过按钮的点击事件,创建一个NavHub实例,为其Return事件加上navHub_Return方法,然后导航到NavHub类实例:
NavHub nextPage = new NavHub();
nextPage.Return += new ReturnEventHandler<TaskData>(navHub_Return);
navWindow.Navigate(nextPage);
这里的navHub_Return方法,负责在三个颜色页面全都执行完毕后又回到StartPage时,获取存储在ReturnEventArgs中的数据。
由于NavHub类重写了Start方法,所以导航后,这个方法会被激发,进入步骤2。
步骤2:
NavHub类对所有的PageFunction进行动态管理,其中,有一个私有字段task:
private PageFunction<TaskData>[] tasks = = new PageFunction<TaskData>[3];
根据当前时间的毫秒数,得到0、1、2间的一个整数startTask,从而决定使用哪一个task[startTask]对应blue,然后依次对应到Green和Yellow:如下:
tasks[startTask] = blueTask;
tasks[startTask + 1 对3取余数] = greenTask;
tasks[startTask + 2 对3取余数] = yellowTask;
这里,yellowTask是使用了带参(dataObject:TaskData)构造函数的YellowTask对象。这里我们假设startTask为0。
然后,为这些对象的Return事件加上NavHub_Return方法:
tasks[0].Return += new ReturnEventHandler<TaskData>(NavHub_Return);
这里的navHub_Return方法,负责在当前页面结束后,导航到下一个页面,有两种可能,或者是继续遍历下一个颜色页面,或者是结束遍历回到StartPage页面。
最后进行导航,这里是导航到BlueTask页面:
navWindow.Navigate(tasks[0]);
步骤3:
三个颜色页面的主逻辑NextTask和Init方法是一样的,都是根据当前dataObject.Response枚举值,来决定要将其改变为什么新的枚举值;对于NextTask而言,还要将这个新值封装进ReturnEventArgs,调用OnReturn()方法,进行再次导航,这样就又回到了步骤2的navHub_Return方法,由其来决定下一个页面是什么。
26. CustomContentState
使用 CustomContentState,可以在源内容片段的不同状态之间导航,而无需为每个后续导航重新加载源内容。
默认情况下,NavigationService 不在导航历史记录中存储内容对象的实例。实际上,每次使用导航历史记录导航到内容对象时,NavigationService 都会创建该内容对象的新实例。此行为旨在避免导航到大量内容和较大内容片断时过度消耗内存。因此,从一个导航进入下一个导航时,并不会保留内容的状态。但是,WPF 可提供将一种自定义状态与内容片段的导航历史记录条目关联的能力。
与导航历史记录条目关联的自定义状态必须是从 CustomContentState 派生的类。可以使用下列技术之一将 CustomContentState 对象与导航历史记录条目关联:
l 调用 AddBackEntry:
NavigationService.AddBackEntry
NavigationWindow.AddBackEntry
Frame.AddBackEntry.
l 当引发下列事件之一时设置 NavigatingCancelEventArgs.Content:
NavigationService.Navigating
NavigationWindow.Navigating
Frame.Navigating
NavigationWindow.Navigating
l 通过在需要有自定义状态与自己关联的类上实现 IProvideCustomContentState。
说明:如果调用 AddBackEntry 方法,则必须处理 Navigating 事件或实现 IProvideCustomContentState。
当导航到导航历史记录条目时,WPF 将检查是否有自定义 CustomContentState 对象与该条目关联。如果有,它将调用 Replay 以允许自定义 CustomContentState 对象应用它从以前的导航中保留的状态。
自定义CustomContentState 类可以重写 JournalEntryName,以便更改与 CustomContentState 对象关联的导航历史记录条目的显示名称。JournalEntryName 返回的值可以从导航 UI 中看到。
从CustomContentState 派生的类必须是可序列化的,这意味着它必须至少通过 SerializableAttribute 进行扩充,并且可以选择实现 ISerializable。
下面是一个自定义状态的类:
using System.Windows.Controls;
using System.Windows.Navigation;
[Serializable]
public class MyCustomContentState : CustomContentState
{
string dateCreated;
TextBlock dateTextBlock;
public MyCustomContentState(string dateCreated, TextBlock dateTextBlock)
{
this.dateCreated = dateCreated;
this.dateTextBlock = dateTextBlock;
}
public override string JournalEntryName
{
get
{
return "Journal Entry " + this.dateCreated;
}
}
public override void Replay(NavigationService navigationService, NavigationMode mode)
{
this.dateTextBlock.Text = this.dateCreated;
}
}
注意,我们在这个类中传递的是TextBlock类型的dateTextBlock,从而将这个控件恢复到原先状态。
但是,更一般的,我们通常设计一个Delegate,通过在自定义状态类中传递这个Delegate,从而调用在原先窗体定义的任何方法,方法中可以对任何控件进行操作,这就使得自定义状态类更具一般性。
27.StateNavigationSample
This sample illustrates how to implement an application that supports state navigation.
这个例子演示了如何手动控制导航栏,包括前进/后退,添加/移除历史。
对于CustomContentState自定义状态类,前面已经介绍了,这里的UserCustomContentState使用委托ReplayUserSelection来控制Replay方法要恢复那些操作。
而对于主窗体StateNavigationPage,派生于IProvideCustomContentState,从而实现接口方法GetContentState。该方法在点击导航栏上的向前/向后按钮时被激发,从而将当前的状态封装到UserCustomContentState中,并存储在NavigationService。这时NavigationService会自动调整,以保证向前和向后操作不会出错。
userListBox_SelectionChanged方法用于将选择前状态封装到UserCustomContentState中,并存储在NavigationService:
UserCustomContentState userPageState = new UserCustomContentState(previousUser, ReplayUserSelectionFunction);
this.NavigationService.AddBackEntry(userPageState);
而removeBackEntryButton_Click方法则执行相反操作,将当前项之前的历史移除:
JournalEntry entry = this.NavigationService.RemoveBackEntry();
补注:以下并不是重点
1)Users类在xaml中绑定到了userListBox上,所以一开场就可以看到所有的用户。
2)ListBox控件在数据绑定后必然会激发SelectionChanged事件,这个事件的激发是在InitializeComponent方法中进行的,而这又是被封装在系统级别的,而且发生在数据绑定前。
3)ListBox的SelectionChanged事件,有一个SelectionChangedEventArgs类型参数e
这个e具有AddedItems和RemovedItems两个集合,前者长度永远为1,记载刚刚选中的Item;而后者,则以栈的形式存储之前选中项的历史——最近选中的永远位于第1位,可以用e.RemovedItems[0]获取。