46、WPF 数据编辑与验证实战指南

WPF 数据编辑与验证实战指南

1. 数据性能优化选项

在处理数据时,有几种性能优化选项可供选择:
- 回收模式 :不再需要的数据模板对象会进入一个池,并根据需要从池中重用。
- 延迟滚动选项 :设置 ScrollViewer.IsDeferredScrollingEnabled 附加属性为 true 时,滚动时只有滚动条移动,列表框中的项目不会移动。直到用户完成滚动,才需要创建数据模板。从性能角度看,这是最佳选项,但可能会影响用户体验,因此在大多数情况下可能不可行。

2. 数据编辑操作

接下来将介绍如何进行数据编辑,包括检索数据、更新数据、添加验证以及使用 DataGrid 控件进行批量更改。

2.1 更新单条数据

以下示例展示了如何编辑和保存单条数据项。为了实现可取消的编辑功能, ViewModel EditRacerViewModel 实现了 IEditableObject 接口。该接口定义了 BeginEdit CancelEdit EndEdit 方法。 EditRacerViewModel 类还定义了四个命令:
- GetRacerCommand :从数据库中获取赛车手信息。
- EditCommand :进入编辑模式。
- CancelCommand :取消编辑,恢复原始值。
- SaveCommand :保存编辑后的信息到数据库。

using System.ComponentModel;
using System.Data;
using System.Linq;
using Formula1.Infrastructure;
using Formula1.Model;

namespace Formula1.ViewModels
{
    public class EditRacerViewModel : ViewModelBase, IEditableObject
    {
        private DelegateCommand getRacerCommand;
        public DelegateCommand GetRacerCommand
        {
            get
            {
                return getRacerCommand ??
                    (getRacerCommand = new DelegateCommand(param => 
                         this.GetRacer()));
            }
        }

        private void GetRacer()
        {
            using (Formula1Entities data = new Formula1Entities())
            {
                var racer = (from r in data.Racers
                             where r.Id == this.Id
                             select r).Single();

                RacerToVM(racer);
            }
        }

        private void UpdateRacer()
        {
            using (Formula1Entities data = new Formula1Entities())
            {
                Racer r = data.GetObjectByKey(this.key) as Racer;

                Racer current = VMToRacer();

                data.Racers.ApplyCurrentValues(current);

                data.SaveChanges();
            }
        }

        private EntityKey key;

        private int id;
        public int Id
        {
            get { return id; }
            set
            {
                if (!object.Equals(id, value))
                {
                    id = value;
                    RaisePropertyChanged("Id");
                }
            }
        }
        private string firstName;
        public string FirstName
        {
            get { return firstName; }
            set
            {
                if (!object.Equals(firstName, value))
                {
                    firstName = value;
                    RaisePropertyChanged("FirstName");
                }
            }
        }
        private string lastName;
        public string LastName
        {
            get { return lastName; }
            set
            {
                if (!object.Equals(lastName, value))
                {
                    lastName = value;
                    RaisePropertyChanged("LastName");
                }
            }
        }

        private string nationality;
        public string Nationality
        {
            get { return nationality; }
            set
            {
                if (!object.Equals(nationality, value))
                {
                    nationality = value;
                    RaisePropertyChanged("Nationality");
                }
            }
        }

        private int? starts;
        public int? Starts
        {
            get { return starts; }
            set
            {
                if (!object.Equals(starts, value))
                {
                    starts = value;
                    RaisePropertyChanged("Starts");
                }
            }
        }

        private int? wins;
        public int? Wins
        {
            get { return wins; }
            set
            {
                if (!object.Equals(wins, value))
                {
                    wins = value;
                    RaisePropertyChanged("Wins");
                }
            }
        }

        private int? points;
        public int? Points
        {
            get { return points; }
            set
            {
                if (!object.Equals(points, value))
                {
                    points = value;
                    RaisePropertyChanged("Points");
                }
            }
        }

        private void RacerToVM(Racer r)
        {
            this.key = r.EntityKey;
            this.Id = r.Id;
            this.FirstName = r.FirstName;
            this.LastName = r.LastName;
            this.Nationality = r.Nationality;
            this.Starts = r.Starts;
            this.Wins = r.Wins;
            this.Points = r.Points;
        }

