【问题标题】:WPF Grid With Validation Rule And Dependency Property具有验证规则和依赖属性的 WPF 网格
【发布时间】:2017-08-26 00:42:51
【问题描述】:

目前我有一个网格,我正在尝试创建一个带有验证规则的单元格。为了验证它,我需要行的最小值和最大值。

验证类:

public decimal Max { get; set; }

public decimal Min { get; set; }

public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
    var test = i < Min;
    var test2 = i > Max;

    if (test || test2)
        return new ValidationResult(false, String.Format("Fee out of range Min: ${0} Max: ${1}", Min, Max));
    else
        return new ValidationResult(true, null);
}

用户控制:

<telerik:RadGridView SelectedItem ="{Binding SelectedScript}"
                     ItemsSource="{Binding ScheduleScripts}">
    <telerik:RadGridView.Columns>
        <telerik:GridViewDataColumn
            DataMemberBinding="{Binding Amount}" Header="Amount" 
            CellTemplate="{StaticResource AmountDataTemplate}" 
            CellEditTemplate="{StaticResource AmountDataTemplate}"/>   
        <telerik:GridViewComboBoxColumn
            Header="Fee Type" 
            Style="{StaticResource FeeTypeScriptStyle}" 
            CellTemplate="{StaticResource FeeTypeTemplate}"/>           
    </telerik:RadGridView.Columns>
</telerik:RadGridView>

FeeType 类:

public class FeeType
{
    public decimal Min { get; set; }
    public decimal Max { get; set; }
    public string Name { get; set; }
}

我在这里WPF ValidationRule with dependency property 尝试过这个解决方案,效果很好。但是现在我遇到了代理无法通过视图模型实例化的问题。它基于行的选定组合框值的最小值和最大值属性。

例如,组合框示例值如下

Admin Min: $75 Max $500
Late  Min: $0  Max $50

由于网格实际上可以包含任意数量的行,因此我看不出在我的情况下创建代理将如何工作。如果我能得到一些指导提示,将不胜感激。

【问题讨论】:

  • 您的代码中只有一个ComboBox
  • @AnjumSKhan 假设只有一个 ComboBox。组合框值的类型为 FeeType 类。所以无论选择什么都决定了它的最小值和最大值。
  • 您确定这不是XY problem 吗?使用ValidationRule 来完成这项工作需要付出很多努力,而如果您将验证逻辑移至视图模型,这将相当容易完成。
  • @Grx70 我同意使用 ValidationRule 完成这项工作需要付出很多努力,但它改善了用户体验以收到错误/验证失败的通知。
  • @Master 我的意思是,与其实现和使用自定义ValidationRule,不如将​​验证逻辑与实现IDataErrorInfo 一起放在视图模型中,然后使用DataErrorValidationRule,这将做剩下的工作。对于 .Net 4.5 或更高版本,您也可以改用 INotifyDataErrorInfo + NotifyDataErrorValidationRule

标签: c# wpf telerik-grid validationrule


【解决方案1】:

警告:这不是一个确定的解决方案,而是向您展示了一种将验证逻辑完全放在 ViewModel 上的正确方法。

为了简洁起见,我将 FeeTypes 列表创建为 FeeType 类的静态属性:

public class FeeType
{
    public decimal Min { get; set; }
    public decimal Max { get; set; }
    public string Name { get; set; }

    public static readonly FeeType[] List = new[]
    {
        new FeeType { Min = 0, Max = 10, Name = "Type1", },
        new FeeType { Min = 2, Max = 20, Name = "Type2", },
    };
}

这是单个 Grid 行的 ViewModel。我只放置了 Amount 和 Fee 属性。

public class RowViewModel : INotifyPropertyChanged, INotifyDataErrorInfo
{
    public RowViewModel()
    {
        _errorFromProperty = new Dictionary<string, string>
        {
            { nameof(Fee), null },
            { nameof(Amount), null },
        };

        PropertyChanged += OnPropertyChanged;
    }

    private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch(e.PropertyName)
        {
            case nameof(Fee):
                OnFeePropertyChanged();
                break;
            case nameof(Amount):
                OnAmountPropertyChanged();
                break;
            default:
                break;
        }
    }

    private void OnFeePropertyChanged()
    {
        if (Fee == null)
            _errorFromProperty[nameof(Fee)] = "You must select a Fee!";
        else
            _errorFromProperty[nameof(Fee)] = null;

        NotifyPropertyChanged(nameof(Amount));
    }

    private void OnAmountPropertyChanged()
    {
        if (Fee == null)
            return;

        if (Amount < Fee.Min || Amount > Fee.Max)
            _errorFromProperty[nameof(Amount)] = $"Amount must be between {Fee.Min} and {Fee.Max}!";
        else
            _errorFromProperty[nameof(Amount)] = null;
    }

    public decimal Amount
    {
        get { return _Amount; }
        set
        {
            if (_Amount != value)
            {
                _Amount = value;
                NotifyPropertyChanged();
            }
        }
    }
    private decimal _Amount;

    public FeeType Fee
    {
        get { return _Fee; }
        set
        {
            if (_Fee != value)
            {
                _Fee = value;
                NotifyPropertyChanged();
            }
        }
    }
    private FeeType _Fee;

    #region INotifyPropertyChanged
    public event PropertyChangedEventHandler PropertyChanged;
    public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion

    #region INotifyDataErrorInfo
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public bool HasErrors
    {
        get
        {
            return _errorFromProperty.Values.Any(x => x != null);
        }
    }

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName))
            return _errorFromProperty.Values;

        else if (_errorFromProperty.ContainsKey(propertyName))
        {
            if (_errorFromProperty[propertyName] == null)
                return null;
            else
                return new[] { _errorFromProperty[propertyName] };
        }

        else
            return null;
    }

    private Dictionary<string, string> _errorFromProperty;
    #endregion
}

现在,我用原生 DataGrid 测试了它,但是在 Telerik 中结果应该是一样的:

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Rows}">
  <DataGrid.Columns>
    <DataGridTextColumn Binding="{Binding Amount}"/>
    <DataGridComboBoxColumn SelectedItemBinding="{Binding Fee, UpdateSourceTrigger=PropertyChanged}"
                            ItemsSource="{x:Static local:FeeType.List}"
                            DisplayMemberPath="Name"
                            Width="200"/>
  </DataGrid.Columns>
</DataGrid>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        Rows = new List<RowViewModel>
        {
            new RowViewModel(),
            new RowViewModel(),
        };

        DataContext = this;
    }

    public List<RowViewModel> Rows { get; } 
}

如果FeeType 实例可以在运行时修改MinMax,您还需要在该类上实现INotifyPropertyChanged,并适当地处理值更改。

如果您对 “MVVM”、“ViewModels”、“通知更改”等不熟悉,请查看 this article。如果你经常在 WPF 上做大中型项目,那么值得学习如何通过 MVVM 模式来解耦视图和逻辑。这使您能够以更快、更自动化的方式测试逻辑,并使事情井井有条并集中注意力。

【讨论】:

  • 虽然在这个特定示例中可能不需要,但最好同时引发 INotifyDataErrorInfo.ErrorsChanged 事件。
  • @Grx70,好吧,实际上 WPF 并不要求您在简单的场景中引发 ErrorsChanged 事件:它会在引发 PropertyChanged 事件时重新检查错误。如果您将我的代码复制到 VS 解决方案中并运行它,您可以看到这一点。引发 ErrorsChanged 事件仅在更复杂的场景中有用,并且由于我的代码已经很长,我不想让它更长,只是为了解决 在这种情况下只是理论上的问题.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-21
  • 2013-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-18
相关资源
最近更新 更多