C# 将Enable属性绑定到BindingGroup.IsDirty

C# 将Enable属性绑定到BindingGroup.IsDirty,c#,wpf,mvvm,data-binding,C#,Wpf,Mvvm,Data Binding,我有一个视图,它的DataContext设置为Employee。 此外,该视图使用BindingGroup和验证规则。 最后视图有两个按钮:保存和取消 保存:验证用户输入,如果成功,保存更改。 取消:回滚用户输入并恢复原始值 直到这一点,它的工作良好 现在是最后一个要求和问题: 为了获得更好的用户体验,我希望在用户开始更改数据时启用“保存”按钮。 为了实现这一点,我将BindingGroup的IsDirty属性绑定到按钮的Enabled属性。 不幸的是,它不起作用。绑定似乎是正确的,但用户界面无

我有一个视图,它的DataContext设置为Employee。 此外,该视图使用BindingGroup和验证规则。 最后视图有两个按钮:保存和取消

保存:验证用户输入,如果成功,保存更改。
取消:回滚用户输入并恢复原始值

直到这一点,它的工作良好

现在是最后一个要求和问题:
为了获得更好的用户体验,我希望在用户开始更改数据时启用“保存”按钮。 为了实现这一点,我将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;
    }

}