你的问题是一个非常有趣的问题,但它的范围实际上非常大。
在这种情况下,一个非常有用的工具是ILSpy,它允许您查看框架实现。
我会反对的一件事是以下声明:
我得到的答案是 C# 中的数据绑定引擎与 WPF/Windows 窗体 UI 紧密耦合
我不同意; 数据绑定引擎与 .Net 事件实现紧密耦合,但 Target 和 Source 可以是任何东西 - 大多数示例将是 Windows Forms、WPF 或 ASP.Net,因为它们是 .Net 语言最常见的前端,但它是完全可以在没有 UI 的其他场景中使用多重绑定。
添加双向绑定时会发生什么?好吧,如果我们查看MultiBinding的源代码,我们会注意到一些有趣的事情:
- 它公开了一个BindingMode属性,该属性描述了绑定场景——通常是
OneWay
或者TwoWay
- 它暴露了两个有趣的事件:
NotifyOnSourceUpdated
和NotifyOnTargetUpdated
其中有基本形式:
// System.Windows.Data.MultiBinding
/// <summary>Gets or sets a value that indicates whether to raise the <see cref="E:System.Windows.FrameworkElement.SourceUpdated" /> event when a value is transferred from the binding target to the binding source.</summary>
/// <returns>true if the <see cref="E:System.Windows.FrameworkElement.SourceUpdated" /> event will be raised when the binding source value is updated; otherwise, false. The default value is false.</returns>
[DefaultValue(false)]
public bool NotifyOnSourceUpdated
{
get
{
return base.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated);
}
set
{
bool flag = base.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated);
if (flag != value)
{
base.CheckSealed();
base.ChangeFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated, value);
}
}
}
即我们使用事件来告诉我们何时更新源(OneWay
)以及何时更新目标(用于TwoWay
绑定)
请注意,还有一个PriorityBinding
类以类似的方式运行,只是您可以订阅多个数据源,并且它将优先考虑最快返回数据的那个。
所以它的工作原理很清楚 - 当我们创建绑定时,我们订阅一侧的更改(用于只读更新)或两侧的更改(例如,当数据可以在 GUI 中更改并发回时)到数据源),所有通知都通过事件管理。
下一个问题是,真的,谁来管理这些事件?简单的答案是 Target 和 Source 都可以。这就是为什么实现INotifyPropertyChanged
很重要,例如——所有绑定真正做的是为双方应该如何订阅彼此的更改创建一个契约——它是目标和源紧密耦合的契约,真的。
ObservableCollection是一个值得研究的有趣测试用例,因为它广泛用于 GUI 应用程序中,用于将数据源中的更新推广到 UI,并将 UI 中数据的更改发送回底层数据源。
请注意(通过查看代码)用于传达事物已更改的实际事件非常简单,但是用于管理添加、删除、更新的代码实际上非常依赖于通过 SimpleMonitor 属性(BlockReentrancy
和CheckReentrancy
)的一致性 - 它有效地保证了这些操作是原子的,并且订阅者会按照发生的顺序收到更改通知,并且基础集合与更新的集合一致。
这确实是整个操作的棘手部分。
简而言之,.Net 中的 DataBinding 实现并没有与 GUI 技术紧密耦合;只是大多数示例将在 Windows 窗体、WPF 或 ASP.Net 应用程序的上下文中呈现 DataBinding。实际的数据绑定是事件驱动的,为了利用它,更重要的是同步和管理对数据的更改 - DataBinding 框架只允许您通过合约将 Target 和 Source 在共享数据更新中耦合在一起(接口)它定义。
玩得开心 ;-)
编辑:
我坐下来创建了两个类,MyCharacter
明确的目的是在和属性MyCharacterAttribute
之间设置双向数据绑定:Health
HealthValue
public class MyCharacter : DependencyObject
{
public static DependencyProperty HealthDependency =
DependencyProperty.Register("Health",
typeof(Double),
typeof(MyCharacter),
new PropertyMetadata(100.0, HealthDependencyChanged));
private static void HealthDependencyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
}
public double Health
{
get
{
return (double)GetValue(HealthDependency);
}
set
{
SetValue(HealthDependency, value);
}
}
public void DrinkHealthPotion(double healthRestored)
{
Health += healthRestored;
}
}
public class MyCharacterAttributes : DependencyObject
{
public static DependencyProperty HealthDependency =
DependencyProperty.Register("HealthValue",
typeof(Double),
typeof(MyCharacterAttributes),
new PropertyMetadata(100.0, HealthAttributeDependencyChanged));
public double HealthValue
{
get
{
return (Double)GetValue(HealthDependency);
}
set
{
SetValue(HealthDependency, value);
}
}
public List<BindingExpressionBase> Bindings { get; set; }
public MyCharacterAttributes()
{
Bindings = new List<BindingExpressionBase>();
}
private static void HealthAttributeDependencyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
}
}
这里要注意的最重要的事情是从DependencyObject的继承和DependencyProperty的实现。
那么,在实践中,会发生以下情况。我创建了一个简单的 WPF 表单并设置了以下代码:
MyCharacter Character { get; set; }
MyCharacterAttributes CharacterAttributes = new MyCharacterAttributes();
public MainWindow()
{
InitializeComponent();
Character = new MyCharacter();
CharacterAttributes = new MyCharacterAttributes();
// Set up the data binding to point at Character (Source) and
// Property Health (via the constructor argument for Binding)
var characterHealthBinding = new Binding("Health");
characterHealthBinding.Source = Character;
characterHealthBinding.NotifyOnSourceUpdated = true;
characterHealthBinding.NotifyOnTargetUpdated = true;
characterHealthBinding.Mode = BindingMode.TwoWay;
characterHealthBinding.IsAsync = true;
// Now we bind any changes to CharacterAttributes, HealthDependency
// to Character.Health via the characterHealthBinding Binding
var bindingExpression =
BindingOperations.SetBinding(CharacterAttributes,
MyCharacterAttributes.HealthDependency,
characterHealthBinding);
// Store the binding so we can look it up if necessary in a
// List<BindingExpressionBase> in our CharacterAttributes class,
// and so it "lives" as long as CharacterAttributes does, too
CharacterAttributes.Bindings.Add(bindingExpression);
}
private void HitChracter_Button(object sender, RoutedEventArgs e)
{
CharacterAttributes.HealthValue -= 10.0;
}
private void DrinkHealth_Button(object sender, RoutedEventArgs e)
{
Character.DrinkHealthPotion(20.0);
}
单击 HitCharacter 按钮将CharacterAttributes.HealthValue
属性减少 10。这会触发一个事件,通过我们之前设置的 Binding,该事件也会从Character.Health
值中减去 10.0。点击 DrinkHealth 按钮可恢复Character.Health
20.0 并增加CharacterAttributes.HealthValue
20.0。
另请注意,这些东西确实已融入 UI 框架FrameworkElement
(继承自UIElement
)SetBinding
并GetBinding
在其上实现。这是有道理的——DataBinding GUI 元素对于用户界面来说是一个完全有效的场景!但是,如果你看得更深入,SetValue
例如,它只是调用BindingOperations.SetBinding
一个内部接口,所以我们可以实现它而无需实际使用 a UIElement
(如上例所示)。但是,我们必须继承的一个依赖项是DependencyObject
and DependencyProperty
- 这些是 DataBinding 工作所必需的,但是,只要您的对象继承自DependencyObject
,您就不需要靠近文本框 :-)
然而,缺点是一些 Binding 的东西是通过internal
方法实现的,所以你可能会遇到想要实现的绑定操作可能需要你编写额外代码的场景,因为你根本无法访问原生的框架实现班可以。但是,如上例所示,TwoWay 数据绑定是完全可能的,如所示。