【実務・中級編】【初心者向け】WPFとVB.NETで始める初めてのデスクトップアプリ開発:Windows Formsとの違いとXAMLの基本 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク
スポンサーリンク

WinFormsの呪縛を解け:WPFとXAMLで手に入れる「モダン・デスクトップ開発」へのパラダイムシフト

かつて、我々がデスクトップアプリ開発において「正解」としていたのは、Windows Forms(WinForms)でした。ボタンを配置し、ダブルクリックしてイベントハンドラを書き、コントロールのプロパティを直接操作する。その手続き型(Imperative)のスタイルは直感的であり、小規模なツールを作るには十分でした。

しかし、業務が複雑化し、高解像度(DPI)への対応や、洗練されたUIデザイン、そして保守性の高いコードが求められる現代において、WinFormsの設計思想は「限界」を迎えています。

もし君が、コントロールのプロパティをコードビハインド(`Form1.vb`)で書き換え続け、スパゲッティコードの迷宮に迷い込んでいるのなら、今こそWPF (Windows Presentation Foundation) という新たなパラダイムへ移行すべき時だ。

本稿では、WinFormsからWPFへと足を踏み入れるエンジニアに向けて、単なる「書き方の違い」ではなく、「設計思想の決定的な違い」を伝授する。

1. 「命令」から「宣言」へ:WinFormsとWPFの決定的差異

WinFormsの設計は「命令型(Imperative)」です。
「ボタンが押されたら、テキストボックスの色を赤に変え、ラベルの文字を書き換えろ」という、手順を一つずつ指示するスタイルです。これは、UIのロジックとビジネスロジックが密結合(Tight Coupling)しやすく、規模が大きくなるとテスト不能なコードへと変貌します。

対して、WPFは「宣言型(Declarative)」です。
UIはXAML (Extensible Application Markup Language) というXMLベースの言語で記述されます。「このラベルは、このデータと同期している状態である」という「状態」を定義するのです。

この「状態」を管理するのがデータバインディング(Data Binding)という魔法です。コード側で「UIを書き換える命令」を書く必要はなくなる。データが変われば、UIは勝手に、そして美しく追随する。この分離こそが、堅牢な設計の第一歩だ。

2. XAMLレイアウトの極意:GridとStackPanelを使い分けろ

初心者が陥る最大の罠は、すべてのコントロールを `StackPanel` で並べてしまうことだ。

StackPanel:単純な「積み上げ」

`StackPanel` は、要素を垂直または水平に単純に並べる。使いやすいが、ウィンドウサイズが変更された際の「レスポンシブな挙動」を制御するのは苦手だ。

Grid:真のレイアウト・エンジン

プロフェッショナルなUIは、すべて `Grid` で構築される。`Grid` は、行(Row)と列(Column)の概念を持ち、比率(`*`)による動的なサイズ調整が可能だ。

【Bad Practice】
「とりあえずStackPanelに入れて、サイズを固定値(Pixel)で指定する」
→ ウィンドウを広げても、UIが左上に固まったままの、使いにくいツールが出来上がる。

【Best Practice】
「Gridを用い、`Width=”*”` や `Height=”Auto”` を駆使して、空間を論理的に分割する」
→ どんな解像度のモニターでも、美しく整列したUIが保たれる。

3. 実践:プロダクション品質のコード・テンプレート

では、実際に「ファイルの中身を読み込み、リストに表示する」という、業務自動化ツールで頻出する機能を例に、堅牢な設計を示そう。

ここでは、WinForms的な「コードビハインドへの書き込み」を排除し、WPFの核心であるMVVM (Model-View-ViewModel) のエッセンスを取り入れた実装を行う。

【View】MainWindow.xaml

UIの構造を定義する。ロジックは一切書かない。

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Modern Data Loader" Height="350" Width="500">
    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/> <!-- 操作エリア -->
            <RowDefinition Height="*"/>    <!-- 表示エリア -->
        </Grid.RowDefinitions>

        <!-- 操作エリア: StackPanelはボタン等の小さな塊に限定して使う -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
            <Button Content="ファイルを読み込む" 
                    Command="{Binding LoadFileCommand}" 
                    Padding="10,5" Margin="0,0,10,0"/>
            <TextBlock Text="{Binding StatusMessage}" VerticalAlignment="Center" Foreground="Gray"/>
        </StackPanel>

        <!-- 表示エリア: Gridではなく、リスト表示にはListBoxを使用 -->
        <ListBox Grid.Row="1" ItemsSource="{Binding FileLines}" />
    </Grid>
</Window>

