本章では、VB.NETにおけるデスクトップアプリケーション開発の中でも、 特にWindows FormsとWPFを対象に、業務現場で使える高度なUI設計について学習します。
業務アプリケーションのUIは、単に画面を作るだけでは不十分です。 入力しやすさ、誤操作の防止、処理中の応答性、エラー時の分かりやすさ、 大量データの表示、保守しやすい画面構造など、多くの観点を考慮する必要があります。
特にVB.NETは、長年にわたってWindows Formsを中心とした業務システム開発で利用されてきました。 そのため、既存のWindows Forms資産を保守しながら、一部をWPF化したり、 将来的なモダナイゼーションを見据えた設計にしたりする場面も多くあります。
本章のゴールは、画面に部品を配置できるようになることではありません。 イベント駆動、データバインディング、入力チェック、UIスレッド、非同期処理、 MVVM、UIとロジックの分離を理解し、業務現場で長く使える堅牢なデスクトップアプリを設計できるようになることです。
1. Windows FormsとWPFの違い
VB.NETでデスクトップアプリケーションを開発する場合、代表的な選択肢としてWindows FormsとWPFがあります。 どちらもWindows向けのUIアプリケーションを作成できますが、設計思想や得意分野が大きく異なります。
Windows Formsとは
Windows Formsは、比較的シンプルにWindowsデスクトップアプリケーションを開発できるUIフレームワークです。 ボタン、テキストボックス、コンボボックス、DataGridViewなどのコントロールを画面に配置し、 イベントに対して処理を書いていく開発スタイルが基本です。
Windows Formsは、既存の業務システムで非常に多く使われています。 特に、社内向けの管理画面、マスタメンテナンス画面、帳票出力画面、データ登録画面などで利用されることが多いです。
Windows Formsの特徴
- 学習コストが比較的低い
- ドラッグアンドドロップで画面を作りやすい
- イベント駆動の構造が分かりやすい
- 既存業務システムでの採用実績が多い
- DataGridViewなど業務向けコントロールが扱いやすい
WPFとは
WPFは、Windows Presentation Foundationの略で、より柔軟で表現力の高いUIを構築できるフレームワークです。 XAMLを使ってUIを定義し、データバインディング、スタイル、テンプレート、アニメーション、MVVMなどを活用できます。
WPFは、単に見た目をきれいにするための技術ではありません。 UIとロジックを分離しやすく、保守性やテスト容易性を高めやすい構造を持っています。
WPFの特徴
- XAMLでUIを宣言的に定義できる
- データバインディングが強力
- MVVMパターンと相性が良い
- スタイルやテンプレートで見た目を統一しやすい
- 複雑なUIやリッチな画面表現に強い
Windows FormsとWPFの比較
| 項目 | Windows Forms | WPF |
|---|---|---|
| UI定義 | フォームデザイナ中心 | XAML中心 |
| 学習コスト | 低め | 高め |
| データバインディング | 基本的な機能が中心 | 非常に強力 |
| 画面デザイン | 標準的な業務画面向き | 柔軟でリッチなUI向き |
| 設計パターン | イベント駆動中心 | MVVMと相性が良い |
| 既存資産 | 業務システムで多い | 比較的新しい設計で採用されやすい |
| 保守性 | 設計次第で差が大きい | 設計分離しやすい |
実務では、新規開発ならWPFを検討する価値があります。 一方、既存システムがWindows Formsで構築されている場合は、 無理に全面移行するのではなく、保守性を高めながら段階的に改善する判断も重要です。
2. イベント駆動プログラミング
Windows FormsやWPFのUIアプリケーションは、イベント駆動で動作します。 イベント駆動とは、ユーザー操作やシステム状態の変化をきっかけに処理が実行される仕組みです。
たとえば、ボタンをクリックしたとき、テキストボックスの値が変わったとき、 画面が読み込まれたとき、選択行が変更されたときなどにイベントが発生します。
Windows Formsのイベント例
Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
Dim keyword As String = txtKeyword.Text
If String.IsNullOrWhiteSpace(keyword) Then
MessageBox.Show("検索条件を入力してください。")
Return
End If
SearchData(keyword)
End Sub
この例では、btnSearchがクリックされたタイミングでbtnSearch_Clickが実行されます。 Windows Formsでは、このようにイベントハンドラに処理を書く形式が一般的です。
イベント駆動で注意すべき点
イベント駆動は分かりやすい一方で、処理をイベントハンドラに直接書きすぎると、 画面クラスが急速に肥大化します。
たとえば、ボタンクリックイベントの中に、入力チェック、業務ロジック、DBアクセス、ログ出力、 画面更新処理をすべて書いてしまうと、保守が非常に難しくなります。
悪い例:イベントハンドラに処理を書きすぎる
Private Sub btnRegister_Click(sender As Object, e As EventArgs) Handles btnRegister.Click
If txtUserName.Text = "" Then
MessageBox.Show("ユーザー名を入力してください。")
Return
End If
Dim connectionString As String = "Data Source=..."
Using connection As New SqlClient.SqlConnection(connectionString)
connection.Open()
Dim sql As String = "INSERT INTO Users (UserName) VALUES (@UserName)"
Using command As New SqlClient.SqlCommand(sql, connection)
command.Parameters.AddWithValue("@UserName", txtUserName.Text)
command.ExecuteNonQuery()
End Using
End Using
MessageBox.Show("登録しました。")
End Sub
このようなコードは、初期実装では簡単に見えます。 しかし、画面仕様や登録ロジックが変わるたびにイベントハンドラを修正する必要があり、 テストや再利用も難しくなります。
良い例:イベントハンドラは処理の入口にする
Private Sub btnRegister_Click(sender As Object, e As EventArgs) Handles btnRegister.Click
Dim dto As New UserRegisterDto With {
.UserName = txtUserName.Text,
.Email = txtEmail.Text
}
Dim result = _userService.Register(dto)
If result.Success Then
MessageBox.Show("登録しました。")
Else
MessageBox.Show(result.Message)
End If
End Sub
イベントハンドラは、画面から値を取得し、Serviceを呼び出し、結果を画面に反映する程度に留めるのが理想です。 業務ロジックはService、DBアクセスはRepository、入力チェックはValidatorなどに分離します。
イベント設計の基本方針
- イベントハンドラに業務ロジックを書きすぎない
- イベントハンドラは処理の入口として扱う
- 入力チェック、業務処理、DBアクセスを分離する
- 画面更新処理と業務ロジックを混ぜない
- 同じ処理を複数イベントに重複して書かない
3. DataGridViewの高度制御
Windows Formsの業務アプリケーションでは、DataGridViewを使った一覧表示が非常に多く登場します。 顧客一覧、受注一覧、商品マスタ、社員一覧、ログ一覧など、表形式のデータ表示には欠かせないコントロールです。
DataGridViewは便利ですが、使い方を誤ると、表示が遅い、編集制御が複雑、 入力チェックが散らばる、列定義が保守しにくいなどの問題が発生します。
基本的なデータバインド
Dim users As List(Of User) = _userService.GetUsers()
DataGridView1.DataSource = users
List(Of T)をDataSourceに設定することで、オブジェクトのプロパティを列として表示できます。 ただし、この方法だけでは列名や表示順、編集可否などを細かく制御しにくい場合があります。
列を明示的に定義する
DataGridView1.AutoGenerateColumns = False
DataGridView1.Columns.Clear()
Dim idColumn As New DataGridViewTextBoxColumn()
idColumn.DataPropertyName = "Id"
idColumn.HeaderText = "ユーザーID"
idColumn.ReadOnly = True
DataGridView1.Columns.Add(idColumn)
Dim nameColumn As New DataGridViewTextBoxColumn()
nameColumn.DataPropertyName = "UserName"
nameColumn.HeaderText = "ユーザー名"
DataGridView1.Columns.Add(nameColumn)
DataGridView1.DataSource = users
業務システムでは、AutoGenerateColumnsをFalseにして、 列を明示的に定義する方が保守しやすいことが多いです。 表示順、ヘッダ名、編集可否、幅、フォーマットなどをコード上で制御できるためです。
読み取り専用制御
DataGridView1.ReadOnly = True
DataGridView1.AllowUserToAddRows = False
DataGridView1.AllowUserToDeleteRows = False
DataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect
DataGridView1.MultiSelect = False
一覧画面では、誤操作防止のために読み取り専用にすることがよくあります。 特にマスタ選択画面や検索結果一覧では、行全体を選択する設定にすると操作性が向上します。
セル単位の入力チェック
Private Sub DataGridView1_CellValidating(sender As Object, e As DataGridViewCellValidatingEventArgs) Handles DataGridView1.CellValidating
If DataGridView1.Columns(e.ColumnIndex).Name = "Quantity" Then
Dim value As Integer
If Not Integer.TryParse(e.FormattedValue.ToString(), value) Then
DataGridView1.Rows(e.RowIndex).ErrorText = "数量は数値で入力してください。"
e.Cancel = True
Else
DataGridView1.Rows(e.RowIndex).ErrorText = ""
End If
End If
End Sub
DataGridViewでは、CellValidatingイベントを利用してセル入力時のチェックを行えます。 ただし、複雑な業務チェックをすべて画面イベント内に書くと保守性が下がるため、 必要に応じてValidatorクラスに分離することが重要です。
大量データ表示時の注意点
DataGridViewに大量のデータを一度にバインドすると、画面表示が重くなることがあります。 特に数万件以上のデータを扱う場合は、検索条件による絞り込み、ページング、 仮想モード、非同期読み込みなどを検討する必要があります。
DataGridView設計のポイント
- 列は自動生成ではなく明示的に定義する
- 一覧表示と編集画面の責務を分ける
- 入力チェックをイベント内に書きすぎない
- 大量データはページングや絞り込みを検討する
- 選択行の取得処理を共通化する
- 表示用DTOを用意し、Entityをそのまま表示しない設計も検討する
4. 入力チェックとエラー表示設計
業務アプリケーションにおいて、入力チェックは非常に重要です。 入力チェックが不十分だと、不正なデータ登録、DBエラー、業務フローの破綻、 ユーザーの誤操作などにつながります。
また、入力チェックは単にエラーを検出すればよいわけではありません。 ユーザーが何を修正すればよいのか、どの項目に問題があるのかを分かりやすく伝える必要があります。
入力チェックの種類
- 必須チェック
- 文字数チェック
- 数値チェック
- 日付チェック
- 範囲チェック
- 形式チェック
- 相関チェック
- 存在チェック
- 重複チェック
- 業務ルールチェック
必須チェックの例
If String.IsNullOrWhiteSpace(txtUserName.Text) Then
MessageBox.Show("ユーザー名を入力してください。")
txtUserName.Focus()
Return
End If
単純な画面ではこのようなチェックでも問題ありません。 しかし、項目数が多い画面では、入力チェックがイベント内に散らばると保守性が低下します。
Validatorクラスに分離する例
Public Class UserInputValidator
Public Function Validate(dto As UserRegisterDto) As List(Of String)
Dim errors As New List(Of String)()
If String.IsNullOrWhiteSpace(dto.UserName) Then
errors.Add("ユーザー名を入力してください。")
End If
If String.IsNullOrWhiteSpace(dto.Email) Then
errors.Add("メールアドレスを入力してください。")
ElseIf Not dto.Email.Contains("@") Then
errors.Add("メールアドレスの形式が不正です。")
End If
Return errors
End Function
End Class
Validatorクラスに入力チェックを分離することで、画面に依存しない検証処理になります。 同じ入力チェックを別画面やAPIでも再利用しやすくなります。
複数エラーをまとめて表示する
Dim errors = _validator.Validate(dto)
If errors.Count > 0 Then
MessageBox.Show(String.Join(Environment.NewLine, errors), "入力エラー")
Return
End If
1つ目のエラーで処理を止める方法もありますが、 業務アプリでは複数の入力エラーをまとめて表示した方がユーザーに親切な場合があります。
ErrorProviderを使ったエラー表示
Windows Formsでは、ErrorProviderを使うことで、項目単位でエラーを表示できます。
ErrorProvider1.SetError(txtUserName, "ユーザー名を入力してください。")
エラーが解消された場合は、空文字を設定してエラー表示を消します。
ErrorProvider1.SetError(txtUserName, "")
エラー表示設計のポイント
- どの項目がエラーなのか分かるようにする
- 何を修正すればよいか具体的に伝える
- システム都合のメッセージをそのまま表示しない
- 入力エラーとシステムエラーを分ける
- 項目単位エラーと画面全体エラーを使い分ける
- エラーメッセージを共通化し、表記ゆれを防ぐ
悪いエラーメッセージ例
- エラーが発生しました
- 不正です
- 処理に失敗しました
- System.NullReferenceException
良いエラーメッセージ例
- ユーザー名を入力してください。
- 開始日は終了日以前の日付を入力してください。
- 数量は1以上999以下の数値で入力してください。
- 入力された社員番号は既に登録されています。
エラー表示は、ユーザー体験だけでなく、問い合わせ対応や運用効率にも影響します。 分かりにくいエラーは、結果的に現場の負担を増やします。
5. UIスレッドとバックグラウンド処理
Windows FormsやWPFのアプリケーションでは、画面操作や画面更新は基本的にUIスレッド上で実行されます。 UIスレッド上で重い処理を実行すると、画面が固まったように見える「UIフリーズ」が発生します。
たとえば、DB検索、ファイル読み込み、外部API通信、大量データ処理、帳票生成などを ボタンクリックイベント内でそのまま実行すると、処理が完了するまで画面が応答しなくなります。
UIフリーズが起きやすい例
Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
Dim result = _userService.SearchUsers(txtKeyword.Text)
DataGridView1.DataSource = result
End Sub
SearchUsersの処理が重い場合、検索が終わるまで画面が操作できなくなります。 ユーザーから見ると、アプリケーションが停止したように感じられます。
Async / Awaitを使った非同期処理
Private Async Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
btnSearch.Enabled = False
lblStatus.Text = "検索中です..."
Try
Dim result = Await Task.Run(Function()
Return _userService.SearchUsers(txtKeyword.Text)
End Function)
DataGridView1.DataSource = result
lblStatus.Text = "検索が完了しました。"
Catch ex As Exception
MessageBox.Show("検索中にエラーが発生しました。")
Finally
btnSearch.Enabled = True
End Try
End Sub
Task.RunとAwaitを利用することで、重い処理をバックグラウンドで実行し、 UIスレッドの応答性を保つことができます。
UIスレッドへのアクセス
バックグラウンドスレッドから直接UIコントロールを操作すると、例外や不安定な動作の原因になります。 Windows Formsでは、InvokeやBeginInvokeを使ってUIスレッドに処理を戻す必要があります。
If lblStatus.InvokeRequired Then
lblStatus.Invoke(Sub()
lblStatus.Text = "処理中です..."
End Sub)
Else
lblStatus.Text = "処理中です..."
End If
Async / Awaitを適切に使う場合、Awaitの後続処理は基本的にUIスレッドに戻るため、 UI更新を比較的安全に記述できます。 ただし、ConfigureAwaitや別スレッド処理を使う場合は、スレッドの扱いに注意が必要です。
処理中のUI制御
バックグラウンド処理を行う場合、ユーザーが同じボタンを連打したり、 処理中に画面を閉じたりしないように制御する必要があります。
- 処理中はボタンを無効化する
- ステータスメッセージを表示する
- 進捗バーを表示する
- キャンセル可能な処理ではキャンセルボタンを用意する
- 処理完了後に必ずUI状態を戻す
CancellationTokenによるキャンセル制御
Private _cancellationTokenSource As CancellationTokenSource
Private Async Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
_cancellationTokenSource = New CancellationTokenSource()
Dim token = _cancellationTokenSource.Token
Try
Await Task.Run(Sub()
For i As Integer = 1 To 100
token.ThrowIfCancellationRequested()
Threading.Thread.Sleep(100)
Next
End Sub, token)
MessageBox.Show("処理が完了しました。")
Catch ex As OperationCanceledException
MessageBox.Show("処理をキャンセルしました。")
End Try
End Sub
Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
_cancellationTokenSource?.Cancel()
End Sub
長時間処理では、キャンセルできる設計にすることで、ユーザーの操作性が大きく向上します。
6. MVVMの基礎とWPF設計
WPFでは、MVVMという設計パターンがよく利用されます。 MVVMは、Model、View、ViewModelの3つに責務を分ける設計手法です。
MVVMを使うことで、UIの見た目と業務ロジックを分離しやすくなり、 保守性やテスト容易性を高められます。
MVVMの構成
| 要素 | 役割 |
|---|---|
| Model | 業務データ、業務ルール、データアクセスなどを扱う |
| View | 画面表示を担当する。XAMLで定義する |
| ViewModel | Viewに表示するデータやコマンドを保持し、ModelとViewを仲介する |
ViewModelの例
Public Class UserViewModel
Implements ComponentModel.INotifyPropertyChanged
Private _userName As String
Public Property UserName As String
Get
Return _userName
End Get
Set(value As String)
If _userName <> value Then
_userName = value
RaiseEvent PropertyChanged(Me, New ComponentModel.PropertyChangedEventArgs(NameOf(UserName)))
End If
End Set
End Property
Public Event PropertyChanged As ComponentModel.PropertyChangedEventHandler Implements ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
INotifyPropertyChangedを実装することで、ViewModelのプロパティ変更をViewに通知できます。 これにより、画面とデータをバインディングで連動させられます。
XAMLでのバインディング例
<TextBox Text="{Binding UserName, UpdateSourceTrigger=PropertyChanged}" />
この例では、TextBoxのTextプロパティとViewModelのUserNameプロパティをバインドしています。 ユーザーが画面上で値を入力すると、ViewModel側のUserNameにも反映されます。
Commandによる処理呼び出し
MVVMでは、ボタンクリックなどの処理をコードビハインドに直接書くのではなく、 CommandとしてViewModelに定義することが一般的です。
Public Class RelayCommand
Implements ICommand
Private ReadOnly _execute As Action
Private ReadOnly _canExecute As Func(Of Boolean)
Public Sub New(execute As Action, Optional canExecute As Func(Of Boolean) = Nothing)
_execute = execute
_canExecute = canExecute
End Sub
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
If _canExecute Is Nothing Then
Return True
End If
Return _canExecute()
End Function
Public Sub Execute(parameter As Object) Implements ICommand.Execute
_execute()
End Sub
Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
End Class
Commandを使うことで、Viewは「ボタンが押された」という事実だけを扱い、 実際の処理はViewModel側に集約できます。
MVVMのメリット
- UIとロジックを分離しやすい
- ViewModelの単体テストがしやすい
- データバインディングにより画面更新を簡潔に書ける
- 画面デザインと処理の責務を分けられる
- 複雑な画面でも構造を整理しやすい
MVVM導入時の注意点
- 小規模画面に過剰適用すると複雑になる
- ViewModelが肥大化しないようにServiceへ処理を分離する
- ModelとViewModelの責務を混同しない
- CommandやBindingのエラーは初学者には追いづらい
- 命名規則やフォルダ構成を統一する
WPFではMVVMが非常に強力ですが、形だけ導入しても保守性は上がりません。 重要なのは、View、ViewModel、Modelの責務を正しく分けることです。
7. UIとロジックの分離
業務アプリケーションの保守性を高めるうえで、UIとロジックの分離は非常に重要です。 Windows FormsでもWPFでも、画面に業務ロジックを書きすぎると、 画面変更と業務処理変更が密接に結びつき、保守が難しくなります。
UIに書いてよい処理
- 画面項目から値を取得する処理
- 画面項目に値を設定する処理
- ボタンの有効・無効制御
- メッセージ表示
- 画面遷移
- フォーカス制御
- 表示形式の調整
UIに書くべきでない処理
- 複雑な業務判断
- DBアクセス処理
- 外部API通信の詳細
- ファイル出力の詳細
- トランザクション制御
- 複数機能で再利用される入力チェック
- 権限判定などの共通業務ルール
分離前の悪い例
Private Sub btnApprove_Click(sender As Object, e As EventArgs) Handles btnApprove.Click
If cmbStatus.SelectedValue.ToString() <> "申請中" Then
MessageBox.Show("申請中のデータのみ承認できます。")
Return
End If
If txtApprover.Text = "" Then
MessageBox.Show("承認者を入力してください。")
Return
End If
' DB更新処理
' メール送信処理
' ログ出力処理
MessageBox.Show("承認しました。")
End Sub
このコードでは、承認可否の業務判断、入力チェック、DB更新、メール送信、ログ出力が 画面イベント内に混在しています。
分離後の設計例
Private Sub btnApprove_Click(sender As Object, e As EventArgs) Handles btnApprove.Click
Dim request As New ApprovalRequestDto With {
.ApplicationId = CInt(txtApplicationId.Text),
.Approver = txtApprover.Text
}
Dim result = _approvalService.Approve(request)
If result.Success Then
MessageBox.Show("承認しました。")
Else
MessageBox.Show(result.Message)
End If
End Sub
Public Class ApprovalService
Public Function Approve(request As ApprovalRequestDto) As ProcessResult
' 入力チェック
' 承認可否の業務判断
' DB更新
' メール送信
' ログ出力
Return ProcessResult.SuccessResult()
End Function
End Class
画面側は、入力値をDTOに詰めてServiceに渡し、結果を表示するだけにしています。 業務処理の本体はServiceに集約されるため、画面に依存せずテストしやすくなります。
UIとロジックを分離するメリット
- 画面変更の影響を業務ロジックに波及させにくい
- 業務ロジックを単体テストしやすい
- 同じ処理を別画面やAPIから再利用しやすい
- 画面クラスの肥大化を防げる
- 担当者が変わっても構造を理解しやすい
8. 業務UI設計で重要な操作性の考え方
業務アプリケーションでは、見た目の美しさだけでなく、 毎日使うユーザーにとって操作しやすいことが重要です。
特に、経理、人事、営業事務、金融、物流、製造管理などの現場では、 同じ画面を何度も繰り返し使うことがあります。 そのため、少しの使いにくさが大きな業務負担になります。
入力順序を意識する
画面項目は、ユーザーが実際に入力する順番に並べる必要があります。 Tabキーで自然に移動できるようにTabIndexを設定することも重要です。
txtUserName.TabIndex = 0
txtEmail.TabIndex = 1
cmbDepartment.TabIndex = 2
btnRegister.TabIndex = 3
誤操作を防ぐ
- 削除処理では確認ダイアログを表示する
- 処理中はボタンを無効化する
- 権限がない操作ボタンは非表示または無効化する
- 入力できない項目はReadOnlyにする
- 確定前に処理内容を確認できるようにする
状態を分かりやすく表示する
業務画面では、現在の状態が分かりやすいことが重要です。 処理中、保存済み、未保存、エラーあり、承認済み、差戻し中などの状態を明確に表示します。
lblStatus.Text = "検索中です..."
btnRegister.Enabled = False
ショートカットキーを活用する
業務ユーザーはマウスよりもキーボード操作を好む場合があります。 検索、登録、クリア、閉じるなどの操作にはショートカットキーを設定すると便利です。
Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
If e.KeyCode = Keys.F5 Then
ExecuteSearch()
End If
End Sub
業務UIの設計ポイント
- 入力順序を業務フローに合わせる
- 必須項目を分かりやすく示す
- 処理中の状態を表示する
- 誤操作を防ぐ確認や制御を入れる
- 一覧画面と詳細画面の役割を分ける
- エラー時にユーザーが次の行動を判断できるようにする
9. 実務でありがちなUI設計の問題
ここでは、VB.NETの業務アプリケーションでよく見られるUI設計上の問題を整理します。
画面クラスが巨大化している
Windows Formsでは、フォームクラスにすべての処理を書けてしまいます。 その結果、1つのFormクラスが数千行になり、修正が非常に難しくなることがあります。
この問題を防ぐには、入力チェック、業務処理、DBアクセス、画面表示用データ作成などを 別クラスへ分離する必要があります。
処理中に画面が固まる
DB検索やファイル出力をUIスレッド上で実行していると、画面がフリーズします。 ユーザーは処理が進んでいるのか、アプリが停止したのか判断できません。
重い処理はバックグラウンドで実行し、処理中メッセージや進捗表示を用意することが重要です。
エラーメッセージが分かりにくい
「エラーが発生しました」のようなメッセージだけでは、ユーザーは何を直せばよいか分かりません。 入力エラー、業務エラー、システムエラーを区別し、具体的なメッセージを表示する必要があります。
DataGridViewに業務ロジックが入りすぎている
DataGridViewのCellValueChangedやCellValidatingイベントに複雑な業務ロジックを書くと、 処理の流れが追いにくくなります。 セル制御は画面側で行い、業務判断はServiceやValidatorへ分離することが望ましいです。
UIとDBが直接結びついている
画面イベントから直接SQLを実行する設計は、保守性が低くなります。 DB構造の変更が画面に直接影響しやすく、テストも難しくなります。
画面はServiceを呼び出し、ServiceはRepositoryを呼び出すように層を分けることが重要です。
10. 演習課題
演習1:検索画面の責務分離
ユーザー検索画面を想定し、検索条件入力、入力チェック、検索処理、一覧表示をそれぞれ分離してください。
- SearchConditionDtoを作成する
- SearchValidatorを作成する
- UserSearchServiceを作成する
- UserRepositoryを作成する
- Form側はServiceを呼び出すだけにする
演習2:DataGridViewの列を明示的に定義する
AutoGenerateColumnsをFalseに設定し、ユーザーID、ユーザー名、メールアドレス、登録日を表示する列を手動で定義してください。
演習3:入力エラーをErrorProviderで表示する
ユーザー登録画面を作成し、未入力項目に対してErrorProviderでエラーを表示してください。 エラーが解消された場合は、表示をクリアするようにしてください。
演習4:重い検索処理を非同期化する
検索ボタン押下時に、疑似的に3秒かかる検索処理を作成し、 Async / Awaitを使ってUIがフリーズしないようにしてください。
演習5:WPFでMVVM構成の画面を作成する
TextBoxに入力したユーザー名をViewModelにバインドし、 ボタン押下時にCommand経由でメッセージを表示する画面を作成してください。
11. まとめ
本章では、Windows FormsとWPFを対象に、VB.NETにおける高度なUI設計について学習しました。
Windows Formsは、業務アプリケーション開発で長く利用されてきた実績のあるUIフレームワークです。 シンプルで扱いやすい一方、イベントハンドラに処理を書きすぎると、 画面クラスが肥大化し、保守が難しくなります。
WPFは、XAML、データバインディング、スタイル、テンプレート、MVVMなどを活用できる、 より設計分離に向いたUIフレームワークです。 学習コストは高めですが、複雑な画面や長期保守を前提としたアプリケーションでは大きな効果を発揮します。
イベント駆動プログラミングでは、イベントハンドラを処理の入口として扱い、 業務ロジックやDBアクセスを直接書きすぎないことが重要です。 DataGridViewでは、列定義、編集制御、入力チェック、大量データ表示などを意識し、 業務用途に耐えられる設計を行う必要があります。
入力チェックとエラー表示では、ユーザーが何を修正すればよいか分かるメッセージを表示することが重要です。 また、入力エラー、業務エラー、システムエラーを区別することで、 ユーザー対応や運用保守の負担を減らせます。
UIスレッドとバックグラウンド処理の理解も欠かせません。 重い処理をUIスレッド上で実行すると画面がフリーズするため、 Async / Await、Task、CancellationTokenなどを活用して、 応答性の高いアプリケーションを設計する必要があります。
さらに、WPFではMVVMを活用することで、View、ViewModel、Modelの責務を分離し、 保守性やテスト容易性を高められます。 Windows Formsでも、ServiceやValidator、Repositoryを分離することで、 UIとロジックの分離は十分に実現できます。
本章のゴールは、見た目だけの画面を作ることではなく、 業務現場で実際に使われ続ける、堅牢で保守しやすいデスクトップアプリケーションを設計できるようになることです。 この考え方は、次章以降のDB連携、例外処理、非同期処理、テスト設計にも直結します。