การตรวจจับข้อผิดพลาดในการตรวจสอบ WPF


115

ใน WPF คุณสามารถตรวจสอบการติดตั้งอยู่บนพื้นฐานของข้อผิดพลาดโยนในชั้นข้อมูลของคุณในระหว่างการผูกข้อมูลโดยใช้หรือExceptionValidationRuleDataErrorValidationRule

สมมติว่าคุณมีการตั้งค่าการควบคุมมากมายด้วยวิธีนี้และคุณมีปุ่มบันทึก เมื่อผู้ใช้คลิกปุ่มบันทึกคุณต้องตรวจสอบให้แน่ใจว่าไม่มีข้อผิดพลาดในการตรวจสอบความถูกต้องก่อนที่จะดำเนินการบันทึก หากมีข้อผิดพลาดในการตรวจสอบความถูกต้องคุณต้องตะโกนใส่พวกเขา

ใน WPF คุณจะทราบได้อย่างไรว่าการควบคุม Data Bound ของคุณมีข้อผิดพลาดในการตรวจสอบความถูกต้องหรือไม่?

คำตอบ:


137

โพสต์นี้มีประโยชน์มาก ขอบคุณทุกคนที่มีส่วนร่วม นี่คือเวอร์ชัน LINQ ที่คุณจะรักหรือเกลียด

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
ฉันชอบวิธีแก้ปัญหานี้มาก!
ChristopheD

เพิ่งสะดุดกับกระทู้นี้ ฟังก์ชั่นเล็ก ๆ น้อย ๆ ที่มีประโยชน์มาก ขอบคุณ!
Olav Haugen

มีวิธีใดบ้างที่จะระบุเฉพาะ DependencyObjects ที่ผูกพันกับ DataContext เฉพาะหรือไม่ ฉันไม่ชอบความคิดของ Treewalk อาจมีการรวบรวมการผูกที่เชื่อมโยงกับแหล่งข้อมูลเฉพาะ
ZAB

5
แค่สงสัยว่าเรียกใช้IsValidฟังก์ชันได้อย่างไร? ฉันเห็นว่าคุณได้ตั้งค่าCanExecuteซึ่งฉันเดาว่าเกี่ยวข้องกับคำสั่งของปุ่มบันทึก จะได้ผลหรือไม่ถ้าฉันไม่ได้ใช้คำสั่ง และปุ่มนี้เกี่ยวข้องกับส่วนควบคุมอื่น ๆ ที่ต้องตรวจสอบอย่างไร? ความคิดเดียวของฉันในการใช้สิ่งนี้คือการเรียกร้องIsValidให้แต่ละการควบคุมที่ต้องได้รับการตรวจสอบ แก้ไข: ดูเหมือนว่าคุณกำลังตรวจสอบความถูกต้องsenderซึ่งฉันคาดว่าจะเป็นปุ่มบันทึก นั่นดูเหมือนจะไม่เหมาะกับฉัน
Nicholas Miller

1
@ นิกมิลเลอร์Windowยังเป็นวัตถุอ้างอิง ฉันอาจจะตั้งค่าด้วยตัวจัดการเหตุการณ์บางอย่างในไฟล์Window. หรือคุณสามารถโทรติดต่อโดยตรงIsValid(this)จากWindowชั้นเรียน
akousmata

47

รหัสต่อไปนี้ (จากหนังสือ Programming WPF โดย Chris Sell & Ian Griffiths) ตรวจสอบความถูกต้องของกฎการผูกมัดทั้งหมดบนวัตถุอ้างอิงและลูกของมัน:

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;
    }

}

คุณสามารถเรียกสิ่งนี้ได้ในปุ่มบันทึกของคุณคลิกตัวจัดการเหตุการณ์เช่นนี้ในหน้า / หน้าต่างของคุณ

private void saveButton_Click(object sender, RoutedEventArgs e)
{

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

    ....
   }
}

33

