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

SelectedValue 和 DisplayMemberPath 如何拯救了我

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (12投票s)

2008 年 12 月 11 日

CPOL

5分钟阅读

viewsIcon

109583

downloadIcon

73

WPF 中的依赖数据可视化和绑定

引言

请先原谅我的英语,我有点生疏了。

几天前,我需要编写一个 WPF 用户控件(这对我来说是“未知领域”),当时我非常渴望能有一篇我正在写的文章。好吧……我希望它能像预期的那样有用 :)

免责声明:我不是 WPF 大师。这只是 MSDN 参考资料中的经验(而且是匆忙和极端条件下收集的=双重价值的经验),供那些想了解细节和深入原理的人参考。

背景

我不想用真实项目的细节来烦扰您。我们将检查一个简化的但对新手来说相当复杂的问题。假设我们有一个数据实体


     internal class Person
    {
        public string Name { get; set; }
        public string Company { get; set; }
        public string Department { get; set; }
    }    

仍然很简单,不是吗?
接下来,我们需要一些数据源。让我们让它看起来像这样

    internal class PersonProvider
    {
        public IEnumerable<Person> Source
        {
            get
            {
                Person p = new Person() {Name = "Jeff"};
                yield return p;
                p = new Person() {Name = "Mike",Company="Google"};
                yield return p;
                p = new Person() { Name = "Alex" , Company = "Ksema",
                    Department="Analysis"};
                yield return p;
            }
        }
    }

因此,我们的人员在公司工作,并在部门中担任职位。我们需要一种方法来编辑这些数据。有一个限制:我们有一个预定义的公司/部门列表可供选择,我建议我们将它放在一个漂亮的 XML 文件中。

<?xml version="1.0" encoding="utf-8" ?>
<companies>
  <company name="Ksema" description="Pleasant work atmosphere [Ksema co]">
    <department name="Analysis"/>
    <department name="Sales"/>
    <department name="Marketing"/>
  </company>
  <company name="Google" description="For real math geeks [Google]">
    <department name="Development"/>
    <department name="HR"/>    
  </company>
</companies>

就是这样,现在我们获得了所有数据。

真正的任务

我提供了窗口的所有 XAML 描述,稍后将逐一分析。

<Window x:Class="SampleWPF.MainWnd"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Entity="clr-namespace:SampleWPF.Entity"
    Title="Window1" Height="Auto" Width="Auto">
    <Window.Resources>
        <ResourceDictionary>
            <Entity:PersonProvider x:Key="PersonProvider"/>
            <XmlDataProvider x:Key="CompaniesDataProvider" Source="Data\companies.xml"/>
        </ResourceDictionary>
    </Window.Resources>
    <DockPanel>
        <ListBox x:Name="lstPersons" SelectedIndex="0"
                 DockPanel.Dock="Left" Width="100" DisplayMemberPath="Name"
                 ItemsSource="{Binding Source={StaticResource PersonProvider},Path=Source}"/>
        <Grid DockPanel.Dock="Right" DataContext="{Binding Mode=Default,
            Source={StaticResource CompaniesDataProvider}, XPath=/companies/company}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>                
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            
            <Label Grid.Column="0" Grid.Row="0" Content="Name"/>
            <TextBox Grid.Column="1" Grid.Row="0"
                Text="{Binding Path=SelectedItem.Name,ElementName=lstPersons}"/>
            
            <Label Grid.Column="0" Grid.Row="1" Content="Company"/>
            <ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding }"
                DisplayMemberPath="@description" SelectedValuePath="@name"
                SelectedValue="{Binding Path=SelectedItem.Company,
                ElementName=lstPersons, Mode=Default}"
                IsSynchronizedWithCurrentItem="False" x:Name="comboBox" />
            
            <Label Grid.Column="0" Grid.Row="2" Content="Department"/>
            <ComboBox Grid.Column="1" Grid.Row="2" ItemsSource="{Binding Mode=Default,
                XPath=department}" DisplayMemberPath="@name"
                SelectedValue="{Binding Path=SelectedItem.Department,
                ElementName=lstPersons, Mode=Default}" SelectedValuePath="@name"
                IsSynchronizedWithCurrentItem="False" />
            
        </Grid>
    </DockPanel>
</Window>
        

您在 <Window> 元素中看到的所有内容都可以同样成功地放置在 <UserControl> 元素中,所以当我说是“用户控件”时,我并没有骗您。 

我假设您至少对 Windows Presentation Foundation 有一些零散的了解。并且您知道界面有 XAML 标记,而代码隐藏文件与之对应(顺便看看示例项目中的代码)))。尽管如此,如果您有 Visual Studio(可能还有 Expression),所有枯燥的工作都将由这些出色的 IDE 完成。

我们想编辑人员集合,因此我们需要让 WPF 了解要绑定到其控件的数据。我们在以下块中进行操作

    <Window.Resources>
        <ResourceDictionary>
            <Entity:PersonProvider x:Key="PersonProvider"/>
            <XmlDataProvider x:Key="CompaniesDataProvider" Source="Data\companies.xml"/>
        </ResourceDictionary>
    </Window.Resources>

您现在需要了解的关于它的最简单的事情是,<ResourceDictionary> 中的每个子元素都必须提供 x:Key 属性,该属性稍后可以在标记中使用以标识本地静态资源。 供好奇者参考

