抱歉,您的瀏覽器無法訪問本站
本頁面需要瀏覽器支持(啟用)JavaScript
了解詳情 >

在軟體開發中,處理使用者輸入是一個常見的任務,同時也時常伴隨著數據驗證的需求。

在 WPF 或其他 Windows 平台的 MVVM 架構中,要完成數據驗證,可以選擇使用 ValidationRule、IDataErrorInfo 和 INotifyDataErrorInfo 這幾個類別或接口。

其中 IDataErrorInfo 和 INotifyDataErrorInfo 的部分為 interface ,不僅要進行實作以外,因 MVVM 架構也需要一併實作 INotifyPropertyChanged 。而在 ViewModel 實作上,綁定的屬性也需要撰寫許多涉及 Property 的程式碼,使的開發的過程稍嫌繁瑣。

本篇的主題, CommunityToolkit.Mvvm 套件提供了實作 MVVM 架構時所需要的物件,其中的一個部分便是關於數據驗證,其中提供的物件,不只實作了 INotifyDataErrorInfo 與 INotifyPropertyChanged 等, MVVM 所需實現的interface外,同時也有提供方便的 Attribute 來簡化程式碼。

本次將進行該套件的數據驗證物件並使用 WPF 的示範。

本篇文章為 CommunityToolkit.Mvvm 套件系列的第二部分,若想了解關於該套件的基礎使用或 Messenger 通訊機制,可從下列網址前往。

使用ToolKit.MVVM實現ViewModel – Part 1 (基礎 MVVM 架構)

使用ToolKit.MVVM實現ViewModel – Part 3 (Messenger 通訊機制)

本次的開發環境,如下

  • IDE : Visual Studio 2022
  • .NET版本 : . NET 6.0
  • Nuget套件: CommunityToolkit.Mvvm 8.2.2

開始使用 ObservableValidator

先從簡單的 View 開始,

View 的 XAML 部分如下,其中包含3個 TextBox 用於輸入資料 , 分別綁定 Name、Phone 和 Email 屬性,以及一個綁定 ShowInfoCommand 的按鈕。

Phone 屬性的 TextBox 設定了 Validation.ErrorTemplate ,當輸入的數值不符合設定的條件時,預期會在該 TextBox 的下方顯示紅色的錯誤訊息。

XAML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<Window.Resources>
<Thickness x:Key="Margin.Top">0,25,0,0</Thickness>
</Window.Resources>

<Window.DataContext>
<local:Window1ViewModelA/>
</Window.DataContext>

<Grid>

<StackPanel Margin="20">

<!--Name-->
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Path=Name,
UpdateSourceTrigger=PropertyChanged}"/>

<!--Phone-->
<TextBlock Margin="{StaticResource Margin.Top}"
Text="Phone:"/>
<TextBox Text="{Binding Path=Phone,
UpdateSourceTrigger=PropertyChanged}">

<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder/>
<TextBlock Text="{Binding [0].ErrorContent}"
Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>

</TextBox>

<!--Email-->
<TextBlock Margin="{StaticResource Margin.Top}"
Text="Email:"/>
<TextBox Text="{Binding Path=Email,
UpdateSourceTrigger=PropertyChanged}"/>

<Button Margin="{StaticResource Margin.Top}"
Content="Show Info"
Command="{Binding Path=ShowInfoCommand}"/>

</StackPanel>

</Grid>

ViewModel 需要使用的 namespace 如下所示,包含 ToolKit.Mvvm 以及 ValidationAttribute 的部分。

1
2
3
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.ComponentModel.DataAnnotations;

再來則是 ViewModel Window1ViewModelA 的主要程式碼,

繼承部分,

  • 繼承套件內的 ObservableValidator 類別,該類別不僅繼承 MVVM 所使用的 ObservableObject ,還實作了數據驗證所需的 INotifyDataErrorInfo 介面。

Property 部分,

  • set 使用 SetProperty 方法來通知 View
  • Name 和 Phone 屬性上方,使用了 Required Attribute ,該 Attribute 用於數據驗證,作用是指是該屬性是必填的,不能為 null 或空字串。

Command 部分,

  • ShowInfo 方法執行時,首先呼叫 ValidateAllProperties 方法驗證所有的屬性的數據,然後,使用 HasErrors 屬性,來確認是否有數據驗證未通過,並顯示內容為 HasErrors 的 MessageBox。若通過驗證的話,則顯示輸入的內容的 MessageBox。

