【问题标题】:Why do DbConnectionStringBuilder descendants break Property Grid binding so thoroughly?为什么 DbConnectionStringBuilder 后代会如此彻底地破坏属性网格绑定?
【发布时间】:2021-11-03 21:08:28
【问题描述】:

我正在尝试建立一种配置数据库连接的方法。我在 GitHub 上找到了a simple property grid,将其添加到我的项目中,并绑定了一个DbConnectionStringBuilder 后代,它立即崩溃了。它找到了所有属性的名称和类型,但似乎并没有真正链接到对象实例,因此所有属性都显示空值,尝试编辑它们会导致各种问题。

向开发人员提交错误报告后我一无所获,我尝试了大约六种其他属性网格,包括免费和(演示版)商业产品,每一个都有同样的问题! 一些连接字符串构建器工作得很好,另一些则中断,有时它对于不同的网格是不同的,但它们都没有正确绑定到我正在使用的整个 5 个集合,用于 SQL Server、Postgres、Firebird、MySQL 和我开发的一个非常简单的测试用例类只是为了重现这一点。 (事实上​​,我连接到“CSV 数据库”的简单测试用例是唯一一个破坏了所有测试用例!)

WPF 数据绑定对DbConnectionStringBuilder 有什么特别奇怪的地方吗?

如果有人想尝试,请复制案例。 CSV数据库配置类:

using System;
using System.ComponentModel;
using System.Data.Common;

namespace Repro
{
    public class CsvConfigurator : DbConnectionStringBuilder
    {
        public CsvConfigurator() { }

        public CsvConfigurator(string conf)
        {
            ConnectionString = conf;
        }

        public string Delimiter
        {
            get => GetString(nameof(Delimiter));
            set => this[nameof(Delimiter)] = value;
        }

        public bool AutoDetectDelimiter
        {
            get => GetBool(nameof(AutoDetectDelimiter));
            set => this[nameof(AutoDetectDelimiter)] = value;
        }

        public bool UsesHeader
        {
            get => GetBool(nameof(UsesHeader));
            set => this[nameof(UsesHeader)] = value;
        }

        public bool UsesQuotes
        {
            get => GetBool(nameof(UsesQuotes));
            set => this[nameof(UsesQuotes)] = value;
        }

        public char QuoteChar
        {
            get => GetChar(nameof(QuoteChar), '"');
            set => this[nameof(QuoteChar)] = value;
        }

        public char EscapeChar
        {
            get => GetChar(nameof(EscapeChar), '\\');
            set => this[nameof(EscapeChar)] = value;
        }

        protected string GetString(string key) => TryGetValue(key, out var value) ? (string)value : null;
        protected bool GetBool(string key) => TryGetValue(key, out var value) ? Convert.ToBoolean(value) : false;
        protected char GetChar(string key, char defaultValue)
        {
            var result = GetString(key);
            return string.IsNullOrEmpty(result) ? defaultValue : result[0];
        }
    }
}
  1. 创建一个带有属性网格(任何属性网格)的项目。
  2. 实例化上述类。
  3. 将其绑定到网格作为要检查的对象。
  4. 在 getter 和 setter 上设置断点,以便您查看数据绑定的实际发生时间。
  5. 注意一切实际上并没有被绑定。 (尽管有问题的属性不是bool? 类型,但请查看复选框控件以及它们如何处于空状态。)
  6. 进行一些编辑。
  7. 注意不要命中断点。

【问题讨论】:

  • 你的绑定在哪里?
  • @mm8 在 XAML 中,正是您所期望的。
  • 如果绑定不起作用,显然不是预期的。你还没有发布你的 XAML...
  • 它解释了一切,但也许你不明白。我不确定“优雅地绑定并成功编辑”是什么意思。 PropertyGrids 与 TypeDescriptor 绑定。由于 DbConnectionStringBuilder 实现了 ICustomTypeDescriptor,因此它与 ICustomTypeDescriptor 通过 GetProperties 公开的属性绑定,而不是与 .NET/C# 编译的属性绑定。它非常类似于 WPF 控件依赖属性与绑定时不使用的 .NET/C# 属性)。现在可能有些属性网格没有绑定,或者没有使用 TypeDescriptor 或者不起作用。
  • @BradleyGrainger - 是的,这是一种令人费解的方式来表达“除非为继承而设计的密封”:-) 是的,微软提供的所有类都是密封的,我会在他们的类中解决同样的评论,因为 DbConnectionStringBuilder 确实是为继承而设计的(它甚至可以是抽象的)。

标签: wpf data-binding propertygrid


【解决方案1】:

是的,派生自 DbConnectionStringBuilder 的类在与属性网格一起使用时具有特殊行为是有原因的。

这是因为它实现了ICustomTypeDescriptor Interface。 通常,属性网格使用TypeDescriptor.GetProperties method,如果实现,默认情况下将遵循 ICustomTypeDescriptor。

这意味着属性网格将使用已编译的 .NET/C# 属性来表示实例,而是使用来自 ICustomTypeDescriptor 接口的属性和自定义 PropertyDescriptor 实例.

因此,属性网格根本不会使用已编译的 .NET/C# 属性,只会使用由内部 DbConnectionStringBuilder 代码组成的“虚拟”属性(您可以在此处查看其代码 https://github.com/microsoft/referencesource/blob/master/System.Data/System/Data/Common/DbConnectionStringBuilder.cs#L335)。这些“虚拟”属性将使用 .NET 编译的属性构建,但它们的代码不会用于获取或设置它们。

这类似于 WPF 依赖项属性功能,其中 .NET 类的已编译属性仅由 .NET 代码使用,而不是由 WPF 绑定/XAML 引擎使用(除了 WPF 使用 DependencyProperty.Register 代码来定义依赖属性,而不是编译的)。

如果你想支持 WPF 绑定引擎,你可以像这样在你的类中实现INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

// thanks to how DbConnectionStringBuilder is designed,
// we can override this central method
public override object this[string keyword]
{
    get => base[keyword];
    set
    {
        object existing = null;
        try
        {
            existing = base[keyword];
        }
        catch
        {
            // do nothing
        }

        if (existing == null)
        {
            if (value == null)
                return;
        }
        else if (existing.Equals(value))
            return;

        base[keyword] = value;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(keyword));
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ConnectionString)));
    }
}

对于诸如MySqlConnectionStringBuilder 之类的现有类,您无能为力(除非使用与DynamicTypeDescriptor 类似的方法将它们与另一个实现ICustomTypeDescriptor 的类一起包装)。并非每个 .NET 类都可以与 WPF 绑定甚至标准 Winforms 绑定一起正常工作。而且是密封的……

【讨论】:

    猜你喜欢
    • 2021-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-24
    • 1970-01-01
    • 2017-06-03
    • 2019-01-22
    • 1970-01-01
    相关资源
    最近更新 更多