你的问题非常有趣,但它的范围实际上非常广。
在这种情况下一个非常有用的工具是ILSpy,它允许你查看框架实现。
我要提出的一件事是以下声明:
我得到的答案是C#中的数据绑定引擎与WPF / Windows Forms UI密切耦合
我不同意; 数据绑定引擎紧密耦合于.Net事件实现,但Target和Source可以是任何东西 - 大多数示例将是Windows Forms,WPF或ASP.Net,因为它们是.Net语言最常见的前端,但在没有UI的其他情况下也完全可以使用多绑定。
当您添加双向绑定时会发生什么?嗯,如果我们查看MultiBinding的源代码,我们会注意到一些有趣的事情:
- 它公开了一个BindingMode属性,描述绑定场景 - 通常是
OneWay
或TwoWay
- 它公开了两个有趣的事件:
NotifyOnSourceUpdated
和NotifyOnTargetUpdated
它们具有基本形式:
[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中更改并发送回数据源时),所有通知都通过事件管理。
下一个问题是,真正的事件管理者是谁?简单的答案是源和目标都需要。这就是为什么实现INotifyPropertyChanged
很重要的原因,所有绑定实际上都是为双方应该如何订阅彼此的变化创建的一个协议——这实际上是目标和源紧密耦合的协议。
ObservableCollection 是一个有趣的测试用例,因为它在GUI应用程序中被广泛使用,用于将数据源中的更新推广到UI,并将UI中的更改发送回底层数据源。
请注意(通过查看代码),通知事物已更改的实际事件非常简单,但是管理添加、删除、更新的代码实际上非常依赖一致性,通过SimpleMonitor属性(BlockReentrancy
和CheckReentrancy
),它有效地保证操作是原子的,并且订阅者按照发生的顺序收到更改通知,并且底层集合与这些更新保持一致。
这确实是整个操作中棘手的部分。
简而言之,在.NET中的DataBinding实现与GUI技术没有紧密耦合;只是大多数示例将DataBinding呈现在Windows Forms、WPF或ASP.Net应用程序的上下文中。实际的数据绑定是事件驱动的,为了利用它,更重要的是同步和管理您的数据更改——DataBinding框架只允许您通过定义的协议(接口)将目标和源耦合在共享数据更新中。
玩得开心;-)
编辑:
我坐下来创建了两个类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();
var characterHealthBinding = new Binding("Health");
characterHealthBinding.Source = Character;
characterHealthBinding.NotifyOnSourceUpdated = true;
characterHealthBinding.NotifyOnTargetUpdated = true;
characterHealthBinding.Mode = BindingMode.TwoWay;
characterHealthBinding.IsAsync = true;
var bindingExpression =
BindingOperations.SetBinding(CharacterAttributes,
MyCharacterAttributes.HealthDependency,
characterHealthBinding);
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。这会触发一个事件,通过之前设置的绑定,也会从Character.Health值中减去10.0。点击DrinkHealth按钮会将Character.Health恢复20.0,并将CharacterAttributes.HealthValue增加20.0。
还要注意的是,这些都确实嵌入在UI框架中 - FrameworkElement(从UIElement继承)已经实现了SetBinding和GetBinding。这很有道理 - 数据绑定GUI元素是用户界面的一个完全有效的场景!如果你更深入地了解,SetValue
只是在内部接口上调用BindingOperations.SetBinding
,因此我们可以实现它而不必使用UIElement(如上例所示)。但是,我们必须传递的一个依赖项是DependencyObject和DependencyProperty - 这些对于数据绑定是强制性的,但只要您的对象继承自DependencyObject,您就不需要接近文本框 :-)
然而,缺点是一些绑定相关的内容已经通过内部方法实现,因此您可能会遇到需要编写额外代码的情况,因为您无法像本地类那样访问框架实现。然而,像上面的双向绑定一样的TwoWay数据绑定是完全可能的,正如所示。