        private Racer VMToRacer()
        {
            var current = Racer.CreateRacer(this.Id, this.FirstName, 
                 this.LastName);
            current.Nationality = this.Nationality;
            current.Starts = this.Starts;
            current.Wins = this.Wins;
            current.Points = this.Points;
            return current;
        }

        private DelegateCommand editCommand;
        public DelegateCommand EditCommand
        {
            get
            {
                return editCommand ??
                    (editCommand = new DelegateCommand(
                        param => this.BeginEdit()));
            }
        }

        private DelegateCommand cancelCommand;
        public DelegateCommand CancelCommand
        {
            get
            {
                return cancelCommand ??
                    (cancelCommand = new DelegateCommand(param => 
                        this.CancelEdit()));
            }
        }

        private DelegateCommand saveCommand;
        public DelegateCommand SaveCommand
        {
            get
            {
                return saveCommand ??
                    (saveCommand = new DelegateCommand(
                        param => this.EndEdit()));
            }
        }

        public void BeginEdit()
        {
            IsEditMode = true;
        }

        public void CancelEdit()
        {
            GetRacer();
            IsEditMode = false;
        }

        public void EndEdit()
        {
            UpdateRacer();
            IsEditMode = false;
        }

        private bool isEditMode;
        public bool IsEditMode
        {
            get { return isEditMode; }
            private set
            {
                if (!object.Equals(isEditMode, value))
                {
                    isEditMode = value;
                    RaisePropertyChanged("IsEditMode");
                }
            }
        } 
    }
}
2.2 数据验证

默认情况下,如果用户输入的数据不被 ViewModel 接受, ViewModel 的属性不会反映这些更改,并且用户不会收到关于更改失败的通知。为了解决这个问题,可以使用 WPF 数据绑定提供的验证规则。

.NET 框架定义了两种具体实现:
- ExceptionValidationRule :处理 ViewModel 类抛出的异常。
- DataErrorValidationRule :需要实现 IDataErrorInfo 接口。

以下是在 EditRacerViewModel 类中实现 IDataErrorInfo 接口的示例:

string IDataErrorInfo.Error
{
    get { throw new System.NotImplementedException(); }
}

string IDataErrorInfo.this[string columnName]
{
    get 
    {
        string message = null;
        switch (columnName)
        {
            case "Wins":
            case "Starts":
                if (Wins > Starts)
                    message = "Wins must be smaller or equal to Starts";
                break;
            default:
                break;
        }
        return message;
    }
}

在 XAML 中,可以使用以下代码为 TextBox 添加验证规则:

<TextBox IsEnabled="{Binding IsEditMode}" Grid.Row="3" Grid.Column="1">
    <TextBox.Text>
        <Binding Path="Starts">
            <Binding.ValidationRules>
                <DataErrorValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>
3. 错误显示

为了显示通过 IDataErrorInfo 接口返回的错误消息,可以为 TextBox 创建一个自定义样式,其中包含一个触发器。当 Validation.HasError 附加属性为 true 时,触发器会将 TextBox ToolTip 属性设置为错误消息。

<Style TargetType="TextBox">
    <Setter Property="Margin" Value="5" />
    <Setter Property="VerticalAlignment" Value="Center" />
    <Setter Property="Validation.ErrorTemplate" 
            Value="{StaticResource validationTemplate}" />
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="ToolTip" Value="{Binding RelativeSource=
                {x:Static RelativeSource.Self}, 
                Path=(Validation.Errors)[0].ErrorContent}" />
            <Setter Property="Background" Value="Red" />
        </Trigger>
    </Style.Triggers>
</Style>

<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <TextBlock Foreground="Red" FontSize="32" FontWeight="Bold" 
                   Text="!" DockPanel.Dock="Left" />
        <AdornedElementPlaceholder DockPanel.Dock="Right" />
    </DockPanel>
</ControlTemplate>
4. 使用 DataGrid 进行批量编辑

当需要一次性更改多条记录时,可以使用 DataGrid 控件。以下是一个使用 DataGrid 控件添加比赛结果到数据库的示例。

4.1 界面布局

界面包含两个按钮:“Add a Race” 和 “Edit a Race”,分别绑定到 AddRaceCommand EditRaceCommand

<StackPanel Orientation="Horizontal" Grid.Row="0">
    <Button Content="Add a Race" Command="{Binding AddRaceCommand}" 
            Margin="5" Padding="3" />
    <Button Content="Edit a Race" Command="{Binding EditRaceCommand}" 
            Margin="5" Padding="3" />
