验证按钮上的ItemsControl单击WPF

时间:2014-06-24 22:52:52

标签: c# wpf validation data-binding itemscontrol

我有一个ItemsControl,其中包含一个包含两个ComboBox的项目模板。对于任何给定项目,如果第一个ComboBox具有选定值,则需要第二个ComboBox。我已在视图模型上使用IDataErrorInfo设置了此验证。

当用户在ComboBox1中选择一个值时,我想要在用户尝试保存时执行验证,而不是将ComboBox#2标记为无效。有一个表格"叫喊"这有点烦人。在你还没有机会进入的领域,你做错了什么。

通常,您可以通过检索ComboBox的BindingExpression并调用UpdateSource()来强制执行此验证,然后通过调用Validation.GetHasError()传递ComboBox来确定是否存在错误。由于ComboBox是由ItemsControl动态生成的,因此它并不容易。所以我有两个问题:1。如何确保在单击保存按钮时对所有控件执行验证。 2.单击保存按钮时,如何检查是否存在验证错误。对于ItemsControl,Validation.GetHasError仍为false,即使其中的ComboBox2有错误也是如此。谢谢。

编辑: 我跟着this article实现了IDataErrorInfo,以便相对于彼此验证组合框属性。

public class IntroViewModel : INotifyPropertyChanged, IDataErrorInfo
{
    public Guid ClassScheduleID
    {
        get { return _intro.ClassScheduleID; }
        set
        {
            _intro.ClassScheduleID = value;
            OnPropertyChanged("ClassScheduleID");

            //OnPropertyChanged("TrialDate"); //This will trigger validation on ComboBox2 when bound ComboBox1 changes
        }
    }

    public DateTime TrialDate
    {
        get { return _intro.TrialDate; }
        set
        {
            _intro.TrialDate = value;
            OnPropertyChanged("TrialDate");
        }
    }

    public string Error
    {
        get { return null; }
    }

    public string this[string columnName]
    {
        get { return ValidateProperty(columnName); }
    }

    private string ValidateProperty(string propertyName)
    {
        string error = null;

        switch (propertyName)
        {
            case "TrialDate":
                if (_intro.TrialDate == DateTime.MinValue && _intro.ClassScheduleID != Guid.Empty)
                    error = "Required";
                break;
            default:
                error = null;
                break;
        }

        return error;
    }
}

3 个答案:

答案 0 :(得分:1)

我尝试根据一些假设创建您需要的行为

样品

XAML

<StackPanel>
    <StackPanel Orientation="Horizontal">
        <Button Command="{Binding AddItem}"
                Content="Add Item" />
        <Button Command="{Binding Save}"
                Content="Save" />
    </StackPanel>
    <ItemsControl ItemsSource="{Binding Data}"
                  Grid.IsSharedSizeScope="True">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border x:Name="border"
                        BorderThickness="1"
                        Padding="2"
                        Margin="2">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition SharedSizeGroup="value1" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <ComboBox Text="{Binding Value1}"
                                  ItemsSource="{Binding Source={StaticResource sampleData}}" />
                        <ComboBox Text="{Binding Value2}"
                                  ItemsSource="{Binding Source={StaticResource sampleData}}"
                                  Grid.Column="1" />
                    </Grid>
                </Border>
                <DataTemplate.Triggers>
                    <DataTrigger Binding="{Binding IsValid}"
                                 Value="False">
                        <Setter TargetName="border"
                                Property="BorderBrush"
                                Value="Red" />
                    </DataTrigger>
                </DataTemplate.Triggers>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</StackPanel>

主虚拟机

    public ViewModel()
    {

        AddItem = new SimpleCommand(i => Data.Add(new DataViewModel(new DataModel())));
        Save = new SimpleCommand(i =>
            {
                foreach (var vm in Data)
                {
                    vm.ValidateAndSave();
                }
            }
        );
        Data = new ObservableCollection<DataViewModel>();
    }
    public ObservableCollection<DataViewModel> Data { get; set; }
    public ICommand AddItem { get; set; }
    public ICommand Save { get; set; }

数据VM和模型

public class DataModel
{
    public object Value1 { get; set; }
    public object Value2 { get; set; }
}

public class DataViewModel : INotifyPropertyChanged
{
    DataModel model;
    public DataViewModel(DataModel model)
    {
        this.model = model;
        IsValid = true;
    }

