C# 将Enable属性绑定到BindingGroup.IsDirty
我有一个视图,它的DataContext设置为Employee。 此外,该视图使用BindingGroup和验证规则。 最后视图有两个按钮:保存和取消 保存:验证用户输入,如果成功,保存更改。C# 将Enable属性绑定到BindingGroup.IsDirty,c#,wpf,mvvm,data-binding,C#,Wpf,Mvvm,Data Binding,我有一个视图,它的DataContext设置为Employee。 此外,该视图使用BindingGroup和验证规则。 最后视图有两个按钮:保存和取消 保存:验证用户输入,如果成功,保存更改。 取消:回滚用户输入并恢复原始值 直到这一点,它的工作良好 现在是最后一个要求和问题: 为了获得更好的用户体验,我希望在用户开始更改数据时启用“保存”按钮。 为了实现这一点,我将BindingGroup的IsDirty属性绑定到按钮的Enabled属性。 不幸的是,它不起作用。绑定似乎是正确的,但用户界面无
取消:回滚用户输入并恢复原始值 直到这一点,它的工作良好 现在是最后一个要求和问题:
为了获得更好的用户体验,我希望在用户开始更改数据时启用“保存”按钮。 为了实现这一点,我将BindingGroup的IsDirty属性绑定到按钮的Enabled属性。 不幸的是,它不起作用。绑定似乎是正确的,但用户界面无法识别IsDirty的更改 谁能解决这个问题 我的模型:
public class EmployeeModel:ModelBase
{
private int _nr;
private string _firstname;
private string _lastname;
public int Nr
{
get
{
return _nr;
}
set
{
_nr = value;
OnChanged(nameof(Nr));
}
}
public string Firstname
{
get
{
return _firstname;
}
set
{
_firstname = value;
OnChanged(nameof(Firstname));
}
}
public string Lastname
{
get
{
return _lastname;
}
set
{
_lastname = value;
OnChanged(nameof(Lastname));
}
}
}
模型库:
public class ModelBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnChanged(string propertyname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
}
验证规则:
public class EmployeeValidationRule:ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
BindingGroup bindingGroup = (BindingGroup)value;
if (bindingGroup.Items.Count == 2)
{
EmployeeModel employee = (EmployeeModel)bindingGroup.Items[1];
string firstname = (string)bindingGroup.GetValue(employee, "Firstname");
string lastname = (string)bindingGroup.GetValue(employee, "Lastname");
if (firstname.Length == 0)
return new ValidationResult(false, "Firstname can not be empty.");
if (lastname.Length == 0)
return new ValidationResult(false, "Lastname can not be empty.");
}
return ValidationResult.ValidResult;
}
}
我的ViewModel:
public class EmployeeViewModel
{
private EmployeeModel _employeeModel;
public EmployeeModel Employee
{
get
{
return _employeeModel;
}
set
{
_employeeModel = value;
}
}
public EmployeeViewModel()
{
LoadData();
}
private void LoadData()
{
//Employee = (from e in _context.Employee
// where e.Nr == 158
// select e).FirstOrDefault();
Employee = new EmployeeModel() { Firstname = "Billy", Lastname = "Wilder" };
}
public void Save()
{
//_context.SaveChanges();
}
}
最后,我认为:
<Window x:Class="WpfApplication3_Validation.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:WpfApplication3_Validation"
xmlns:vm="clr-namespace:WpfApplication3_Validation.ViewModel"
xmlns:vr="clr-namespace:WpfApplication3_Validation.ValidationRules"
mc:Ignorable="d"
Title="Employee" Height="250" Width="525"
Validation.ValidationAdornerSite="{Binding ElementName=lbErrors}" Loaded="Window_Loaded">
<Window.DataContext>
<vm:EmployeeViewModel/>
</Window.DataContext>
<Window.BindingGroup>
<BindingGroup x:Name="MyBindingGroup">
<BindingGroup.ValidationRules>
<vr:EmployeeValidationRule/>
</BindingGroup.ValidationRules>
</BindingGroup>
</Window.BindingGroup>
<Grid x:Name="gridMain">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Content="Nr:"/>
<TextBlock Grid.Column="1" Text="{Binding Employee.Nr}"/>
<Label Grid.Row="1" Content="Vorname:" Target="{Binding ElementName=tbFirstname}"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="tbFirstname" Text="{Binding Employee.Firstname}"/>
<Label Grid.Row="2" Content="Nachname:" Target="{Binding ElementName=tbLastname}"/>
<TextBox Grid.Row="2" Grid.Column="1" x:Name="tbLastname" Text="{Binding Employee.Lastname}"/>
<Label Grid.Row="4" Grid.Column="0" x:Name="lbErrors" Content="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)[0].ErrorContent}"
Foreground="Red" FontWeight="Bold"/>
<StackPanel Grid.Row="4" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right">
<TextBlock x:Name="tbIsDirty"/>
<Button x:Name="btn1" Content="IsDirty?" Click="btn1_Click"/>
<Button x:Name="btnSave" Content="Save1" Click="btnSave_Click" />
<Button x:Name="btnSave1" Content="Save2" Click="btnSave_Click" IsEnabled="{Binding ElementName=MyBindingGroup, Path=IsDirty}"/>
<Button x:Name="btnCancel" Content="Cancel" Click="btnCancel_Click"/>
</StackPanel>
</Grid>
由于BindingGroup.IsDirty未实现INotifyPropertyChanged,因此它不是此类数据绑定的有用来源 可能的解决方案:
-在视图中实现INotifyPropertyChanged
-使用INotifyPropertyChanged在视图中创建自己的IsDirty
-添加KeyUp的事件处理程序,在BindingGroup.IsDirty的情况下设置我的IsDirty。
-已启用的绑定到新属性
缺点:需要在视图中更改InotifyProperty的实现 优点:有效 代码隐藏视图:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnChanged(string propertyname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
private bool _isDirty;
public bool IsDirty
{
get
{
return _isDirty;
}
set
{
_isDirty = value;
OnChanged(nameof(IsDirty));
}
}
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.MyBindingGroup.BeginEdit(); // Not really needed?
gridMain.KeyUp += GridMain_KeyUp;
}
private void GridMain_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (this.MyBindingGroup.IsDirty)
{
IsDirty = true;
}
}
private void btnSave_Click(object sender, RoutedEventArgs e)
{
if (this.BindingGroup.CommitEdit())
{
EmployeeViewModel vm = (EmployeeViewModel)this.DataContext;
vm.Save();
IsDirty = false;
}
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
this.BindingGroup.CancelEdit();
IsDirty = false;
}
}
进一步改进:现在我将IsDirty移到了我的ViewModel,所以我不必在视图中实现INPC。另一个优点是,通过这种方式,命令可以使用该属性,最后我不必对已启用的属性使用数据绑定,因为我通过命令获得了该属性。您似乎没有正确使用
BindingGroup
。但是如果没有一个能清楚显示你想要做什么的好消息,就不可能确定地说。请改进你的问题。解释你具体希望代码做什么,它现在正在做什么,以及为什么你认为BindingGroup
将帮助你实现目标。我已经重写了原始帖子,并为我的测试应用程序添加了完整的代码。希望它能更好地描述我的意图和问题。好吧,我想我明白你想做什么了。我认为这不会奏效。虽然BindingGroup
继承了DependencyObject
,但我不知道为什么,因为它没有任何依赖属性。而且它不实现INotifyPropertyChanged
。因此,当IsDirty
更改时,绑定系统无法注意到这一点。你需要用另一种方式来处理这个问题。FWIW,我不需要做太多这类事情。但当我这样做时,我编写了一个附加属性,以引用附加到标记中特定元素的验证规则。我只需要提供一个属性来传递给附加属性,该属性返回元素列表(例如文本框等)
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnChanged(string propertyname)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
}
private bool _isDirty;
public bool IsDirty
{
get
{
return _isDirty;
}
set
{
_isDirty = value;
OnChanged(nameof(IsDirty));
}
}
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.MyBindingGroup.BeginEdit(); // Not really needed?
gridMain.KeyUp += GridMain_KeyUp;
}
private void GridMain_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (this.MyBindingGroup.IsDirty)
{
IsDirty = true;
}
}
private void btnSave_Click(object sender, RoutedEventArgs e)
{
if (this.BindingGroup.CommitEdit())
{
EmployeeViewModel vm = (EmployeeViewModel)this.DataContext;
vm.Save();
IsDirty = false;
}
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
this.BindingGroup.CancelEdit();
IsDirty = false;
}
}