รหัสที่โพสต์ใช้ไม่ได้สำหรับฉันเมื่อใช้กล่องรายการ ฉันเขียนใหม่และตอนนี้ใช้งานได้:

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
โหวตโซลูชันของคุณสำหรับการทำงานกับ ItemsControl ของฉัน
Jeff T.

1
ฉันใช้วิธีนี้เพื่อตรวจสอบว่าดาต้ากริดของฉันมีข้อผิดพลาดในการตรวจสอบความถูกต้องหรือไม่ อย่างไรก็ตามวิธีนี้เรียกว่าเมธอด viewmodel ของฉันสามารถเรียกใช้งานได้และฉันคิดว่าการเข้าถึงออบเจ็กต์แผนผังภาพนั้นละเมิดรูปแบบ MVVM ใช่ไหม ทางเลือกอื่น ๆ ?
Igor Kondrasovas

16

มีปัญหาเดียวกันและลองวิธีแก้ไขที่ให้มา การรวมกันของโซลูชันของ H-Man2 และ skiba_k ทำงานได้ดีสำหรับฉันเกือบทุกข้อยกเว้น: My Window มี TabControl และกฎการตรวจสอบจะได้รับการประเมินสำหรับ TabItem ที่มองเห็นได้ในปัจจุบันเท่านั้น ดังนั้นฉันจึงแทนที่ VisualTreeHelper ด้วย LogicalTreeHelper ตอนนี้ใช้งานได้แล้ว

    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

นอกเหนือจากการใช้ LINQ ที่ยอดเยี่ยมของ Dean แล้วฉันยังสนุกกับการตัดโค้ดเป็นส่วนขยายสำหรับ DependencyObjects:

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

สิ่งนี้ทำให้ดีมากเมื่อพิจารณาถึงการนำกลับมาใช้ใหม่


2

ฉันจะเสนอการเพิ่มประสิทธิภาพเล็กน้อย

หากคุณทำสิ่งนี้หลายครั้งบนตัวควบคุมเดียวกันคุณสามารถเพิ่มโค้ดด้านบนเพื่อเก็บรายการตัวควบคุมที่มีกฎการตรวจสอบความถูกต้อง จากนั้นเมื่อใดก็ตามที่คุณต้องการตรวจสอบความถูกต้องให้ทำเฉพาะการควบคุมเหล่านั้นแทนที่จะเป็นแผนผังภาพทั้งหมด สิ่งนี้จะพิสูจน์ได้ว่าจะดีกว่ามากหากคุณมีการควบคุมดังกล่าวมากมาย


2

นี่คือไลบรารีสำหรับการตรวจสอบแบบฟอร์มใน WPF Nuget แพคเกจที่นี่

ตัวอย่าง:

<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>

แนวคิดก็คือเรากำหนดขอบเขตการตรวจสอบความถูกต้องผ่านคุณสมบัติที่แนบมาเพื่อบอกว่าอินพุตควบคุมใดที่จะติดตาม จากนั้นเราสามารถทำได้:

<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

คุณสามารถวนซ้ำบนโครงสร้างการควบคุมทั้งหมดของคุณแบบวนซ้ำและตรวจสอบการตรวจสอบคุณสมบัติที่แนบมาได้ HasErrorProperty จากนั้นมุ่งเน้นไปที่รายการแรกที่คุณพบในนั้น

คุณยังสามารถใช้โซลูชันที่เขียนไว้แล้วมากมายซึ่งคุณสามารถตรวจสอบชุดข้อความนี้เพื่อดูตัวอย่างและข้อมูลเพิ่มเติมได้


0

คุณอาจจะสนใจในBookLibraryโปรแกรมตัวอย่างของWPF Application Framework (WAF) แสดงวิธีใช้การตรวจสอบความถูกต้องใน WPF และวิธีควบคุมปุ่มบันทึกเมื่อมีข้อผิดพลาดในการตรวจสอบความถูกต้อง


0

ในรูปแบบคำขวัญแทนการย้ำอย่างชัดเจนผ่านกฎการตรวจสอบความถูกต้องดีกว่าเพียงแค่เรียกใช้ 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;
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.