是否可以在 WPF 中使用 ReactiveUI 绑定仅通过 INotifyDataErrorInfo 验证用户输入?

2024-01-16

我们在 .Net Core WPF 应用程序中使用 ReactiveUI.WPF 11.0.1。我们正在考虑将所有基于 XAML 的绑定替换为基于 ReactiveUI 的绑定。 域类型有一个 ViewModel 实现了 INotifyPropertyChanged 和 INotifyDataErrorInfo:

public class ItemViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
    private string Error => string.IsNullOrEmpty(Name) ? "Empty name" : string.Empty;
    private string _name;

    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            OnPropertyChanged();
        }
    }

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(Error))
            return Enumerable.Empty<string>();
        return new[] {Error};
    }

    public bool HasErrors => !string.IsNullOrEmpty(Error);
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public event PropertyChangedEventHandler PropertyChanged;

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

窗口有一个 ViewModel:

public class MainWindowViewModel: ReactiveObject
{
    public ItemViewModel ItemA { get; } = new ItemViewModel();
    public ItemViewModel ItemB { get; } = new ItemViewModel();
}

还有一个主窗口:

<reactiveUi:ReactiveWindow
    x:TypeArguments="local:MainWindowViewModel"
    x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp1"
    xmlns:reactiveUi="http://reactiveui.net"
    mc:Ignorable="d">
    <StackPanel>
        <TextBox Text="{Binding ItemA.Name}" />
        <TextBox x:Name="ItemBTextBox" />
    </StackPanel>
</reactiveUi:ReactiveWindow>
public partial class MainWindow : ReactiveWindow<MainWindowViewModel>
{
    public MainWindow()
    {
        InitializeComponent();
        ViewModel = new MainWindowViewModel();
        DataContext = ViewModel;
        this.WhenActivated(disposables =>
        {
            this.Bind(ViewModel, x => x.ItemB.Name, x => x.ItemBTextBox.Text);
        });
    }
}

当第一个 TextBox 的 Text 属性为空时,它显示默认的 WPF ErrorTemplate(红色边框)。但是,第二个(使用基于 ReactiveUI 的绑定)则不然。有没有办法在不更改 ItemViewModel 类的情况下自动使用 ReactiveUI 与 WPF ErrorTemplates 的绑定?


所以,过了一段时间我再次尝试解决这个问题。 ReactiveUI 的绑定不支持 INotifyDataErrorInfo 验证。因此,我必须在绑定值后手动绑定验证错误。这可以简单地这样做:

public MainWindow() {
    // some initialization code should be here.

    this.WhenActivated(cleanUp => {
        // binding ItemB's Name property to ItemBTextBox's Text property.
        this.Bind(ViewModel, x => x.ItemB.Name, x => x.ItemBTextBox.Text)
            .DisposeWith(cleanUp);
        // binding ItemB's Name property's validation errrors to ItemBTextBox.
        ViewModel.ItemB.WhenAnyPropertyChanged()
            .StartWith(ViewModel.ItemB)
            .Subscribe(itemB =>
            {
                if (!itemB.HasErrors)
                {
                    ClearValidationErrors(ItemBTextBox);
                    return;
                }

                var errorForName = newEmployee
                    .GetErrors(nameof(newEmployee.Name))
                    .Cast<string>()
                    .FirstOrDefault();
                if (string.IsNullOrEmpty(nameError))
                {
                    ClearValidationErrors(ItemBTextBox);
                    return;
                }
                SetValidationError(ItemBTextBox, errorForName);
            })
            .DisposeWith(cleanUp);
    });
}

但是,仍然存在以下问题:如何使 WPF UI 元素(ItemBTextBox)显示我们从代码隐藏设置的错误? ClearValidationErrors() 和 SetValidationError() 方法应该如何实现?我能找到的为 UI 元素设置验证错误的唯一方法(因此验证模板会显示它)是使用 WPF 绑定的以下代码:

Validation.ClearInvalid(ItemBTextBox.GetBindingExpression(TextBox.TextProperty));
Validation.MarkInvalid(
    ItemBTextBox.GetBindingExpression(TextBox.TextProperty),
    new ValidationError(new NotifyDataErrorValidationRule(), itemB, errorForName, null));

