技术开发 频道

在WPF中使用Window和Page功能创建向导

  【IT168 技术文档】传统的导航是基于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
    ' "Create a new contact" dialog
    Sub LaunchNewContactWizard(ByVal sender As Object, _
                               ByVal e As RoutedEventArgs)
        
' for the Add Contact Wizard, lets start with a Navigation Window
        Dim addContactWizard As NavigationWindow = New NavigationWindow()
        addContactWizard.Title
= "Contact Information"
        addContactWizard.Width
= 500
        addContactWizard.Height
= 400
        addContactWizard.WindowStyle
= Windows.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
= _
                                   Windows.WindowStartupLocation.CenterScreen
        addContactWizard.ShowDialog()
    End Sub

   导航窗口的源设置为了UILessPageFunction.xaml,我们稍后会创建这个页面。我们在应用的属性包里引用了窗口对象,这样这个窗口对象可以在控制窗口功能的其它地方被使用。

  2. 接下来我们来定义初始页面。我们要使用PageFunction,但不会添加UI。在您的项目里,添加一个新的WPF Page Function,命名为UILessPageFunction.xaml。您可以通过Solution Explorer,右键点击项目,选择Add?New Item,在对话框中选择PageFunction(WPF)。

  TypeArgument指定的是调用页面会返回什么样的PageFunction。让我们修改标记语言,将类型参数设为Object。稍后我们会在代码文件中修改同样的类型参数。

<PageFunction
    
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
="UILessPageFunction"
    x:TypeArguments
="sys:Object"
    Title
="UILessPageFunction">
    
<Grid>

    
</Grid>
</PageFunction>

 

  3. 在之前我们创建了一个叫做UILessPageFunction的功能页面,它本身并没有子元素,它会作为向导UI的调用页面。

  下面我们会创建两个有UI的PageFunctions。第一个叫ContactDetailPage1。这个页面上会有一些输入框,可以输入联系人的姓名,email地址,和主页URL。这次我们在标记语言中把类型参数的默认值String改成Object。

<PageFunction
    
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
="ContactDetailPage1"
    x:TypeArguments
="sys:Object"
    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" >
      Email
    
</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。

Partial Public Class ContactDetailPage1
    Inherits System.Windows.Navigation.PageFunction(Of Object)

    Public Sub New()
        InitializeComponent()
    End Sub
End Class

   我们想以两种模式显示这个页面:Read和Edit。我们可以重用Frame_RightPane,也就是显示联系人详细信息的面板上面的Read模式。我们为ContactDetailPage1定义一个构造函数,接受一个Boolean值来设置是否激活Edit模式,还接受一个int类型的索引,引用到ContactList里面的一个联系人项,也就是当前显示在页面上的这个联系人。

    ' Create a ContactDetailPage1 specifying edit mode value and index
    ' of ContactList item to be displayed when in Read mode.
    Public Sub New(ByVal editMode As Boolean, ByVal itemNumber As Integer)
        InitializeComponent()
        Me.KeepAlive
= True

        
' if in read mode...
        If (editMode = False) Then
            Dim cl As ContactList
= _
                   CType(Application.Current.Properties(
"ContactList"), _
                                                        ContactList)

            
' if the contact list is populated get an object using index
            If Not (cl Is Nothing) AndAlso cl.Count > 0 Then
                Dim c As Contact
= cl(itemNumber)
                Me.RootGrid.DataContext
= c

                
' set the input fields to read only
                Me.txtEmailAddress.IsEnabled = False
                Me.txtFirstName.IsEnabled
= False
                Me.txtLastName.IsEnabled
= False
                Me.ButtonPanel1.Visibility
= Windows.Visibility.Hidden
                Me.ButtonPanel2.Visibility
= Windows.Visibility.Hidden
            End If
        Else
            Me.btnHomePage.Visibility
= Windows.Visibility.Hidden
            Me.btnEmailAddress.Visibility
= Windows.Visibility.Hidden
        End If

    End Sub

 

  5. 添加一个Cancel和一个Finish的事件处理函数。OnCancelClick方法用来关闭窗口。OnFinishClick从额面上读出输入框里的内容,组成一个联系人对象,并将它传递给Page Function注册的返回句柄。

    ' Closes the window when cancel button is clicked
    Private Sub OnCancelClick(ByVal sender As Object, _
                                  
ByVal e As RoutedEventArgs)
        
CType((Me.Parent), NavigationWindow).Close()
    
End Sub

    
' Creates and populates a Contact object with page data. Then calls
    ' the OnReturn method to pass this object along to calling class
    Private Sub OnFinishClick(ByVal sender As Object, _
                              
ByVal e As RoutedEventArgs)
        
