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
}
}
您应该将 Binding
的 Mode
设置为 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 });
我正在开发的这个用户控件正在运行,现在它已经停止运行,我无法弄清楚发生了什么变化。 (我已经关闭并重新打开 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
}
}
您应该将 Binding
的 Mode
设置为 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 });