OneWayToSource 绑定似乎在 .NET 4.0 中被破坏

Posted

技术标签:

【中文标题】OneWayToSource 绑定似乎在 .NET 4.0 中被破坏【英文标题】:OneWayToSource Binding seems broken in .NET 4.0 【发布时间】:2011-06-20 00:45:38 【问题描述】:

OneWayToSource .NET 4.0 中的绑定似乎被破坏了

我有这个简单的 Xaml

<StackPanel>
    <TextBox Text="Binding TextProperty, Mode=OneWayToSource"/>
    <Button/>
</StackPanel>

我后面的代码是这样的

public MainWindow()

    InitializeComponent();
    this.DataContext = this;

private string m_textProperty;
public string TextProperty

    get
    
        return "Should not be used in OneWayToSource Binding";
    
    set
    
        m_textProperty = value;
    

在 .NET 3.5 中可以正常工作,但除外。在TextBox 中添加一些文本,按 Tab 使其失去焦点,TextProperty 会更新为 TextBox 中输入的任何文本

在 .NET 4.0 中,如果我在 TextBox 中键入一些文本,然后按 Tab 使其失去焦点,TextBox 将恢复为 TextProperty 的值(意思是 “不应在 OneWayToSource 绑定中使用”)。这种重新阅读是否适用于 .NET 4.0 中的 OneWayToSource 绑定?我只希望TextBox 将其值推入TextProperty,而不是相反。

更新 为这个问题添加赏金,因为这已成为我项目中的市长不便,我想知道这种情况发生变化的原因。似乎在 Binding 更新了源之后调用了 get。这是 .NET 4.0 中 OneWayToSource 绑定所需的行为吗?

如果是

它在 3.5 中的工作方式有什么问题? 在什么情况下这种新行为更好?

或者这实际上是我们希望在未来版本中修复的错误吗?

【问题讨论】:

【参考方案1】:

Karl Shifflett 的博客和@Simpzon 的回答已经涵盖了他们为什么添加此功能以及为什么对于始终获得设置的属性来说这不是问题。在您自己的代码中,您始终使用具有适当语义的中间属性进行绑定,并使用具有所需语义的内部属性。我将中间属性称为“阻塞”属性,因为它会阻止 getter 到达您的内部属性。

但如果您无权访问要设置属性的实体的源代码并且想要旧行为,则可以使用转换器。

这是一个带有状态的阻塞转换器:

public class BlockingConverter : IValueConverter

    public object lastValue;

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    
        return lastValue;
    

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    
        lastValue = value;
        return value;
    

您可以将它用于您的示例,如下所示:

<Grid>
    <Grid.Resources>
        <local:BlockingConverter x:Key="blockingConverter" x:Shared="False"/>
    </Grid.Resources>
    <StackPanel>
        <TextBox Text="Binding TextProperty, Mode=OneWayToSource, Converter=StaticResource blockingConverter"/>
        <Button Content="Click"/>
    </StackPanel>
</Grid>

请注意,由于转换器具有状态,因此每次使用资源时都需要一个单独的实例,为此我们可以在资源上使用 x:Shared="False" 属性。

【讨论】:

感谢您的回答和转换器,但我不同意。他的回答和 Karl Shifflett 的博客介绍了为什么除了 OneWayToSource 之外的所有模式都添加了此功能。假设我在 setter 中进行了一些转换、格式化或其他操作,我仍然不希望我的 TextBox 更新为该值。他们不仅打破了以前的工作方式(我从未听到有人抱怨过),而且现在还具有误导性,因为它不再是 OneWayToSource。它介于 TwoWay 之间,没有更新通知 我没有提到我发现OneWayToSource 的新行为令人惊讶且无益。我并不是故意要证明这一点,只是承认我认为我们不会获得任何牵引力,因为那些想要它的人可能比那些不想要它的人更有发言权。所以一个解决方法至少给了我们一个解决方案。 另外,什么时候会有人想要这种行为?您希望TextBox 在从TextBox 设置后调用get,但如果该属性是从其他地方设置的,则不是?对我来说没有意义.. 好的 :) 在我看来,它只是与其他模式一起滑入了那里。感谢您的解决方法! +1 许多软件犯罪都是以对称的名义犯下的。我认为这个论点应该是这样的:如果你得到你设置的东西,那没有问题,如果你得到的不是你设置的东西,那么你可能想要那个,即使有一种来源.【参考方案2】:

这确实是设计使然。通常它不应该造成麻烦,但我会说,您的属性实现至少是非常规的。 getter 和 setter(访问器)确实应该比 get 和 set 做的更多,并且每个 get 都应该与最后一个对应的 set 保持一致。 (抱歉,没有来源,这正是我们在我所参加的所有开发团队中所说的良好公民)。

