Mendeteksi Kesalahan Validasi WPF

115

Di WPF, Anda dapat menyiapkan validasi berdasarkan kesalahan yang terjadi di Lapisan Data Anda selama Pengikatan Data menggunakan ExceptionValidationRuleatau DataErrorValidationRule.

Misalkan Anda memiliki banyak kontrol yang disiapkan dengan cara ini dan Anda memiliki tombol Simpan. Saat pengguna mengklik tombol Simpan, Anda perlu memastikan tidak ada kesalahan validasi sebelum melanjutkan penyimpanan. Jika ada kesalahan validasi, Anda ingin berteriak padanya.

Di WPF, bagaimana Anda mengetahui apakah ada kontrol Data Bound Anda yang memiliki kesalahan validasi?

Kevin Berridge
sumber

Jawaban:

137

Posting ini sangat membantu. Terima kasih untuk semua yang berkontribusi. Ini adalah versi LINQ yang akan Anda sukai atau benci.

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);
}
Dekan
sumber
1
Saya sangat menyukai solusi khusus ini!
ChristopheD
Baru saja tersandung utas ini. Fungsi kecil yang sangat berguna. Terima kasih!
Olav Haugen
Apakah ada cara untuk menambahkan hanya DependencyObjects yang terikat ke DataContext tertentu? Saya tidak suka ide treewalk. Mungkin ada kumpulan binding yang ditautkan ke sumber data tertentu.
ZAB
5
Hanya ingin tahu, bagaimana Anda memanggil IsValidfungsinya? Saya melihat Anda telah menyiapkan yang CanExecutemenurut saya terkait dengan perintah tombol Simpan. Apakah ini akan berhasil jika saya tidak menggunakan perintah? Dan bagaimana tombol tersebut terkait dengan kontrol lain yang perlu diperiksa? Satu-satunya pemikiran saya tentang cara menggunakan ini adalah dengan memanggil IsValidsetiap kontrol yang perlu divalidasi. Sunting: Sepertinya Anda memvalidasi senderyang saya harapkan menjadi tombol simpan. Itu sepertinya tidak benar bagi saya.
Nicholas Miller
1
@ Nick Miller a Windowjuga merupakan objek ketergantungan. Saya dia mungkin mengaturnya dengan semacam event handler di Window. Atau, Anda bisa langsung menelepon langsung IsValid(this)dari Windowkelas.
akousmata
47

Kode berikut (dari buku Programming WPF oleh Chris Sell & Ian Griffiths) memvalidasi semua aturan yang mengikat pada objek ketergantungan dan turunannya:

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

}

Anda dapat memanggil ini di event handler klik tombol simpan seperti ini di halaman / jendela Anda

private void saveButton_Click(object sender, RoutedEventArgs e)
{

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

    ....
   }
}
aogan
sumber
33

Kode yang diposting tidak berfungsi untuk saya saat menggunakan ListBox. Saya menulis ulang dan sekarang berhasil:

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;
}
H-Man2
sumber
1
Pilih solusi Anda untuk mengerjakan ItemsControl saya.
Jeff T.
1
Saya menggunakan solusi ini untuk memeriksa apakah datagrid saya memiliki kesalahan validasi. Namun, metode ini dipanggil pada metode perintah viewmodel canexecute saya, dan saya pikir mengakses objek pohon visual entah bagaimana melanggar derai MVVM, bukan? Ada Alternatif?
Igor Kondrasovas
16

Punya masalah yang sama dan mencoba solusi yang disediakan. Kombinasi solusi H-Man2 dan skiba_k bekerja hampir dengan baik untuk saya, untuk satu pengecualian: Jendela Saya memiliki TabControl. Dan aturan validasi hanya dievaluasi untuk TabItem yang saat ini terlihat. Jadi saya mengganti VisualTreeHelper dengan LogicalTreeHelper. Sekarang berhasil.

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

sumber
7

Selain implementasi LINQ yang hebat dari Dean, saya bersenang-senang membungkus kode menjadi ekstensi untuk DependencyObjects:

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

Ini membuatnya sangat bagus mengingat dapat digunakan kembali.

Matthias Loerke
sumber
2

Saya akan menawarkan pengoptimalan kecil.

Jika Anda melakukan ini berkali-kali pada kontrol yang sama, Anda dapat menambahkan kode di atas untuk menyimpan daftar kontrol yang sebenarnya memiliki aturan validasi. Kemudian kapan pun Anda perlu memeriksa validitas, cukup buka kontrol tersebut, bukan seluruh pohon visual. Ini akan terbukti jauh lebih baik jika Anda memiliki banyak kontrol seperti itu.

sprite
sumber
2

Berikut adalah perpustakaan untuk validasi formulir di WPF. Paket nuget disini .

Sampel:

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

Idenya adalah kita mendefinisikan cakupan validasi melalui properti terlampir yang memberitahukan kontrol input apa yang harus dilacak. Kemudian kita bisa melakukan:

<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>
Johan Larsson
sumber
0

Anda dapat mengulang semua pohon kontrol Anda secara rekursif dan memeriksa properti terlampir Validation.HasErrorProperty, lalu fokus pada yang pertama Anda temukan di dalamnya.

Anda juga dapat menggunakan banyak solusi yang sudah tertulis, Anda dapat memeriksa utas ini untuk contoh dan informasi lebih lanjut

pengguna21243
sumber
0

Anda mungkin tertarik dengan contoh aplikasi BookLibrary dari WPF Application Framework (WAF) . Ini menunjukkan bagaimana menggunakan validasi di WPF dan bagaimana mengontrol tombol Simpan ketika ada kesalahan validasi.

jbe
sumber
0

Dalam bentuk jawaban aogan, daripada mengulang secara eksplisit melalui aturan validasi, lebih baik panggil saja 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;
    }
}
Dan Is Fiddling By Firelight
sumber