    object _value1;
    public object Value1
    {
        get
        {
            return _value1;
        }
        set
        {
            _value1 = value;
        }
    }

    object _value2;
    public object Value2
    {
        get
        {
            return _value2;
        }
        set
        {
            _value2 = value;
        }
    }

    public bool IsValid { get; set; }

    public void ValidateAndSave()
    {
        IsValid = !(_value1 != null && _value2 == null);
        PropertyChanged(this, new PropertyChangedEventArgs("IsValid"));

        if (IsValid)
        {
            model.Value1 = _value1;
            model.Value2 = _value2;
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

因此,当您单击“保存”时,VM将验证所有项目,并且仅保存那些有效的项目。否则会将IsValid属性标记为false,并将通知UI

答案 1 :(得分:1)

我无法告诉你如何在代码中实现IDataErrorInfo接口,但在我的实现中,做你想做的事情很简单。对于将来的用户,您可以在MSDN上的IDataErrorInfo Interface页面上找到有关此界面的信息。在链接页面上,您将看到需要实现Item索引器和Error属性。

这就是你所需要的,因为如果你已经正确地实现了它,那么你只需检查Error属性的值就可以找出你的数据(实现)项是否有错误: / p>

bool hasError = string.IsNullOrEmpty(yourDataTypeInstance.Error);
if (!hasError) Save(yourDataTypeInstance);
else MessageBox.Show("Invalid data!");

更新&gt;&gt;&gt;

请尝试使用此代码:

public DateTime TrialDate
{
    get { return _intro.TrialDate; }
    set
    {
        _intro.TrialDate = value;
        OnPropertyChanged("TrialDate");
        OnPropertyChanged("Error");

    }
}

public string Error
{
    get { return this["TrialDate"]; }
}

我将让您完成余下的工作,主要是管理string

答案 2 :(得分:0)

以下是我在等待答案时如何完成它。启动保存时,调用ValidateTrials()以确保为组合框启动了验证,然后调用TrialsHaveErrors()以检查它们是否存在验证错误。这是我想避免的蛮力方法,但确实有效。

    //Force validation on each combobox2
    private void ValidateTrials()
    {
        foreach (IntroViewModel introVm in icTrials.Items)
        {
            ContentPresenter cp = (ContentPresenter)icTrials.ItemContainerGenerator.ContainerFromItem(introVm);

            if (cp == null) continue;

            ComboBox cb2 = (ComboBox)cp.ContentTemplate.FindName("cb2", (FrameworkElement)cp);

            //Update the source to force validation.
            cb2.GetBindingExpression(ComboBox.SelectedValueProperty).UpdateSource();
        }
    }

    //Recursively searches the Visual Tree for ComboBox elements and checks their errors state
    public bool TrialsHaveError(DependencyObject ipElement)
    {
        if (ipElement!= null)
        {
            for (int x = 0; x < VisualTreeHelper.GetChildrenCount(ipElement); x++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(ipElement, x);
                if (child != null && child is ComboBox)
                {
                    if (Validation.GetHasError(child))
                        return true;
                }

                if (TrialsHaveError(child)) return true;   //We found a combobox with an error
            }
        }

        return false;
    }

减少XAML:

    <ItemsControl Name="icTrials" ItemsSource="{Binding Intros}" Margin="10,6,10,0" >
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid>

                    <Grid Grid.Row="2">

                        <ComboBox Name="cb1"
                                    SelectedValuePath="ID"
                                    SelectedValue="{Binding Path=ClassScheduleID, Converter={StaticResource nullEmptyConverter}, ConverterParameter=System.Guid}"
                                    ItemsSource="{Binding ClassesSource}">
                            <ComboBox.ItemTemplate>
                                <DataTemplate>
                                    ...
                                </DataTemplate>
                            </ComboBox.ItemTemplate>
                        </ComboBox>

                        <ComboBox Name="cb2" 
                                    ItemsSource="{Binding AvailableStartDates}"
                                    DisplayMemberPath="Date"
                                    ItemStringFormat="{}{0:d}"
                                    SelectedValue="{Binding Path=TrialDate, Converter={StaticResource nullEmptyConverter}, ConverterParameter=System.DateTime, ValidatesOnDataErrors=True}">
                        </ComboBox>


                    </Grid>
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

为了避免在用户有机会设置之前标记字段无效的问题,我更新了cb1绑定属性的setter,ClassScheduleID根据值的大小有条件地触发TrialDate属性的通知正在改变。