当前位置: 首页 > article >正文

WPF进阶 | WPF 数据绑定进阶:绑定模式、转换器与验证

在这里插入图片描述
在这里插入图片描述

WPF进阶 | WPF 数据绑定进阶:绑定模式、转换器与验证

  • 一、前言
  • 二、WPF 数据绑定基础回顾
    • 2.1 数据绑定的基本概念
    • 2.2 数据绑定的基本语法
  • 三、绑定模式
    • 3.1 单向绑定(One - Way Binding)
    • 3.2 双向绑定(Two - Way Binding)
    • 3.3 单向到源绑定(One - Way - To - Source Binding)
    • 3.4 一次性绑定(One - Time Binding)
  • 四、数据绑定转换器
    • 4.1 转换器的概念与作用
    • 4.2 实现IValueConverter接口
    • 4.3 在 XAML 中使用转换器
    • 4.4 多值转换器(Multi - Value Converter)
  • 五、数据验证
    • 5.1 数据验证的重要性
    • 5.2 基于绑定的验证
    • 5.3 实现IDataErrorInfo接口
    • 5.4 实现INotifyDataErrorInfo接口
    • 5.5 数据验证的综合应用
  • 六、总结
  • 结束语
  • 优质源码分享

WPF进阶 | WPF 数据绑定进阶:绑定模式、转换器与验证 ,在 WPF 应用程序开发中,数据绑定是连接用户界面(视图)与应用程序数据(模型)的桥梁。它使得视图能够自动反映数据的变化,反之亦然,极大地提高了开发效率和代码的可维护性。基础的数据绑定知识是开发者的必备技能,而深入理解绑定模式、掌握转换器的使用以及实现有效的数据验证,则是构建复杂且健壮的 WPF 应用程序的关键。本文将深入探讨这些进阶主题,帮助开发者充分发挥 WPF 数据绑定的强大功能。

一、前言

    在数字浪潮汹涌澎湃的时代,程序开发宛如一座神秘而宏伟的魔法城堡,矗立在科技的浩瀚星空中。代码的字符,似那闪烁的星辰,按照特定的轨迹与节奏,组合、交织、碰撞,即将开启一场奇妙且充满无限可能的创造之旅。当空白的文档界面如同深邃的宇宙等待探索,程序员们则化身无畏的星辰开拓者,指尖在键盘上轻舞,准备用智慧与逻辑编织出足以改变世界运行规则的程序画卷,在 0 和 1 的二进制世界里,镌刻下属于人类创新与突破的不朽印记。

    在当今数字化时代,桌面应用程序的用户界面(UI)设计至关重要,它直接影响着用户体验与产品的竞争力。而 WPF(Windows Presentation Foundation)作为微软推出的一款强大的 UI 框架,其布局系统更是构建精美界面的核心要素。WPF 布局系统为开发者提供了丰富多样的布局方式,能够轻松应对各种复杂的界面设计需求,无论是简洁明了的工具软件,还是功能繁杂的企业级应用,都能借助其打造出令人惊艳的视觉效果与流畅的交互体验。

    WPF从入门到精通专栏,旨在为读者呈现一条从对 WPF(Windows Presentation Foundation)技术懵懂无知到精通掌握的学习路径。首先从基础入手,介绍 WPF 的核心概念,涵盖其独特的架构特点、开发环境搭建流程,详细解读布局系统、常用控件以及事件机制等基础知识,帮助初学者搭建起对 WPF 整体的初步认知框架。随着学习的深入,进阶部分聚焦于数据绑定、样式模板、动画特效等关键知识点,进一步拓展 WPF 开发的能力边界,使开发者能够打造出更为个性化、交互性强的桌面应用界面。高级阶段则涉及自定义控件开发、MVVM 设计模式应用、多线程编程等深层次内容,助力开发者应对复杂的业务需求,构建大型且可维护的应用架构。同时,通过实战项目案例解析,展示如何将所学知识综合运用到实际开发中,从需求分析到功能实现再到优化测试,全方位积累实践经验。此外,还探讨了性能优化、与其他技术集成以及安全机制等拓展性话题,让读者对 WPF 技术在不同维度有更深入理解,最终实现对 WPF 技术的精通掌握,具备独立开发高质量桌面应用的能力。