有关此功能的更多详细信息:http://karlshifflett.wordpress.com/2009/05/27/wpf-4-0-data-binding-change-great-feature/

【讨论】:

这只是一个重现我的问题的例子,我没有这样的实现。此外,如果我删除了 setter,则每次调用 LostFocus 时,TextBox 将始终为空白。这对我来说似乎仍然不对 无论如何,为您的解释和链接+1 karlshifflett 博客已不存在。 Karl Shifflett 删除了他的旧博客(“内容过时”twitter.com/kdawg02/status/732197390665535488)。以下是提到的博客条目的存档:web.archive.org/web/20150925210516/https://…【参考方案3】:

错误,肯定的。

如果它是一个“功能”,那就是一个非常糟糕的......

似乎他们在 set() 完成后添加了对 get() 函数的调用,即使是在 OneWayToSource 模式下……谁能解释原因?

另外,感谢您指出这一点,它解释了自从我将项目升级到 .net 4.0 后我遇到的一个问题,直到现在我才能解释......

只是一个旁注:我最终通过使用依赖属性解决了这个问题。

【讨论】:

我的猜测是反映您在设置期间可能应用的任何过滤,以便 TextBox 的内容实际上反映了“真正”设置的值。那么,OneWayToSource 唯一不同的是它只会在应用 set() 后调用 get(),而不是在发送属性更改通知时调用 get()。 它甚至是一个“伟大的功能”;):karlshifflett.wordpress.com/2009/05/27/… @Meleak:是的,我明白你的意思,但是当你的系列中发生一些转换时,它可能会很有用。如果属性是从“外部”设置的,它绝对不应该调用 getter。 我同意 meleak 的观点,但仍然不认为这是一个好功能,当然也不是一个“伟大”的功能。我当然理解为什么提供的示例代码在某种程度上可以被视为“黑客”,但我个人认为不是。例如,它可以完全像那样用作 placeHolder,而新的“功能”正好可以做到这一点。 您是否尝试过使用 dp 并简单地设置一个默认值? (不知道你的项目是什么样子的,所以很难想出解决方法;但可能值得一试......)【参考方案4】:

这显然是一个错误。似乎有人至少报告过一次。 https://connect.microsoft.com/VisualStudio/feedback/details/612444/onewaytosource-broken-in-net-4-0

我同意其他许多人的观点,即一种方式应该是一种方式时期。

我的场景很复杂,在框架版本之间更改功能让我非常头疼。

我有一个绑定到属性的文本框。我有一个转换器可以即时更改格式。 EG:我在文本框中输入 EU5,属性得到 EU005。我已将绑定设置为在属性更改时触发,因为我需要在用户键入时在 ViewModel 中进行查找。新的实现会在我键入时更改文本框的值。因此,如果我想输入 EU512,我不能轻易输入,因为文本框文本会不断变化。

我已经尝试了很多东西 - 显式绑定(您决定何时更新以及更新方式。)这有同样的问题。如果我说 UpdateSource,它会这样做,但也会重新读取属性并更改目标。

我尝试了 OneWayToSource 并遇到了同样的问题。如果不对我的虚拟机进行烦人的更改,我就没有办法解决这个问题。唯一的另一种方法是删除该字段上的绑定并开始触发事件,这会很糟糕。

如果 MS 使绑定按逻辑命名,那么我的问题就会消失。即使是绑定上的一个属性来选择退出 .net4 实现并表现为 3.5 也对我有用。

有人对我如何解决这个问题有任何建议吗?

【讨论】:

【参考方案5】:

对于双向绑定,我遇到了这个问题的变体。我意识到这与正在讨论的问题并不完全相同,但这个答案在搜索时出现在首位,它导致了我的解决方案。希望有人觉得它有帮助。

我的解决方案阻止了支持属性的重新读取,但当它被其他来源更改时会更新 UI。我的解决方案基于 Rick Sladkey 回答中的阻塞转换器。

它只是向转换添加一个检查以查看lastValue 字段是否会转换为相同的后备存储值。如果不是,则后备存储值必须已从其他来源更改,并且应该更新 UI。

public class MyConverter : IValueConverter

    public object lastValue;

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    
        if (LastValue != null && MyConvertBack(LastValue).Equals(value))
            return lastValue;
        else
            return MyConvert(value);

    

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    
        lastValue = value;
        return MyConvertBack(value);
    

    private object MyConvertBack(Object value)
    
        //Conversion Code Here
    

    private object MyConvert(Object value)
    
        //Conversion Code Here
    

