更改属性时,数据网格复选框列不会在 MVVM 中更新



我正在处理的应用程序中有两个键布尔值,用于指示订单是否有效,然后指示订单是否已获得批准。我想要的是当 ObservableCollection 对象中的值更改时,复选框在两列上更新,以便用户知道订单是否已准备好获得批准以及是否已获得批准。

模型中的值正确更新,视图模型中的批准按钮命令按预期设置批准的布尔值,但是我无法获得复选框以动态更新。目前,它仅通过更改为其他视图然后更改回订单视图来更新。

我仍在学习数据绑定和 MVVM 模型,所以我尝试的一些是实验。到目前为止,我已经尝试了几种不同的方式来尝试获得我想要的行为,但没有一种奏效。下面是视图模型代码:

<UserControl x:Class="US_Wholesale_App_V2.Views.SPSOrderView"
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:local="clr-namespace:US_Wholesale_App_V2.Views"
xmlns:viewModels="clr-namespace:US_Wholesale_App_V2.ViewModels"
xmlns:models="clr-namespace:US_Wholesale_App_V2.Models"
mc:Ignorable="d" 
d:DesignHeight="800" d:DesignWidth="1100">
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModels:SPSLineVM}">
<local:SPSLineView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:CustomerInfoVM}">
<local:CustomerInfoView/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="AUTO" MinWidth="312"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel
Orientation="Horizontal"
HorizontalAlignment="Right"
Grid.Row="0"
Grid.Column="0">
<Button
Content="Lines"
Height="20"
Width="75"
Margin="5,5,5,10"
Command="{Binding SPSNavCommand}"
CommandParameter="Lines"/>
<Button
Content="Shipping"
Height="20"
Width="75"
Margin="5,5,5,10"
Command="{Binding SPSNavCommand}"
CommandParameter="Shipping"/>
</StackPanel>
<DataGrid
x:Name="OrderHeaderGrid"
AutoGenerateColumns="False"
Margin="5"
Grid.Row="1"
Grid.Column="0"
ItemsSource="{Binding SpsData}"
SelectedItem="{Binding SelectedOrder, Mode=TwoWay}"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn
Header="PO Number"
x:Name="PONumberCol"
Binding="{Binding PONumber}"/>
<DataGridTextColumn
Header="Customer"
x:Name="CustomerCol"
Binding="{Binding Customer}"/>
<DataGridTextColumn
Header="Retailer PO Number"
x:Name="RetailersPONumberCol"
Binding="{Binding RetailersPONumber}"/>
<DataGridTextColumn
Header="DC Code"
x:Name="DCCodeCol"
Binding="{Binding DCCode}"/>
<DataGridCheckBoxColumn
Binding="{Binding IsValid, Mode=OneWay}"
IsReadOnly="True"/>
<DataGridTemplateColumn
Header="Approve">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Content="Approve"
Command="{Binding DataContext.ApproveButton, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding SelectedOrder}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridCheckBoxColumn
Binding="{Binding IsApproved, Mode=OneWay}"
IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
<ContentControl
Grid.Row="1"
Grid.Column="1"
Margin="5">
<ContentControl Content="{Binding SPSCurrentVM}"/>
</ContentControl>
</Grid>
</UserControl>

如果它有用,这里也是模型的代码。RGLibrary项目只是我需要的常见类的库,例如SQL连接器或FTP方法。

