【実務・中級編】【中級者・実務】VB.NETアプリにおけるWPF Styles, Triggers, Templatesによる統一感のあるデザイン適用 – Visual Basic (VB / VB.NET)解析バイブル

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

UIの「一貫性」を設計せよ:WPFのStyles, Triggers, Templatesで構築する堅牢な業務アプリケーション

業務アプリケーションの開発において、多くのエンジニアが陥る「罠」がある。それは、「コントロールごとに個別にプロパティを設定してしまうこと」だ。

ボタンの色を一つずつ設定し、マウスホバー時の挙動を一つずつイベントハンドラで書き込む。一見、動いているように見える。しかし、その設計は「技術的負債」の塊だ。要件変更で「ボタンの色をすべてコーポレートカラーの紺色に変更してくれ」と言われた瞬間、あなたは数百箇所のコードを修正する羽目になり、その過程で必ずどこかのボタンの挙動を壊す。

プロフェッショナルなアーキテクトは、UIを「個別の部品の集合」ではなく、「一貫したルールに基づいたシステムの表現」として捉える。

本稿では、WPF(Windows Presentation Foundation)におけるStyles, Triggers, Templatesを駆使し、保守性が高く、変更に強いモダンなUIをVB.NETで構築するための極限の知見を伝授する。

1. Single Source of Truth:App.xamlによるグローバルな統制

デザインの統一感を出すための第一歩は、デザインの定義を各画面(WindowやUserControl)に散らさないことだ。すべての共通ルールは `App.xaml`、あるいはプロジェクト内の `ResourceDictionary` に集約しなければならない。

Implicit Style(暗黙的スタイル)の活用

`x:Key` を指定しないスタイルを「暗黙的スタイル」と呼ぶ。これを使うことで、プロジェクト内のすべての `Button` に対して、個別にコードを書くことなく一括でデザインを適用できる。

<!-- App.xaml または ResourceDictionary 内 -->
<Application.Resources>
    <!-- 
        カラーパレットをリソースとして定義する。
        これにより、ブランドカラーの変更が一行で完了する。
    -->
    <SolidColorBrush x:Key="PrimaryColor" Color="#0056b3" />
    <SolidColorBrush x:Key="HoverColor" Color="#004494" />
    <SolidColorBrush x:Key="DisabledColor" Color="#cccccc" />
    <SolidColorBrush x:Key="TextColor" Color="White" />

    <!-- 
        すべてのButtonに適用される暗黙的スタイル 
    -->
    <Style TargetType="Button">
        <Setter Property="Background" Value="{StaticResource PrimaryColor}" />
        <Setter Property="Foreground" Value="{StaticResource TextColor}" />
        <Setter Property="Padding" Value="10,5" />
        <Setter Property="FontSize" Value="14" />
        <Setter Property="BorderThickness" Value="0" />
        <Setter Property="Cursor" Value="Hand" />
    </Style>
</Application.Resources>

アーキテクトの視点:
「色を直接 `Background=”#0056b3″` と書くのは素人の仕事だ。必ず `SolidColorBrush` としてリソース化せよ。これが『変更への耐性』を生む。」

2. Triggers:ロジックと表現の分離

業務システムにおいて、ボタンの「無効化(IsEnabled=False)」や「マウスホバー」の表現は極めて重要だ。ここで初心者は `MouseEnter` や `MouseLeave` イベントをVB.NETのコードビハインドに書こうとする。それは絶対にやめてくれ。

UIの状態変化(Visual State)は、XAMLの `Trigger` で完結させるべきだ。コードビハインドにUIの見た目のロジックを混ぜると、ユニットテストが不可能になり、保守性が著しく低下する。

Property Triggerによる状態制御

前述のスタイルに、トリガーを追加して「マウスホバー時」と「無効時」の挙動を定義する。

<Style TargetType="Button">
    <!-- (前述のSetterは省略) -->
    <Setter Property="Background" Value="{StaticResource PrimaryColor}" />
    
    <Style.Triggers>
        <!-- マウスホバー時の挙動 -->
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="{StaticResource HoverColor}" />
        </Trigger>
        
        <!-- コントロールが無効化されている時の挙動 -->
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="Background" Value="{StaticResource DisabledColor}" />
            <Setter Property="Foreground" Value="Gray" />
        </Trigger>
    </Style.Triggers>
</Style>

アーキテクトの視点:
「`IsMouseOver` をトリガーで制御することで、VB.NET側のコードは『ビジネスロジック』だけに集中できる。UIの色の変化という『副作用』をロジックから切り離せ。」

3. ControlTemplate:コントロールの「骨格」を再定義する

`Style` は既存のプロパティ(BackgroundやFontSizeなど)を書き換えるものだが、`ControlTemplate` はコントロールの「見た目の構造そのもの」を作り直すものだ。

例えば、「角丸のモダンなボタン」を作りたい場合、単に `CornerRadius` プロパティを変えるだけでは不十分な場合がある(Buttonには本来CornerRadiusプロパティがない)。その場合は、`Template` を書き換える必要がある。

プロダクションレベルのテンプレート実装例