Window1ViewModelA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public partial class Window1ViewModelA : ObservableValidator
{
private string? _name;
[Required]
public string? Name
{
get => _name;
set => SetProperty(ref _name, value);
}

private string? _phone;
[Required]
public string? Phone
{
get => _phone;
set => SetProperty(ref _phone, value);
}

private string? _email;
public string? Email
{
get => _email;
set => SetProperty(ref _email, value);
}

public Window1ViewModelA()
{

}

[RelayCommand]
private void ShowInfo()
{
ValidateAllProperties();
if (HasErrors)
{
MessageBox.Show("HasErrors", "MessageBox");
return;
}

string info = $"Name: {Name}\nPhone: {Phone}\nEmail: {Email}";
MessageBox.Show(info, "MessageBox");
}
}

程式執行後,如下圖。

在沒有輸入資料的情況下,按下 ShowInfo 按鈕,便會彈出 MessageBox 並顯示 HasErrors 。

同時可以看到,設定了數據驗證 Attribute [Required] 的屬性,相關的 UI 有了變化,

  • Name 的 TextBox 外圍多了紅框,這是因為數據驗證未通過時,TextBox 預設的 ErrorTemplate 是顯示紅框,以提示使用者出現了錯誤狀態。
  • Phone 的 TextBox 則與 Name 的不同,下方顯示了驗證錯誤的訊息,有這樣的差異是因為,該 TextBox 有設定了 ErrorTemplate 。
    繼承 ObservableValidator 類別,並使用 Required Attribute的 WPF 程式執行結果

透過上面的範例,已經初步學會如何使用套件中的 ObservableValidator 類別,並成功實現了以下功能:

  • 在必填欄位未填寫時,按下執行按鈕後,檢查並提示使用者數據驗證錯誤。

即時驗證數據

那如果想要在輸入數據時,即時進行數據驗證,該如何實現呢?

其中一個方式是使用 ObservableValidator 內提供的 SetProperty 方法。如下面程式碼中,可以看到SetProperty 方法的最後一個參數 ( validate ) 帶入了 true ,這樣會在設定屬性值的同時,自動呼叫內部的驗證方法進行數據驗證。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private string? _name;
[Required]
public string? Name
{
get => _name;
set => SetProperty(ref _name, value, true);
}

private string? _phone;
[Required]
public string? Phone
{
get => _phone;
set => SetProperty(ref _phone, value, true);
}

前往 ObservableValidator 類別中的 SetProperty 方法的內容,如下圖,程式首先執行, ObservableObject 的 SetProperty 方法,接著,如果屬性值有變動且 validate 參數為 true 時,將會執行 ValidateProperty 的方法來驗證。
ObservableValidator 類別中的 SetProperty 方法的內容

完整程式碼如下。除了在 SetProperty 方法中使用 validate 參數進行驗證,這個驗證只有在屬性的 set 方法被觸發時才會執行。為了在程式一開始時就進行一次驗證,下面程式的建構式裡使用了 ValidateAllProperties 方法,這樣就能在程式啟動後就先觸發數據驗證一次。

Window1ViewModelB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public partial class Window1ViewModelB : ObservableValidator
{
private string? _name;
[Required]
public string? Name
{
get => _name;
set => SetProperty(ref _name, value, true);
}

private string? _phone;
[Required]
public string? Phone
{
get => _phone;
set => SetProperty(ref _phone, value, true);
}

private string? _email;
public string? Email
{
get => _email;
set => SetProperty(ref _email, value);
}

public Window1ViewModelB()
{
ValidateAllProperties();
}

[RelayCommand]
private void ShowInfo()
{
if (HasErrors)
{
MessageBox.Show("HasErrors", "MessageBox");
return;
}

string info = $"Name: {Name}\nPhone: {Phone}\nEmail: {Email}";
MessageBox.Show(info, "MessageBox");
}
}

替換過ViewModel後的結果如下,當 TextBox 從為空白狀態輸入資料後,錯誤提示便會立即消失,就像在網頁上填寫資料時的效果一樣。
即時驗證數據的 WPF 程式執行結果

使用 Attribute 簡化程式

到了這個階段,已經能基本使用 ObservableValidator 類別,在 MVVM 架構中進行數據驗證。

那麼 ObservableValidator 類別,是否有像上篇文章中 ObservableObject 類別那樣,能透過 Attribute 來減少程式碼嗎?

答案是可以的。