🛕 点击进入WPF从入门到精通专栏

在这里插入图片描述

二、WPF 数据绑定基础回顾

2.1 数据绑定的基本概念

    数据绑定是一种将数据源(如对象的属性)与目标元素(如 UI 控件的属性)连接起来的机制。通过数据绑定,当数据源发生变化时,目标元素能够自动更新以反映这些变化;反之,当目标元素的值改变时,数据源也可以相应地更新。例如,将一个 TextBoxText 属性绑定到一个视图模型中的字符串属性,当用户在 TextBox 中输入内容时,视图模型中的属性值会自动更新,反之亦然。

2.2 数据绑定的基本语法

    在 XAML 中,数据绑定的基本语法如下:

<TextBox Text="{Binding Path=MyProperty, Source={StaticResource MyDataSource}}"/>

    上述代码中,Path指定了要绑定的数据源属性,Source指定了数据源对象。如果数据源是视图模型,并且视图的DataContext已经设置为该视图模型实例,那么可以省略Source属性,简化为:

<TextBox Text="{Binding Path=MyProperty}"/>

三、绑定模式

3.1 单向绑定(One - Way Binding)

定义与原理

    单向绑定是指数据从数据源流向目标元素。当数据源中的属性值发生变化时,目标元素会自动更新,但目标元素的值变化不会影响数据源。例如,将一个 LabelContent 属性绑定到一个视图模型中的字符串属性,用于显示一些只读信息。

<Label Content="{Binding Path=ReadOnlyProperty}"/>

    在代码背后,当ReadOnlyProperty的值发生变化时,Label 的显示内容会自动更新。

public class MyViewModel
{
    private string _readOnlyProperty;
    public string ReadOnlyProperty
    {
        get { return _readOnlyProperty; }
        set
        {
            if (_readOnlyProperty!= value)
            {
                _readOnlyProperty = value;
                // 通知属性更改(通常使用 INotifyPropertyChanged 接口)
            }
        }
    }
}

适用场景

    单向绑定适用于显示静态数据或不需要用户交互修改的数据,如显示应用程序的版本号、当前登录用户的名称等。

3.2 双向绑定(Two - Way Binding)

定义与原理

    双向绑定允许数据在数据源和目标元素之间双向流动。即当数据源的属性值改变时,目标元素会更新;反之,当目标元素的值改变时,数据源的属性值也会相应更新。例如,在一个文本框中输入用户信息,然后将这些信息保存到视图模型的属性中。

<TextBox Text="{Binding Path=UserInput, Mode=TwoWay}"/>

    在视图模型中,当UserInput属性的值发生变化时,TextBox 的文本会更新;当用户在 TextBox 中输入内容时,UserInput属性的值也会随之改变。

public class MyViewModel
{
    private string _userInput;
    public string UserInput
    {
        get { return _userInput; }
        set
        {
            if (_userInput!= value)
            {
                _userInput = value;
                // 通知属性更改(通常使用 INotifyPropertyChanged 接口)
            }
        }
    }
}

适用场景

    双向绑定常用于用户输入数据的场景,如登录界面的用户名和密码输入框、表单填写等。

3.3 单向到源绑定(One - Way - To - Source Binding)

定义与原理

    单向到源绑定与单向绑定相反,数据从目标元素流向数据源。当目标元素的值发生变化时,数据源的属性值会更新,但数据源的变化不会影响目标元素。例如,在一个滑块(Slider)控件中,用户通过拖动滑块改变值,这个值需要更新到视图模型的某个属性中。

<Slider Value="{Binding Path=SliderValue, Mode=OneWayToSource}"/>

    当用户拖动滑块时,SliderValue属性的值会更新。

public class MyViewModel
{
    private double _sliderValue;
    public double SliderValue
    {
        get { return _sliderValue; }
        set
        {
            if (_sliderValue!= value)
            {
                _sliderValue = value;
                // 通知属性更改(通常使用 INotifyPropertyChanged 接口)
            }
        }
    }
}

适用场景

    单向到源绑定适用于用户操作引发数据更新,但不需要实时反映到 UI 上的场景,如一些后台配置参数的设置。

3.4 一次性绑定(One - Time Binding)

