Phát hiện lỗi xác thực WPF


115

Trong WPF, bạn có thể thiết lập xác thực dựa trên các lỗi được đưa ra trong Lớp dữ liệu của bạn trong quá trình Liên kết dữ liệu bằng cách sử dụng ExceptionValidationRulehoặc DataErrorValidationRule.

Giả sử bạn có một loạt các điều khiển được thiết lập theo cách này và bạn có nút Lưu. Khi người dùng nhấp vào nút Lưu, bạn cần đảm bảo rằng không có lỗi xác thực nào trước khi tiến hành lưu. Nếu có lỗi xác thực, bạn muốn xử lý chúng.

Trong WPF, làm cách nào để bạn tìm hiểu xem có bất kỳ điều khiển Ranh giới dữ liệu nào của bạn có đặt lỗi xác thực hay không?

Câu trả lời:


137

Bài đăng này rất hữu ích. Cảm ơn tất cả những người đã đóng góp. Đây là một phiên bản LINQ mà bạn sẽ yêu hoặc ghét.

private void CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
    e.CanExecute = IsValid(sender as DependencyObject);
}

private bool IsValid(DependencyObject obj)
{
    // The dependency object is valid if it has no errors and all
    // of its children (that are dependency objects) are error-free.
    return !Validation.GetHasError(obj) &&
    LogicalTreeHelper.GetChildren(obj)
    .OfType<DependencyObject>()
    .All(IsValid);
}

1
Tôi thích giải pháp cụ thể này rất nhiều!
ChristopheD

Chỉ tình cờ gặp chủ đề này. Chức năng nhỏ rất hữu ích. Cảm ơn!
Olav Haugen

Có cách nào để chỉ liệt kê những DependencyObjects bị ràng buộc với một DataContext cụ thể không? Tôi không thích ý tưởng về treewalk. Có thể có một tập hợp các ràng buộc được liên kết với một nguồn dữ liệu cụ thể.
ZAB

5
Chỉ tự hỏi, làm thế nào để bạn gọi IsValidchức năng? Tôi thấy bạn đã thiết lập một CanExecutemà tôi đoán có liên quan đến lệnh của nút Lưu. Điều này sẽ hoạt động nếu tôi không sử dụng lệnh? Và nút có liên quan như thế nào đến các điều khiển khác cần được kiểm tra? Suy nghĩ duy nhất của tôi về cách sử dụng điều này là gọi IsValidmỗi điều khiển cần được xác thực. Chỉnh sửa: Có vẻ như bạn đang xác thực cái sendermà tôi mong đợi là nút lưu. Điều đó dường như không đúng với tôi.
Nicholas Miller,

1
@Nick Miller a Windowcũng là một đối tượng phụ thuộc. Tôi có lẽ anh ấy đang thiết lập nó với một số loại trình xử lý sự kiện trên Window. Ngoài ra, bạn có thể gọi nó trực tiếp IsValid(this)từ Windowlớp học.
akousmata

47

Đoạn mã sau (từ cuốn sách Lập trình WPF của Chris Sell & Ian Griffiths) xác thực tất cả các quy tắc ràng buộc trên một đối tượng phụ thuộc và con của nó:

public static class Validator
{

    public static bool IsValid(DependencyObject parent)
    {
        // Validate all the bindings on the parent
        bool valid = true;
        LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
        while (localValues.MoveNext())
        {
            LocalValueEntry entry = localValues.Current;
            if (BindingOperations.IsDataBound(parent, entry.Property))
            {
                Binding binding = BindingOperations.GetBinding(parent, entry.Property);
                foreach (ValidationRule rule in binding.ValidationRules)
                {
                    ValidationResult result = rule.Validate(parent.GetValue(entry.Property), null);
                    if (!result.IsValid)
                    {
                        BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
                        System.Windows.Controls.Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null));
                        valid = false;
                    }
                }
            }
        }

        // Validate all the bindings on the children
        for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
        {
            DependencyObject child = VisualTreeHelper.GetChild(parent, i);
            if (!IsValid(child)) { valid = false; }
        }

        return valid;
    }

}

Bạn có thể gọi điều này trong trình xử lý sự kiện nhấp vào nút lưu của bạn như thế này trong trang / cửa sổ của bạn

private void saveButton_Click(object sender, RoutedEventArgs e)
{

  if (Validator.IsValid(this)) // is valid
   {

    ....
   }
}

33

Mã đã đăng không hoạt động đối với tôi khi sử dụng ListBox. Tôi đã viết lại nó và bây giờ nó hoạt động:

public static bool IsValid(DependencyObject parent)
{
    if (Validation.GetHasError(parent))
        return false;

    // Validate all the bindings on the children
    for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
    {
        DependencyObject child = VisualTreeHelper.GetChild(parent, i);
        if (!IsValid(child)) { return false; }
    }

    return true;
}

1
Bỏ phiếu cho giải pháp của bạn để làm việc trên ItemsControl của tôi.
Jeff T.

