c# - WPF 用户控件的绑定组在使用数据上下文或如何将验证错误转发到用户控件时不起作用
问题描述
情况:我在 WPF MVVM 应用程序中有一个自定义编辑对话框,其中包含一个选项卡控件和用于确定/取消的按钮。编辑对话框的视图模型由其他用于个人数据、地址数据等的视图模型组成。每个选项卡项只加载一个用户控件,该控件应显示来自相应视图模型的数据。在下文中,我将展示我的实际代码的精简版本,它只关注人员数据(名字和姓氏)。
/// ViewModelBase just provides INotifyPropertyChanged logic
public class PersonViewModel : ViewModelBase
{
private Person model;
public string FirstName
{
get => model.FirstName;
set
{
if (!Equals(model.FirstName, value))
{
model.FirstName = value;
OnPropertyChanged();
}
}
}
public string LastName
{
get => model.LastName;
set
{
if (!Equals(model.LastName, value))
{
model.LastName = value;
OnPropertyChanged();
}
}
}
// ...
}
个人控制.xaml:
<UserControl x:Class="MvvmDemo.Views.PersonControl"
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:vm="clr-namespace:MvvmDemo.ViewModels"
mc:Ignorable="d"
d:DesignHeight="250" d:DesignWidth="400"
d:DataContext="{d:DesignInstance Type=vm:PersonViewModel}">
<!-- Variant A: using dependency properties
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:PersonControl}}}">
-->
<!-- Variant B: using parent data context -->
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type Label}">
<Setter Property="HorizontalAlignment" Value="Right"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Margin" Value="5"/>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="_First Name:" Target="{Binding ElementName=txtFirstName}"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="txtFirstName" Text="{Binding FirstName}"/>
<Label Grid.Row="1" Grid.Column="0" Content="_Last Name:" Target="{Binding ElementName=txtLastName}"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="txtLastName" Text="{Binding LastName}"/>
</Grid>
</UserControl>
PersonControl.xaml.cs:
public partial class PersonControl : UserControl
{
#region Properties
public static readonly DependencyProperty FirstNameProperty =
DependencyProperty.Register("FirstName", typeof(string), typeof(PersonControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register("LastName", typeof(string), typeof(PersonControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string FirstName
{
get => (string)GetValue(FirstNameProperty);
set => SetValue(FirstNameProperty, value);
}
public string LastName
{
get => (string)GetValue(LastNameProperty);
set => SetValue(LastNameProperty, value);
}
// ...
#endregion
public PersonControl()
{
InitializeComponent();
}
}
EditPersonView.xaml:
<Window x:Class="MvvmDemo.Views.EditPersonView"
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:views="clr-namespace:MvvmDemo.Views"
xmlns:viewmodels="clr-namespace:MvvmDemo.ViewModels"
mc:Ignorable="d"
Title="Edit Person" Height="450" Width="600"
WindowStartupLocation="CenterOwner" WindowStyle="ToolWindow" ShowInTaskbar="False" Loaded="HandleWindowLoaded"
d:DataContext="{d:DesignInstance Type=viewmodels:EditPersonViewModel}">
<Window.BindingGroup>
<BindingGroup Name="editBindingGroup">
</BindingGroup>
</Window.BindingGroup>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TabControl Grid.Row="0">
<!-- Variant A: dependency property for each parameter -->
<TabItem Name="tabGeneral" Header="_General">
<views:PersonControl FirstName="{Binding Person.FirstName}"
LastName="{Binding Person.LastName}"/>
</TabItem>
<!-- Variant B: set data context for control -->
<TabItem Name="tabGeneral" Header="_General">
<views:PersonControl DataContext="{Binding Person, BindingGroupName=editBindingGroup}"/>
</TabItem>
</TabControl>
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Name="btnOk" Content="_OK" Width="60" Margin="10,10,0,10" IsDefault="True" Click="OkClicked"/>
<Button Name="btnCancel" Content="_Cancel" Width="60" Margin="10" IsCancel="True" Click="CancelClicked"/>
</StackPanel>
</Grid>
</Window>
EditPersonView.xaml.cs
public partial class EditPersonView : Window
{
public EditPersonView()
{
InitializeComponent();
}
private void OkClicked(object sender, RoutedEventArgs e)
{
e.Handled = true;
if (BindingGroup.CommitEdit())
{
DialogResult = true;
Close();
}
}
private void CancelClicked(object sender, RoutedEventArgs e)
{
e.Handled = true;
BindingGroup.CancelEdit();
DialogResult = false;
Close();
}
}
起初,我使用用户控件的依赖属性来设置值。例如,PersonControl
将具有 DP FirstName
,LastName
并且这些将通过编辑对话框中的正常数据绑定设置(请参阅变体 A 的 XAML)。但是,当向视图模型添加数据验证时,整个用户控件将由红色边框标记,而不是受影响的文本框(考虑到绑定的内容,这是有道理的)。
因为我不知道如何将验证错误转发到用户控件中的正确文本框,所以我决定切换到变体 B 并将DataContext
控件的设置为相应的子视图模型。但是现在绑定组成员身份丢失了。如果我编辑文本框,源会立即更新,而不仅仅是在按下 OK 按钮时。
在调试时,我注意到如果我设置数据上下文,绑定不会添加到绑定组的项目列表中。仅当我使用依赖项属性时才会出现人员视图模型。
因此,要解决这个问题,我必须找到一种方法:a)如果我使用 DP,将验证错误转发到正确的用户控件文本框,或者 b)在设置控件的数据上下文时保持绑定组完整的方法。我真的走到了死胡同,我希望我能在这里找到一些帮助。
BindingGroup.CommitEdit()
我还尝试“嵌套”绑定组,即人员控件提供提交/取消功能,但如果其中一个调用由于验证错误而失败,则可能会导致部分更新。必须有一种方法可以将它放在同一个绑定组中,并让框架的逻辑处理这个问题。
解决方案
推荐阅读
- python - 向所有频道发送消息 — Discord.py
- excel - 如何解决 vlookup 功能的错误 2042
- c# - 添加异步并发以替换 foreach 循环 C#
- javascript - 是否有任何功能可以保持每次选择的数据变化
- python - jupyter notebook security - 限制输入令牌的最大数量(密码)
- linux - Linux和Windows之间的Matlab脚本问题
- python - Flask/Heroku:Main 调用的模块的相对路径
- amazon-web-services - 使用 codebuild 将从 aws ssm 参数获取的文件内容复制到容器
- flutter - 如何更改charts_flutter中的标记点大小?
- docker - 如何在 GitHub 操作中等待容器健康?