</StackPanel>
4.2 ViewModel 实现

EditRaceViewModel 类定义了相关命令和属性:

public class EditRaceViewModel : ViewModelBase, IDisposable
{
    private Formula1Entities data;

    public EditRaceViewModel()
    {
        AddRaceVisibility = Visibility.Collapsed;
        EditRaceVisibility = Visibility.Collapsed;
        RaceResultVisibility = Visibility.Collapsed;
        AddRacerVisibility = Visibility.Collapsed;

        if (!IsDesignTime)
        {
            data = new Formula1Entities();
        }
    }

    private DelegateCommand addRaceCommand;
    public DelegateCommand AddRaceCommand
    {
        get
        {
            return addRaceCommand ??
                (addRaceCommand = new DelegateCommand(
                     param => this.AddRace()));
        }
    }

    public void AddRace()
    {
        RaceDate = DateTime.Today;
        AddRaceVisibility = Visibility.Visible;
    }

    private Visibility addRaceVisibility;
    public Visibility AddRaceVisibility
    {
        get 
        { 
            return addRaceVisibility; 
        }
        set 
        { 
            addRaceVisibility = value;
            RaisePropertyChanged("AddRaceVisibility");
        }
    }
    private List<Circuit> circuits;
    public IEnumerable<Circuit> Circuits
    {
        get
        {
            if (!IsDesignTime)
            {
                return circuits ??
                    (circuits = new List<Circuit>(
                        from c in data.Circuits
                        orderby c.Country
                        select c));
            }
            else
                return null;
        }
    }
    //...
}
4.3 DataGrid 配置

DataGrid 控件用于显示和编辑比赛结果。以下是 DataGrid 的配置示例:

<Grid Grid.Row="1" Visibility="{Binding RaceResultVisibility}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid DataContext="{Binding RaceResults}" Grid.Row="0">
        <DataGrid x:Name="raceGrid" ItemsSource="{Binding}" 
                  AutoGenerateColumns="False" SelectionMode="Single" 
                  SelectionUnit="FullRow"> 
            <DataGrid.Columns>
                <DataGridTextColumn Width="Auto" Header="Pos" 
                                    Binding="{Binding Position}" />
                <DataGridTemplateColumn Width="*" Header="Racer" 
                                        MinWidth="210">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <ComboBox MinWidth="190" 
                                    ItemsSource="{Binding Racers}"
                                    TextSearch.TextPath="Name" 
                                    SelectedItem=
                                        "{Binding SelectedRacer, 
                                        Mode=OneWayToSource,
                                        UpdateSourceTrigger=LostFocus}" 
                                    IsEditable="True"
                                    Text="{Binding NewRacer, 
                                        Mode=OneWayToSource,
                                        UpdateSourceTrigger=LostFocus}" 
                                />
                                <Button Content="+" 
                                    Command="{Binding 
                                        AddRacerCommand}" />
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Width="*" Header="Team" 
                                        MinWidth="120">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <ComboBox ItemsSource="{Binding Teams}" 
                                TextSearch.TextPath="Name" 
                                SelectedItem=
                                    "{Binding SelectedTeam, 
                                    Mode=OneWayToSource,
                                    UpdateSourceTrigger=LostFocus}" 
                                IsEditable="True" 
                                Text="{Binding NewTeam,
                                    Mode=OneWayToSource,
                                    UpdateSourceTrigger=LostFocus}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Width="Auto" Header="Grid" 
                                    Binding="{Binding Grid}" />
                <DataGridTextColumn Width="Auto" Header="Pts" 
                                    Binding="{Binding Points}" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Grid>

总结

通过以上介绍,我们了解了 WPF 中数据编辑、验证和批量编辑的相关技术。回收模式和延迟滚动选项可以优化数据处理性能;实现 IEditableObject 接口可以实现可取消的单条数据编辑;使用验证规则可以确保用户输入的数据的有效性;自定义样式可以显示错误消息; DataGrid 控件可以方便地进行批量数据编辑。这些技术可以帮助我们开发出更加高效、稳定和用户友好的 WPF 应用程序。

流程图

