SelectedValue 和 DisplayMemberPath 如何拯救了我






4.75/5 (12投票s)
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.Column 和 Grid.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)。
各位,就这些了
建议
这是一个很好的起点。
1) 尝试使用 IsSynchronizedWithCurrentItem 属性
2) 在 Persons 的 Source 开头使用 yield break,纯属娱乐
3) 在 DockPanel 的顶部添加 ToolBar
4) 在 ToolBar 中添加几个按钮(当然,您知道如何命名它们 ;) )
5) 创建几个路由命令
6) 为 Person 创建一个正常的集合(例如 ObservableCollection)
7) 使它能够通过第 5 步创建的命令添加和删除元素
附注
如果这篇文章至少对一个Person 有用 :) 我承诺继续写作,所以我会等待一些反馈,包括纠正和掌声……随意评论。