WPF UserControl - 为什么值不传播?

WPF UserControl - Why is the value not propagating?

我正在开发的这个用户控件正在运行,现在它已经停止运行,我无法弄清楚发生了什么变化。 (我已经关闭并重新打开 VS2019 几次,它在构建或 运行 时没有显示任何错误。)为了清楚起见,我在下面包含了相关的代码部分,但我在底部包含了所有代码。

MessagePanel.xaml.cs

public string MessageResponse
{
    get { return (string)GetValue(MessageResponseProperty); }
    set { SetValue(MessageResponseProperty, value); }
}

// Using a DependencyProperty as the backing store for MessageResponse.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty MessageResponseProperty =
    DependencyProperty.Register("MessageResponse", typeof(string), typeof(MessagePanel), new PropertyMetadata(""));

private void ButtonProceed_Click(object sender, RoutedEventArgs e)
{
    MessageResponse = "Proceed";
}

private void ButtonHalt_Click(object sender, RoutedEventArgs e)
{
    MessageResponse = "Halt";
}

MessagePanel.xaml

<TextBox Text="{Binding MessageResponse, RelativeSource={RelativeSource AncestorType=UserControl}}" />

MainWindow.xaml

<uc:MessagePanel MessageResponse="{Binding MainMessageResponse}" />

MainVM.cs

private string mainMessageResponse;
public string MainMessageResponse
{
    get => mainMessageResponse;
    set
    {
       mainMessageResponse = value;
       NotifyPropertyChanged();
    }
}

据我所知,MessagePanel 中的 DependencyProperty MessageResponse 应该传播到视图模型 MainVM.cs 中的 MainMessageResponse 属性。当然,如果我在视图模型中插入代码来设置 MainMessageResponse 值,NotifyPropertyChanged() 将触发并且该值出现在 MainWindow.xaml 中的绑定 TextBox 中。但是,当我单击用户控件的任一按钮时,尽管该值出现在 MessagePanel.xaml 中的绑定 TextBox 中,但该值不再传播到 MainMessageResponse.

我在这里错过了什么?

完整代码如下(只剩下最基本的部分):

MessagePanel.xaml

<UserControl x:Class="Common.UserControls.MessagePanel"
    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="100" d:DesignWidth="400"
    Visibility="Visible" >
    <Grid>
        <Border
            MinWidth="50" MinHeight="50"
            Background="LightCoral" 
            BorderBrush="Black"
            BorderThickness="1" 
            HorizontalAlignment="Center"
            VerticalAlignment="Center">
            <StackPanel>
                <StackPanel Width="400" Margin="5,5,5,0">
                    <TextBox x:Name="Title" TextWrapping="Wrap" MinHeight="16" Background="LightPink" HorizontalAlignment="Center" />
                    <TextBox x:Name="Message" IsReadOnly="True" TextWrapping="Wrap" MinHeight="42" Background="LightPink" HorizontalAlignment="Stretch" />
                </StackPanel>
                <DockPanel >
                    <CheckBox x:Name="CheckBoxConfirm" Checked="CheckBoxConfirm_Checked" Unchecked="CheckBoxConfirm_Unchecked" FontStyle="Italic" FontWeight="Bold" HorizontalAlignment="Center" />
                </DockPanel>
                <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                    <Button x:Name="ButtonProceed" Click="ButtonProceed_Click" Width="50" Margin="5,5" />
                    <Button x:Name="ButtonHalt" Click="ButtonHalt_Click" Width="50" Margin="5,5" />
                </StackPanel>
                <TextBox Visibility="Visible" Name="Response" Text="{Binding MessageResponse, RelativeSource={RelativeSource AncestorType=UserControl}}" />
            </StackPanel>
        </Border>
    </Grid>
</UserControl>

MesssagePanel.xaml.cs

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

namespace Common.UserControls
{
    /// <summary>
    /// Interaction logic for MessagePanel.xaml
    /// </summary>
    public partial class MessagePanel : UserControl
    {
        public enum MessageType
        {
            Ok,
            OkCancel,
            YesNo
        }

        public MessagePanel()
        {
            InitializeComponent();

            Title.Text = "This is a title";
            Message.Text = "This is a test message with title and [Yes] and [No] buttons and requires a confirmation.";
            ButtonProceed.Content = "Yes";
            ButtonHalt.Content = "No";
            CheckBoxConfirm.Visibility = Visibility.Collapsed;
        }

        #region Dependeny Properties

        public string MessageResponse
        {
            get { return (string)GetValue(MessageResponseProperty); }
            set { SetValue(MessageResponseProperty, value); }
        }

        // Using a DependencyProperty as the backing store for MessageResponse.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MessageResponseProperty =
            DependencyProperty.Register("MessageResponse", typeof(string), typeof(MessagePanel), new PropertyMetadata(""));

        #endregion

        #region Event Handlers

        private void ButtonProceed_Click(object sender, RoutedEventArgs e)
        {
            // User wants to proceed
            MessageResponse = "Proceed";
        }

        private void ButtonHalt_Click(object sender, RoutedEventArgs e)
        {
            // User wants to not proceed
            MessageResponse = "Halt";
        }

        private void CheckBoxConfirm_Checked(object sender, RoutedEventArgs e)
        {
            ButtonProceed.IsEnabled = true;
        }

        private void CheckBoxConfirm_Unchecked(object sender, RoutedEventArgs e)
        {
            ButtonProceed.IsEnabled = false;
        }

        #endregion
    }
}

MainWindow.xaml

<Window x:Class="WpfUserControl.Views.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:uc="clr-namespace:Common.UserControls;assembly=Common"
        xmlns:local="clr-namespace:WpfUserControl"
        mc:Ignorable="d"
        Title="Demo"
        Height="300" Width="600">
    <Grid>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="10" />
                <RowDefinition Height="50" />
                <RowDefinition Height="50" />
                <RowDefinition Height="10" />
            </Grid.RowDefinitions>
            <StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
                <TextBox Text="{Binding MainMessageResponse}" Width="50" Height="22" />
            </StackPanel>
        </Grid>
        <uc:MessagePanel MessageResponse="{Binding MainMessageResponse}" />
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;
using WpfUserControl.ViewModels;

namespace WpfUserControl.Views
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            MainVM vm = new MainVM();
            DataContext = vm;
        }
    }
}

MainVM.cs

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace WpfUserControl.ViewModels
{
    public partial class MainVM : INotifyPropertyChanged
    {
        public MainVM()
        {
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        #region MessagePanel

        private string mainMessageResponse;
        public string MainMessageResponse
        {
            get => mainMessageResponse;
            set
            {
                mainMessageResponse = value;
                NotifyPropertyChanged();
            }
        }

        #endregion

    }
}

您应该将 BindingMode 设置为 TwoWay。您可以对 XAML 标记中的单个绑定执行此操作:

<uc:MessagePanel MessageResponse="{Binding MainMessageResponse, Mode=TwoWay}" />

或者您可以在控件中注册依赖项属性时为所有绑定指定默认值:

public static readonly DependencyProperty MessageResponseProperty =
    DependencyProperty.Register("MessageResponse", typeof(string), typeof(MessagePanel),
        new FrameworkPropertyMetadata("") { BindsTwoWayByDefault = true });