问题在于整个 WPF 验证机制基于 WPF 绑定。 ReactiveUI 的绑定不依赖于这些。解决方法是创建一个虚拟 WPF 绑定,并使用上面的代码从代码隐藏中清除和设置验证错误。

ItemBTextBox.SetBinding(TextBox.TextProperty, new Binding("Non_existent_property.") 
    { Mode = BindingMode.OneTime }); // invoke this in MainWindow constructor.

这种方法有效,但本质上相当丑陋(我们必须使用虚拟 WPF 绑定才能使其工作,这些虚拟绑定显然会引发绑定错误等)。如果有人知道如何使用 WPF 的 ValidationTemplates 来显示没有 WPF 绑定的 UI 元素的验证错误(可以从代码隐藏中设置),请告诉我。

UPD:所以我想出了其他方法来操作 WPF 的 Validation.Errors 属性。它依赖于反射以及 Validation 类具有内部静态方法 AddValidationError() 和 RemoveValidationError() 的事实。所以我可以声明新的静态类:

public static class ValidationHelper
{
    private static readonly MethodInfo AddValidationErrorMethod =
        typeof(Validation).GetMethod("AddValidationError", BindingFlags.NonPublic | BindingFlags.Static);

    private static readonly MethodInfo RemoveValidationErrorMethod =
        typeof(Validation).GetMethod("RemoveValidationError", BindingFlags.NonPublic | BindingFlags.Static);

    public static void AddValidationError(
        ValidationError validationError,
        DependencyObject targetElement)
    {
        AddValidationErrorMethod
            .Invoke(null, new object[] {validationError, targetElement, true});
    }

    public static void ClearValidationErrors(DependencyObject targetElement)
    {
        foreach (var error in Validation.GetErrors(targetElement).ToArray())
            RemoveValidationErrorMethod
                .Invoke(null, new object[] { error, targetElement, true });
    }
}

并像这样使用它:

ValidationHelper.ClearValidationErrors(ItemBTextBox);
ValidationHelper.AddValidationError(new ValidationError(new NotifyDataErrorValidationRule(), itemB, errorForName, null),
                            ItemBTextBox);

它远非完美,但它确实有效。并且您不需要使用任何虚拟 WPF 绑定。

UPD2:这可能与最初的问题不太相关,但我还将添加我的天真的扩展方法,用于将 INotifyDataErrorInfo 错误绑定到 WPF 控件的 ValidationTemplate 到答案,以防遇到相同问题的任何人需要参考。

// just a helper method to extract property name from the expression.
private static string GetPropertyName<T, TProperty>(this Expression<Func<T, TProperty>> property)
    where T : class
{
    if (!(property.Body is MemberExpression member))
        throw new ArgumentException("A method is provided instead of a property.");
    if (!(member.Member is PropertyInfo propertyInfo))
        throw new ArgumentException("A field is provided instead of a property");
    return propertyInfo.Name;
}

public static IDisposable BindValidationError
    <TView, TViewModel, TValidatableObject, TProperty>(
        this TView view,
        TViewModel viewModel,
        Expression<Func<TViewModel, TValidatableObject>> objectToValidateName,
        Expression<Func<TValidatableObject, TProperty>> propertyToValidate,
        Func<TView, DependencyObject> uiElementDelegate)
    where TViewModel : class
    where TView : IViewFor<TViewModel>
    where TValidatableObject : class, INotifyDataErrorInfo
{
    string lastError = null;
    var propertyToValidateName = propertyToValidate.GetPropertyName();
    return viewModel.WhenAnyValue(objectToValidateName)
        .StartWith(objectToValidateName.Compile().Invoke(viewModel))
        .Do(objectToValidate =>
        {
            var uiElement = uiElementDelegate.Invoke(view);
            if (objectToValidate == null)
            {
                ValidationHelper.ClearValidationErrors(uiElement);
                return;
            }

            ValidateProperty(
                objectToValidate,
                propertyToValidateName,
                uiElement,
                ref lastError);
        })
        .Select(objectToValidate => objectToValidate != null
            ? Observable.FromEventPattern<DataErrorsChangedEventArgs>(objectToValidate,
                nameof(objectToValidate.ErrorsChanged))
            : Observable.Empty<EventPattern<DataErrorsChangedEventArgs>>())
        .Switch()
        .Subscribe(eventArgs =>
        {
            if (eventArgs.EventArgs.PropertyName != propertyToValidateName)
                return;
            var objectToValidate = (INotifyDataErrorInfo) eventArgs.Sender;
            var uiElement = uiElementDelegate.Invoke(view);
            ValidateProperty(
                objectToValidate,
                propertyToValidateName,
                uiElement,
                ref lastError);
        });
}

