如何实现“传递”DataBinding?

时间:2022-05-18 14:13:48

A little background: I am loading a WPF UI from a database which is stored in a table of properties (Control type, label, margin, etc.) which I load into a class I call ControlPresenter. Basically I set ControlPresenter as the DataContext of a ContentPresenter and use a TemplateSelector to choose which type of control to load. The DataTemplate(s) load their properties off the DependencyProperties exposed by the ControlPresenter.

一点背景:我从数据库加载WPF UI,该数据库存储在属性表(控件类型,标签,边距等)中,我将其加载到一个名为ControlPresenter的类中。基本上我将ControlPresenter设置为ContentPresenter的DataContext,并使用TemplateSelector选择要加载的控件类型。 DataTemplate将其属性从ControlPresenter公开的DependencyProperties中加载。

This all works extremely well. The problem I'm running into comes when I try to bind data (e.g. TextBox's Text property to a presenter's Name property) in other presenters (which have normal properties that implement INotifyPropertyChanged) to these controls. The control's DataContext is the associated ControlPresenter so I can't bind directly to the other presenters and I can't set up two bindings on the same dependency property (I can't bind the control and the desired presenter property to the same DP).

一切都很好。当我尝试将数据(例如TextBox的Text属性与Presenter的Name属性)绑定到这些控件的其他演示者(具有实现INotifyPropertyChanged的常规属性)时,我遇到的问题就出现了。控件的DataContext是关联的ControlPresenter,因此我无法直接绑定到其他演示者,我无法在同一依赖项属性上设置两个绑定(我无法将控件和所需的演示者属性绑定到同一个DP) 。

Possible solutions:

  1. I could convert all of the other presenters to use DPs (extremely time consuming and prone to cause problems with inheritance)
  2. 我可以将所有其他演示者转换为使用DP(非常耗时且容易导致继承问题)

  3. I could use two DPs for each property I want to pass through and try to tie them together through changed notifications
  4. 我可以为我想要传递的每个属性使用两个DP,并尝试通过更改的通知将它们绑定在一起

Both of these seem problematic and prone to breaking so I'm hoping someone else has come up with a better solution.

这两个似乎都有问题,容易破裂,所以我希望其他人提出更好的解决方案。

Edit: I came up with a solution that works fairly well (see below). Thanks to everyone that looked at this and if you come up with a better method than I did please let me know.

编辑:我提出了一个相当好的解决方案(见下文)。感谢所有看过这个的人,如果你想出一个比我更好的方法,请告诉我。

2 个解决方案

#1


There are several ways of accessing out of scope DataContexts.

有几种方法可以访问范围外的DataContexts。

1) ElementName Binding

1)ElementName绑定

This is probably the least useful as, in most real world scenarios, what you are trying to bind to is out of namescope anyway. But it is a way to access a parallel or parent Data Context as long as the Framework Element is within namescope.

这可能是最不实用的,因为在大多数现实世界的场景中,你想要绑定的东西无论如何都是在名称范围之外。但只要框架元素位于名称范围内,它就是访问并行或父数据上下文的一种方法。

<TextBox Text="{Binding ElementName=ControlSomewhereElseBoundToSomeOtherControlPresenter, Path=DataContext.SomeTextPropertyOnTheControlPresenter}" />

2) RelativeSource Binding

2)RelativeSource绑定

This is similar to #1 but using RelativeSource to navigate to the appropriate visual element and grab the DataContext. Of course, this assumes that the DataContext you are trying to get at is ABOVE where you currently are in the Visual Tree.

这与#1类似,但使用RelativeSource导航到适当的可视元素并获取DataContext。当然,这假设您尝试获取的DataContext是您当前在Visual Tree中的位置。

3) Use a Static Relay

3)使用静态继电器

You could expose your alternative ControlPresenters within a static class that acts as a facade to your ViewModel. Then, within each ViewModel construct instance properties that pass through to the static methods / properties. This is a common way for achieving shared storage across multiple View Models. I realize this technique would require you to alter your pattern a bit, but adding a simple ViewModel wrapper around these "ControlPresenter" classes seems a lot more simple than the options you described.