Dim cl As ContactList = _
                  
CType(Application.Current.Properties("ContactList"), _
                                                        ContactList)
        
Dim c As Contact = New Contact()
        c.FirstName
= Me.txtFirstName.Text
        c.LastName
= Me.txtLastName.Text
        c.EmailAddress
= Me.txtEmailAddress.Text
        
Try
            c.HomePage
= New Uri(Me.txtHomePage.Text)
        
Catch generatedExceptionVariable0 As UriFormatException
            c.HomePage
= Nothing
        
End Try
        OnReturn(
New ReturnEventArgs(Of Object)(c))

    
End Sub

  6. 下面我们为ContactDetailPage1这个功能页面创建返回事件处理函数。这就是调用页面。打开UILessPageFunction.xaml.cs这个代码文件,添加返回事件的处理函数。我们会在UILessPageFunction的OnLoaded方法里初始化一个ContactDetailPage1页面。同样的,将类型参数从默认值String改成Object。

Partial Public Class UILessPageFunction
    
Inherits PageFunction(Of Object)

    
Public Sub New()
        InitializeComponent()
    
End Sub

    
' The OnLoaded handler is run automatically when the class is loaded
    Private Sub OnLoaded(ByVal sender As Object, _
                        
ByVal e As RoutedEventArgs) Handles Me.Loaded
        
Dim pageFunction As ContactDetailPage1 = _
                            
New ContactDetailPage1(True, 0)
        
AddHandler pageFunction.Return, AddressOf pageFunction1_Return
        System.Windows.Navigation.NavigationService.GetNavigationService(
Me).Navigate(pageFunction)

    
End Sub

    
' This is the ContactDetailPage1 page function's return handler
    Sub pageFunction1_Return(ByVal sender As Object, _
            
ByVal e As System.Windows.Navigation.ReturnEventArgs(Of Object))
        
Dim cl As ContactList = _
              
CType(Application.Current.Properties("ContactList"), _
                                                     ContactList)
        
Dim c As Contact = e.Result
        cl.Add(c)

    
End Sub

End Class

 

  在UILessPageFunction.xaml页面里,注册OnLoaded事件处理函数:

<PageFunction
    
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
="UILessPageFunction"
    x:TypeArguments
="sys:Object"
    Loaded
="OnLoaded"
    Title
="UILessPageFunction">
    
<Grid>

    
</Grid>
</PageFunction>

 

  7. 在Read模式情况下,我们想要email地址和主页URL这两项显示成按钮。点击email地址这个按钮应该打开默认的email应用程序,点击主页URL的按钮应该在Frame_RightPane里显示主页的内容。我们创建两个URI,在按钮的事件处理函数里,只要简单地导航到这两个URI就可以了。我们在ContactDetailPage1.xaml.cs里修改代码:

    ' Navigates to home page Uri. This results in the WebOC displaying
    ' the web site in the right hand side frame
    Private Sub NavigateToHome(ByVal sender As Object, _
                              
ByVal e As RoutedEventArgs)
        
If (Not (Me.txtHomePage.Text Is Nothing)) AndAlso _
           (
Not (Me.txtHomePage.Text = "")) Then
            System.Windows.Navigation.NavigationService.GetNavigationService(
Me).Navigate(New Uri(Me.txtHomePage.Text))
        
End If
    
End Sub

    
' Navigates to email address as Uri. This results in the launching of
    ' the default mail client.
    Private Sub SendEmail(ByVal sender As Object, ByVal e As RoutedEventArgs)
        
If Not (Me.txtEmailAddress Is Nothing) Then
            System.Windows.Navigation.NavigationService.GetNavigationService(
Me).Navigate(New Uri("mailto:" + Me.txtEmailAddress.Text))
        
End If
    
End Sub

 

   8. 像我们在第一个page function里做过的一样,我们创建第二个功能页ContactDetailPage2,设定类型参数为Object。这个页面由两个TextBox组成,一个显示家庭地址,另一个显示公司地址。另外还添加一个Cancel和一个Finish按钮。

<PageFunction
    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="ContactDetailPage2"
    x:TypeArguments
="sys:Object"
    Title
="Contact Details - 2">
  
<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。

Partial Public Class ContactDetailPage2
    
Inherits System.Windows.Navigation.PageFunction(Of Object)

    
Public Sub New()
        InitializeComponent()
    
End Sub

    
' 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.
    Public Sub New(ByVal editMode As Boolean, ByVal itemNumber As Integer)
        InitializeComponent()
        
Me.KeepAlive = True
        
If (editMode = False) Then
            
Dim cl As ContactList = CType(Application.Current.Properties("ContactList"), ContactList)
            