<Style x:Key="ModernButtonStyle" TargetType="Button">
    <!-- 基本プロパティの設定 -->
    <Setter Property="Background" Value="{StaticResource PrimaryColor}" />
    <Setter Property="Foreground" Value="White" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <!-- 
                    Borderを使ってボタンの形状を定義する。
                    ここでのポイントは ContentPresenter を忘れないことだ。
                    これが、ボタン内に配置されたテキストやアイコンを表示する「窓」になる。
                -->
                <Border x:Name="border"
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"
                        CornerRadius="4" 
                        SnapsToDevicePixels="true">
                    <ContentPresenter x:Name="contentPresenter" 
                                      Focusable="False" 
                                      HorizontalAlignment="Center" 
                                      VerticalAlignment="Center" 
                                      Margin="{TemplateBinding Padding}"/>
                </Border>

                <ControlTemplate.Triggers>
                    <!-- テンプレート内でのトリガー定義 -->
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="border" Property="Background" Value="{StaticResource HoverColor}" />
                    </Trigger>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter TargetName="border" Property="Opacity" Value="0.7" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

アーキテクトの視点:
「`TemplateBinding` を使え。これにより、`Style` で設定した `Background` の値が、テンプレート内の `Border` に動的に伝搬する。これを怠ると、スタイルを変更しても見た目が変わらないという、デバッグの地獄に陥ることになる。」

4. VB.NET 実装:堅牢なデータバインディングとの連携

UIが完成したら、次はそれをどう制御するかだ。業務アプリでは、ボタンの有効/無効を「データの状態」に基づいて制御する必要がある。ここでは、MVVMパターンを意識した堅牢な実装例を示す。

ViewModel (VB.NET)

UIの状態(ボタンが押せるかどうか)は、ViewではなくViewModelが決定する。

Imports System.ComponentModel

''' <summary>
''' 業務ロジックを司るViewModel
''' </summary>
Public Class MainViewModel
    Implements INotifyPropertyChanged

    Private _isProcessing As Boolean = False

    Public Property IsProcessing As Boolean
        Get
            Return _isProcessing
        End Get
        Set(value As Boolean)
            If _isProcessing <> value Then
                _isProcessing = value
                OnPropertyChanged(NameOf(IsProcessing))
                ' 処理中フラグが変わったら、ボタンのIsEnabledも自動的に変わるように通知
                OnPropertyChanged(NameOf(CanExecuteProcess))
            End If
        End Set
    End Property

    ''' <summary>
    ''' ボタンの有効状態を判定するプロパティ
    ''' </summary>
    Public ReadOnly Property CanExecuteProcess As Boolean
        Get
            ' 処理中でなければボタンを押せる
            Return Not _isProcessing
        End Get
    End Property

    Public Event PropertyPropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Protected Sub OnPropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(propertyName, New PropertyChangedEventArgs(propertyName))
    End Sub

    ''' <summary>
    ''' 実行ボタンが押された時の処理
    ''' </summary>
    Public Async Function ExecuteProcessAsync() As Task
        If Not CanExecuteProcess Then Return

        IsProcessing = True
        Try
            ' データベース連携やファイル操作などの重い処理をシミュレート
            Await Task.Delay(3000) 
            ' ここに実際の業務ロジックを記述
        Finally
            IsProcessing = False
        End Try
    End Function
End Class

View (XAML)

ViewModelのプロパティと、先ほど定義したスタイルを紐付ける。

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="業務自動化ツール" Height="300" Width="400">
    
    <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
        <!-- 
            Styleに定義したModernButtonStyleを適用。
            IsEnabledプロパティは、ViewModelのCanExecuteProcessにバインドされる。
        -->
        <Button Content="データ処理実行" 
                Style="{StaticResource ModernButtonStyle}"
                Width="150" Height="40"
                IsEnabled="{Binding CanExecuteProcess}"
                Command="{Binding ExecuteCommand}" /> 
        
        <!-- 
            注意:Commandパターンを使うのがベストだが、
            シンプルにするために今回はコードビハインドからの呼び出し例とする 
        -->
        <TextBlock Text="処理中..." 
                   Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}"
                   HorizontalAlignment="Center" Margin="10"/>
    </StackPanel>
</Window>

5. まとめ:プロフェッショナルのためのチェックリスト

優れた業務アプリケーションを構築するために、以下の原則を脳に刻んでおけ。

1. Magic Numbers/Colorsを排除せよ: 色、フォントサイズ、余白はすべて `ResourceDictionary` で定義し、名前を付けて管理すること。
2. コードビハインドを汚すな: `MouseEnter` や `Click` で見た目を変えるな。それは `Trigger` の仕事だ。
3. TemplateBindingをマスターせよ: `Style` の値を `ControlTemplate` 内の要素に伝播させる仕組みを理解せよ。
4. 状態はViewModelが持つ: 「ボタンが押せるかどうか」はUIの属性ではなく、ビジネスロジックの結果である。

これらの設計原則を守ることで、あなたの作るアプリケーションは、単なる「動くツール」から、「変化に強く、メンテナンスが容易な、プロフェッショナルな製品」へと昇華する。

現場へ戻り、その設計思想をコードに反映させろ。期待している。

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