graph TD;
    A[开始] --> B[选择性能优化选项];
    B --> C{是否使用延迟滚动};
    C -- 是 --> D[设置 ScrollViewer.IsDeferredScrollingEnabled 为 true];
    C -- 否 --> E[使用回收模式];
    D --> F[进行数据编辑操作];
    E --> F;
    F --> G[更新单条数据];
    G --> H[添加数据验证];
    H --> I[显示错误消息];
    I --> J[使用 DataGrid 进行批量编辑];
    J --> K[结束];

表格

功能 实现方式
性能优化 回收模式、延迟滚动(设置 ScrollViewer.IsDeferredScrollingEnabled true
单条数据编辑 实现 IEditableObject 接口,定义相关命令
数据验证 使用 ExceptionValidationRule DataErrorValidationRule ,实现 IDataErrorInfo 接口
错误显示 TextBox 创建自定义样式,设置 ToolTip 为错误消息
批量编辑 使用 DataGrid 控件,配置列绑定和模板

WPF 数据编辑与验证实战指南(续)

5. 数据编辑流程详解

在前面的内容中,我们已经介绍了数据编辑的各个方面,下面将详细梳理整个数据编辑的流程,以便更好地理解和应用这些技术。

5.1 数据检索与初始化

当用户触发 GetRacerCommand 时,会从数据库中检索赛车手信息,并将其填充到 ViewModel 类的属性中。具体步骤如下:
1. 执行 GetRacer 方法,使用 Formula1Entities 上下文从数据库中查询指定 Id 的赛车手。
2. 将查询结果的属性值复制到 ViewModel 类的对应属性中,以便进行数据绑定。

private void GetRacer()
{
    using (Formula1Entities data = new Formula1Entities())
    {
        var racer = (from r in data.Racers
                     where r.Id == this.Id
                     select r).Single();

        RacerToVM(racer);
    }
}
5.2 进入编辑模式

当用户点击 “Edit” 按钮时,会触发 EditCommand ,将 ViewModel 类的 IsEditMode 属性设置为 true ,从而进入编辑模式。此时,UI 中的 TextBox 元素会根据 IsEditMode 属性的绑定状态变为可编辑状态。

private DelegateCommand editCommand;
public DelegateCommand EditCommand
{
    get
    {
        return editCommand ??
            (editCommand = new DelegateCommand(
                param => this.BeginEdit()));
    }
}

public void BeginEdit()
{
    IsEditMode = true;
}
5.3 数据验证

在用户输入数据时,会根据配置的验证规则进行验证。如果输入的数据不符合规则,会显示相应的错误消息。验证规则的实现主要通过 IDataErrorInfo 接口,在 EditRacerViewModel 类中实现该接口的索引器方法,根据属性名进行验证。

string IDataErrorInfo.this[string columnName]
{
    get 
    {
        string message = null;
        switch (columnName)
        {
            case "Wins":
            case "Starts":
                if (Wins > Starts)
                    message = "Wins must be smaller or equal to Starts";
                break;
            default:
                break;
        }
        return message;
    }
}
5.4 保存或取消编辑
  • 保存编辑 :当用户点击 “Save” 按钮时,会触发 SaveCommand ,调用 EndEdit 方法,将 ViewModel 类的属性值更新到数据库中,并将 IsEditMode 属性设置为 false ,退出编辑模式。
private DelegateCommand saveCommand;
public DelegateCommand SaveCommand
{
    get
    {
        return saveCommand ??
            (saveCommand = new DelegateCommand(
                param => this.EndEdit()));
    }
}

public void EndEdit()
{
    UpdateRacer();
    IsEditMode = false;
}
  • 取消编辑 :当用户点击 “Cancel” 按钮时,会触发 CancelCommand ,调用 CancelEdit 方法,重新从数据库中检索原始数据,填充到 ViewModel 类的属性中,并将 IsEditMode 属性设置为 false ,退出编辑模式。
private DelegateCommand cancelCommand;
public DelegateCommand CancelCommand
{
    get
    {
        return cancelCommand ??
            (cancelCommand = new DelegateCommand(param => 
                this.CancelEdit()));
    }
}

public void CancelEdit()
{
    GetRacer();
    IsEditMode = false;
}
6. DataGrid 高级配置

在使用 DataGrid 进行批量编辑时,除了基本的配置外,还可以进行一些高级配置,以满足更复杂的需求。

6.1 自定义列模板

DataGridTemplateColumn 可以让我们自定义列的显示和编辑方式。例如,在显示赛车手信息的列中,使用 ComboBox 控件让用户可以选择现有的赛车手,也可以输入新的赛车手名称。

<DataGridTemplateColumn Width="*" Header="Racer" 
                        MinWidth="210">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <ComboBox MinWidth="190" 
                    ItemsSource="{Binding Racers}"
                    TextSearch.TextPath="Name" 
                    SelectedItem=
                        "{Binding SelectedRacer, 
                        Mode=OneWayToSource,
                        UpdateSourceTrigger=LostFocus}" 
                    IsEditable="True"
                    Text="{Binding NewRacer, 
                        Mode=OneWayToSource,
                        UpdateSourceTrigger=LostFocus}" 
                />
                <Button Content="+" 
                    Command="{Binding 
                        AddRacerCommand}" />
            </StackPanel>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
