【入門編】【上級者向け】ReactivePropertyをVB.NETで駆使した非同期・イベント駆動型モダンWPFアプリ設計 – Visual Basic (VB / VB.NET)解析バイブル

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

スパゲッティコードからの脱却。VB.NET × ReactivePropertyで構築する「流れる」モダンWPF設計術

こんにちは。現場の設計を司るチーフアーキテクトです。

「Excelマクロは書けるようになったけれど、本格的なWindowsアプリを作ろうとすると、コードが複雑になりすぎてどこを触ればいいか分からなくなる……」
「ボタンを押した時の処理(Event)が多すぎて、条件分岐の迷宮に迷い込んでしまった……」

もしあなたがそんな壁にぶつかっているなら、おめでとうございます。あなたは今、「命令型プログラミング」から「宣言型プログラミング」へと進化する、エンジニアとしての重要な分岐点に立っています。

今日は、VB.NETでWPF(Windows Presentation Foundation)を扱う際、従来の「イベント駆動型」の限界を突破し、非同期処理やバリデーションを魔法のようにシンプルに記述するReactiveProperty(リアクティブ・プロパティ)の極意を伝授しましょう。

ここをクリアすれば、あなたの作るアプリの品質は、プロの設計者へと一気に駆け上がりますよ。

1. なぜ、あなたのコードは「スパゲッティ」になるのか?