定义与原理

    一次性绑定只在数据绑定建立时将数据源的值应用到目标元素,之后数据源的变化不会再影响目标元素。这种绑定方式适用于数据在初始化后不会再改变的情况。

<TextBlock Text="{Binding Path=InitialValue, Mode=OneTime}"/>

    数据绑定建立时,InitialValue的值会被设置到 TextBlockText 属性中,之后InitialValue的任何变化都不会影响 TextBlock 的显示。

public class MyViewModel
{
    private string _initialValue;
    public string InitialValue
    {
        get { return _initialValue; }
        set
        {
            if (_initialValue!= value)
            {
                _initialValue = value;
                // 通知属性更改(通常使用 INotifyPropertyChanged 接口)
            }
        }
    }
}

适用场景

    一次性绑定适用于显示一些初始配置信息或不经常变化的数据,如应用程序启动时加载的版权声明等。

四、数据绑定转换器

4.1 转换器的概念与作用

    数据绑定转换器是实现IValueConverter接口的类,用于在数据源和目标元素之间进行数据转换。有时候,数据源的数据类型与目标元素所需的数据类型不匹配,或者需要对数据进行格式化处理,这时就需要使用转换器。例如,将一个表示日期的DateTime对象转换为特定格式的字符串,以便在 TextBlock 中显示。

4.2 实现IValueConverter接口

Convert 方法

    Convert方法用于将数据源的值转换为目标元素所需的值。例如,将一个布尔值转换为可见性(Visibility)枚举值,用于控制控件的显示与隐藏。

public class BooleanToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool boolValue)
        {
            return boolValue? Visibility.Visible : Visibility.Collapsed;
        }
        return Visibility.Collapsed;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

ConvertBack 方法

    ConvertBack方法用于将目标元素的值转换回数据源的值,通常在双向绑定中使用。例如,将一个表示可见性的枚举值转换回布尔值。

public class VisibilityToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is Visibility visibility)
        {
            return visibility == Visibility.Visible;
        }
        return false;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is bool boolValue)
        {
            return boolValue? Visibility.Visible : Visibility.Collapsed;
        }
        return Visibility.Collapsed;
    }
}

4.3 在 XAML 中使用转换器

    在 XAML 中,可以通过以下方式使用转换器:

<Window.Resources>
    <local:BooleanToVisibilityConverter x:Key="boolToVisConverter"/>
</Window.Resources>
<Button Visibility="{Binding Path=IsButtonVisible, Converter={StaticResource boolToVisConverter}}"/>

    上述代码中,IsButtonVisible是视图模型中的一个布尔属性,通过BooleanToVisibilityConverter将其转换为 ButtonVisibility 属性所需的值。

4.4 多值转换器(Multi - Value Converter)

概念与应用场景

    多值转换器实现IMultiValueConverter接口,它可以将多个数据源的值转换为一个目标值。例如,在一个用户登录界面中,需要根据用户名和密码是否都不为空来决定登录按钮是否可用。这时可以使用多值转换器,将用户名和密码两个属性的值作为输入,输出一个布尔值来控制按钮的 IsEnabled 属性。

实现IMultiValueConverter接口

public class UsernamePasswordToButtonEnabledConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values!= null && values.Length == 2 &&
            values[0] is string username &&!string.IsNullOrEmpty(username) &&
            values[1] is string password &&!string.IsNullOrEmpty(password))
        {
            return true;
        }
        return false;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

    在 XAML 中使用多值转换器

<Window.Resources>
    <local:UsernamePasswordToButtonEnabledConverter x:Key="usernamePasswordToButtonEnabledConverter"/>
</Window.Resources>
<StackPanel>
    <TextBox x:Name="usernameTextBox"/>
    <TextBox x:Name="passwordTextBox" PasswordChar="*"/>
    <Button Content="登录" IsEnabled="{Binding Path={x:Static local:ViewModelLocator.ViewModel.Username, ElementName=usernameTextBox, Mode=OneWay}, 
                                       Path2={x:Static local:ViewModelLocator.ViewModel.Password, ElementName=passwordTextBox, Mode=OneWay}, 
                                       Converter={StaticResource usernamePasswordToButtonEnabledConverter}}"/>
</StackPanel>

五、数据验证