在这里,我们定义了 XmlDataProvider,它稍后用于公司/部门选择,并提供有关 PersonProvider 类型的一些信息。

这里最简单的情况是,我们只需将 XmlDataProvider 的 Source 属性设置为我们的 XML 文件的位置。 其他可能性

我们希望在一边显示人员列表,在窗口(控件)的另一边显示每个选定人员的详细数据。这里是 <DockPanel> 元素,它允许我们在其中放置许多控件并设置它们的停靠方向(顶部、底部、左侧、右侧、填充)- 您应该记住,Window 或 UserControl 只允许在其 Content 中放置一个子控件,因此如果您想要更多,则需要使用具有容器功能的控件(通常用于控制布局)- StackPanel、DockPanel、Grid 是常用的,但这里还涵盖了其他一些。

要影响与父容器的交互,请在子控件上使用<ContainerType>.<ContainerProperty> 属性。例如

<ListBox x:Name="lstPersons" SelectedIndex="0"
              DockPanel.Dock="Left" Width="100" DisplayMemberPath="Name" 
                  ItemsSource="{Binding Source={StaticResource PersonProvider},
                  Path=Source}"/>
       

这里 DockPanel.Dock="Left" 告诉 DockPanel ListBox 应停靠在左侧。 ItemsSource 告诉了所有关于自身的信息,这里我们放置了魔术构造 {Binding Source=,Path=} 并设置 Source 为静态资源 PersonProvider,Path 设置为我们要使用的其成员(正如您所记得的,它是返回 IEnumerable<Person> 的属性)。 DisplayMemberPath 告诉应该显示选定项目的哪个成员,这里我们想要一个姓名列表。 绑定基础知识

现在我们想编辑个人详细信息。对于此类需求,我建议使用 <Grid> 容器 - 非常有用的东西,它允许创建表格并定义其行和列属性,并将控件放置在其单元格内。

<Grid DockPanel.Dock="Right" DataContext="{Binding Mode=Default,
     Source={StaticResource CompaniesDataProvider}, XPath=/companies/company}">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>                
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

这里值得关注的是 DataContext<Grid.ColumnDefinitions><Grid.RowDefinitions>。简而言之,DataContext 允许为内部元素定义上下文并使用简化的绑定语法。 更多...
定义列和行的高度和宽度很简单。您可以使用值(支持点、像素等)

Width="120"

自动调整大小

Height="Auto"

以及一次填充剩余的可用空间

Width="*" 

因此,我们定义了布局,现在来看控件本身

<Label Grid.Column="0" Grid.Row="0" Content="Name"/>
<TextBox Grid.Column="1" Grid.Row="0"  Text="{Binding Path=SelectedItem.Name,
    ElementName=lstPersons}"/>

看看 Grid.ColumnGrid.Row,您还记得 DockPanel.Dock - 是的,这是同一种情况……嗯,几乎一样。这种类型的属性称为附加属性。此时,我们使用它们来指示控件在网格中的位置。此外,我们还将列表 lstPersons(您可以在上面看到名称由 x:Name 属性定义)的 SelectedItem.Name 绑定到 TextBox 控件的 Text 属性。

现在是最有趣的 - 两个组合框,一个用于公司,一个用于部门。部门组合框项目当然取决于所选的公司。

<ComboBox Grid.Column="1" Grid.Row="1" ItemsSource="{Binding }"
    DisplayMemberPath="@description" SelectedValuePath="@name"
    SelectedValue="{Binding Path=SelectedItem.Company, ElementName=lstPersons,
    Mode=Default}" IsSynchronizedWithCurrentItem="False" x:Name="comboBox" />
          
<ComboBox Grid.Column="1" Grid.Row="2" ItemsSource="{Binding Mode=Default,
    XPath=department}" DisplayMemberPath="@name"
    SelectedValue="{Binding Path=SelectedItem.Department, ElementName=lstPersons,
    Mode=Default}" SelectedValuePath="@name" IsSynchronizedWithCurrentItem="False" />
             

您看,您看……正如我之前所说,DataContext 是神奇的,ItemSource="{Binding}" 就是这样,因为上下文是 XmlDataProvider,其中 XPath="/companies/company"。此外,DisplayMemberPath 在这里是 xpath 形式,这是 WPF 非常有用的功能,接下来是 SelectedValuePath - 它指示选定的值不是显示的那个,而是不同的,稍后我们将其绑定到列表 SelectedItem Company 属性。仔细阅读,给自己一些时间来理解数据之间的联系。
现在是部门,它的列表取决于公司,我们分别填充 ItemsSource (请记住,我们仍然具有相同的 DataContext)。

各位,就这些了

second.jpg

建议

这是一个很好的起点。

1) 尝试使用 IsSynchronizedWithCurrentItem 属性
2) 在 Persons 的 Source 开头使用 yield break,纯属娱乐
3) 在 DockPanel 的顶部添加 ToolBar
4) 在 ToolBar 中添加几个按钮(当然,您知道如何命名它们 ;) )
5) 创建几个路由命令
6) 为 Person 创建一个正常的集合(例如 ObservableCollection)
7) 使它能够通过第 5 步创建的命令添加和删除元素

附注

如果这篇文章至少对一个Person 有用 :) 我承诺继续写作,所以我会等待一些反馈,包括纠正和掌声……随意评论。 

© . All rights reserved.