【问题标题】:How to reference BindingProxy instance from ResourceDictionary如何从 ResourceDictionary 引用 BindingProxy 实例
【发布时间】:2025-02-10 14:45:02
【问题描述】:

wpf 程序员迟早会开始使用BindingProxy

我试图通过将一些资源移动到单独的资源字典中来拆分 xaml。我的问题是资源包含对BindingProxy 的引用。

我该如何处理这种情况?

例如,假设有一个带有BindingProxy 的资源在某处使用

<Window.Resources>
    <local:BindingProxy x:Key="proxy" />
    <ControlTemplate x:Key="test">
        <TextBlock Text="{Binding DataContext.Test, Source={StaticResource proxy}}" />
    </ControlTemplate>
</Window.Resources>
<Control Template="{StaticResource test}" />

和后面的代码

public partial class MainWindow : Window
{
    public string Test { get; set; } = "Test 123";

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }
}

这可能不是最好的例子,使用BindingProxy 并不合理,但它很好地服务于演示目的。在运行时窗口中将显示文本"Test 123"


现在让我们尝试将资源移动到资源字典Dictionary1.xaml

<ResourceDictionary ... >
    <ControlTemplate x:Key="test">
        <TextBlock Text="{Binding Test, Source={StaticResource proxy}}"  /> <!-- error here -->
    </ControlTemplate>
</ResourceDictionary>

并将主窗口资源更改为

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml" />
        </ResourceDictionary.MergedDictionaries>
        <local:BindingProxy x:Key="proxy" />
    </ResourceDictionary>
</Window.Resources>
<Control Template="{StaticResource test}" />

会导致设计器和运行时异常

System.Windows.Markup.XamlParseException: ''在 'System.Windows.Markup.StaticResourceHolder' 上提供值引发了异常。'行号“5”和行位置“20”。'
内部异常
例外:找不到名为“代理”的资源。资源名称区分大小写。

如何引用proxy?是否存在另一种技术来引用资源字典中的某些内容?也许某种RelativeResource 方法,但对于不在视觉树中的东西?我无法将proxy 移动到ResourceDictionary1.xaml 中,原因很明显:它不会捕获窗口的DataContext

【问题讨论】:

  • 也许您可以使用动态资源...除此之外,您的层次结构颠倒了。使用合并字典,您可以在当前范围内使用引用字典中的资源。但是您不能真正使用合并字典中的当前范围资源。
  • 你能展示一下代理是如何实例化的吗?如果代理是单例的,你可以使用 ServiceLocator 模式。
  • 您能否将您的示例扩展到一个实际需要代理的简单案例?现在,您总是可以跳过整个代理并绑定到隐式可用的数据上下文。我有 3 种不同的想法如何解决这种情况,但我无法通过所提供的示例确定正确的方法。
  • @grek40,在文章中是DataGridTextColumn,在我的例子中是GridViewColumn(参见相关问题)。任何不属于视觉树的东西都会做,例如ContextMenu,如果我没记错的话Popup 等等。在那里你不能使用RelativeSource,需要以某种方式通过DataContext
  • @Sinatr 我回答了你的另一个问题,因为我认为这不是一个 XY 问题。我大多同意 LuckyBrain。绑定代理是我发现在非常有限的情况下有用的东西。大多数时候有更好的方法,当代理和它的用户(一些模板资源)之间的连接变得不明确时,是时候继续前进,而不是试图修复不应该存在的东西。

标签: c# wpf data-binding


【解决方案1】:

尽管我不推荐在 MVVM 中使用 BindingProxy,但我认为您的问题是这样解决的:

  • 请记住,当您在视图 XAML 中包含 ResourceDictionary 时,它会自动继承视图的 DataContext,因此您可以将 BindingProxy 保留在 ResourceDictionary 中,但您需要明确指定绑定。
  • 记住还要从 View XAML 中删除代理声明,因为它现在在字典中。
  • 您无法更改BindingProxyDataContext,它将使用消费者视图的DataContext

资源字典:

<ResourceDictionary ...>
    <!-- NOTE: Data property grabs the DataContext of the consumer view -->
    <local:BindingProxy x:Key="proxy" Data="{Binding}" />
    <ControlTemplate x:Key="text">
        <TextBlock Text="{Binding Data.Test, Source={StaticResource proxy}}" />
    </ControlTemplate>
</ResourceDictionary>

窗口:

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

快照:


你真的需要BindingProxy吗?

上面的建议违背了BindingProxy 的目的,因为它不再需要了;请注意,您可以按如下方式更改ResourceDictionary,它的工作原理完全相同,无需BindingProxy

<ResourceDictionary ...>
    <ControlTemplate x:Key="test">
        <TextBlock Text="{Binding Test}" />
    </ControlTemplate>
</ResourceDictionary>

【讨论】:

  • 是的,我真的需要BindingProxy。用GridViewColumn问这么多显示源代码的问题让我很紧张。你测试过你的解决方案吗?我已尝试将proxy 移动到资源字典,但没有成功,可能是我犯了一个错误,将再次检查。
  • 我当然测试过,它 100% 工作:VS2019,.NET Core 3.1。完全按照我说的放置资源。您是否在代理中包含属性Data="{Binding}"?我修改了答案以包含工作应用程序的快照。
最近更新 更多