5.1 数据验证的重要性

    在 WPF 应用程序中,数据验证确保用户输入的数据符合特定的规则和格式。例如,在一个注册表单中,用户输入的邮箱地址需要符合邮箱格式,密码需要满足一定的长度和复杂度要求。有效的数据验证可以提高数据的准确性和完整性,避免因错误数据导致的程序异常。

5.2 基于绑定的验证

使用 ValidationRules

    可以通过创建自定义的ValidationRule类来实现数据验证。例如,创建一个验证整数是否在指定范围内的ValidationRule。

public class IntegerRangeValidationRule : ValidationRule
{
    public int Min { get; set; }
    public int Max { get; set; }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        if (int.TryParse(value as string, out int number))
        {
            if (number >= Min && number <= Max)
            {
                return ValidationResult.ValidResult;
            }
            else
            {
                return new ValidationResult(false, $"值必须在 {Min} 到 {Max} 之间");
            }
        }
        else
        {
            return new ValidationResult(false, "请输入有效的整数");
        }
    }
}

    在 XAML 中使用该验证规则:

<TextBox>
    <TextBox.Text>
        <Binding Path="MyIntegerProperty" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:IntegerRangeValidationRule Min="1" Max="100"/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

数据验证的反馈

    当数据验证失败时,WPF 提供了一些内置的机制来反馈给用户。例如,TextBox 的背景会变为红色,并且可以通过ToolTip显示错误信息。

<TextBox>
    <TextBox.Text>
        <Binding Path="MyIntegerProperty" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:IntegerRangeValidationRule Min="1" Max="100"/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
    <TextBox.Style>
        <Style TargetType="TextBox">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="true">
                    <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </TextBox.Style>
</TextBox>

5.3 实现IDataErrorInfo接口

接口概述

    IDataErrorInfo接口提供了另一种数据验证的方式。在视图模型类中实现该接口,可以对属性进行验证。例如,验证一个字符串是否为有效的邮箱地址。

public class UserViewModel : IDataErrorInfo
{
    private string _email;
    public string Email
    {
        get { return _email; }
        set
        {
            if (_email!= value)
            {
                _email = value;
                // 通知属性更改(通常使用 INotifyPropertyChanged 接口)
            }
        }
    }

    public string Error => string.Empty;

    public string this[string columnName]
    {
        get
        {
            if (columnName == nameof(Email))
            {
                if (!Regex.IsMatch(Email, @"^[a-zA - Z0 - 9_.+-]+@[a-zA - Z0 - 9 -]+\.[a-zA - Z0 - 9-.]+$"))
                {
                    return "请输入有效的邮箱地址";
                }
            }
            return string.Empty;
        }
    }
}

在 XAML 中使用IDataErrorInfo验证

    在 XAML 中,不需要额外配置验证规则,WPF 会自动检测视图模型是否实现了IDataErrorInfo接口并进行验证。

<TextBox Text="{Binding Path=Email}"/>

5.4 实现INotifyDataErrorInfo接口

接口优势

    INotifyDataErrorInfo接口是IDataErrorInfo接口的增强版本,它支持动态验证和多个错误信息。例如,在一个复杂的表单中,一个字段可能有多个验证规则,并且验证结果可能会根据其他字段的值动态变化。
接口实现

public class AdvancedUserViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
    private string _password;
    public string Password
    {
        get { return _password; }
        set
        {
            if (_password!= value)
            {
                _password = value;
                OnPropertyChanged(nameof(Password));
                ValidatePassword();
            }
        }
    }

    private string _confirmPassword;
    public string ConfirmPassword
    {
        get { return _confirmPassword; }
        set
        {
            if (_confirmPassword!= value)
            {
                _confirmPassword = value;
                OnPropertyChanged(nameof(ConfirmPassword));
                ValidatePassword();
            }
        }
    }

    private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

    public bool HasErrors => _errors.Any();

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName) ||!_errors.ContainsKey(propertyName))
        {
            return null;
        }
        return _errors[propertyName];
    }

    private void ValidatePassword()
    {
        List<string> passwordErrors = new List<string>();
        if (string.IsNullOrEmpty(Password))
        {
            passwordErrors.Add("密码不能为空");
        }
        if (Password.Length < 6)
        {
            passwordErrors.Add("密码长度至少为6位");
        }
        if (!string.IsNullOrEmpty(ConfirmPassword) && Password!= ConfirmPassword)
        {
            passwordErrors.Add("两次输入的密码不一致");
        }

        if (_errors.ContainsKey(nameof(Password)))
        {
            _errors.Remove(nameof(Password));
        }
        if (passwordErrors.Any())
        {
            _errors.Add(nameof(Password), passwordErrors);
        }
if (_errors.ContainsKey(nameof(ConfirmPassword)))
{
_errors.Remove(nameof(ConfirmPassword));
}
if (passwordErrors.Any())
{
_errors.Add(nameof(ConfirmPassword), passwordErrors);
}
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Password)));
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(ConfirmPassword)));
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

