WPF 数据绑定实用指南
在 WPF(Windows Presentation Foundation)开发中,数据绑定是一项核心技术,它能够实现数据与界面的分离,提高代码的可维护性和可测试性。本文将详细介绍 WPF 中的数据绑定,包括命令绑定、简单数据绑定、值转换、多属性绑定以及列表绑定等内容。
1. 命令绑定基础
在 WPF 中,命令触发(比如按钮点击)时的操作并非由命令本身定义,而是通过命令绑定将命令与事件处理程序关联起来。例如,在以下代码中,
Window
的
CommandBindings
定义了如果“Open”命令被触发,将调用
OnOpen
方法。
<Window.CommandBindings>
<CommandBinding Command="Open" Executed="OnOpen" />
</Window.CommandBindings>
命令绑定会在层次结构中进行搜索,
CommandBindings
属性定义在
UIElement
类中,而
UIElement
是每个 WPF 元素的基类。这样,控件可以定义命令绑定并实现处理程序,只需定义命令源即可。例如,
TextBox
类实现了“Cut”、“Copy”、“Paste”和“Undo”命令的处理程序,只需为这些命令定义命令源(如
MenuItem
元素)。
1.1 使用 MVVM 和 DelegateCommand
在 MVVM(Model-View-ViewModel)模式中,直接在 XAML 文件中进行命令绑定可能不太合适,因为这种方式会导致与命令目标的紧密耦合。在 MVVM 中,命令和处理程序由
ViewModel
类定义,通过绑定命令实现松散耦合。
为了实现这一点,需要实现
ICommand
接口,以下是
DelegateCommand
类的实现:
using System;
using System.Windows.Input;
namespace Formula1.Infrastructure
{
public class DelegateCommand : ICommand
{
private readonly Action<object> execute;
private readonly Func<object, bool> canExecute;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute == null ? true : canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
execute(parameter);
}
}
}
这种
DelegateCommand
类是大多数 MVVM 框架的一部分。
2. 创建 ViewModel
以一个 Formula 1 应用程序为例,展示如何在用户界面上显示
Racer
类型的值。该示例通过命令查询特定名称的赛车手。
2.1 定义 ViewModel 类
ShowRacerViewModel
类派生自
ViewModelBase
,并定义了一个命令
FindRacerCommand
:
using System.Data;
using System.Data.Objects;
using System.Linq;
using Formula1.Infrastructure;
using Formula1.Model;
namespace Formula1.ViewModels
{
public class ShowRacerViewModel : ViewModelBase
{
private DelegateCommand findRacerCommand;
public DelegateCommand FindRacerCommand
{
get
{
return findRacerCommand ??
(findRacerCommand = new DelegateCommand(
param => this.FindRacer(param)));
}
}
public void FindRacer(object name)
{
try
{
string filter = (string)name;
using (Formula1Entities data = new Formula1Entities())
{
var q = (from r in data.Racers
where r.LastName.StartsWith(filter)
select r);
Racer = (q as ObjectQuery<Racer>).
Execute(MergeOption.NoTracking).FirstOrDefault();
}
}
catch (EntityException)
{
SetError("Verify the database connection");
}
}
private Racer racer;
public Racer Racer
{
get { return racer; }
set
{
if (!object.Equals(racer, value))
{
racer = value;
RaisePropertyChanged("Racer");
}
}
}
}
}
2.2 命令绑定流程
-
在 XAML 代码中,定义一个按钮,将其
Command属性绑定到ViewModel类的FindRacerCommand属性:
<TextBox x:Name="textName" Grid.Row="0" Grid.Column="0"
Margin="5" VerticalAlignment="Center" />
<Button Grid.Row="0" Grid.Column="1" Content="Find Racer" Margin="5"
Command="{Binding FindRacerCommand}"
CommandParameter="{Binding ElementName=textName, Path=Text,
Mode=OneWay}" />
-
当按钮被点击时,
FindRacer方法被调用,该方法使用 ADO.NET EF 进行查询,并将结果赋值给Racer属性。
3. 使用简单数据绑定
为了显示赛车手的信息,创建多个
TextBlock
元素,将其
Text
属性绑定到
Nationality
、
Starts
和
Wins
等属性。这些控件位于一个
Grid
控件中,
Grid
的
DataContext
设置为绑定到
ViewModel
的
Racer
属性。
<UserControl x:Class="Formula1.Views.ShowRacerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-
compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:utils="clr-namespace:Formula1.Infrastructure"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style TargetType="TextBlock">
<Style.Setters>
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="10,7,5,7" />
</Style.Setters>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox x:Name="textName" Grid.Row="0" Grid.Column="0"
Margin="5" VerticalAlignment="Center" />
<Button Grid.Row="0" Grid.Column="1" Content="Find Racer" Margin="5"
Command="{Binding FindRacerCommand}"
CommandParameter="{Binding ElementName=textName, Path=Text,
Mode=OneWay}" />
<Grid Grid.Row="1" Grid.ColumnSpan="2" DataContext="{Binding Racer}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="Name" Grid.Row="0" Grid.Column="0" />
<TextBlock Grid.Row="0" Grid.Column="1" />
<TextBlock Text="Country" Grid.Row="1" Grid.Column="0" />
<TextBlock Grid.Row="1" Grid.Column="1"
Text="{Binding Nationality}" />
<TextBlock Text="Starts" Grid.Row="2" Grid.Column="0" />
<TextBlock Grid.Row="2" Grid.Column="1" Text="{Binding Starts}" />
<TextBlock Text="Wins" Grid.Row="3" Grid.Column="0" />
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding Wins}" />
<TextBlock Text="Points" Grid.Row="4" Grid.Column="0" />
<TextBlock Grid.Row="4" Grid.Column="1" Text="{Binding Points}" />
</Grid>
<TextBlock Foreground="Red" Text="{Binding ErrorMessage}" Grid.Row="2"
Grid.Column="0" Grid.ColumnSpan="2" />
</Grid>
</UserControl>
3.1 简单数据绑定步骤
-
创建
TextBlock元素用于显示信息。 -
将
TextBlock的Text属性绑定到相应的属性。 -
设置
Grid的DataContext为ViewModel的Racer属性。
4. 值转换
为了仅在出现错误时显示错误信息的
TextBlock
,可以将
TextBlock
的
Visibility
属性绑定到
ViewModelBase
类的
HasError
属性。由于
HasError
是
bool
类型,而
Visibility
是枚举类型,需要使用转换器进行绑定。
4.1 实现转换器
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace Formula1.Infrastructure
{
[ValueConversion(typeof(bool), typeof(Visibility))]
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool input = (bool)value;
if (input)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}
4.2 在 XAML 中使用转换器
<UserControl.Resources>
<utils:VisibilityConverter x:Key="VisibilityConverter" />
</UserControl.Resources>
<TextBlock Foreground="Red" Visibility="{Binding HasError,
Converter={StaticResource VisibilityConverter}}"
Text="{Binding ErrorMessage}" Grid.Row="2" Grid.Column="0"
Grid.ColumnSpan="2" />
4.3 值转换步骤
-
实现
IValueConverter接口的转换器类。 - 在 XAML 资源中定义转换器。
- 在绑定中使用转换器。
5. 多属性绑定
如果需要将源的多个属性绑定到一个 UI 元素,可以使用
MultiBinding
。例如,将
TextBlock
的
Text
属性绑定到
Racer
的
FirstName
和
LastName
属性。
5.1 使用 MultiBinding
<TextBlock Grid.Row="0" Grid.Column="1">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource NameConverter}">
<Binding Path="FirstName" />
<Binding Path="LastName" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
5.2 实现转换器
using System;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;
namespace Formula1.Infrastructure
{
public class NameConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType,
object parameter, CultureInfo culture)
{
if (values == null || values.Count() != 2)
return DependencyProperty.UnsetValue;
return String.Format("{0} {1}", values[0], values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
5.3 多属性绑定步骤
-
在
MultiBinding中定义要绑定的属性。 -
实现
IMultiValueConverter接口的转换器类。 -
在
MultiBinding中指定转换器。
6. 列表绑定
接下来,将绑定赛车手列表。在
ShowRacersView
视图和
ShowRacersViewModel
视图模型中实现。
6.1 定义 ViewModel 类
using System;
using System.Collections.Generic;
using System.Linq;
using Formula1.Infrastructure;
using Formula1.Model;
namespace Formula1.ViewModels
{
public class ShowRacersViewModel : ViewModelBase, IDisposable
{
private Formula1Entities data;
public ShowRacersViewModel()
{
if (!IsDesignTime)
{
data = new Formula1Entities();
}
}
private DelegateCommand getRacersCommand;
public DelegateCommand GetRacersCommand
{
get
{
return getRacersCommand ??
(getRacersCommand = new DelegateCommand(
param => this.GetRacers()));
}
}
public bool FilterCountry { get; set; }
public bool FilterYears { get; set; }
private string[] countries;
public IEnumerable<string> Countries
{
get
{
return countries ??
(countries = data.Racers.Select(
r => r.Nationality).Distinct().ToArray());
}
}
public string SelectedCountry { get; set; }
private int minYear;
public int MinYear
{
get
{
if (IsDesignTime)
minYear = 1950;
return minYear != 0 ? minYear : minYear =
data.Races.Select(r => r.Date.Year).Min();
}
}
private int maxYear;
public int MaxYear
{
get
{
if (IsDesignTime)
maxYear = DateTime.Today.Year;
return maxYear != 0 ? maxYear : maxYear =
data.Races.Select(r => r.Date.Year).Max();
}
}
private int selectedMinYear;
public int SelectedMinYear
{
get
{
return selectedMinYear;
}
set
{
if (!object.Equals(selectedMinYear, value))
{
selectedMinYear = value;
RaisePropertyChanged("SelectedMinYear");
}
}
}
private int selectedMaxYear;
public int SelectedMaxYear
{
get
{
return selectedMaxYear;
}
set
{
if (!object.Equals(selectedMaxYear, value))
{
selectedMaxYear = value;
RaisePropertyChanged("SelectedMaxYear");
}
}
}
public void Dispose()
{
data.Dispose();
}
private void GetRacers()
{
RaisePropertyChanged("Racers");
}
private IQueryable<Racer> GetExpression()
{
var expr = data.Racers as IQueryable<Racer>;
if (FilterCountry)
{
expr = expr.Where(r => r.Nationality == this.SelectedCountry);
}
if (FilterYears)
{
expr = expr.SelectMany(
r => r.RaceResults,
(r1, raceResult) => new { Racer = r1, RaceResult =
raceResult })
.Where(raceInfo =>
raceInfo.RaceResult.Race.Date.Year >= SelectedMinYear &&
raceInfo.RaceResult.Race.Date.Year <= SelectedMaxYear)
.Select(raceInfo => raceInfo.Racer)
.Distinct();
}
return expr;
}
}
}
6.2 在 XAML 中实现 UI
<GroupBox Header="Filter" Grid.Row="0">
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding FilterCountry}" Content="Country"
Grid.Row="0" Grid.Column="0" Margin="5" />
<ComboBox ItemsSource="{Binding Countries}"
SelectedItem="{Binding SelectedCountry,
Mode=OneWayToSource}"
Grid.Row="0" Grid.Column="1" Margin="5" />
<CheckBox IsChecked="{Binding FilterYears}" Content="Years"
Grid.Row="1"
Grid.Column="0" Margin="5" />
<Grid Grid.Row="1" Grid.Column="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="From" Grid.Row="0" Grid.Column="0"
VerticalAlignment="Center" />
<TextBlock Text="To" Grid.Row="1" Grid.Column="0"
VerticalAlignment="Center" />
<Slider x:Name="minSlider" Grid.Row="0" Grid.Column="1"
IsSelectionRangeEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="5" TickPlacement="BottomRight"
AutoToolTipPlacement="TopLeft" Margin="5"
Minimum="{Binding MinYear, Mode=OneTime}"
Maximum="{Binding MaxYear, Mode=OneTime}"
Value="{Binding SelectedMinYear}"
SelectionStart="{Binding MinYear, Mode=OneWay}"
SelectionEnd=
"{Binding ElementName=maxSlider, Path=Value,
Mode=OneWay}" />
<Slider x:Name="maxSlider" Grid.Row="1" Grid.Column="1"
IsSelectionRangeEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="5" TickPlacement="BottomRight"
AutoToolTipPlacement="TopLeft" Margin="5"
Minimum="{Binding MinYear, Mode=OneTime}"
Maximum="{Binding MaxYear, Mode=OneTime}"
Value="{Binding SelectedMaxYear}"
SelectionStart=
"{Binding ElementName=minSlider, Path=Value,
Mode=OneWay}"
SelectionEnd="{Binding MaxYear, Mode=OneWay}" />
</Grid>
<Button Command="{Binding GetRacersCommand}"
Content="Get Racers" Grid.Row="2" Grid.ColumnSpan="2"
HorizontalAlignment="Center" Margin="5" Padding="3" />
</Grid>
</GroupBox>
6.3 列表绑定步骤
- 在 ViewModel 中定义相关属性和命令。
-
在 XAML 中使用
CheckBox、ComboBox和Slider等控件进行筛选设置。 -
绑定
ListBox的ItemsSource属性到ViewModel的Racers属性。
总结
通过本文的介绍,我们了解了 WPF 中数据绑定的多种方式,包括命令绑定、简单数据绑定、值转换、多属性绑定和列表绑定。这些技术能够帮助我们实现数据与界面的分离,提高代码的可维护性和可测试性。在实际开发中,可以根据具体需求选择合适的数据绑定方式。
关键技术点总结
| 技术点 | 描述 |
|---|---|
| 命令绑定 |
通过
CommandBindings
将命令与事件处理程序关联
|
| MVVM 和 DelegateCommand | 在 MVVM 模式中实现命令和处理程序的松散耦合 |
| 简单数据绑定 |
将
TextBlock
等控件的属性绑定到数据属性
|
| 值转换 | 使用转换器处理不同类型的绑定 |
| 多属性绑定 |
使用
MultiBinding
将多个属性绑定到一个 UI 元素
|
| 列表绑定 |
使用
ItemsControl
绑定列表数据,并实现筛选功能
|
流程图:数据绑定流程
graph LR
A[定义命令和事件处理程序] --> B[创建 ViewModel 类]
B --> C[在 XAML 中绑定命令和数据]
C --> D[执行命令,更新数据]
D --> E[显示数据到界面]
F[定义筛选条件] --> G[筛选数据]
G --> E
通过以上步骤和示例代码,你可以在 WPF 应用程序中灵活运用数据绑定技术,实现高效、可维护的界面开发。
6.4 筛选功能的实现原理
在
ShowRacersViewModel
类中,筛选功能主要通过
GetExpression
方法实现。该方法根据
FilterCountry
和
FilterYears
属性的值对赛车手数据进行筛选。
-
国家筛选
:如果
FilterCountry
为
true
,则使用
Where
方法筛选出国籍与
SelectedCountry
相同的赛车手。
-
年份筛选
:如果
FilterYears
为
true
,则使用
SelectMany
方法展开赛车手的比赛结果,然后筛选出比赛年份在
SelectedMinYear
和
SelectedMaxYear
之间的赛车手,并使用
Distinct
方法去除重复项。
6.5 筛选功能的操作步骤
-
设置筛选条件
:在 UI 中,通过
CheckBox控件选择是否启用国家和年份筛选。 -
选择筛选值
:如果启用国家筛选,通过
ComboBox选择国家;如果启用年份筛选,通过Slider控件选择年份范围。 -
触发筛选
:点击“Get Racers”按钮,触发
GetRacersCommand命令,调用GetRacers方法,通知 UIRacers属性发生变化,从而更新列表显示。
7. 数据绑定的优化建议
7.1 性能优化
- 减少不必要的绑定 :避免在界面中绑定过多不必要的数据,只绑定需要显示或交互的数据,减少数据更新时的性能开销。
- 使用延迟加载 :对于一些大数据集,可以使用延迟加载的方式,只在需要时加载数据,避免一次性加载过多数据导致性能下降。
-
优化查询语句
:在
ViewModel中,优化数据查询语句,避免复杂的嵌套查询和不必要的计算,提高数据查询的效率。
7.2 代码可维护性优化
-
封装业务逻辑
:将业务逻辑封装在
ViewModel中,避免在 XAML 中编写过多的代码,提高代码的可维护性和可测试性。 -
使用命名约定
:在命名
ViewModel的属性和命令时,使用清晰、有意义的命名约定,方便团队成员理解和维护代码。 -
模块化开发
:将不同的功能模块拆分成独立的
ViewModel和视图,降低代码的耦合度,提高代码的可扩展性。
7.3 数据验证优化
-
实现数据验证逻辑
:在
ViewModel中实现数据验证逻辑,确保用户输入的数据符合要求。可以使用IDataErrorInfo接口或INotifyDataErrorInfo接口实现数据验证。 -
显示验证错误信息
:在 UI 中显示数据验证错误信息,提示用户输入正确的数据。可以使用
Validation.ErrorTemplate自定义验证错误信息的显示样式。
8. 常见问题及解决方案
8.1 绑定失败问题
- 问题描述 :在 XAML 中绑定数据时,数据无法正确显示。
-
解决方案
:
- 检查绑定路径 :确保绑定路径正确,包括属性名和命名空间。
-
检查
DataContext:确保DataContext已正确设置,并且包含绑定的属性。 - 检查数据类型 :确保绑定的数据类型与目标属性的数据类型兼容。
8.2 命令无法执行问题
- 问题描述 :点击按钮或执行命令时,命令没有被执行。
-
解决方案
:
- 检查命令绑定 :确保命令绑定正确,包括命令名和事件处理程序。
-
检查
CanExecute方法 :确保CanExecute方法返回true,允许命令执行。 -
检查
CommandManager:确保CommandManager已正确初始化,并且能够正确处理命令。
8.3 数据更新不及时问题
- 问题描述 :数据发生变化时,UI 没有及时更新。
-
解决方案
:
-
实现
INotifyPropertyChanged接口 :在ViewModel中实现INotifyPropertyChanged接口,确保属性变化时能够通知 UI 更新。 -
检查绑定模式
:确保绑定模式设置正确,如
OneWay、TwoWay或OneTime。
-
实现
9. 案例分析:完整的 WPF 数据绑定应用
9.1 应用场景
假设我们要开发一个赛车手管理系统,需要显示赛车手列表,并支持按国家和年份筛选。用户可以输入筛选条件,点击按钮获取筛选后的赛车手列表。
9.2 实现步骤
-
创建
ViewModel类 :创建ShowRacersViewModel类,定义赛车手数据、筛选条件和命令。 -
创建视图
:创建
ShowRacersView.xaml文件,使用CheckBox、ComboBox、Slider和ListBox等控件实现 UI,并绑定数据和命令。 -
实现筛选功能
:在
ViewModel中实现GetExpression方法,根据筛选条件筛选赛车手数据。 -
处理命令
:在
ViewModel中实现GetRacersCommand命令,点击按钮时调用GetRacers方法,通知 UI 更新赛车手列表。
9.3 代码示例
以下是完整的代码示例:
// ShowRacersViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Formula1.Infrastructure;
using Formula1.Model;
namespace Formula1.ViewModels
{
public class ShowRacersViewModel : ViewModelBase, IDisposable
{
private Formula1Entities data;
public ShowRacersViewModel()
{
if (!IsDesignTime)
{
data = new Formula1Entities();
}
}
private DelegateCommand getRacersCommand;
public DelegateCommand GetRacersCommand
{
get
{
return getRacersCommand ??
(getRacersCommand = new DelegateCommand(
param => this.GetRacers()));
}
}
public bool FilterCountry { get; set; }
public bool FilterYears { get; set; }
private string[] countries;
public IEnumerable<string> Countries
{
get
{
return countries ??
(countries = data.Racers.Select(
r => r.Nationality).Distinct().ToArray());
}
}
public string SelectedCountry { get; set; }
private int minYear;
public int MinYear
{
get
{
if (IsDesignTime)
minYear = 1950;
return minYear != 0 ? minYear : minYear =
data.Races.Select(r => r.Date.Year).Min();
}
}
private int maxYear;
public int MaxYear
{
get
{
if (IsDesignTime)
maxYear = DateTime.Today.Year;
return maxYear != 0 ? maxYear : maxYear =
data.Races.Select(r => r.Date.Year).Max();
}
}
private int selectedMinYear;
public int SelectedMinYear
{
get
{
return selectedMinYear;
}
set
{
if (!object.Equals(selectedMinYear, value))
{
selectedMinYear = value;
RaisePropertyChanged("SelectedMinYear");
}
}
}
private int selectedMaxYear;
public int SelectedMaxYear
{
get
{
return selectedMaxYear;
}
set
{
if (!object.Equals(selectedMaxYear, value))
{
selectedMaxYear = value;
RaisePropertyChanged("SelectedMaxYear");
}
}
}
public void Dispose()
{
data.Dispose();
}
private void GetRacers()
{
RaisePropertyChanged("Racers");
}
private IQueryable<Racer> GetExpression()
{
var expr = data.Racers as IQueryable<Racer>;
if (FilterCountry)
{
expr = expr.Where(r => r.Nationality == this.SelectedCountry);
}
if (FilterYears)
{
expr = expr.SelectMany(
r => r.RaceResults,
(r1, raceResult) => new { Racer = r1, RaceResult =
raceResult })
.Where(raceInfo =>
raceInfo.RaceResult.Race.Date.Year >= SelectedMinYear &&
raceInfo.RaceResult.Race.Date.Year <= SelectedMaxYear)
.Select(raceInfo => raceInfo.Racer)
.Distinct();
}
return expr;
}
public IQueryable<Racer> Racers
{
get
{
return GetExpression();
}
}
}
}
<!-- ShowRacersView.xaml -->
<GroupBox Header="Filter" Grid.Row="0">
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox IsChecked="{Binding FilterCountry}" Content="Country"
Grid.Row="0" Grid.Column="0" Margin="5" />
<ComboBox ItemsSource="{Binding Countries}"
SelectedItem="{Binding SelectedCountry,
Mode=OneWayToSource}"
Grid.Row="0" Grid.Column="1" Margin="5" />
<CheckBox IsChecked="{Binding FilterYears}" Content="Years"
Grid.Row="1"
Grid.Column="0" Margin="5" />
<Grid Grid.Row="1" Grid.Column="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="From" Grid.Row="0" Grid.Column="0"
VerticalAlignment="Center" />
<TextBlock Text="To" Grid.Row="1" Grid.Column="0"
VerticalAlignment="Center" />
<Slider x:Name="minSlider" Grid.Row="0" Grid.Column="1"
IsSelectionRangeEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="5" TickPlacement="BottomRight"
AutoToolTipPlacement="TopLeft" Margin="5"
Minimum="{Binding MinYear, Mode=OneTime}"
Maximum="{Binding MaxYear, Mode=OneTime}"
Value="{Binding SelectedMinYear}"
SelectionStart="{Binding MinYear, Mode=OneWay}"
SelectionEnd=
"{Binding ElementName=maxSlider, Path=Value,
Mode=OneWay}" />
<Slider x:Name="maxSlider" Grid.Row="1" Grid.Column="1"
IsSelectionRangeEnabled="True"
IsSnapToTickEnabled="True"
TickFrequency="5" TickPlacement="BottomRight"
AutoToolTipPlacement="TopLeft" Margin="5"
Minimum="{Binding MinYear, Mode=OneTime}"
Maximum="{Binding MaxYear, Mode=OneTime}"
Value="{Binding SelectedMaxYear}"
SelectionStart=
"{Binding ElementName=minSlider, Path=Value,
Mode=OneWay}"
SelectionEnd="{Binding MaxYear, Mode=OneWay}" />
</Grid>
<Button Command="{Binding GetRacersCommand}"
Content="Get Racers" Grid.Row="2" Grid.ColumnSpan="2"
HorizontalAlignment="Center" Margin="5" Padding="3" />
</Grid>
</GroupBox>
<ListBox ItemsSource="{Binding Racers}" Grid.Row="1" Grid.ColumnSpan="2" Margin="5" />
9.4 运行效果
运行该应用程序,用户可以在 UI 中设置筛选条件,点击“Get Racers”按钮,即可看到筛选后的赛车手列表。
总结
通过本文的介绍,我们全面了解了 WPF 数据绑定的各种技术,包括命令绑定、简单数据绑定、值转换、多属性绑定和列表绑定。同时,我们还学习了如何实现筛选功能、优化数据绑定性能、解决常见问题,并通过一个完整的案例展示了如何在实际应用中运用这些技术。
关键知识点回顾
| 知识点 | 要点 |
|---|---|
| 命令绑定 |
通过
CommandBindings
关联命令和事件处理程序,在 MVVM 中使用
DelegateCommand
实现松散耦合
|
| 简单数据绑定 |
将控件属性绑定到数据属性,设置
DataContext
实现数据显示
|
| 值转换 |
使用
IValueConverter
处理不同类型的绑定
|
| 多属性绑定 |
使用
MultiBinding
和
IMultiValueConverter
将多个属性绑定到一个 UI 元素
|
| 列表绑定 |
使用
ItemsControl
绑定列表数据,实现筛选功能
|
| 筛选功能 |
在
ViewModel
中实现
GetExpression
方法进行数据筛选
|
| 优化建议 | 从性能、可维护性和数据验证方面优化数据绑定 |
| 常见问题解决 | 解决绑定失败、命令无法执行和数据更新不及时等问题 |
未来展望
WPF 数据绑定技术不断发展,未来可能会有更多的优化和扩展。例如,支持更复杂的数据类型绑定、提供更强大的筛选和排序功能等。开发者可以持续关注 WPF 技术的发展,不断提升自己的开发技能,为用户提供更优质的应用程序。
流程图:完整应用流程
graph LR
A[启动应用] --> B[显示筛选界面]
B --> C[设置筛选条件]
C --> D[点击获取按钮]
D --> E[执行命令,筛选数据]
E --> F[更新列表显示]
G[修改筛选条件] --> C
通过以上内容,希望你能在 WPF 开发中熟练运用数据绑定技术,打造出高效、可维护的应用程序。
超级会员免费看
5065

被折叠的 条评论
为什么被折叠?



