为什么 DbConnectionStringBuilder 后代会如此彻底地破坏 属性 网格绑定?
Why do DbConnectionStringBuilder descendants break Property Grid binding so thoroughly?
我正在尝试建立一种配置数据库连接的方法。我在GitHub上找到了a simple property grid,将它添加到我的项目中,并为其绑定了一个DbConnectionStringBuilder
后代,它立即崩溃了。它找到了所有属性的名称和类型,但它似乎实际上并未链接到对象实例,因此所有属性都显示空值并尝试编辑它们会导致各种问题。
在向开发人员提交错误报告后我一无所获,我尝试了大约六个其他 属性 网格,包括免费和(演示版)商业产品,并且 每一个其中一个遇到了同样的问题! 一些连接字符串构建器工作正常,其他的则损坏,有时不同的网格是不同的,但是 none 它们正确地绑定到整组 5 我是使用 SQL 服务器、Postgres、Firebird、MySQL 和一个非常简单的测试用例 class 我开发的只是为了重现这个。 (事实上,我连接到“CSV 数据库”的简单测试用例是唯一一个破坏所有这些的测试用例!)
WPF 数据绑定对 DbConnectionStringBuilder
有什么特别奇怪的地方过敏吗?
如果有人想尝试,请重现案例。 CSV 数据库配置 class:
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];
}
}
}
- 创建一个带有 属性 网格的项目——任意 属性 网格。
- 实例化上面的class.
- 将其绑定到grid上作为待检对象
- 在 getter 和 setter 上放置断点,这样您就可以看到数据绑定实际发生的时间。
- 看着一切其实并不被束缚。 (查看复选框控件以及它们如何处于空状态,尽管所讨论的属性不是
bool?
类型。)
- 做一些编辑。
- 注意断点不被命中。
是的,从 DbConnectionStringBuilder 派生的 class 在与 属性 网格一起使用时具有特殊行为是有原因的。
因为它实现了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 class 的编译属性仅供 .NET 代码使用,而不供 WPF binding/XAML 引擎使用(WPF 除外)使用 DependencyProperty.Register
代码来定义依赖属性,而不是编译的属性。
如果你想支持 WPF 绑定引擎,你可以将 INotifyPropertyChanged
实现到你的 class,例如:
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)));
}
}
对于现有的 classes,例如这个:MySqlConnectionStringBuilder, there's nothing you can do (unless wrapping them with another class implementing ICustomTypeDescriptor
with an approach similar to this DynamicTypeDescriptor)。并非每个 .NET class 都适用于 WPF 绑定甚至标准 Winforms 绑定。而且是密封的...
我正在尝试建立一种配置数据库连接的方法。我在GitHub上找到了a simple property grid,将它添加到我的项目中,并为其绑定了一个DbConnectionStringBuilder
后代,它立即崩溃了。它找到了所有属性的名称和类型,但它似乎实际上并未链接到对象实例,因此所有属性都显示空值并尝试编辑它们会导致各种问题。
在向开发人员提交错误报告后我一无所获,我尝试了大约六个其他 属性 网格,包括免费和(演示版)商业产品,并且 每一个其中一个遇到了同样的问题! 一些连接字符串构建器工作正常,其他的则损坏,有时不同的网格是不同的,但是 none 它们正确地绑定到整组 5 我是使用 SQL 服务器、Postgres、Firebird、MySQL 和一个非常简单的测试用例 class 我开发的只是为了重现这个。 (事实上,我连接到“CSV 数据库”的简单测试用例是唯一一个破坏所有这些的测试用例!)
WPF 数据绑定对 DbConnectionStringBuilder
有什么特别奇怪的地方过敏吗?
如果有人想尝试,请重现案例。 CSV 数据库配置 class:
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];
}
}
}
- 创建一个带有 属性 网格的项目——任意 属性 网格。
- 实例化上面的class.
- 将其绑定到grid上作为待检对象
- 在 getter 和 setter 上放置断点,这样您就可以看到数据绑定实际发生的时间。
- 看着一切其实并不被束缚。 (查看复选框控件以及它们如何处于空状态,尽管所讨论的属性不是
bool?
类型。) - 做一些编辑。
- 注意断点不被命中。
是的,从 DbConnectionStringBuilder 派生的 class 在与 属性 网格一起使用时具有特殊行为是有原因的。
因为它实现了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 class 的编译属性仅供 .NET 代码使用,而不供 WPF binding/XAML 引擎使用(WPF 除外)使用 DependencyProperty.Register
代码来定义依赖属性,而不是编译的属性。
如果你想支持 WPF 绑定引擎,你可以将 INotifyPropertyChanged
实现到你的 class,例如:
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)));
}
}
对于现有的 classes,例如这个:MySqlConnectionStringBuilder, there's nothing you can do (unless wrapping them with another class implementing ICustomTypeDescriptor
with an approach similar to this DynamicTypeDescriptor)。并非每个 .NET class 都适用于 WPF 绑定甚至标准 Winforms 绑定。而且是密封的...