在 XAML 中使用INotifyDataErrorInfo验证

    在XAML中,WPF会自动识别视图模型实现的INotifyDataErrorInfo接口,并提供相应的验证反馈。当密码或确认密码不符合规则时,相关的TextBox会显示错误样式,例如红色边框,同时可以通过绑定Validation.Errors属性来显示具体的错误信息。

<StackPanel>
    <TextBox Text="{Binding Path=Password}">
        <TextBox.Style>
            <Style TargetType="TextBox">
                <Style.Triggers>
                    <Trigger Property="Validation.HasError" Value="true">
                        <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>
    <TextBox Text="{Binding Path=ConfirmPassword}">
        <TextBox.Style>
            <Style TargetType="TextBox">
                <Style.Triggers>
                    <Trigger Property="Validation.HasError" Value="true">
                        <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>
</StackPanel>

5.5 数据验证的综合应用

结合多种验证方式

    在实际项目中,往往需要综合运用多种数据验证方式。例如,在一个用户注册表单中,对于年龄字段,可以使用ValidationRules来验证其是否为正整数,确保输入的格式正确。同时,在视图模型中实现INotifyDataErrorInfo接口,用于验证用户名是否唯一。这样,通过不同验证方式的结合,既能保证数据格式的正确性,又能满足业务逻辑上的唯一性要求。

// 年龄验证规则

public class AgeValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        if (int.TryParse(value as string, out int age))
        {
            if (age > 0)
            {
                return ValidationResult.ValidResult;
            }
            else
            {
                return new ValidationResult(false, "年龄必须为正整数");
            }
        }
        else
        {
            return new ValidationResult(false, "请输入有效的年龄");
        }
    }
}

public class RegistrationViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
    private string _username;
    public string Username
    {
        get { return _username; }
        set
        {
            if (_username!= value)
            {
                _username = value;
                OnPropertyChanged(nameof(Username));
                ValidateUsername();
            }
        }
    }

    private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

    public bool HasErrors => _errors.Any();

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName) ||!_errors.ContainsKey(propertyName))
        {
            return null;
        }
        return _errors[propertyName];
    }

    private void ValidateUsername()
    {
        List<string> usernameErrors = new List<string>();
        // 假设这里有检查用户名唯一性的逻辑,例如查询数据库
        if (IsUsernameExists(_username))
        {
            usernameErrors.Add("用户名已存在");
        }

        if (_errors.ContainsKey(nameof(Username)))
        {
            _errors.Remove(nameof(Username));
        }
        if (usernameErrors.Any())
        {
            _errors.Add(nameof(Username), usernameErrors);
        }

        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Username)));
    }

    private bool IsUsernameExists(string username)
    {
        // 实际实现中应查询数据库等数据源
        return false;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

    在 XAML 中:

<StackPanel>
    <TextBox>
        <TextBox.Text>
            <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <local:AgeValidationRule/>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
    <TextBox Text="{Binding Path=Username}">
        <TextBox.Style>
            <Style TargetType="TextBox">
                <Style.Triggers>
                    <Trigger Property="Validation.HasError" Value="true">
                        <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>
</StackPanel>

跨字段验证

    跨字段验证在处理复杂业务逻辑时非常重要。比如在一个库存管理系统中,有 “入库数量” 和 “库存总量” 两个字段,当进行入库操作时,需要确保 “入库数量” 加上当前 “库存总量” 不超过仓库的最大容量。这就需要在视图模型中实现跨字段验证逻辑。

public class InventoryViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
    private int _currentStock;
    public int CurrentStock
    {
        get { return _currentStock; }
        set
        {
            if (_currentStock!= value)
            {
                _currentStock = value;
                OnPropertyChanged(nameof(CurrentStock));
                ValidateStock();
            }
        }
    }

    private int _incomingQuantity;
    public int IncomingQuantity
    {
        get { return _incomingQuantity; }
        set
        {
            if (_incomingQuantity!= value)
            {
                _incomingQuantity = value;
                OnPropertyChanged(nameof(IncomingQuantity));
                ValidateStock();
            }
        }
    }

    private const int MaxCapacity = 1000;

    private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

    public bool HasErrors => _errors.Any();

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName) ||!_errors.ContainsKey(propertyName))
        {
            return null;
        }
        return _errors[propertyName];
    }

    private void ValidateStock()
    {
        List<string> stockErrors = new List<string>();
        if (_currentStock + _incomingQuantity > MaxCapacity)
        {
            stockErrors.Add("入库后库存将超过最大容量");
        }

        if (_errors.ContainsKey(nameof(CurrentStock)))
        {
            _errors.Remove(nameof(CurrentStock));
        }
        if (_errors.ContainsKey(nameof(IncomingQuantity)))
        {
            _errors.Remove(nameof(IncomingQuantity));
        }
        if (stockErrors.Any())
        {
            _errors.Add(nameof(CurrentStock), stockErrors);
            _errors.Add(nameof(IncomingQuantity), stockErrors);
        }

        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(CurrentStock)));
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(IncomingQuantity)));
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

    在 XAML 中:

