【IT168 技术文档】WPF是微软新一代图形系统,运行在.NET Framework 3.0架构下,为用户界面、2D/3D 图形、文档和媒体提供了统一的描述和操作方法。基于DirectX 9/10技术的WPF不仅带来了前所未有的3D界面,而且其图形向量渲染引擎也大大改进了传统的2D界面,比如Vista中的半透明效果的窗体等都得益于WPF。 程序员在WPF的帮助下,要开发出媲美Mac程序的酷炫界面已不再是遥不可及的奢望。 WPF相对于Windows客户端的开发来说,向前跨出了巨大的一步,它提供了超丰富的.NET UI 框架,集成了矢量图形,丰富的流动文字支持flow text support,3D视觉效果和强大无比的控件模型框架。
传统的导航是基于URI字符串的。导航到指定的URI,系统就会加载并呈现关联的页。结构化的导航则与这种方式不同,是基于对象调用PageFunction。若需要导航,您要创建适当的页功能的对象,并导航到该对象。系统不是加载和生成指定的页,而是初始化页功能对象,这个对象会控制下一步发生的动作。
在这个实验中,您将学习如何创建一个向导,这个向导用来将联系人添加到您的通讯簿里。您将使用Window和一些Page Functions,通过线性的拓扑结构,实现一个结构化的导航UI。
系统自动利用导航历史(日志)来记录导航信息。要从当前的页功能对象返回到之前的页功能对象,您需要调用OnReturn这个方法。系统会把堆栈里之前的页功能对象返回给您。有了这个特性,您可以从应用程序的某个页面很容易地返回到之前的页面,也可以在页面之间传递数据。
“添加新联系人”向导
1. 我们的添加联系人向导AddContactWizard将由两个UI页面和一个调用页面组成。在向导结束后,数据会被返回给初始页面。首先要做的在MainWindow.xaml.cs里实现LaunchNewContactWizard这个事件处理函数,把AddContactWizard显示在屏幕的中间。
// Triggered when context menu or other toolbar option is clicked to launch
// 'Add new contact' wizard dialog
//
private void LaunchNewContactWizard(object sender, RoutedEventArgs e)
{
// for the Add Contact Wizard, lets start with a
// Navigation Window
NavigationWindow addContactWizard = new NavigationWindow();
addContactWizard.Title = "Contact Information";
addContactWizard.Width = 500;
addContactWizard.Height = 400;
addContactWizard.WindowStyle = WindowStyle.ToolWindow;
addContactWizard.Name = "AddContactWizard";
// point it to the initial page function with no UI
// this will call the subsequent page functions which have UI
addContactWizard.Source = new Uri("UILessPageFunction.xaml",
UriKind.Relative);
Application.Current.Properties["AddContactWizard"]
= addContactWizard;
addContactWizard.WindowStartupLocation
= WindowStartupLocation.CenterScreen;
addContactWizard.ShowDialog();
}
导航窗口的源设置为了UILessPageFunction.xaml,我们稍后会创建这个页面。我们在应用的属性包里引用了窗口对象,这样这个窗口对象可以在控制窗口功能的其它地方被使用。
2. 接下来我们来定义初始页面。我们要使用PageFunction,但不会添加UI。在您的项目里,添加一个新的WPF Page Function,命名为UILessPageFunction.xaml。您可以通过Solution Explorer,右键点击项目,选择Add?New Item,在对话框中选择PageFunction(WPF)。
TypeArgument指定的是调用页面会返回什么样的PageFunction。让我们修改标记语言,将类型参数设为Object。稍后我们会在代码文件中修改同样的类型参数。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:TypeArguments="sys:Object"
Title="UILessPageFunction">
</PageFunction>
3. 在之前我们创建了一个叫做UILessPageFunction的功能页面,它本身并没有子元素,它会作为向导UI的调用页面。
下面我们会创建两个有UI的PageFunctions。第一个叫ContactDetailPage1。这个页面上会有一些输入框,可以输入联系人的姓名,email地址,和主页URL。这次我们在标记语言中把类型参数的默认值String改成Object。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Class="AddressBook.ContactDetailPage1"
x:TypeArguments="sys:Object"
xmlns:c="Lab"
Title="Contact Details - 1">
<Grid Name ="RootGrid"
VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<!-- Labels go here -->
<TextBlock Width="200" Height="30" Grid.Column="0" Grid.Row="1" >
First Name
</TextBlock>
<TextBlock Width="200" Height="30" Grid.Column="0" Grid.Row="2" >
Last Name
</TextBlock>
<TextBlock Width="200" Height="30" Grid.Column="0" Grid.Row="3" >
</TextBlock>
<TextBlock Width="200" Height="30" Grid.Column="0" Grid.Row="4" >
Home Page
</TextBlock>
<!-- Input fields -->
<TextBox Name="txtFirstName" Width="200" Height="30"
Grid.Column="1" Grid.Row="1"
Text="{Binding Path=FirstName, Mode=TwoWay}"/>
<TextBox Name="txtLastName" Width="200" Height="30"
Grid.Column="1" Grid.Row="2"
Text="{Binding Path=LastName, Mode=TwoWay}"/>
<TextBox Name="txtEmailAddress" Width="200" Height="30"
Grid.Column="1" Grid.Row="3"
Text="{Binding Path=EmailAddress, Mode=TwoWay}"/>
<TextBox Name="txtHomePage" Width="200" Height="30"
Grid.Column="1" Grid.Row="4"
Text="{Binding Path=HomePage, Mode=TwoWay}"/>
<Button Name="btnHomePage" Width="200" Height="30"
Grid.Column="1" Grid.Row="4"
Content="{Binding Path=HomePage, Mode=TwoWay}"
Click="NavigateToHome"/>
<Button Name="btnEmailAddress" Width="200" Height="30"
Grid.Column="1" Grid.Row="3"
Content="{Binding Path=EmailAddress, Mode=TwoWay}"
Click="SendEmail"/>
<!-- Navigation buttons -->
<DockPanel Name="ButtonPanel1" Grid.Column="0" Grid.Row="5" >
<Button Name="btnNext" Width="100" Height="30"
Click="OnNextClick">Next</Button>
<Button Name="btnCancel" Width="100" Height="30"
Click="OnCancelClick">Cancel</Button>
</DockPanel>
<DockPanel Name="ButtonPanel2" Grid.Column="1" Grid.Row="5">
<Button Name="btnFinish" Width="100" Height="30"
Click="OnFinishClick">Finish</Button>
</DockPanel>
</Grid>
</PageFunction>
请注意,每个输入框都绑定到联系人对象的某个属性。另外有三个按钮来处理导航事件:Next,Cancel,和Finish。
4. 打开ContactDetailPage1.xaml.cs,在代码文件中,同样的,将类型参数值改成Object。
我们想以两种模式显示这个页面:Read和Edit。我们可以重用Frame_RightPane,也就是显示联系人详细信息的面板上面的Read模式。我们为ContactDetailPage1定义一个构造函数,接受一个Boolean值来设置是否激活Edit模式,还接受一个int类型的索引,引用到ContactList里面的一个联系人项,也就是当前显示在页面上的这个联系人。
/// Creates a Contact Detail page, specifying
/// edit mode and index of ContactList item to
/// be displayed when in Read mode.
/// </summary>
public ContactDetailPage1(bool editmode, int itemNumber)
{
InitializeComponent();
this.KeepAlive = true;
// if in read mode...
if (editmode == false)
{
ContactList cl =
Application.Current.Properties["ContactList"]
as ContactList;
// if the contact list is populated get an object using index
if (cl != null && cl.Count > 0)
{
Contact c = cl[itemNumber];
this.RootGrid.DataContext = c;
// set the input fields to read only
this.txtEmailAddress.IsEnabled = false;
this.txtFirstName.IsEnabled = false;
this.txtLastName.IsEnabled = false;
this.ButtonPanel1.Visibility = Visibility.Hidden;
this.ButtonPanel2.Visibility = Visibility.Hidden;
}
}
else
{
this.btnHomePage.Visibility = Visibility.Hidden;
this.btnEmailAddress.Visibility = Visibility.Hidden;
}
}
5. 添加一个Cancel和一个Finish的事件处理函数。OnCancelClick方法用来关闭窗口。OnFinishClick从额面上读出输入框里的内容,组成一个联系人对象,并将它传递给Page Function注册的返回句柄。
{
((NavigationWindow)(this.Parent)).Close();
}
private void OnFinishClick(object sender, RoutedEventArgs e)
{
ContactList cl = Application.Current.Properties["ContactList"]
as ContactList;
Contact c = new Contact();
c.FirstName = this.txtFirstName.Text;
c.LastName = this.txtLastName.Text;
c.HomePage = new Uri(this.txtHomePage.Text);
c.EmailAddress = this.txtEmailAddress.Text;
OnReturn(new ReturnEventArgs<object>(c));
}
6.下面我们为ContactDetailPage1这个功能页面创建返回事件处理函数。这就是调用页面。打开UILessPageFunction.xaml.cs这个代码文件,添加返回事件的处理函数。我们会在UILessPageFunction的OnLoaded方法里初始化一个ContactDetailPage1页面。同样的,将类型参数从默认值String改成Object。
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace AddressBook
{
/// <summary>
/// Interaction logic for UILessPageFunction.xaml
/// </summary>
public partial class UILessPageFunction : PageFunction<Object>
{
public UILessPageFunction()
{
}
//
// The OnLoaded handler is run automatically when the class is loaded
//
private void OnLoaded(object sender, RoutedEventArgs e)
{
// navigate to the first of two page functions which
// have 'add contact' UI
ContactDetailPage1 pageFunction =
new ContactDetailPage1(true, 0);
pageFunction.Return +=
new ReturnEventHandler<object>(pageFunction1_Return);
NavigationService.GetNavigationService(this).
Navigate(pageFunction);
}
//
// This is the ContactDetailPage1 page function's return handler
//
void pageFunction1_Return(object sender, ReturnEventArgs<object> e)
{
// get a reference to the address book's contact list
ContactList cl =
Application.Current.Properties["ContactList"]
as ContactList;
Contact c = e.Result as Contact;
// add the newly created contact to the list
cl.Add(c);
}
}
}
7.在Read模式情况下,我们想要email地址和主页URL这两项显示成按钮。点击email地址这个按钮应该打开默认的email应用程序,点击主页URL的按钮应该在Frame_RightPane里显示主页的内容。我们创建两个URI,在按钮的事件处理函数里,只要简单地导航到这两个URI就可以了。我们在ContactDetailPage1.xaml.cs里修改代码:
{
if (this.txtHomePage != null)
{
NavigationService ns =
NavigationService.GetNavigationService(this);
ns.Navigate(new Uri(this.txtHomePage.Text));
}
}
private void SendEmail(object sender, RoutedEventArgs e)
{
if (this.txtEmailAddress != null)
{
NavigationService ns =
NavigationService.GetNavigationService(this);
ns.Navigate(new Uri("mailto:" + this.txtEmailAddress.Text));
}
}
8.像我们在第一个page function里做过的一样,我们创建第二个功能页ContactDetailPage2,设定类型参数为Object。这个页面由两个TextBox组成,一个显示家庭地址,另一个显示公司地址。另外还添加一个Cancel和一个Finish按钮。
x:TypeArguments="sys:Object"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="ContactDetailPage2" >
<Grid
Name="RootGrid"
VerticalAlignment="Center"
HorizontalAlignment="Center" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="110"/>
<RowDefinition Height="110"/>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<!-- Labels -->
<TextBlock Width="100" Height="30"
Grid.Column="0" Grid.Row="1" > Business Address</TextBlock>
<TextBlock Width="100" Height="30"
Grid.Column="0" Grid.Row="2" > Home Address</TextBlock>
<!-- Input fields -->
<TextBox Name="txtHomeAddress" Width="200" Height="100"
Grid.Column="1" Grid.Row="1" TextWrapping="Wrap"
Text="{Binding Path=HomeAddress, Mode=TwoWay}"/>
<TextBox Name="txtBusinessAddress" Width="200" Height="100"
Grid.Column="1" Grid.Row="2" TextWrapping="Wrap"
Text="{Binding Path=BusinessAddress, Mode=TwoWay}"/>
<DockPanel Name="ButtonPanel1" Grid.Column="0" Grid.Row="3" >
<Button Name="btnBack" Width="100" Height="30"
Click="OnBackClick">Back</Button>
<Button Name="btnCancel" Width="100" Height="30"
Click="OnCancelClick">Cancel</Button>
</DockPanel>
<DockPanel Name="ButtonPanel2" Grid.Column="1" Grid.Row="3">
<Button Name="btnFinish" Width="100" Height="30"
Click="OnFinishClick">Finish</Button>
</DockPanel>
</Grid>
</PageFunction>
9.ContactDetailPage2功能页的代码非常直观。再次强调要把类型参数改为Object。
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace AddressBook
{
/// <summary>
/// Interaction logic for ContactDetailPage2.xaml
/// </summary>
public partial class ContactDetailPage2 : PageFunction<Object>
{
public ContactDetailPage2()
{
InitializeComponent();
}
/// <summary>
/// Create a ContactDetailPage2 with specified edit mode value.
/// Since we won’t be displaying this page on the right hand side
/// Frame, this constructor does not accept and use the index
/// of selected element in the ContactList.
/// </summary>
public ContactDetailPage2(bool editmode)
{
InitializeComponent();
if (editmode == false)
{
ContactList cl =
Application.Current.Properties["ContactList"]
as ContactList;
Contact c = cl[0];
this.RootGrid.DataContext = c;
this.RootGrid.IsEnabled = false;
this.ButtonPanel1.Visibility = Visibility.Hidden;
this.ButtonPanel2.Visibility = Visibility.Hidden;
}
}
//
// Navigate back if the Back button is clicked
//
private void OnBackClick(object sender, RoutedEventArgs e)
{
NavigationService ns =
NavigationService.GetNavigationService(this);
ns.GoBack();
}
//
// Close window if Cancel button is clicked
//
private void OnCancelClick(object sender, RoutedEventArgs e)
{
((NavigationWindow)(this.Parent)).Close();
}
//
// When the Finish button is clicked, hydrate the Contact
// object with page data and return to our caller, which is
// ContactDetailPage1 page function.
//
private void OnFinishClick(object sender, RoutedEventArgs e)
{
Contact c = new Contact();
c.HomeAddress = this.txtHomeAddress.Text;
c.BusinessAddress = this.txtBusinessAddress.Text;
OnReturn(new ReturnEventArgs<object>(c));
// close the wizard
Window w = Application.Current.Properties["AddContactWizard"]
as Window;
w.Close();
}
}
}
10.到此为止第二个功能页面就完全定义好了。我们把它们和第一个页面上的返回事件处理函数和Next按钮click处理函数绑定到一起。在第一个页面的代码文件ContactDetailPage1.xaml.cs里添加如下代码:
// On clicking the next button on PF1, navigate to PF2
//
private void OnNextClick(object sender, RoutedEventArgs e)
{
NavigationService ns =
NavigationService.GetNavigationService(this);
ContactDetailPage2 pageFunction = new ContactDetailPage2();
pageFunction.Return += new
ReturnEventHandler<Object>(pageFunction2_Return);
ns.Navigate(pageFunction);
}
//
// When the second PF returns, get the Contact object it
// sent back and add the first PF’s data to it
// Then return to the UILessPageFunction that called this
// ContactDetailPage1 PF.
//
void pageFunction2_Return(object sender, ReturnEventArgs<object> e)
{
Contact c = e.Result as Contact;
c.FirstName = this.txtFirstName.Text;
c.LastName = this.txtLastName.Text;
c.HomePage = new Uri(this.txtHomePage.Text);
c.EmailAddress = this.txtEmailAddress.Text;
OnReturn(new ReturnEventArgs<object>(c));
}
11.当联系人列表当中的某一项被选定的时候,我们要把联系人详细信息显示在右边的面板。下面我么就来写这一段代码。我们不是曾经在MainWindow.xaml.cs里面创建了一个空的ListItemSelected方法吗?现在我们把它实现吧:
// Triggered when an item in the Contacts list is selected
//
void ListItemSelected(object sender, SelectionChangedEventArgs args)
{
// show first page function on the right hand side frame
ContactDetailPage1 pageFunction =
new ContactDetailPage1(false, allContacts.SelectedIndex);
pageFunction.Return += new
System.Windows.Navigation.ReturnEventHandler<object>
(pageFunction0_Return);
this.Frame_RightPane.Navigate(pageFunction);
//update status bar
ContactList cl =
Application.Current.Properties["ContactList"]
as ContactList;
Contact c = cl[allContacts.SelectedIndex];
this.tb.Text = c.FirstName + " " + c.LastName;
}
因为ContactDetailPage1对话框是以只读模式显示的,它不应该返回任何值。我们就在MainWindow.xaml.cs里创建一个空的pageFunction0_Return,好让程序能顺利编译。