從一般的寫法,更改為使用套件提供的 Attribute ,變動如下圖所示。紅底部分為原先的一般寫法,綠底則為使用 Attribute 簡化程式後的結果。

  • [ObservableProperty] , 如前篇文章所提到的,套件會自動產生,使用 SetProperty 方法的 Property。
  • [NotifyDataErrorInfo],此 Attribute 是在 ObservableValidator 中可使用的,等同於上一節中,將 validate 參數設為 true 的效果。
  • [Required],用於屬性的數據驗證規則 (此為必填的規則),這部分並不包含在 ToolKit.Mvvm 套件中。

紅底:標準使用
綠底:使用 Attribute
使用 ObservableProperty 和 NotifyDataErrorInfo Attribute 程式碼前後差異比較

來詳細看一下 NotifyDataErrorInfo Attribute 的文件註解,如下圖。

文件中的解釋,當 ObservableProperty 與 NotifyDataErrorInfo Attribute 同時使用時,套件自動產生的對應程式碼。
ObservableValidator Attribute 文件註解說明

那麼前進到自動產生的程式碼中,如下圖,實際上與 ObservableProperty Attribute 自動產生的程式,會是差在使用 ValidateProperty 這個方法。
使用 ObservableProperty 和 NotifyDataErrorInfo Attribute 所自動產生的程式碼

最後附上皆替換為 Attribute 的完整的程式碼。

Window1ViewModelC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public partial class Window1ViewModelC : ObservableValidator
{
[ObservableProperty]
[NotifyDataErrorInfo]
[Required]
private string? _name;

[ObservableProperty]
[NotifyDataErrorInfo]
[Required]
private string? _phone;

[ObservableProperty]
private string? _email;

public Window1ViewModelC()
{
ValidateAllProperties();
}

[RelayCommand]
private void ShowInfo()
{
if (HasErrors)
{
MessageBox.Show("HasErrors", "MessageBox");
return;
}

string info = $"Name: {Name}\nPhone: {Phone}\nEmail: {Email}";
MessageBox.Show(info, "MessageBox");
}
}

使用其他的數據驗證 Attribute

在數據驗證方面,.NET 還提供了其他可用的 Attribute。這些 Attribute 不屬於 ToolKit.Mvvm 套件,而是位於 System.ComponentModel.DataAnnotations 命名空間中。

那麼來試試將 ObservableValidator 搭配幾種不同的 Attribute 使用。

View 的 XAML 部分如下,

在 Window.Resources 中建立 TextBoxStyle ,該 Style 設定了 Validation.ErrorTemplate 屬性,視窗裡的4個 TextBox 皆套用此 Style。那 TextBox 分別綁定 Name、Number、Phone 和 Email 屬性。

XAML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<Window.Resources>

<Thickness x:Key="Margin.Top">0,25,0,0</Thickness>

<Style x:Key="TextBoxStyle" TargetType="TextBox">

<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder/>
<TextBlock Text="{Binding [0].ErrorContent}"
Foreground="Red"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>

</Style>

</Window.Resources>

<Window.DataContext>
<local:Window2ViewModelA/>
</Window.DataContext>

<Grid>

<StackPanel Margin="20">

<!--Name-->
<TextBlock Text="Name:"/>
<TextBox Style="{StaticResource TextBoxStyle}"
Text="{Binding Path=Name,
UpdateSourceTrigger=PropertyChanged}"/>

<!--Number-->
<TextBlock Margin="{StaticResource Margin.Top}"
Text="Number:"/>
<TextBox Style="{StaticResource TextBoxStyle}"
Text="{Binding Path=Number,
UpdateSourceTrigger=PropertyChanged}"/>

<!--Phone-->
<TextBlock Margin="{StaticResource Margin.Top}"
Text="Phone:"/>
<TextBox Style="{StaticResource TextBoxStyle}"
Text="{Binding Path=Phone,
UpdateSourceTrigger=PropertyChanged}"/>

<!--Email-->
<TextBlock Margin="{StaticResource Margin.Top}"
Text="Email:"/>
<TextBox Style="{StaticResource TextBoxStyle}"
Text="{Binding Path=Email,
UpdateSourceTrigger=PropertyChanged}"/>

<Button Margin="{StaticResource Margin.Top}"
Content="Show Info"
Command="{Binding Path=ShowInfoCommand}"/>

</StackPanel>

</Grid>