<StackPanel>
    <TextBox Text="{Binding Path=CurrentStock}">
        <TextBox.Style>
            <Style TargetType="TextBox">
                <Style.Triggers>
                    <Trigger Property="Validation.HasError" Value="true">
                        <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>
    <TextBox Text="{Binding Path=IncomingQuantity}">
        <TextBox.Style>
            <Style TargetType="TextBox">
                <Style.Triggers>
                    <Trigger Property="Validation.HasError" Value="true">
                        <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
        </TextBox.Style>
    </TextBox>
</StackPanel>

六、总结

    WPF 的数据绑定进阶特性,包括绑定模式、转换器与验证,为开发者提供了一套强大且灵活的工具集,用于构建高效、可靠且用户体验良好的应用程序。

    绑定模式的多样化使得开发者能够根据不同的业务场景,精确控制数据在视图与数据源之间的流动方向,确保数据的传递符合应用程序的需求。数据绑定转换器则解决了数据类型不匹配和数据展示格式化的问题,使得数据能够以恰当的形式在 UI 上呈现和交互。而数据验证机制从多个层面保障了数据的质量,无论是简单的格式验证,还是复杂的业务规则验证,都能够有效地防止错误数据进入系统,提升了应用程序的稳定性和可靠性。

    在实际开发中,开发者需要深入理解这些特性,并根据项目的具体需求进行合理组合与应用。例如,在开发企业级业务应用时,可能会在数据输入表单中广泛应用双向绑定结合数据验证,确保用户输入的数据准确无误且能够实时更新到业务模型中;在数据展示界面,则会使用单向绑定和转换器来格式化和呈现数据。随着 WPF 技术的不断发展,数据绑定相关的功能也可能会不断优化和扩展,开发者应持续关注并深入学习,以充分发挥 WPF 的优势,打造出更优秀的桌面应用程序。通过不断地实践和探索,开发者能够更好地利用 WPF 数据绑定的进阶特性,为用户带来更加流畅、高效的应用体验。

