有很多文章讨论绑定的概念,并讲解如何使用StaticResources和DynamicResources绑定属性。这些概念使用WPF提供的数据绑定表达式。在本文中,让我们研究WPF提供的不同类型的数据绑定表达式。
介绍
数据绑定是一种强大的技术,它允许数据在UI元素和业务模型之间流动。当业务模型中的数据发生变化时,它会自动将更改反映到UI元素上。
Models | Description |
---|---|
OneWay | Source → Destination |
TwoWay | Source ←→ Destination |
OneWayToSource | Source ← Destination |
OneTime | Source → Destination (only once) |
这可以通过WPF提供的不同类型的数据绑定表达式来实现。
数据绑定表达式的类型如下所示。
- DataContext绑定
- RelativeSource绑定
- 集合当前项绑定
1、DataContext绑定
DataContext是一个依赖属性,它是绑定的默认源。Datacontext沿着逻辑树继承。因此,如果您设置一个DataContext来控制逻辑树中的所有子元素,它也将引用同一个DataContext,除非并且直到显式指定了另一个源。
让我们举个例子来更详细地理解它。
1.1 创建一个类Book,如下所示。
代码语言:javascript复制public class Book
{
public string Name
{
get;
set;
}
public string Author
{
get;
set;
}
}
1.2 添加一个XAML文件DataContextBinding.XAML
并放置四个TextBlock,如下所示。
<Grid VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="Book Name:" FontWeight="Bold" />
<TextBlock Grid.Column="1" />
<TextBlock Text="Author:" FontWeight="Bold" Grid.Row="1" />
<TextBlock Grid.Row="1" Grid.Column="1" />
</Grid>
现在,让我们看看如何使用这个DataContext属性来显示数据。
它有两种用法,如下所示。
- 1.使用{Binding}表达式
用于直接绑定DataContext。
创建类Book的实例,初始化其属性,并将类的Name属性分配给Window的DataContext属性。
代码语言:javascript复制public partial class DataContextBinding: Window
{
public DataContextBinding()
{
InitializeComponent();
//Create the instance
Book book = new Book();
//initialize the properties
book.Name = "Computer Networking";
//Assign the Property as DataContext
this.DataContext = book.Name;
}
}
由于DataContext是沿着逻辑树和数据book继承的,因此Name被绑定到Control Window。Window的所有子元素也将引用同一个对象(book.Name)。
要显示数据,请将DataContext与Textblock绑定,如下所示。
代码语言:javascript复制<TextBlock Text="Book Name:" FontWeight="Bold"/>
<TextBlock Text="{Binding}" Grid.Column="1" />
输出
- 使用{Binding Property}表达式
绑定Datacontext的属性。
创建类Book的实例,初始化其属性并将类的实例(Book)分配给Window的DataContext属性。
代码语言:javascript复制Book book = new Book();
//initialize the properties
book.Name = "Computer Networking";
book.Author = "James F. Kurose";
//Assign the instance as DataContext
this.DataContext = book;
现在,让我们看看输出。
由于绑定表达式{Binding}用于绑定Book类型的DataContext对象,因此调用ToString()方法,并将数据显示为字符串。为了以正确的格式显示数据,我们必须将数据对象的属性与TextBlock绑定,如下所示:
代码语言:javascript复制<TextBlock Text="Book Name:" FontWeight="Bold"/>
<TextBlock Text="{Binding Name}" Grid.Column="1" />
<TextBlock Text="Author:" FontWeight="Bold" Grid.Row="1" />
<TextBlock Text="{Binding Author}" Grid.Row="1" Grid.Column="1"/>
绑定表达式{Binding Name}用于绑定DataContext绑定的Name属性。
输出
2、RelativeSource 绑定
RelativeSource是一个属性,它用相对关系设置绑定源以绑定目标。此扩展主要用于必须将元素的一个属性绑定到同一元素的另一个属性时。
RelativeSource有四种类型,如下所示。
- Self
- FindAncestor
- TemplatedParent
- PreviousData
让我们一个一个详细地探讨一下。
2.1 Self
Self用于绑定源和绑定目标相同的场景中。对象的一个属性与同一对象的另一个属性绑定。
例如,让我们取一个高度和宽度相同的椭圆。
在XAML文件中添加下面给出的代码。宽度属性与高度属性相对绑定。
代码语言:javascript复制<Grid>
<Ellipse Fill="Black" Height="100" Width="{Binding RelativeSource={RelativeSource Self},Path=Height}">
</Ellipse>
</Grid>
输出
如果改变椭圆的高度,宽度也会相对变化。
2.2 FindAncestor
顾名思义,当绑定源是绑定目标的祖先(父级)之一时使用此选项。使用FindAncestor扩展,可以找到任何级别的祖先。
让我们举个例子来更清楚地理解它。
步骤
创建XAML,它表示下面给出的元素的逻辑树。
代码语言:javascript复制<Grid Name="Parent_3">
<StackPanel Name="Parent_2">
<Border Name="Parent_1">
<StackPanel x:Name="Parent_0" Orientation="Vertical">
<Button></Button>
</StackPanel>
</Border>
</StackPanel>
</Grid>
现在,让我们使用FindAncestor扩展将祖先的Name属性绑定到子元素button的Content属性。
代码语言:javascript复制<Grid Name="Parent_3">
<StackPanel Name="Parent_2" HorizontalAlignment="Center" VerticalAlignment="Center" Width="100">
<Border Name="Parent_1">
<StackPanel x:Name="Parent_0" Orientation="Vertical">
<Button Height="50" Content="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type StackPanel},
AncestorLevel=2},Path=Name}"></Button>
</StackPanel>
</Border>
</StackPanel>
</Grid>
输出
AncestorType为“StackPanel”与AcestorLevel为“2”组合,将button的content属性与StackPanel的Name属性(Parent_2)绑定在一起。
2.3 TemplatedParent
TemplatedParent是一个属性,它使您能够创建一个包含少量未知值的控件模板。这些值取决于应用ControlTemplate的控件的属性。
让我们举个例子来更详细地理解它
步骤
- 为按钮创建一个ControlTemplate,如下所示。
<Window.Resources>
<ControlTemplate x:Key="template">
<Canvas>
<Ellipse Height="110" Width="155"
Fill="Black"/>
<Ellipse Height="100" Width="150"
Fill="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Background}">
</Ellipse>
<ContentPresenter Margin="35"
Content="{Binding RelativeSource={RelativeSource TemplatedParent},Path=Content}"/>
</Canvas>
</ControlTemplate>
</Window.Resources>
在上面给出的代码中,椭圆的Fill属性和ContentPresenter的Content属性依赖于将应用此模板的控件的属性值。
- 添加一个按钮并对其应用模板。
<Button Margin="50" Background="Beige" Template="{StaticResource template}" Height="0" Content="Click me" FontSize="22">
</Button>
在应用模板时,按钮的Background(Beige)与椭圆的Fill属性相对绑定,Content(Click me)与ContentPresenter的Content属性相对绑定。依赖值生效并给出以下输出。
输出
2.4 PreviousData
这是相对使用最少的方式。当数据被分析时,这就出现了,我们需要表示值相对于以前数据的变化。
让我们举个例子来更详细地理解它。
步骤
- 创建一个类Data并实现INotifyPropertyChanged接口,如下所示
public class Data: INotifyPropertyChanged
{
public int DataValue
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropertyName)
{
if (null != PropertyChanged)
{
PropertyChanged(this,
new PropertyChangedEventArgs(PropertyName));
}
}
}
- 创建一个Data类型的列表并将其指定为DataContext。
public RelativeSourcePreviousData()
{
InitializeComponent();
List < Data > data = new List < Data > ();
data.Add(new Data()
{
DataValue = 60
});
data.Add(new Data()
{
DataValue = 100
});
data.Add(new Data()
{
DataValue = 120
});
this.DataContext = data;
}
- 在XAML文件中添加ItemsControl。
<ItemsControl ItemsSource="{Binding}"></ItemsControl>
- 为其创建ItemsPanel模板,如下。
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
- 现在,为了正确地表示数据,创建DataTemplate,如下所示。
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid Margin="30,20,0,0">
<Rectangle Width="80" Height="{Binding DataValue}" Fill="Blue" />
<TextBlock Foreground="White" Margin="35,0,0,0" Text="{Binding DataValue}"></TextBlock>
</Grid>
<TextBlock Margin="30,20,0,0" Text="Previous Data:"></TextBlock>
<TextBlock VerticalAlignment="Center" Margin="5,20,0,0" Text="{Binding
RelativeSource={RelativeSource PreviousData}, Path=DataValue}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
输出
蓝色框的高度是列表中项目的值,旧数据显示在右侧。该项的第一个值为“60”。因此,第一项没有旧值。
3、集合当前项绑定
在处理集合时使用。使用这个绑定表达式,您可以非常容易地读取SelectedItem的属性。斜杠是一种特殊运算符,用于处理集合中的当前项。
下面给出了三种表达式。
- {Binding / }
- {Binding Collection / }
- {Binding Collection / Property}
3.1 {Binding / }
此表达式用于绑定DataContext中的当前项。
让我们采取一个示例:
在下面给出的示例中,DataContext是字符串类型的国家/地区的集合,并且与Listbox绑定在一起。
步骤
- 创建一个Countries类并添加一个GetCountriesName()方法,该方法返回string数据类型的国家的集合,如下所示。
public class Countries
{
public static List <string> GetCountriesName()
{
List <string> countries = new List <string> ();
foreach(CultureInfo culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
{
RegionInfo country = new RegionInfo(culture.LCID);
if (!countries.Contains(country.EnglishName))
countries.Add(country.EnglishName);
}
countries.Sort();
return countries;
}
}
- 添加一个XAMl文件,一个ListBox和TextBlock,如下所示。
<DockPanel Name="Collection">
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True">
</ListBox>
<TextBlock DockPanel.Dock="Top" />
</DockPanel>
- 创建类Countries的实例并将Countries集合指定为DataContext。
public CurrentItemCollection()
{
InitializeComponent();
Countries countries = new Countries();
this.DataContext = countries.GetCountriesName()
}
- 绑定TextBlock的Text属性以将其绑定到集合的当前选定项,如下所示。
<TextBlock DockPanel.Dock="Top" Text="{Binding /}" />
输出
一旦列表项被选中,它将在右侧显示所选国家/地区。
3.2 {Binding Collection /}
此表达式用于绑定DataContext中集合属性的当前项。
例如,
DataContext是Countries类
Collection属性是CounriesList,它与ListBox绑定。
步骤
- 使用上面创建的类似的国家类,只是略有不同。创建返回类型为RegionInfo的方法。
public static List <RegionInfo> GetCountries()
{
List <RegionInfo> countries = new List <RegionInfo> ();
foreach(CultureInfo culture in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
{
RegionInfo country = new RegionInfo(culture.LCID);
if (countries.Where(p => p.Name == country.Name).Count() == 0)
countries.Add(country);
}
return countries.OrderBy(p => p.EnglishName).ToList();
}
- 添加RegionInfo类型的CountriesList属性。
private List <RegionInfo> countries = null;
public List <RegionInfo> CountriesList
{
get
{
if (countries == null)
countries = GetCountries();
return countries;
}
}
下面是CountriesList集合中的值的截图。
- 将类Countries指定为DataContext,并将Listbox与DataContext的CountriesList属性绑定。
<Window.Resources>
<vm:Countries x:Key="Countries"></vm:Countries>
</Window.Resources>
<Grid>
<DockPanel Name="Collection" DataContext="{StaticResource Countries}">
<ListBox ItemsSource="{Binding CountriesList}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding EnglishName}"></TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</Grid>
- 要计算CountriesList属性的当前项,请绑定TextBlock的Text属性,如下所示。
<TextBlock DockPanel.Dock="Top" Text="{Binding CountriesList/}" HorizontalAlignment="Center" FontSize="16" VerticalAlignment="Center" />
输出
右侧显示DataContext(CountriesList)中集合的当前项(CountriesList)。
3.3 {Binding Collection / Property}
此表达式用于绑定DataContext中集合的当前项的属性。
例如,如果必须计算CountriesList集合的当前项的特定属性。
在这个例子中,我想显示属性“EnglishName”的值。
为此,绑定TextBlock的Text属性,如下所示。
代码语言:javascript复制<TextBlock DockPanel.Dock="Top" Text="{Binding CountriesList/EnglishName}" />
输出
现在,当列表中的项被选中时,它显示属性“EnglishName”的值。
结论
我已经详细介绍了所有的数据绑定表达式。我希望这有助于您理解绑定的概念和WPF提供的表达式。