【ViewModel】MainViewModel.vb

ここが心臓部だ。UIの状態と、ビジネスロジックを橋渡しする。`INotifyPropertyChanged` の実装は、WPFエンジニアとしての最低限の嗜みである。

Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Imports System.IO
Imports System.Threading.Tasks
Imports System.Windows.Input

''' <summary>
''' UIの状態とロジックを管理するViewModel
''' </summary>
Public Class MainViewModel
    Implements INotifyPropertyChanged

    ' --- プロパティ ---
    Private _statusMessage As String = "準備完了"
    Private _fileLines As New System.Collections.ObjectModel.ObservableCollection(Of String)()

    Public Property StatusMessage As String
        Get
            Return _statusMessage
        End Get
        Set(value As String)
            _statusMessage = value
            OnPropertyChanged() ' UIに通知
        End Set
    End Property

    Public Property FileLines As System.Collections.ObjectModel.ObservableCollection(Of String)
        Get
            Return _fileLines
        End Get
    End Property

    ' コマンドの定義
    Public Property LoadFileCommand As ICommand

    Public Sub New()
        ' コマンドの初期化(簡易的な実装)
        LoadFileCommand = New RelayCommand(Async Function()
                                               Await LoadFileAsync()
                                           End Function)
    End Sub

    ' --- ロジック ---
    ''' <summary>
    ''' 非同期でファイルを読み込む。UIスレッドをブロックしないことが重要。
    ''' </summary>
    Private Async Function LoadFileAsync() As Task
        Try
            StatusMessage = "読み込み中..."
            
            ' 本来はOpenFileDialog等を使用するが、ここでは例として固定パス
            Dim dummyPath As String = "C:\temp\sample.txt" 
            
            If Not File.Exists(dummyPath) Then
                StatusMessage = "エラー: ファイルが見つかりません。"
                Return
            End If

            ' 非同期でファイルI/Oを実行 (UIフリーズ防止)
            Dim lines = Awake Task.Run(Function() File.ReadAllLines(dummyPath))

            ' コレクションの更新
            _fileLines.Clear()
            For Each line In lines
                _fileLines.Add(line)
            Next

            StatusMessage = $"完了: {lines.Length} 行読み込みました。"

        Catch ex As Exception
            StatusMessage = $"エラーが発生しました: {ex.Message}"
        End Try
    End Function

    ' --- INotifyPropertyChanged 実装 ---
    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    Protected Sub OnPropertyChanged(<CallerMemberName> Optional propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class

''' <summary>
''' コマンドパターンを実現するための最小構成クラス
''' </summary>
Public Class RelayCommand
    Implements ICommand
    Private ReadOnly _execute As Func(Of Task)
    Public Sub New(execute As Func(Of Task))
        _execute = execute
    End Sub
    Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
        Return True
    End Function
    Public Sub Execute(parameter As Object) Implements ICommand.Execute
        ' 非同期実行をFire-and-forgetで呼び出す(実務ではより厳密な管理が必要)
        Dim _ = _execute()
    End Sub
    Public Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
End Class

4. 業務自動化エンジニアが守るべき「鉄則」

WPFへの移行にあたり、以下の3点を脳裏に刻んでおいてほしい。

1. UIスレッドを殺すな (Async/Awaitの徹底)
ファイル操作やデータベースへのクエリを同期的に実行すると、その間UIは完全にフリーズする。ユーザーは「アプリが落ちた」と勘違いし、タスクマネージャーから強制終了させるだろう。重い処理は必ず `Task.Run` または `Async/Await` を使い、バックグラウンドで実行せよ。

2. Code-behind (`.xaml.vb`) にロジックを書くな
`Button_Click` イベントの中に `If…Then` や `SQL` を書くのは、WinForms時代の悪習だ。ロジックは ViewModel、あるいは Model に閉じ込めろ。UIはあくまで「データの表示器」に過ぎない。

3. リソースの解放を忘れるな
データベース接続やファイルストリームを扱う際は、必ず `Using` ブロックを使用せよ。WPFはオブジェクトのライフサイクルが複雑になりがちだ。メモリリークは、長時間稼働する業務ツールにおいて致命的なバグとなる。

結びに代えて

WPFは、習得の初期コストこそWinFormsより高い。しかし、一度その「宣言的」な設計思想をマスターすれば、君が書くコードの堅牢性と、作成するツールの品質は劇的に向上する。

「動けばいい」のレベルから、「保守でき、拡張でき、美しい」のレベルへ。
さあ、XAMLの扉を開け、モダンな開発の世界へ踏み出そう。

タイトルとURLをコピーしました