为什么在WPF中,TemplateBinding无法像Binding一样使用?

55

好的...这让我感到困惑。我有两个WPF控件——一个是用户控件,另一个是自定义控件。我们把它们称作UserFoo和CustomFoo。在CustomFoo的控件模板中,我使用了一个UserFoo的实例,这是一个命名部分,所以我可以在模板应用后访问它。那个工作得很好。

现在,UserFoo和CustomFoo都有一个“Text”属性,在它们各自的代码里定义(不是使用AddOwner定义共享DP。不要问为什么...),声明如下...

public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
    "Text",
    typeof(string),
    typeof(UserFoo), // The other is CustomFoo
    new FrameworkPropertyMetadata(
        null,
        FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        null,
        null,
        true,
        UpdateSourceTrigger.PropertyChanged
    )
);

需要注意的是,模式设置为TwoWay,UpdateSourceTrigger也都设置为PropertyChanged。

因此,在CustomFoo的样式模板中,我希望将CustomFoo的Text属性绑定为内部UserFoo的Text属性的源。通常来说,这很容易实现。只需要将UserFoo的Text属性设置为"{TemplateBinding Text}"即可,但由于某种原因,它只能单向绑定(即UserFoo可以从CustomFoo正确设置,但反过来则不行),尽管两个依赖属性都设置为双向绑定!然而,使用相对源绑定而不是模板绑定时,它却能够正常工作!嗯...怎么回事?

// This one works
Text="{Binding Text, RelativeSource={RelativeSource AncestorType={local:CustomFoo}}, Mode=TwoWay}"

// As does this too...
Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"

// But not this one!
Text="{TemplateBinding Text}"

那么问题出在哪里?我错过了什么吗?


如果您使用可以指定模式的TemplateBinding的长版本,会发生什么情况?例如:Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text, Mode=TwoWay}" - 这将告诉我们问题是出在Mode还是TemplateBinding上。 - Matt West
如果您看上面,我说过长版本可以正常工作。(为了清晰起见,我添加了第二个实例,使用TemplatedParent而不是显式设置控件类型。)再次强调,短版本确实设置了UserFoo的文本,但是对UserFoo的更改不会反映到CustomFoo上。 - Mark A. Donohoe
如果你有具体的问题,请将其带到[元数据],并请阅读[常见问题解答]。 - Jeff Atwood
6
@Jeff...什么?这不是一个关于元社区的问题,而是关于这里的问题。如果你是那个惩罚我因为标记问题并询问为什么我的声望因投票反对错误信息而降低的管理员,那就是因为在SO Meta FAQ的最上面,它说:“首先要诚实。如果你看到错误信息,请将其投票下来。添加注释指出具体的错误之处。提供你自己更好的答案。最好的方法是——编辑和改善现有的问题和答案!”这恰恰是我所做的,我为什么要受到惩罚? - Mark A. Donohoe
2个回答

77

在 MSDN 的论坛帖子中找到了这篇文章:http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0bb3858c-30d6-4c3d-93bd-35ad0bb36bb4/

它说:

TemplateBinding 是一种优化后的绑定形式,适用于模板方案,类似于使用 Binding 构建的绑定。

{Binding RelativeSource={RelativeSource TemplatedParent}}

从提问者的说明来看,与文档中所说的相反,实际上应该是这样的...

{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}

我向文档提出了投诉,虽然他们现在添加了一句话说明它们总是单向的,但代码示例仍未列出模式,但我想这总比没有强。

TemplateBinding将数据从模板父级传输到模板绑定的属性。如果您需要进行双向或相反方向的数据传输,请使用RelativeSource为TemplatedParent创建一个Mode属性设置为OneWayToSource或TwoWay的Binding。

更多内容请参见:http://msdn.microsoft.com/en-us/library/ms742882.aspx

看起来 Mode=OneWay 是使用TemplateBinding的"优化"之一。


实际上,如果您查看MSDN链接,它并没有说任何关于OneWay的内容,只有"{Binding RelativeSource={RelativeSource TemplatedParent}}"。我不是说那个答案是错误的……我是说我看不到MS在哪里说默认情况下是OneWay,但我也相信这是因为我也看到了。然而,微软应该(如果他们确实这样做了)更清楚地记录这一点,而不是让我们认为这只是一个很好的捷径。 - Mark A. Donohoe
2
我同意 - 很让人沮丧的是你必须在他们的论坛回复中找到它。他们应该更新MSDN页面以反映这一点。 - Matt West
我标记了该页面为错误,并解释了这个简单的遗漏给我带来了数天的研究成本,如果有它就可以节省一切。 - Mark A. Donohoe
我看到他们终于在文档中加了一条注释,说明它是单向的,但他们仍然错误地表述了代码语句本身,因为它没有显示模式。我再次标记了他们的文章,指出真的需要添加,因为注释不足够。他们所说的并不类似于他们声称的那样。 - Mark A. Donohoe
1
看起来微软终于更新了文档和代码示例,包括 Mode=OneWay,所以希望这将不再是一个令人困惑的问题。(我回来了,因为这个问题本周刚刚被点赞。) - Mark A. Donohoe

13

TemplateBinding不支持双向绑定,只有Binding支持。即使使用了BindsTwoWayBeDefault选项,它也不支持双向绑定。

更多信息可以在这里找到,但总结如下:

然而,TemplateBinding只能从模板的父元素传输数据到具有该TemplateBinding的元素中,无法实现相反方向或双向数据传输,使用带有RelativeSource为TemplatedParent的Binding是您唯一的选择。例如,在模板内部与TextBox或Slider进行交互仅会在使用双向绑定时才会更改模板的父对象上的属性。


1
我投票支持你是因为在那里找到了其他信息,但无论谁写的,这确实应该在MSDN文档中而不仅仅是一些博客。人们期望文档是最终的、最重要的,而MSDN错过了那个“oneway”的重点,那是非常重要的。 - Mark A. Donohoe
@MarqueIV - 是的,我同意。但是有很多东西在文档中找不到,或者描述得如此简略,以至于没有搜索引擎能够找到它;-) - CodeNaked

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接