技术开发 频道

实际操作WPF:将数据绑定到对象集合

  数据绑定到对象集合

  您可以利用WPF 提供的强大的数据服务,将数据集成到自己的应用程序里。用户界面元素既可以绑定到 CLR 对象,也可以绑定到XML源。数据源一方面管理数据项(业务对象)之间的关系,另一方面提供各种不同的数据绑定功能。对于不同类型的数据,有若干不同类型的数据源,包括ObjectDataProvider和XmlDataProvider。它们都实现了IDataSource接口,因此一旦数据源所引用的数据对象发生了改变,它所依赖的绑定就会得到通知。

  在这个实验中,您将学习如何将property bag里面呈现的联系人集合ContactsList绑定到ListBox。

  ·创建单向绑定

  1. 定义一个ObjectDataProvider,命名为”ContactList”。把它放在MainWindow.xaml文件,Window这个元素的Resources里,作为这个文件的一个资源。ObjectDataProvider的类型名设为ContactList集合类的全名(包括命名空间)。除此之外,MethodName属性被设值为程序集的名字:AddressBook。请确保将Window.Resource这段元素插入到Grid的定义之前。

<Window.Resources>
  
<ObjectDataProvider x:Key="ContactList"
                 MethodName
="AddressBook.ContactList,AddressBook" />
</Window.Resources>

   2. 利用Styles,您的应用、文档、或UI设计器可以将产品的风格统一化。数据模板则可以用来定义数据的显示外观。为了配置联系人列表数据的UI,请定义一个数据模板,命名为“ContactNameTemplate”,将ContactList里面联系人对象的FirstName属性绑定到一个TextBlock上。

<Window.Resources>
  
<ObjectDataProvider x:Key="ContactList"
                 MethodName
="AddressBook.ContactList,AddressBook" />
  
<DataTemplate x:Key="ContactNameTemplate" >
    
<TextBlock Text="{Binding Path=FirstName}" />
  
</DataTemplate>
</Window.Resources>

   3. 现在我们可以为联系人的ListBox指定ItemsSource并赋给它ContactNameTemplate。

    <ListBox Name="allContacts"
        SelectionChanged
="ListItemSelected"
        ItemsSource
="{Binding }"
        ItemTemplate
="{DynamicResource ContactNameTemplate}"
        IsSynchronizedWithCurrentItem
="True">
        
<ListBox.ContextMenu>
            
<ContextMenu>
                
<MenuItem Header="Add a Contact"
                    Click
="LaunchNewContactWizard"/>
                
<MenuItem Header="Add a Group"
                    Click
="NotImplementedMsg"/>
            
</ContextMenu>
        
</ListBox.ContextMenu>
    
</ListBox>

   4. 接下来的工作就剩下设定DockPanel_LeftPane的数据上下文的值了。请将它设为Property Bag里面的ContactList。以下这行代码要放在MainWindow.xaml.cs文件中WindowLoaded方法里。

DockPanel_LeftPane.DataContext = Application.Current.Properties["ContactList"];

   5. 构建并运行您的应用程序。这时候联系人的信息应该可以显示在左边面板的ListBox里了。

  使用Window和Page功能创建一个向导

  传统的导航是基于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();
        }

   您也需要为System.Windows.NavigationWindow添加一个using指令:

using System.Windows.Navigation;
namespace AddressBook
{
    
/// <summary>

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

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

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

<PageFunction x:Class="AddressBook.UILessPageFunction"  
    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。

<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
="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" >
      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。

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

        /// <summary>
        
/// 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注册的返回句柄。

        private void OnCancelClick(object sender, RoutedEventArgs e)
        {
            ((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;
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);
        }
    }
}

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

<PageFunction
    
x:Class="AddressBook.UILessPageFunction"  
    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"
    Loaded
="OnLoaded"
    Title
="UILessPageFunction">
</PageFunction>

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

        private void NavigateToHome(object sender, RoutedEventArgs e)
        {
            
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按钮。

<PageFunction x:Class="AddressBook.ContactDetailPage2"
              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;
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,好让程序能顺利编译。

        //
        
// 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.
        
//
        void pageFunction0_Return(object sender,
                         System.Windows.Navigation.ReturnEventArgs
<object> e)
        {
        }

 

0
相关文章