您可以在静态类中公开替代ControlPresenters,该静态类充当ViewModel的外观。然后,在每个ViewModel构造实例属性中传递给静态方法/属性。这是跨多个View模型实现共享存储的常用方法。我意识到这种技术需要你稍微改变你的模式,但是在这些“ControlPresenter”类中添加一个简单的ViewModel包装似乎比你描述的选项简单得多。

#2


I ended up setting the bindings in code by setting Binding.Source to the desired ViewModel and then using the path in the database for Binding.Path. I store the bindings in a Dictionary(Of DependencyProperty, BindingBase) and when the Control loads I use BindingOperations.SetBinding to set the bindings on the control. This seems to work pretty well except for occasional order of operations issues (e.g. If you set SelectedItem/SelectedValue prior to the ItemsSource it will be set but won't display in a ComboBox).

我最终通过将Binding.Source设置为所需的ViewModel,然后使用数据库中的路径为Binding.Path设置代码中的绑定。我将绑定存储在Dictionary(Of DependencyProperty,BindingBase)中,当Control加载时,我使用BindingOperations.SetBinding来设置控件上的绑定。除了偶尔的操作顺序问题之外,这似乎工作得很好(例如,如果在ItemsSource之前设置SelectedItem / SelectedValue,它将被设置但不会显示在ComboBox中)。

#1


There are several ways of accessing out of scope DataContexts.

有几种方法可以访问范围外的DataContexts。

1) ElementName Binding

1)ElementName绑定

This is probably the least useful as, in most real world scenarios, what you are trying to bind to is out of namescope anyway. But it is a way to access a parallel or parent Data Context as long as the Framework Element is within namescope.

这可能是最不实用的,因为在大多数现实世界的场景中,你想要绑定的东西无论如何都是在名称范围之外。但只要框架元素位于名称范围内,它就是访问并行或父数据上下文的一种方法。

<TextBox Text="{Binding ElementName=ControlSomewhereElseBoundToSomeOtherControlPresenter, Path=DataContext.SomeTextPropertyOnTheControlPresenter}" />

2) RelativeSource Binding

2)RelativeSource绑定

This is similar to #1 but using RelativeSource to navigate to the appropriate visual element and grab the DataContext. Of course, this assumes that the DataContext you are trying to get at is ABOVE where you currently are in the Visual Tree.

这与#1类似,但使用RelativeSource导航到适当的可视元素并获取DataContext。当然,这假设您尝试获取的DataContext是您当前在Visual Tree中的位置。

3) Use a Static Relay

3)使用静态继电器

You could expose your alternative ControlPresenters within a static class that acts as a facade to your ViewModel. Then, within each ViewModel construct instance properties that pass through to the static methods / properties. This is a common way for achieving shared storage across multiple View Models. I realize this technique would require you to alter your pattern a bit, but adding a simple ViewModel wrapper around these "ControlPresenter" classes seems a lot more simple than the options you described.

您可以在静态类中公开替代ControlPresenters,该静态类充当ViewModel的外观。然后,在每个ViewModel构造实例属性中传递给静态方法/属性。这是跨多个View模型实现共享存储的常用方法。我意识到这种技术需要你稍微改变你的模式,但是在这些“ControlPresenter”类中添加一个简单的ViewModel包装似乎比你描述的选项简单得多。

#2


I ended up setting the bindings in code by setting Binding.Source to the desired ViewModel and then using the path in the database for Binding.Path. I store the bindings in a Dictionary(Of DependencyProperty, BindingBase) and when the Control loads I use BindingOperations.SetBinding to set the bindings on the control. This seems to work pretty well except for occasional order of operations issues (e.g. If you set SelectedItem/SelectedValue prior to the ItemsSource it will be set but won't display in a ComboBox).

我最终通过将Binding.Source设置为所需的ViewModel,然后使用数据库中的路径为Binding.Path设置代码中的绑定。我将绑定存储在Dictionary(Of DependencyProperty,BindingBase)中,当Control加载时,我使用BindingOperations.SetBinding来设置控件上的绑定。除了偶尔的操作顺序问题之外,这似乎工作得很好(例如,如果在ItemsSource之前设置SelectedItem / SelectedValue,它将被设置但不会显示在ComboBox中)。