1
Tôi đang sử dụng giải pháp này để kiểm tra xem datagrid của tôi có lỗi xác thực hay không. Tuy nhiên, phương thức này được gọi trong phương thức canexecute lệnh viewmodel của tôi và tôi nghĩ rằng việc truy cập các đối tượng cây trực quan bằng cách nào đó vi phạm MVVM pattern, phải không? Bất kỳ lựa chọn thay thế?
Igor Kondrasovas

16

Gặp vấn đề tương tự và đã thử các giải pháp được cung cấp. Sự kết hợp giữa các giải pháp của H-Man2 và skiba_k gần như hoạt động tốt đối với tôi, ngoại trừ một ngoại lệ: Cửa sổ của tôi có TabControl. Và các quy tắc xác thực chỉ được đánh giá cho TabItem hiện đang hiển thị. Vì vậy, tôi đã thay thế VisualTreeHelper bằng LogicalTreeHelper. Bây giờ nó hoạt động.

    public static bool IsValid(DependencyObject parent)
    {
        // Validate all the bindings on the parent
        bool valid = true;
        LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
        while (localValues.MoveNext())
        {
            LocalValueEntry entry = localValues.Current;
            if (BindingOperations.IsDataBound(parent, entry.Property))
            {
                Binding binding = BindingOperations.GetBinding(parent, entry.Property);
                if (binding.ValidationRules.Count > 0)
                {
                    BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
                    expression.UpdateSource();

                    if (expression.HasError)
                    {
                        valid = false;
                    }
                }
            }
        }

        // Validate all the bindings on the children
        System.Collections.IEnumerable children = LogicalTreeHelper.GetChildren(parent);
        foreach (object obj in children)
        {
            if (obj is DependencyObject)
            {
                DependencyObject child = (DependencyObject)obj;
                if (!IsValid(child)) { valid = false; }
            }
        }
        return valid;
    }

7

Ngoài việc triển khai LINQ tuyệt vời của Dean, tôi đã rất vui khi gói mã thành một phần mở rộng cho DependencyObjects:

public static bool IsValid(this DependencyObject instance)
{
   // Validate recursivly
   return !Validation.GetHasError(instance) &&  LogicalTreeHelper.GetChildren(instance).OfType<DependencyObject>().All(child => child.IsValid());
}

Điều này làm cho nó trở nên cực kỳ tốt khi xem xét sự tái sinh.


2

Tôi sẽ cung cấp một tối ưu hóa nhỏ.

Nếu bạn thực hiện điều này nhiều lần trên cùng một điều khiển, bạn có thể thêm đoạn mã trên để giữ danh sách điều khiển thực sự có quy tắc xác thực. Sau đó, bất cứ khi nào bạn cần kiểm tra tính hợp lệ, chỉ xem xét các điều khiển đó, thay vì toàn bộ cây trực quan. Điều này sẽ được chứng minh là tốt hơn nhiều nếu bạn có nhiều điều khiển như vậy.


2

Đây là một thư viện để xác thực biểu mẫu trong WPF. Gói Nuget đây .

Mẫu vật:

<Border BorderBrush="{Binding Path=(validationScope:Scope.HasErrors),
                              Converter={local:BoolToBrushConverter},
                              ElementName=Form}"
        BorderThickness="1">
    <StackPanel x:Name="Form" validationScope:Scope.ForInputTypes="{x:Static validationScope:InputTypeCollection.Default}">
        <TextBox Text="{Binding SomeProperty}" />
        <TextBox Text="{Binding SomeOtherProperty}" />
    </StackPanel>
</Border>

Ý tưởng là chúng ta xác định phạm vi xác nhận thông qua thuộc tính đính kèm cho nó biết những điều khiển đầu vào nào cần theo dõi. Sau đó, chúng tôi có thể làm:

<ItemsControl ItemsSource="{Binding Path=(validationScope:Scope.Errors),
                                    ElementName=Form}">
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="{x:Type ValidationError}">
            <TextBlock Foreground="Red"
                       Text="{Binding ErrorContent}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

0

Bạn có thể lặp lại đệ quy tất cả các cây điều khiển của mình và kiểm tra thuộc tính kèm theo Validation.HasErrorProperty, sau đó tập trung vào cái đầu tiên bạn tìm thấy trong đó.

bạn cũng có thể sử dụng nhiều giải pháp đã được viết sẵn, bạn có thể kiểm tra chuỗi này để biết ví dụ và biết thêm thông tin


0

Bạn có thể quan tâm đến ứng dụng mẫu BookLibrary của Khung ứng dụng WPF (WAF) . Nó chỉ ra cách sử dụng xác thực trong WPF và cách kiểm soát nút Lưu khi có lỗi xác thực.


0

Trong biểu mẫu câu trả lời aogan, thay vì lặp lại rõ ràng qua các quy tắc xác thực, tốt hơn chỉ nên gọi expression.UpdateSource():

if (BindingOperations.IsDataBound(parent, entry.Property))
{
    Binding binding = BindingOperations.GetBinding(parent, entry.Property);
    if (binding.ValidationRules.Count > 0)
    {
        BindingExpression expression 
            = BindingOperations.GetBindingExpression(parent, entry.Property);
        expression.UpdateSource();

        if (expression.HasError) valid = false;
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.