Binding causes StackOverflow

半城伤御伤魂 提交于 2019-12-17 21:18:11

问题


Im not sure what I am doing wrong here.

Lets say, I have two UserControls BoxAand BoxB. Both have a DependencyProperty called Text

BoxB wraps BoxA which has a regular TextBox.

Binding should work like this 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>

Codebehind of both BoxA and 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();
        }
    }
}

MainWindow

<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>

As soon as I type something into the BoxB i get a StackoverflowException. If I remove the Mode=TwoWay or the UpdateSourceTrigger the StackOverflow is gone, but the binding doesnt work either.


回答1:


If you are building a UserControl with bindable properties (i.e. dependency properties), you must under no circumstances explicitly set the UserControl's DataContext, be it to the control instance or to any private view model.

If you do that, a Binding like

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

will no longer work. That Binding expects a Title property in the object in the current DataContext. The DataContext property value is usually inherited from the parent element of the UserControl, e.g. the Window. However, since you've explicitly set the DataContext, this mechanism is avoided.

This becomes particularly confusing with equally named properties in UserControls. When you write

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

in UserControl BoxB, your expectation is that the Binding source property is BoxB.Text. In fact it is BoxA.Text, because BoxA's DataContext is the BoxA instance.


So remove any

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

lines and write the Bindings in the UserControl's XAML with RelativeSource like this:

<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}"/>



回答2:


With any form of Change Notificaiton, one danger is what I call the "Ping Pong" problem. Example:

  1. Property A changes.
  2. Property B is changed to match A.
  3. Property B changed.
  4. Property A is changed to match B.
  5. Recurse to 1

In order to avoid that, the exampel code for Properties with Change notificaiton looks like this:

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

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

If the input is the same as output, nothing is done. The squence goes:

  1. Property A changes
  2. Property B is chagned to match A
  3. Proeprty B changes
  4. Property A notices it already has that value anyway, so nothing is done.

My best guess is that WPF Elements have no such protection. It is one of those cases were "trying to be smart could result in being really dumb".



来源:https://stackoverflow.com/questions/48707331/binding-causes-stackoverflow

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!