Dim c As Contact = cl(0)
            
Me.RootGrid.DataContext = c
            
Me.ButtonPanel1.Visibility = Windows.Visibility.Hidden
            
Me.ButtonPanel2.Visibility = Windows.Visibility.Hidden
        
End If

    
End Sub

    
' Navigate back if the Back button is clicked
    Private Sub OnBackClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
        System.Windows.Navigation.NavigationService.GetNavigationService(
Me).GoBack()

    
End Sub

    
' Close window if Cancel button is clicked
    Private Sub OnCancelClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
        
CType((Me.Parent), NavigationWindow).Close()
    
End Sub

    
' When the Finish button is clicked, hydrate the Contact
    ' object with page data and return to our caller, which is
    ' ContactDetailPage1 page function.
    Private Sub OnFinishClick(ByVal sender As Object, ByVal e As RoutedEventArgs)
        
Dim c As Contact = New Contact()
        c.HomeAddress
= Me.txtHomeAddress.Text
        c.BusinessAddress
= Me.txtBusinessAddress.Text
        OnReturn(
New ReturnEventArgs(Of Object)(c))
        
Dim w As Window = _
                        
CType(Application.Current.Properties("AddContactWizard"), _
                                                               Window)
        w.Close()
    
End Sub

End Class

 

  10. 到此为止第二个功能页面就完全定义好了。我们把它们和第一个页面上的返回事件处理函数和Next按钮click处理函数绑定到一起。在第一个页面的代码文件ContactDetailPage1.xaml.cs里添加如下代码:

   ' On clicking the next button on PF1, navigate to PF2
    Private Sub OnNextClick(ByVal sender As Object, _
                            
ByVal e As RoutedEventArgs)
        
Dim pageFunction As ContactDetailPage2 = New ContactDetailPage2
        
AddHandler pageFunction.Return, AddressOf pageFunction2_Return
        System.Windows.Navigation.NavigationService.GetNavigationService(
Me).Navigate(pageFunction)
    
End Sub


    
' 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.
    Sub pageFunction2_Return(ByVal sender As Object, _
                            
ByVal e As ReturnEventArgs(Of Object))
        
Dim c As Contact = CType(e.Result, Contact)
        c.FirstName
= Me.txtFirstName.Text
        c.LastName
= Me.txtLastName.Text
        c.EmailAddress
= Me.txtEmailAddress.Text
        
Try
            c.HomePage
= New Uri(Me.txtHomePage.Text)
        
Catch generatedExceptionVariable0 As UriFormatException
            c.HomePage
= Nothing
        
End Try
        OnReturn(
New ReturnEventArgs(Of Object)(c))
  
    
End Sub

 

  11. 您的应用程序就差两步就完成了!当联系人列表当中的某一项被选定的时候,我们要把联系人详细信息显示在右边的面板。下面我么就来写这一段代码。我们不是曾经在MainWindow.xaml.cs里面创建了一个空的ListItemSelected方法吗?现在我们把它实现吧:

    ' Triggered when an item in the Contacts list is selected
    Sub ListItemSelected(ByVal sender As Object, _
                        
ByVal e As SelectionChangedEventArgs)
        
' show first page function on the right hand side frame
        Dim pageFunction As ContactDetailPage1 = _
                    
New ContactDetailPage1(False, allContacts.SelectedIndex)
        
AddHandler pageFunction.Return, AddressOf pageFunction0_Return
        
Me.Frame_RightPane.Navigate(pageFunction)

        
' update status bar
        Dim cl As ContactList = _
                  
CType(Application.Current.Properties("ContactList"), _
                                                        ContactList)
        
Dim c As Contact = cl(allContacts.SelectedIndex)
        
Me.tb.Text = c.FirstName + " " + c.LastName
    
End Sub

 

  因为ContactDetailPage1对话框是以只读模式显示的,它不应该返回任何值。我们就在MainWindow.xaml.cs里创建一个空的pageFunction0_Return,好让程序能顺利编译。

  12. 构建并运行您的应用程序。地址簿应用程序的联系人应该能够显示出来了:

   ' This is the ContactDetailPage1 page function's return handler.
    ' Merely a placeholder since the page is displayed in read mode and
    ' doesn't return anything.
    Sub pageFunction0_Return(ByVal sender As Object, _
            
ByVal e As System.Windows.Navigation.ReturnEventArgs(Of Object))
        
'place holder. no logic needed
    End Sub

 

  如果您点击工具栏上的加号按钮,或者选择File ? Add Contact菜单项,或者在ListBox的右键菜单里选择Add New Contact,您可以看到Add Contact向导。

1
相关文章