我有一个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;
}
}
答案 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属性的通知正在改变。