在我的特定用例中,我将长度和尺寸后缀存储在文本框中(10m、100mm 等)。转换器将其解析为双精度值或添加后缀(取决于转换方向)。如果没有转换器,它将在每次更新文本框时添加一个后缀。尝试键入“10”将导致“1m0”,因为转换器将在第一次击键后运行。

【讨论】:

【参考方案6】:

这是 .NET 4.0 中 OneWayToSource 绑定所需的行为吗?

是的。这样做是为了让开发人员能够在没有笨拙的转换器的情况下更改提供的值。

它在 3.5 中的工作方式有什么问题?

没问题。它在 3.5 中的工作方式不允许更正提供的值。

这种新行为在什么场景下更好?

当您需要更正提供的值时。如果您不需要它,那么您应该只编写正确的属性的 getter 和 setter。

public string TextProperty

    get;
    set;

但是,正如我所见,将 UpdateSourceTrigger 更改为“PropertyChanged”可以防止重新读取值(因此您可以保留旧的属性声明):

<StackPanel>
    <TextBox Text="Binding TextProperty, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged"/>
    <Button/>
</StackPanel>

    private string m_textProperty;
    public string TextProperty
    
        get
        
            return "Should not be used in OneWayToSource Binding";
        
        set
        
            m_textProperty = value;
        
    

【讨论】:

您好亚历克斯,感谢您的回答。我将在今晚晚些时候阅读您的答案的详细信息以消化。起初使用UpdateSourceTrigger=PropertyChanged 似乎可行,但仔细观察会发现TextBoxText 属性不再与显示的Text 匹配,这是由这种行为引起的一个非常奇怪的错误(TextBox.Text仍然会说"Should not be used in OneWayToSource Binding",但显示的文本是您输入的任何内容)。 当然 TextBox.Text 显示“不应该...”,因为您要求它的值,所以您强制绑定到 READ 值,即调用属性 getter(它返回字符串“不应该.. 。”)。实际上,如果您在调试模式下执行应用程序,您会看到在每个 setter 之后仍然调用 getter,因此 UpdateSourceTrigger 不会更改 4.0 的行为,而是由 UI 以不同的方式处理(TextBlock.Text 不会重置为以前的值)。我想微软团队最终会修复这个 hack,所以我建议你编写设计良好的 getter/setter,而不是使用 UpdateSourceTrigger。 你完全误解了我的评论。当然,我并不是说我使用 Text 属性和显示的文本之间的不匹配作为解决方法。而且我期望使用OneWayTwoSource 绑定的方式不涉及将属性用作某种“值转换器”。但是我在所有其他答案中都已经解决了这个问题,所以不需要再做一次了。此外,问题中的代码只是为了重现我的问题,并不是我正在使用的东西,但显然不清楚 @Meleak:抱歉我的解释不好,我有点不正确,但又一次。在 4.0 中,您不能在不重新读取源代码的情况下使用 OneWayToSource 绑定模式。好的?在开发对象的设计过程中,您应该考虑到 getter 将在 setter 之后立即调用。这意味着在您的场景中 TextBox.Text 永久等于“不应该......”。但是,如果您只需要显示正确键入的单词并在将来使用 m_textProperty - 您可以使用 UpdateSourceTrigger。如果您需要正确的 TextBox.Text 值 - 您应该公开设计良好的属性,该属性可以获取设置。 在实际场景中,我有一个 ITestCase 视图,在该视图中我有一个从列表填充的 ComboBox,但列表中的每个值都不适用于每个 TestCase。根据客户要求,无论显示哪个 TestCase,所有值都应显示在 ComboBox 中,如果选择了无效值,则将 null 设置为 setter 中的支持属性。 OneWayToSource 绑定在我们升级项目之前运行良好,但现在,选择无效值会将 ComboBox 重置为 null,因此我们必须重新设计。我想这就是它现在的工作方式..

以上是关于OneWayToSource 绑定似乎在 .NET 4.0 中被破坏的主要内容,如果未能解决你的问题,请参考以下文章

WPF .Net 4 - OneWayToSource 绑定到只写属性适用于某些机器!如何?

升级 .NET 版本后,TwoWay 或 OneWayToSource 绑定无法在只读属性上工作

InvalidOperationException - TwoWay 或 OneWayToSource 绑定无法在只读属性上工作

InvalidOperationException - TwoWay 或 OneWayToSource 绑定无法在只读属性上工作

WPF 绑定,OneWayToSource,“找不到属性获取方法。”

为啥 Run.Text 默认是双向绑定的?