6.2 数据绑定模式和触发更新

ComboBox 的绑定中,使用 OneWayToSource 模式和 LostFocus 触发更新,确保只有在用户输入完成并失去焦点时才更新绑定源。

<ComboBox SelectedItem=
    "{Binding SelectedRacer, 
    Mode=OneWayToSource,
    UpdateSourceTrigger=LostFocus}" 
    IsEditable="True"
    Text="{Binding NewRacer, 
    Mode=OneWayToSource,
    UpdateSourceTrigger=LostFocus}" 
/>
7. 总结与回顾

通过本文的介绍,我们全面了解了 WPF 中数据编辑、验证和批量编辑的相关技术。从性能优化选项的选择,到单条数据的编辑和验证,再到使用 DataGrid 进行批量编辑,每个环节都有详细的实现方法和示例代码。

在实际开发中,我们可以根据具体需求选择合适的技术和实现方式,提高应用程序的性能和用户体验。同时,通过合理的配置和优化,确保数据的准确性和完整性。

流程图

graph TD;
    A[开始数据编辑] --> B[检索数据];
    B --> C[进入编辑模式];
    C --> D{是否验证通过};
    D -- 是 --> E[保存编辑];
    D -- 否 --> F[显示错误消息];
    F --> C;
    E --> G{是否继续编辑};
    G -- 是 --> C;
    G -- 否 --> H[结束编辑];

表格

步骤 操作内容
数据检索 触发 GetRacerCommand ,从数据库中查询数据并填充到 ViewModel
进入编辑模式 触发 EditCommand ,设置 IsEditMode true
数据验证 使用 IDataErrorInfo 接口进行验证
保存编辑 触发 SaveCommand ,更新数据库并退出编辑模式
取消编辑 触发 CancelCommand ,恢复原始数据并退出编辑模式
批量编辑 使用 DataGrid 控件,配置列模板和绑定
先展示下效果 https://pan.quark.cn/s/a4b39357ea24 遗传算法 - 简书 遗传算法的理论是根据达尔文进化论而设计出来的算法: 人类是朝着好的方向(最优解)进化,进化过程中,会自动选择优良基因,淘汰劣等基因。 遗传算法(英语:genetic algorithm (GA) )是计算数学中用于解决最佳化的搜索算法,是进化算法的一种。 进化算法最初是借鉴了进化生物学中的一些现象而发展起来的,这些现象包括遗传、突变、自然选择、杂交等。 搜索算法的共同特征为: 首先组成一组候选解 依据某些适应性条件测算这些候选解的适应度 根据适应度保留某些候选解,放弃其他候选解 对保留的候选解进行某些操作,生成新的候选解 遗传算法流程 遗传算法的一般步骤 my_fitness函数 评估每条染色体所对应个体的适应度 升序排列适应度评估值,选出 前 parent_number 个 个体作为 待选 parent 种群(适应度函数的值越小越好) 从 待选 parent 种群 中随机选择 2 个个体作为父方和母方。 抽取父母双方的染色体,进行交叉,产生 2 个子代。 (交叉概率) 对子代(parent + 生成的 child)的染色体进行变异。 (变异概率) 重复3,4,5步骤,直到新种群(parentnumber + childnumber)的产生。 循环以上步骤直至找到满意的解。 名词解释 交叉概率:两个个体进行交配的概率。 例如,交配概率为0.8,则80%的“夫妻”会生育后代。 变异概率:所有的基因中发生变异的占总体的比例。 GA函数 适应度函数 适应度函数由解决的问题决定。 举一个平方和的例子。 简单的平方和问题 求函数的最小值,其中每个变量的取值区间都是 [-1, ...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值