在视图的 WhenActivated 中使用它:

this.Bind(
    ViewModel,
    viewModel => viewModel.ItemB.Name,
    view => view.ItemBTextBox.Text)
    .DisposeWith(cleanUp);
this.BindValidationError(
    ViewModel,
    viewModel => viewModel.ItemB,
    itemB => itemB.Name,
    view => view.NewEmployeeNameTextBox)
    .DisposeWith(cleanUp);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

是否可以在 WPF 中使用 ReactiveUI 绑定仅通过 INotifyDataErrorInfo 验证用户输入? 的相关文章

  • 允许 WPF Web 浏览器中的弹出窗口

    有没有办法允许在嵌入式 WPF WebBrowser 控件中弹出窗口 我没有找到别人的解决方案 也没有找到允许弹出窗口的COM接口 我不想更改用户注册表设置或使用类似的侵入方法 因为应用程序旨在通过 ClickOnce 分发 您可以通过处理
  • 当 ToString() 具有协作对象时,为什么 WPF 数据绑定不显示文本?

    在一个简单的形式中 我绑定到许多不同的对象 有些放在列表框中 有些放在列表框中 有些放在列表框中 一些在文本块中 其中一些对象具有协作对象 在这些对象上ToString 方法在执行其工作时调用 通常是某种格式化程序 当我单步执行代码时 我发
  • 如何使用代码隐藏创建 StackPanel -> 边框 -> 背景

    我正在尝试设置 a 的属性TreeViewItem gt StackPanel在 C 中就像这个问题 https stackoverflow com questions 8203316 adding content to a treevie
  • 在辅助监视器中创建 WPF 窗口时遇到问题

    我正在努力在应用程序中实现一些类似 Chrome 的选项卡功能 但在正确生成新实例时遇到一些问题 我已经对各种解决方案进行了大量搜索和迭代 但尚未能够在第二台显示器上生成新窗口 这是使用线程 打开文件 将当前选项卡拖至其他显示器 新的应用程
  • DataGrid、TextBox - 绑定和即时更新

    我的应用程序 包含图中的窗口 DataGrid 的 ItemsSource 设置为 editList 声明为 IList editList 数据网格设置为只读 供应商名称文本框的绑定设置为 Text Binding ElementName
  • PHP 中的 Javascript“unes​​cape”

    我的图像主机有一个 Google Chrome 扩展程序 它会向我的网站发送一个 URL 该网址得到encoded通过 JavaScript 的escape method 编码的 URLescape看起来像这样 http 253A 4 bp
  • 绑定到多个索引器

    我正在尝试将索引属性与两个索引器绑定 该属性看起来像这样 public Item this int x int y get return items x y set items x y value 根据http msdn microsoft
  • 慢速 WPF 文本框

    我正在开发一个简单的串行数据查看器 它将用于观察传输到计算机串行端口之一的数据 我使用 C 和 WPF 编写了一个测试应用程序 它只是将最近读取的行放入文本块中 但是 它会跳过所有其他行 我的理论是 在 WPF 渲染窗口之前 新数据会被放入
  • Django ModelForm 验证失败,没有错误

    好吧 我已经盯着这几个小时试图弄清楚发生了什么 但无济于事 我正在尝试使用 instance 关键字创建一个 ModelForm 将其传递给一个现有的模型实例 然后保存它 这是 ModelForm 在我试图找出此问题的原因时 从原始版本中删
  • 使用画布矩形裁剪图像

    裁剪图像无法正常工作 我哪里错了 我的Xaml
  • EditText.setError 不显示错误文本,仅显示图标

    我的应用程序中有一个简单的验证 这里我使用了四个EdtiText 我显示错误时EditTex不会失去焦点 但问题在于失去焦点EditText只显示图标 没有错误消息 我尝试过使用requestFocus 方法 现在可以看到错误 但问题是 现
  • 带有添加新选项卡按钮 (+) 的 TabControl

    在 WPF 中选项卡控件的选项卡条中的所有选项卡项的末尾添加 按钮选项卡的正确方法是什么 它应该可以正确地处理多个选项卡标题行 它应该位于所有选项卡项目的末尾 Tab cycling should work correctly Alt Ta
  • 如何在MVVM中实现appSettings

    我正在尝试摆脱我使用的警告appSettings在 WPF 项目中 应用程序配置
  • 使用 keyup 事件仅触发表单验证中文本框的部分规则,并取消其余规则的事件

    我的应用程序中有一个表单 我正在使用 jQuery 表单验证插件对其进行验证 其中一项规则是远程类型 用于验证用户名是否不存在 我已经能够覆盖 onkeyup 事件 因此规则不会为我输入的每个字母发送到服务器 我完成的方式如下 var va
  • 使用(linq to sql)更新错误

    我有两个表 通过外键 CarrierID 绑定 Carrier CarrierID CarrierName CarrierID 1 CarrierName DHL CarrierID 2 CarrierName Fedex Vendor V
  • 禁止/阻止选择 wpf 中禁用的组合框项目

    我正在编写一个应用程序 其中我想禁用其中的一些项目ComboBox并且还想禁止 阻止选择禁用的项目 请注意ComboBox在主窗口中有另一个 ComboBox 作为 ComboBox Item init 在运行时由DataTemplateS
  • 该捆绑包无效。 Apple 目前不接受使用此版本操作系统构建的应用程序。 (小牛队)

    我首先说我已经安装了 Mavericks 我很好奇看到新功能 最好还是等待 因为 显然 没有什么耸人听闻的变化 无论如何 我正在尝试将我的应用程序提交到应用程序商店 但经过与代码签名的真正艰苦斗争后 我最终得到了以下消息 此捆绑包无效 Ap
  • 如何使用 MVC 属性验证列表是否具有大于零的非空元素?

    我正在尝试实现一个可以获取不同数量的文件的文件上传器 文件输入元素的名称都相同 因此会生成 MVC3 乐意绑定的文件列表 所以在我的控制器中我有 public virtual ViewResult UploadReceive IEnumer
  • Android - 从服务器获取响应时验证 JSON 以避免 JSONException

    在我的一些与服务器通信并使用 http 获取响应的应用程序中 我使用 json 来格式化数据服务器端 当它到达设备时 我使用类似于我在 stackoverflow 上找到的代码 private class LoadData extends
  • 为基于架构的 XML 文件创建 WPF 编辑器

    这是场景 我们的服务器产品之一使用大型 XML 配置文件 该文件的布局相当好 并且针对 XSD 文件进行了验证 现在是时候构建一个配置 GUI 来维护这个文件了 我想深入研究 WPF 来完成它 我可以为每个配置部分布置一个单独的表单 每次向

