将Enable属性绑定到BindingGroup.IsDirty

时间:2017-02-27 18:09:20

标签: c# wpf mvvm data-binding

我有一个View谁将DataContext设置为Employee。 此外,该视图使用BindingGroup和Validation Rules。 最后,视图有2个按钮:保存并取消

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

直到这一点它才能正常工作。

现在最后一个要求和问题:
为了获得更好的用户体验,我想在用户开始更改数据时启用保存按钮。 为此,我将BindingGroup的IsDirty属性绑定到Button的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>

代码背后:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        this.MyBindingGroup.BeginEdit();        // Not really needed?
    }   

    private void btnSave_Click(object sender, RoutedEventArgs e)
    {
        if (this.BindingGroup.CommitEdit())
        {
            EmployeeViewModel vm = (EmployeeViewModel)this.DataContext;
            vm.Save();
        }
    }

    private void btnCancel_Click(object sender, RoutedEventArgs e)
    {
        this.BindingGroup.CancelEdit();
    }

    private void btn1_Click(object sender, RoutedEventArgs e)
    {
        tbIsDirty.Text = BindingGroup.IsDirty.ToString();
    }

}

1 个答案:

答案 0 :(得分:0)

由于BindingGroup.IsDirty没有实现INotifyPropertyChanged,因此它不是这种数据绑定的有用源。

可能的解决方案:
  - 在视图中实现INotifyPropertyChanged
  - 使用INotifyPropertyChanged在视图中创建自己的IsDirty   - 为KeyUp添加事件处理程序,在BindingGroup.IsDirty的情况下设置我的IsDirty - 启用绑定到新属性

缺点:如果在视图中实现INotifyPropertyChanged,则需要。

优点:它有效。

ViewBehind of View:

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。另一个优点是,通过这种方式,命令可以使用属性,最后我不必对启用的属性使用数据绑定,因为我通过命令获取它。