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

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

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

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

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

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

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

使用ToolKit.MVVM實現ViewModel – Part 1

本次的開發環境,如下

  • 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 的文件註解,如下圖。

文件中的解釋,當 ObservablePropertyNotifyDataErrorInfo 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 作為主題,總訪問量為