using RGLibrary;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.ComponentModel;
using System.Threading.Tasks;
namespace US_Wholesale_App_V2.Models
{
public class SPSOrderModel : Observable_Object, IDataErrorInfo
{
private bool _IsApproved;
private string _PONumber;
private string _RetailersPONumber;
private string _PODate;
private string _ShipDate;
private string _CancelDate;
private string _POPurpose;
private string _POType;
private string _VendorNumber;
private string _Customer;
private string _DCCode;
private string _DepartmentNo;
private ObservableCollection<SPSLineModel> _Lines;
private BillingCustomerModel _BillingInfo;
private ShippingCustomerModel _ShippingInfo;
private ShippingChargesModel _Charges;

public bool IsApproved
{
get { return _IsApproved; }
set { _IsApproved = value; }
}
public string PONumber
{
get { return _PONumber; }
set { _PONumber = value; }
}
public string RetailersPONumber
{
get { return _RetailersPONumber; }
set { _RetailersPONumber = value; }
}
public string PODate
{
get { return _PODate; }
set { _PODate = value; }
}
public string ShipDate
{
get { return _ShipDate; }
set { _ShipDate = value; }
}
public string CancelDate
{
get { return _CancelDate; }
set { _CancelDate = value; }
}
public string POPurpose
{
get { return _POPurpose; }
set { _POPurpose = value; }
}
public string POType
{
get { return _POType; }
set { _POType = value; }
}
public string VendorNumber
{
get { return _VendorNumber; }
set { _VendorNumber = value; }
}
public string Customer
{
get { return _Customer; }
set { _Customer = value; }
}
public string DCCode
{
get { return _DCCode; }
set { _DCCode = value; }
}
public string DepartmentNo
{
get { return _DepartmentNo; }
set { _DepartmentNo = value; }
}
public ObservableCollection<SPSLineModel> Lines
{
get { return _Lines; }
set { _Lines = value; }
}
public BillingCustomerModel BillingInfo
{
get { return _BillingInfo; }
set { _BillingInfo = value; }
}
public ShippingCustomerModel ShippingInfo
{
get { return _ShippingInfo; }
set { _ShippingInfo = value; }
}
public ShippingChargesModel Charges
{
get { return _Charges; }
set { _Charges = value; }
}
public string Error
{
get
{
var errorMsg = new StringBuilder();
if (string.IsNullOrWhiteSpace(DepartmentNo))
errorMsg.AppendLine("The department number for this order cannot be blank");
if (string.IsNullOrWhiteSpace(PODate))
errorMsg.AppendLine("You must enter a purchase order date");
else if (!DateValidate("PODate", PODate))
errorMsg.AppendLine("The PO date format must be MM/DD/YYY");
if (string.IsNullOrWhiteSpace(CancelDate))
errorMsg.AppendLine("You must enter a cancellation date");
else if (!DateValidate("CancelDate", CancelDate))
errorMsg.AppendLine("The cancellation date format must be MM/DD/YYY");
if (string.IsNullOrWhiteSpace(ShipDate))
errorMsg.AppendLine("You must enter a shipment date");
else if (!DateValidate("ShipDate", ShipDate))
errorMsg.AppendLine("The shipment date format must be MM/DD/YYY");
if (string.IsNullOrWhiteSpace(DCCode))
errorMsg.AppendLine("You must enter a DC code");
if (Lines.Any(p => !p.IsValid))
errorMsg.AppendLine("You must correct all errors in the purchase order lines, or remove any lines with errors");
if (SumDispatch(Lines) < 1)
errorMsg.AppendLine("The total dispatch quantity must be at least 1");
return errorMsg.ToString();
}
}
public bool IsValid
{
get
{
bool check = true;
if (IsHeaderValid == false)
check = false;
if (BillingValid == false)
check = false;
if (ShippingValid == false)
check = false;
if (ChargesValid == false)
check = false;
if (LinesValid == false)
check = false;
return check;
}
}
public bool BillingValid
{
get
{
if (BillingInfo != null)
return BillingInfo.IsValid;
else
return false;
}
}
public bool ShippingValid
{
get
{
if (ShippingInfo != null)
return ShippingInfo.IsValid;
else
return false;
}
}
public bool ChargesValid
{
get
{
if (Charges != null)
return Charges.IsValid;
else
return false;
}
}
public bool LinesValid
{
get
{
bool check = true;
if (Lines.Any(l => l.IsValid == false))
check = false;
return check;
}
}
public bool IsHeaderValid
{
get
{
bool check = true;
if (string.IsNullOrWhiteSpace(PODate))
check = false;
if (!DateValidate("PODate", PODate))
check = false;
if (string.IsNullOrWhiteSpace(CancelDate))
check = false;
if (!DateValidate("CancelDate", CancelDate))
check = false;
if (string.IsNullOrWhiteSpace(ShipDate))
check = false;
if (!DateValidate("ShipDate", ShipDate))
check = false;
if (string.IsNullOrWhiteSpace(DCCode))
check = false;
if (Lines.Any(p => !p.IsValid))
check = false;
if (SumDispatch(Lines) < 1)
check = false;
if (string.IsNullOrWhiteSpace(DepartmentNo))
check = false;
return check;
}
}
public string this[string name]
{
get
{
string result = null;
if (name == "PODate")
{
if (string.IsNullOrWhiteSpace(PODate))
result = "You must enter a date";
else if (!DateValidate("PODate", PODate))
result = "Date format must be MM/DD/YYY";
}
if (name == "CancelDate")
{
if (string.IsNullOrWhiteSpace(CancelDate))
result = "You must enter a date";
else if (!DateValidate("CancelDate", CancelDate))
result = "Date format must be MM/DD/YYY";
}
if (name == "ShipDate")
{
if (string.IsNullOrWhiteSpace(ShipDate))
result = "You must enter a date";
else if (!DateValidate("ShipDate", ShipDate))
result = "Date format must be MM/DD/YYY";
}
if (name == "DCCode")
{
if (string.IsNullOrWhiteSpace(DCCode))
result = "You must enter a DC code";
}
return result;
}
}

private static bool DateValidate(string sender, object value)
{
bool check = true;
if (sender == "PODate" || sender == "CancelDate" || sender == "ShipDate")
{
string _date = value.ToString();
string _expression = @"(([0]d)|(11|12))/(([012]d)|(30|31))/(20)d{2}";
Regex _Regex = new Regex(_expression);
Match _match = _Regex.Match(_date);
if (!_match.Success)
check = false;
}
return check;
}
private static int SumDispatch(ObservableCollection<SPSLineModel> lines)
{
int dispatchQty = 0;
foreach (SPSLineModel line in lines)
{
dispatchQty += line.DispatchQty;
}
return dispatchQty;
}
}
}

正如max在这个问题上提到的,我需要在IsApproved和IsValid字段上实现一个PropertyChanged事件。对于IsApproved,这相当简单,因为我可以从继承的Observable Object类中重用该事件:

using System;
using System.ComponentModel;
using System.Diagnostics;
namespace RGLibrary
{
public abstract class Observable_Object : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public virtual void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
}
}

因此,SPSOrderModel 类中 IsApproved 的实现是:

public bool IsApproved
{
get { return _IsApproved; }
set { _IsApproved = value; OnPropertyChanged("IsApproved"); }
}

一旦我有了 IsValid 属性的答案,我将编辑此响应,因为这是一个只读属性,尝试向其添加 OnPropertyChanged 会导致堆栈溢出错误。

最新更新