【问题标题】:Binding causes StackOverflow绑定导致 StackOverflow
【发布时间】:2018-02-09 13:47:28
【问题描述】:

我不确定我在这里做错了什么。

可以说,我有两个用户控件BoxABoxB。两者都有一个名为 Text

的 DependencyProperty

BoxB 包装了具有常规 TextBox 的 BoxA。

绑定应该像这样 BoxB.Text BoxA.Text TextBox.Text

Xaml BoxA:

<UserControl x:Class="SandBoxWpf.BoxA"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             DataContext="{Binding RelativeSource={RelativeSource Self}}">

    <TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></TextBox>

</UserControl>

Xaml BoxB:

<UserControl x:Class="SandBoxWpf.BoxB"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:SandBoxWpf"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300"
             DataContext="{Binding RelativeSource={RelativeSource Self}}">

    <local:BoxA Text="{Binding Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"></local:BoxA>

</UserControl>

BoxA 和 BoxB 的代码隐藏

using System.Windows;
using System.Windows.Controls;

namespace SandBoxWpf
{
    /// <summary>
    /// Interaktionslogik für BoxA.xaml
    /// </summary>
    public partial class BoxX : UserControl
    {
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
                "Text",
                typeof(string),
                typeof(BoxX),
                new PropertyMetadata(default(string)));

        public string Text
        {
            get => (string) GetValue(TextProperty);
            set => SetValue(TextProperty, value);
        }

        public BoxX()
        {
            InitializeComponent();
        }
    }
}

主窗口

<Window x:Class="SandBoxWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SandBoxWpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <Grid>
        <local:BoxB Width="100" Height="20" Text="{Binding Title}"></local:BoxB>
    </Grid>
</Window>

只要我在 BoxB 中输入内容,我就会收到 StackoverflowException。 如果我删除 Mode=TwoWay 或 UpdateSourceTrigger,StackOverflow 就会消失,但绑定也不起作用。

【问题讨论】:

  • 听起来像是某种形式的无限递归。你能告诉我们你的变更通知代码吗?有一种常见的递归情况(未检测到设置相同的值)。
  • @Christopher 除了依赖属性之外没有代码。
  • 当我使用UserControl 时,我通常在InitlialiseComponents 后面的代码中设置DataContext,试试看它是否有效。顺便说一句,我认为是这个:DataContext="{Binding RelativeSource={RelativeSource Self}}" 导致了这个问题。
  • @XAMlMAX 同样的问题。但是,当我使用代码而不是 Xaml 进行绑定时,它会起作用。
  • 你能把它上传到 git repo 吗?我想在我的机器上重现它。你在两个 UC 上都这样做了吗?盒子A和盒子B?

标签: c# wpf xaml binding


【解决方案1】:

如果您正在构建具有可绑定属性(即依赖项属性)的 UserControl,则必须在任何情况下都不得显式设置 UserControl 的 DataContext,无论是控件实例还是任何私有视图模型。

如果你这样做,一个像

这样的 Binding
<local:BoxB Text="{Binding Title}">

将不再起作用。该绑定需要当前 DataContext 中的对象中的 Title 属性。 DataContext 属性值通常继承自 UserControl 的父元素,例如窗户。但是,由于您已显式设置 DataContext,因此可以避免这种机制。

这对于 UserControls 中同名的属性尤其令人困惑。当你写

<local:BoxA Text="{Binding Text, ...}"/>

在 UserControl BoxB 中,您的期望是 Binding 源属性是 BoxB.Text。其实是BoxA.Text,因为BoxA的DataContext就是BoxA的实例。


所以删除任何

DataContext="{Binding RelativeSource={RelativeSource Self}}"

在 UserControl 的 XAML 中使用RelativeSource 行并编写绑定,如下所示:

<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay},
                RelativeSource={RelativeSource AncestorType=UserControl}"/>

<local:BoxA Text="{Binding Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay},
                   RelativeSource={RelativeSource AncestorType=UserControl}"/>

【讨论】:

  • 是的,这有点混乱,因为绑定表达式现在更长了。但至少它有效。我现在考虑改用 ElementName _this。我希望这没有问题?!
  • 您也可以只在 UserControl 中设置顶级面板的 DataContext,例如&lt;Grid DataContext="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"&gt;
【解决方案2】:

对于任何形式的变更通知,一个危险就是我所说的“乒乓”问题。示例:

  1. 属性 A 发生变化。
  2. 属性 B 已更改为匹配 A。
  3. 属性 B 已更改。
  4. 属性 A 已更改为匹配 B。
  5. 递归到 1

为了避免这种情况,带有更改通知的属性的示例代码如下所示:

public string PhoneNumber
{
    get
    {
        return this.phoneNumberValue;
    }

    set
    {
        if (value != this.phoneNumberValue)
        {
            this.phoneNumberValue = value;
            NotifyPropertyChanged();
        }
    }
}

如果输入与输出相同,则什么也不做。顺序是:

  1. 属性 A 更改
  2. 属性 B 已更改为匹配 A
  3. B 属性变化
  4. 属性 A 注意到它已经具有该值,因此什么也没做。

我最好的猜测是 WPF 元素没有这种保护。其中一种情况是“试图变得聪明可能会导致非常愚蠢”。

【讨论】:

  • “WPF 元素没有这样的保护”。好吧,事实上它们有,或者至少有依赖属性。再次为依赖属性分配相同的值不会触发其 PropertyChangedCallback 或任何 Binding。
  • @Clemens:“我最好的猜测是 WPF 元素......”。所以显然我的猜测是错误的。
猜你喜欢
  • 1970-01-01
  • 2011-07-20
  • 1970-01-01
  • 2015-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多