View的樣子,如下圖。
使用其他的數據驗證 Attribute 的範例 WPF 程式畫面

以下是 ViewModel 的程式碼,4 個變數除了使用 [Required] 進行數據驗證外,還各自搭配了不同的 Attribute。

Window2ViewModelA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public partial class Window2ViewModelA : ObservableValidator
{
[ObservableProperty]
[NotifyDataErrorInfo]
[Required]
[MinLength(4)]
[MaxLength(10)]
private string? _name;

[ObservableProperty]
[NotifyDataErrorInfo]
[Required]
[Range(0, 100)]
private string? _number;

[ObservableProperty]
[NotifyDataErrorInfo]
[Required]
[Phone]
private string? _phone;

[ObservableProperty]
[NotifyDataErrorInfo]
[Required]
[EmailAddress]
private string? _email;

public Window2ViewModelA()
{
ValidateAllProperties();
}

[RelayCommand]
private void ShowInfo()
{
if (HasErrors)
{
MessageBox.Show("HasErrors", "MessageBox");
return;
}

string info = $"Name: {Name}\nNumber: {Number}\nPhone: {Phone}\nEmail: {Email}";
MessageBox.Show(info, "MessageBox");
}
}

以下是各種 Attribute在實際執行程式時的效果:

  • Name 變數使用了 MinLength 和 MaxLength Attribute,將數據的長度限制在最小長度和最大長度之間。
    使用 MinLength 和 MaxLength Attribute 數據驗證結果畫面


  • Number 變數使用了 Range Attribute, 將數據的數值限制在設定的範圍內。
    使用 Range Attribute 數據驗證結果畫面


  • Phone 變數使用了 Phone Attribute,將會檢查數據的內容是否符合電話格式。
    使用 Phone Attribute 數據驗證結果畫面


  • Email 變數使用了 EmailAddress Attribute,將會檢查數據的內容是否符合電子信箱格式。
    使用 Email Attribute 數據驗證結果畫面

總結

在 MVVM 架構中實現數據驗證時,使用 CommunityToolkit.Mvvm 套件中的 ObservableValidator 類別是一個不錯的選擇。它可以節省大量時間和功夫,並且可以配合套件內的 Attribute 自動生成程式碼,使程式碼更為精簡,從而讓開發者能更專注於主要的邏輯。

需要注意的是,由於 ObservableValidator 實作了 INotifyDataErrorInfo 介面,因此在 View 上綁定的屬性,在數據驗證後,無論是否通過驗證,數據都會更新到 ViewModel 的屬性中。因此,開發者需要確保在適當的時機觸發數據驗證方法,並且在出現驗證錯誤時,確保程式邏輯能夠阻止執行下一步驟,讓程式正確運作。

而如果希望在 View 層就攔截錯誤數據,使其不進入 ViewModel,那麼實作並使用 ValidationRule 會比較符合這種情況。

另外,本篇的重點著重在 ObservableValidator 類別上,因此未深入探討數據驗證 Attribute。除了本文中介紹的現成屬性外,數據驗證屬性還有一些其他的功能和實作方法,例如:

  • 在驗證錯誤時,使用 ErrorMessage 顯示自訂的錯誤訊息
  • 使用 CustomValidation Attribute 自訂驗證規則
  • 繼承 ValidationAttribute 類別來自訂驗證類別

以上這些內容在網路上已有許多文章介紹。如果有機會,未來會再撰寫一篇番外篇,結合 ObservableValidator 類別,進一步探討這些數據驗證屬性的使用方法。

自言自語543

在撰寫 MVVM 架構時,個人還沒做過太多資料驗證的需求,唯一的幾次經驗則是使用 IDataErrorInfo 並配合較傳統的寫法來實現。

自從使用 CommunityToolkit.Mvvm 套件後,發現了 ObservableValidator 的存在,於是花了些時間了解怎麼使用,說不定未來有機會需要用到。學習的過程中,這才發現原來有數據驗證用的 Attribute ,不但讓程式達成需要的功能、能重複使用以外,還能讓程式碼更精簡,真的是很神奇的東西。

過去,個人在使用 Attribute ,通常是在配合 Winform 的 PropertyGrid 元件時,會在類別的 Property 上添加一些用於在 PropertyGrid 上使用的設定。如今套件中有自動產生程式碼的 Attribute 外,還有數據驗證用的,看來還有很多需要學習的地方呢…

評論




本站使用 Volantis 作為主題,總訪問量為 次