结束语

        展望未来,WPF 布局系统依然有着广阔的发展前景。随着硬件技术的不断革新,如高分辨率屏幕、折叠屏设备的日益普及,WPF 布局系统有望进一步强化其自适应能力,为用户带来更加流畅、一致的体验。在应对高分辨率屏幕时,能够更加智能地缩放和布局元素,确保文字清晰可读、图像不失真;对于折叠屏设备,可动态调整布局结构,充分利用多屏空间,实现无缝切换。

        性能优化方面,微软及广大开发者社区将持续努力,进一步降低复杂布局的计算开销,提高布局更新的效率,使得 WPF 应用在处理大规模数据、动态界面时依然能够保持高效响应。通过改进算法、优化内存管理等手段,让 WPF 布局系统在性能上更上一层楼。

        亲爱的朋友,无论前路如何漫长与崎岖,都请怀揣梦想的火种,因为在生活的广袤星空中,总有一颗属于你的璀璨星辰在熠熠生辉,静候你抵达。

         愿你在这纷繁世间,能时常收获微小而确定的幸福,如春日微风轻拂面庞,所有的疲惫与烦恼都能被温柔以待,内心永远充盈着安宁与慰藉。

        至此,文章已至尾声,而您的故事仍在续写,不知您对文中所叙有何独特见解?期待您在心中与我对话,开启思想的新交流。


--------------- 业精于勤,荒于嬉 ---------------
 

请添加图片描述

--------------- 行成于思,毁于随 ---------------

优质源码分享

  • 【百篇源码模板】html5各行各业官网模板源码下载

  • 【模板源码】html实现酷炫美观的可视化大屏(十种风格示例,附源码)

  • 【VUE系列】VUE3实现个人网站模板源码

  • 【HTML源码】HTML5小游戏源码

  • 【C#实战案例】C# Winform贪吃蛇小游戏源码


在这里插入图片描述


     💞 关注博主 带你实现畅游前后端

     🏰 大屏可视化 带你体验酷炫大屏

     💯 神秘个人简介 带你体验不一样得介绍

     🎀 酷炫邀请函 带你体验高大上得邀请


     ① 🉑提供云服务部署(有自己的阿里云);
     ② 🉑提供前端、后端、应用程序、H5、小程序、公众号等相关业务;
     如🈶合作请联系我,期待您的联系。
    :本文撰写于CSDN平台,作者:xcLeigh所有权归作者所有) ,https://blog.csdn.net/weixin_43151418,如果相关下载没有跳转,请查看这个地址,相关链接没有跳转,皆是抄袭本文,转载请备注本文原地址。


     亲,码字不易,动动小手,欢迎 点赞 ➕ 收藏,如 🈶 问题请留言(评论),博主看见后一定及时给您答复,💌💌💌


原文地址:https://blog.csdn.net/weixin_43151418/article/details/145304085(防止抄袭,原文地址不可删除)


http://www.kler.cn/a/522037.html

相关文章:

  • DeepSeek大模型技术解析:从架构到应用的全面探索
  • [C语言日寄] <stdio.h> 头文件功能介绍
  • 团体程序设计天梯赛-练习集——L1-025 正整数A+B
  • QT+mysql+python 效果:
  • Deepseek的RL算法GRPO解读
  • 设计模式面试题
  • 病理AI领域基础模型及多实例学习方法的性能评估|顶刊精析·25-01-27
  • 算法刷题Day29:BM67 不同路径的数目(一)
  • 前端性能优化指标 - DCL - 计算 DCL 时间(使用 JavaScript、使用 Chrome DevTools)
  • LeetCode讲解篇之88. 合并两个有序数组
  • C语言算法实现:探索经典排序与查找算法的奥秘
  • 在亚马逊云科技上用Stable Diffusion 3.5 Large生成赛博朋克风图片(下)
  • Day27-【13003】短文,线性表两种基本实现方式空间效率、时间效率比较?兼顾优点的静态链表是什么?如何融入空闲单元链表来解决问题?
  • 二分查找题目:寻找两个正序数组的中位数
  • Maya快捷键
  • 遗传算法与深度学习实战(32)——生成对抗网络详解与实现
  • Python dataclasses模块介绍
  • UE学习日志#13 (还是记录看资料,没内容)GAS--理解GC和UE网络同步原理
  • 格式化指南:格式化选NTFS、FAT32还是 exFAT?
  • 28. 【.NET 8 实战--孢子记账--从单体到微服务】--简易报表--报表定时器与报表数据修正
  • DeepSeek学术写作测评第一弹:论文润色,中译英效果如何?
  • 【算法篇·更新中】C++秒入门(附练习用题目)
  • 【C语言基础】编译并运行第一个C程序
  • 消息队列MQ面试题解,基础面试题
  • 美国本科申请文书PS写作中的注意事项
  • 【Linux基础指令】第二期