WPF。使用MVVM了解UserControl中的错误验证

时间:2015-06-17 20:26:55

标签: wpf validation mvvm user-controls

我正在尝试验证UserControl元素中的一个表单,该表单正由Window内的另一个UserControl使用。 我正在使用MVVM模式,并且我在最后一个UserControl子节点的ViewModel中实现了INotifyDataErrorInfo。 问题是,当发生错误时,UserControl中的TextBox绑定到生成错误的属性,而UserControl本身被一个指示错误的红色框包围,我想只需要TextBox突出显示。

以下是代码:

具有MainView(或第一个UserControl)的窗口:

<Grid>
    <pages:MainPage>
        <pages:MainPage.DataContext>
            <vm:MainViewModel/>
        </pages:MainPage.DataContext>
    </pages:MainPage>
</Grid>

(它只包含一个UserControl作为页面)

&#34; MainPage&#34;的UserControl,其中包含另一个(和最后一个)UserControl作为页面内的页面:

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary>
                <DataTemplate DataType="{x:Type vm:SearchViewModel}">
                    <pages:SearchPage/>
                </DataTemplate>

                ...

        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

...

<ContentControl Content="{Binding CurrentPage}"/>

好的,现在相信我,&#34; CurrentPage&#34;有一个取自MainViewModel属性的ViewModel对象,所以我们假设&#34; CurrentPage&#34;是一个&#34; SearchViewModel&#34;对象,所以我们有SearchPage UserControl。

现在是最后一个UserControl,SearchPage:

        <TextBox Grid.Column="1" Grid.Row="0" Text="{Binding CaseNumber}"/>
        <TextBox Grid.Column="1" Grid.Row="1" Text="{Binding PatientNumber}"/>
        <TextBox Grid.Column="1" Grid.Row="2" Text="{Binding PatientName}"/>
        <TextBox Grid.Column="1" Grid.Row="3" Text="{Binding PatientFamilyName}"/>
        <TextBox Grid.Column="1" Grid.Row="4" Text="{Binding PatientMotherMaidenName}"/>
        <TextBox Grid.Column="1" Grid.Row="5" Text="{Binding DoctorName}"/>

为了让帖子尽可能小,我刚刚添加了#34;表格&#34; UserControl的一部分。

现在最重要的部分是带有INotifyDataErrorInfo实现的SearchViewModel:

public class SearchViewModel : ViewModelBase, INotifyDataErrorInfo, IVMPage
{
    private SearchModel searchModel = new SearchModel();
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    private Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>();


    private string patientNumber;

    public string PatientNumber
    {
        get { return patientNumber; }
        set 
        {
            int number;

            patientNumber = value; 

            if (int.TryParse(value, out number))
            {
                searchModel.PatientNumber = number;
                ClearErrors("PatientNumber");
            }
            else
            {
                AddErrors("PatientNumber", new List<string> { "The value must be a number" });
            }

            RaisePropertyChanged("PatientNumber");
        }
    }
    private string caseNumber;

    public string CaseNumber
    {
        get { return caseNumber; }
        set 
        {
            int number;

            caseNumber = value;

            if (int.TryParse(value, out number))
            {
                searchModel.CaseNumber = number;
                ClearErrors("CaseNumber");
            }
            else
            {
                AddErrors("CaseNumber", new List<string> { "The value must be a number" });
            }

            RaisePropertyChanged("CaseNumber"); 
        }
    }

....

private void ClearErrors(string propertyName)
    {
        errors.Remove(propertyName);

        if (ErrorsChanged != null)
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }

    private void AddErrors(string propertyName, List<string> newErrors)
    {
        errors.Remove(propertyName);
        errors.Add(propertyName, newErrors);

        if(ErrorsChanged != null)
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }
public System.Collections.IEnumerable GetErrors(string propertyName)
    {
        if(string.IsNullOrEmpty(propertyName))
        {
            return errors.Values;
        }
        else
        {
            if(errors.ContainsKey(propertyName))
            {
                return errors[propertyName];
            }
            else
            {
                return null;
            }
        }
    }

    public bool HasErrors
    {
        get { return (errors.Count() > 0); }
    }

所以,问题是: 例如,如果我在&#34; CaseNumber&#34;中引入字符。 TextBox,它被一条红线包围,表示错误,所有的SearchPage UserControl也被另一条红线包围。我想要的只是用红线标记TextBox以指示错误而不是所有UserControl。

奇怪的是,如果我在AddError和ClearError方法中注释了ErrorChanged事件被触发的部分,那么UserControl不再被红线包围......但我不知道为什么..

很抱歉这个问题很长,谢谢。

2 个答案:

答案 0 :(得分:0)

好的,答案很简单。 问题在于这一行:

<ContentControl Content="{Binding CurrentPage}"/>

因为默认情况下WPF将ValidatesOnNotifyDataErrors属性设置为true,所以当“CurrentPage”UserControl内部发生错误时,在UserControl中生成错误的TextBox会按预期指示错误,并在其周围显示红线,但也是ContentControl检查“GetErrors”方法并在所有“CurrentPage”UserControl中绘制另一条红线。

要避免这种情况,只是在TextBox而不是所有UserControl中指出错误,只需将其添加到ContentControl声明:

<ContentControl Content="{Binding CurrentPage, ValidatesOnNotifyDataErrors=False}"/> 

答案 1 :(得分:-1)

简要版

为文本框的背景而不是边框​​着色。

(可选)详细版本

去过那里,碰到那个问题。文本框的边界很难改变,因为很多东西都在玩它。例如,如果您使用的是DevExpress,则必须覆盖整个文本框样式以获取边框,然后在选中该框时开始失去自然突出显示等。

因此,我建议将文本框的背景着色以指示错误。它对用户来说更加明显,看起来很棒,并且在实践中运作良好。

使用浅红色,此页面适合查找与页面现有颜色方案一致的颜色:

https://color.adobe.com/create/color-wheel/