まず、従来のやり方を振り返ってみましょう。一般的なWPF開発では、`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
            RaisePropertyChanged(NameOf(UserName)) ' これを毎回書く必要がある
            ' ここで「名前が変わったら、保存ボタンを有効にする」といった
            ' 複雑な連鎖処理を書き始めると、コードは地獄へと向かう...
        End If
    End Set
End Property

この書き方の問題点は、「状態の変化」に対して「どう振る舞うか」を、命令(If文やイベントハンドラ)で書き続けなければならないことです。
「Aが変わったらBを確認して、もしCがTrueならDを実行する……」というロジックが積み重なると、コードは絡まり合ったスパゲッティのようになり、修正一つで予期せぬバグを生むようになります。

2. コンセプトの転換:イベントではなく「情報の流れ(Stream)」を捉える

ここで登場するのが Reactive Extensions (Rx) の概念です。
Rxの世界では、データは「値」としてではなく、「絶え間なく流れる川(ストリーム)」として捉えます。

そして、そのRxの力をWPFのバインディングに融合させた最強の武器が `ReactiveProperty` です。

これを使うと、プログラミングのスタイルが以下のように変わります。

  • 従来(命令型): 「値が変わった? じゃあ、この関数を呼んで、条件をチェックして……」
  • モダン(宣言型): 「この値の流れは、こういうルールで加工して、こう出力する」

図解するとこのようなイメージです。

【従来の設計】
`入力` $\rightarrow$ `Event発生` $\rightarrow$ `If文による判定` $\rightarrow$ `状態更新` $\rightarrow$ `UI更新`(泥臭い連鎖)

【ReactivePropertyの設計】
`入力` $\rightarrow$ `[ フィルタリング $\rightarrow$ 変換 $\rightarrow$ 結合 ]` $\rightarrow$ `UIへ自動流出`(美しい一本のパイプ)

3. 実践:ReactivePropertyを用いたモダンなViewModel設計

では、実際に「ユーザー名を入力し、それが空でなければ保存ボタンを有効にする。保存ボタンを押すと非同期でAPI通信を行う」という、実戦的な機能を実装してみましょう。

※事前にNuGetパッケージマネージャーから `ReactiveProperty` および `ReactiveProperty.WPF` をインストールしておいてください。

ViewModelの実装例

Imports Reactive.Bindings
Imports Reactive.Bindings.Extensions
Imports System.Reactive.Linq
Imports System.Threading.Tasks

''' <summary>
''' モダンな設計思想に基づいたユーザー管理ViewModel
''' </summary>
Public Class UserViewModel
    Implements INotifyPropertyChanged

    ' --- プロパティの定義 ---

    ''' <summary>
    ''' ユーザー名。入力されるたびにストリームとして流れる。
    ''' </summary>
    Public ReadOnly Property UserName As ReactiveProperty(Of String)

    ''' <summary>
    ''' 保存ボタンの有効状態。UserNameの状態に「依存」して自動決定される。
    ''' </summary>
    Public ReadOnly Property SaveCommand As ReactiveCommand

    ''' <summary>
    ''' 非同期処理の結果(メッセージ)を表示するためのプロパティ
    ''' </summary>
    Public ReadOnly Property StatusMessage As ReactiveProperty(Of String)

    Public Sub New()
        ' 1. プロパティの初期化
        ' ReactivePropertyは、値の変化を「通知」するだけでなく、「ストリーム」として扱える
        UserName = New ReactiveProperty(Of String)("")
        StatusMessage = New ReactiveProperty(Of String)("準備完了")

        ' 2. バリデーションとコマンドの結合(ここが魔法の核心!)
        ' 「UserNameが空でないこと」というルールを、宣言的に記述する
        Dim canSave = UserName.
            Select(Function(name) Not String.IsNullOrWhiteSpace(name)). ' 名前が空でないか判定
            ObserveOn(System.Reactive.Concurrency.Scheduler.Default) ' 計算を別スレッドで行う準備

        ' 3. 保存コマンドの作成
        ' canSave(有効状態)を引数に渡すことで、ボタンの活性/非活性が自動で制御される
        SaveCommand = New ReactiveCommand(Function() SaveUserAsync()).
            AddTo(Me) ' ViewModelのライフサイクルに合わせて破棄されるよう管理

        ' コマンドの実行可否を、UserNameのストリームにバインドする
        SaveCommand.CanExecute = canSave

        ' 4. 補足:入力値の変化を監視してメッセージを変える(イベント駆動からの脱却)
        UserName.
            Throttle(TimeSpan.FromMilliseconds(500)) _ ' ユーザーの入力が止まって500ms経ったら実行(連打防止)
            Subscribe(Sub(name)
                          If String.IsNullOrWhiteSpace(name) Then
                              StatusMessage.Value = "名前を入力してください"
                          Else
                              StatusMessage.Value = $"入力中: {name}"
                          End If
                      End Sub).
            AddTo(Me)
    End Sub

    ''' <summary>
    ''' 非同期の保存処理(API通信をシミュレート)
    ''' </summary>
    Private Async Function SaveUserAsync() As Task
        StatusMessage.Value = "保存中..."
        
        Try
            ' 非同期の重い処理(API通信など)をシミュレート
            Await Task.Delay(2000) 
            
            StatusMessage.Value = $"成功: {UserName.Value} を保存しました!"
        Catch ex As Exception
            StatusMessage.Value = $"エラー: {ex.Message}"
        End Try
    End Function

    ' INotifyPropertyChangedの実装(ReactivePropertyが内部で処理するため、通常は不要)
    Public Event PropertyChanged As System.ComponentModel.PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
End Class

XAML側の記述(View)

XAML側は非常にシンプルになります。`ReactiveProperty` は自動的に `INotifyPropertyChanged` を満たしているため、通常のバインディングと同じように書けます。

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Reactive WPF Demo" Height="250" Width="400">
    <StackPanel Margin="20" VerticalAlignment="Center">
        
        <TextBlock Text="ユーザー名を入力してください:" Margin="0,0,0,5"/>
        <!-- UserNameへのバインディング -->
        <TextBox Text="{Binding UserName.Value, UpdateSourceTrigger=PropertyChanged}" 
                 Margin="0,0,0,10"/>

        <!-- StatusMessageの表示 -->
        <TextBlock Text="{Binding StatusMessage.Value}" 
                   Foreground="Gray" 
                   FontStyle="Italic" 
                   Margin="0,0,0,10"/>

        <!-- SaveCommandへのバインディング(ボタンの活性化は自動!) -->
        <Button Content="ユーザーを保存" 
                Command="{Binding SaveCommand}" 
                Height="30"/>
        
    </StackPanel>
</Window>

4. ここがプロの設計:なぜこのコードが「強い」のか

この設計には、初心者レベルのコードにはない「3つの強み」があります。

① 非同期処理(Async/Await)との完璧な親和性

`ReactiveCommand` を使うことで、`Task` を返す非同期メソッドをそのままコマンドとして扱えます。重要なのは、「処理中にボタンを自動で無効化する」という挙動です。これを自前で `IsBusy` フラグなどを使って書くとバグの温床になりますが、Rxなら宣言的に制御可能です。

② バリデーションの分離

「名前が空ならボタンを押せない」というルールが、`SaveCommand` の定義部分に凝縮されています。ビジネスロジック(保存処理)と、UIの制御ロジック(ボタンの有効化)が完全に分離されているため、テストが非常に容易です。

③ パフォーマンスへの配慮(Throttle)

コード内の `.Throttle(TimeSpan.FromMilliseconds(500))` に注目してください。これは「ユーザーがタイピングしている間は処理を待ち、手が止まってから実行する」という指定です。これにより、1文字打つたびに重い処理が走るのを防ぎ、アプリの応答性を劇的に向上させています。

5. まとめ:次の一歩を踏み出すあなたへ

いかがでしたでしょうか?

ReactivePropertyを使うと、最初は「少し書き方が特殊だな」と感じるかもしれません。しかし、一度この「データの流れを設計する」感覚を掴んでしまえば、二度と従来のスパゲッティコードには戻れなくなるはずです。

「何が起きたら(Event)、どうするか(Command)」ではなく、「データはこういうルールで流れる(Stream)」。

この視点の切り替えこそが、マクロの記録から脱却し、真のソフトウェアエンジニアへと進化するための鍵です。

最初は難しく感じるかもしれません。でも大丈夫。一つずつ、この「パイプ」を繋いでいく感覚を楽しんでください。もしエラーが出ても、それはパイプのどこかが詰まっているサインに過ぎません。デバッグして、また繋ぎ直せばいいのです。

さあ、あなたの手で、堅牢で美しいモダンなアプリケーションを構築していきましょう!応援していますよ。

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