随机推荐

  • 如何从列表理解中获取多个列表作为单独的结果?

    假设我有这样的代码 def f x return 2 x x x x range 3 xlist ylist f value for value in x 我怎样才能巧妙地得到这样的结果 xlist 0 2 4 ylist 0 1 4 注意
  • 如何使用 ant 将 jar 文件包含到 Ear 文件的 lib 文件夹中?

    我有以下文件夹结构 project ear lib folder ProjectEJBClient jar META INF folder projectEJB jar My build xml包含以下行来创建 EAR 包
  • 如何使用 Knockout 3.0 取消对可观察数组的更改?

    我的数组发生了变化 我正在使用 Sanderson 最新的数组订阅方法来捕获添加 删除更改 在此订阅中 我打算捆绑并通过网络发送我的请求 如果请求因任何原因失败 我希望能够取消对集合的任何可能的更改 我已经验证该订阅在更改传播之前就已生效
  • Silverlight DependencyProperty.SetCurrentValue 等效项

    我正在寻找相当于 NET 4 的 SL4设置当前值 http msdn microsoft com en us library system windows dependencyobject setcurrentvalue aspxAPI
  • WiX - 在两个不同位置安装相同的文件

    在我的安装程序中 我有两个可选功能 它们是同一软件版本 5 和 6 的插件 他们将相同的文件 相同的名称 相同的二进制内容 安装到应用程序的 plugins 文件夹中 但我有以下错误 C Users FooBar Documents pro
  • 如何将环境变量从 docker-compose 传递到 NodeJS 项目中?

    我有一个 NodeJS 应用程序 我想要调整其大小 该应用程序由两部分组成 服务器部分 运行一个从数据库获取数据的 API 它在端口 3000 上运行 客户端部分 它从服务器部分调用 API 端点 它运行在端口 8080 上 这样 我的客户
  • 有没有办法为嵌套函数生成 pydoc? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找一种为嵌套函数生成文档 本例中为 pydoc 的方法 这可以用 pydoc 实现吗 用其他工
  • 使用 RegEx 可靠地解析 HTML 元素 [重复]

    这个问题在这里已经有答案了 可能的重复 使用 PHP 解析 HTML 的最佳方法 https stackoverflow com questions 3577641 best methods to parse html with php 我
  • va_list的重用

    我需要对一个进行两次 或更多 次传递va list 我有一个一定大小的缓冲区 我想用 sprintf 向其中写入一个格式化字符串 如果格式化的字符串不适合分配的空间 我想将分配的空间加倍并重复直到适合为止 作为旁注 我希望能够首先计算格式化
  • 显示享受sql的日子[重复]

    这个问题在这里已经有答案了 我的查询是我有两个表 一个称为sec users包含以下字段 pk user name days available 另一个电话solicitud包含以下字段 pk solicitud fk empleado n
  • .arff 文件与 scikit-learn 一起使用吗?

    我想用一个属性关系文件格式 http www cs waikato ac nz ml weka arff html用 scikit learn 来做一些 NLP 任务 这可能吗 如何使用 arff文件与scikit learn 我真的推荐利
  • Zeppelin 上的皮肤可以自定义吗?

    Zeppelin 上的皮肤可以自定义吗 换句话说 把齐柏林飞艇的标志换成别的东西 是的 很有可能 如您所知 Apache Zeppelin 正在孵化 是一个开源项目 因此只需 克隆它来自github com apache incubator
  • 如何检查 Android 4.0+ 中自动旋转屏幕设置是否打开/关闭

    我认为每个 Android 设备都有能力打开 关闭自动旋转功能 通常你可以在以下位置找到它settings gt display gt auto rotate on off 如何从我的应用程序中读取此设置状态 我怎样才能访问这个设置值 如果
  • 根据 Winforms/C# 中的文本量和字体大小确定标签大小

    我想知道是否有更好的方法来解决这个问题 我想调整标签的大小 垂直 以容纳一定数量的文本 我的标签具有固定宽度 在必须换行之前大约 60 个字符宽 大约 495 像素 字体也是固定大小 据我所知是 12 点 但文本不是 我想要做的是当有 换行
  • 批处理脚本 - 以编程方式在 Windows XP 中创建用户

    有没有办法通过批处理脚本在 Windows XP 中创建用户 甚至为其分配管理员 有限用户值 假设用户名是 rased 密码是 passS net user rased pAsS add net localgroup administrat
  • fork()如何知道自己是在子进程还是在父进程? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 当执行 fork 系统调用时 处理器转入内核模式 因此 在 fork 调用结束时 会生成一个新进程 其中包含调用进程的几乎所有结构的副
  • 了解 aiohttp.TCPConnector 池和连接限制

    我正在尝试limit and limit per host参数为aiohttp connector TCPConnector 在下面的脚本中 我通过connector aiohttp connector TCPConnector limit
  • 从 EPS 中提取图像数据

    我有一个封装的 PostScript http en wikipedia org wiki Encapsulated PostScript文件似乎只包装了一个图像文件 有没有工具可以从中提取图像数据 convert 将使用ghostscri
  • 发送标头后重定向用户

    据我所知 只要数据已发送到浏览器 那么标头就无法修改 有什么方法 使用 PHP 可以执行重定向以将用户带到另一个页面 显然不使用标头 如果是这样 您能给我指出一些文档吗 决定编写我自己的 php 函数来实现 javascript 重定向 请
  • 是否可以在 WPF 中使用 ReactiveUI 绑定仅通过 INotifyDataErrorInfo 验证用户输入?

    我们在 Net Core WPF 应用程序中使用 ReactiveUI WPF 11 0 1 我们正在考虑将所有基于 XAML 的绑定替换为基于 ReactiveUI 的绑定 域类型有一个 ViewModel 实现了 INotifyProp