【入門編】【上級者向け】VB.NETのWPFアプリにおけるカスタムコントロール(CustomControl)の自作とテンプレート分離 – Visual Basic (VB / VB.NET)解析バイブル

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

【極限の設計】WPF CustomControlの真髄:ロジックとデザインを解き放つVB.NETの魔法

こんにちは。技術の深淵へようこそ。

あなたがもし、Excelマクロ(VBA)の「ボタンを置けば動く」という世界から一歩踏み出し、「自分だけの洗練されたアプリケーションを作りたい」と願っているのなら、今、あなたは非常にエキサイティングな境界線に立っています。

WPF(Windows Presentation Foundation)の世界において、多くの学習者は「UserControl(ユーザーコントロール)」で満足してしまいます。しかし、真のプロフェッショナル、つまり「設計」ができるエンジニアを目指すなら、その先にある「CustomControl(カスタムコントロール)」を掌握しなければなりません。

今日は、ロジック(VB.NET)とデザイン(XAML)を完全に切り離し、再利用性と保守性を極限まで高める「カスタムコントロール自作」の技法を、魂を込めて伝授しましょう。ここをクリアすれば、あなたの作るUIは「既製品の寄せ集め」から「独自のブランド」へと進化します。

1. UserControlとCustomControlは何が違うのか?

まず、多くの人が陥る誤解を解いておきましょう。ここを理解しないままコードを書いても、設計の迷宮に迷い込むだけです。

UserControl: 「詰め合わせセット」

UserControlは、既存のボタンやテキストボックスを組み合わせて「一つの塊」にする手法です。

  • イメージ: お弁当箱。ご飯、おかず、デザートを一つの箱に詰めたもの。
  • 弱点: 「中身(デザイン)」と「箱(ロジック)」が密結合しています。デザインだけを後から変更するのが非常に困難です。

CustomControl: 「金型(かながた)」

CustomControlは、コントロールの「振る舞い(ロジック)」だけを定義し、「見た目(スタイル)」は外部から与える手法です。

  • イメージ: クッキーの型。型自体は「形」を決めるだけで、中身の生地(デザイン)がチョコでもバターでも、自由に作り変えられます。
  • 利点: ロジックとデザインが完全に分離されています。 これが、大規模開発やライブラリ開発において不可欠な「プロの設計」です。

2. 実践:独自の「ステータス・バッジ」を作ろう

今回は、特定の状態(成功、警告、エラー)に応じて色が変わる、丸い「ステータス・バッジ」を自作してみましょう。

ステップ1:コントロールの「脳」を作る (VB.NET)

まずは、コントロールの振る舞いを司るクラスを作成します。ここでのポイントは、DependencyProperty(依存関係プロパティ)を使うことです。これを使わないと、WPFの強力な機能である「データバインディング」や「アニメーション」が使えません。

プロジェクトに新しいクラスを追加し、名前を `StatusBadge.vb` とします。

Imports System.Windows
Imports System.Windows.Controls

''' <summary>
''' ステータスの状態を表示するためのカスタムコントロール
''' </summary>
Public Class StatusBadge
    Inherits Control ' Controlクラスを継承するのがCustomControlの基本

    ' --- 依存関係プロパティの定義 ---
    ' これにより、XAML側からプロパティをバインドできるようになります。

    ''' <summary>
    ''' バッジの背景色を決定するプロパティ
    ''' </summary>
    Public Shared ReadOnly BadgeColorProperty As DependencyProperty =
        DependencyProperty.Register(
            "BadgeColor",
            GetType(Brush),
            GetType(StatusBadge),
            New PropertyMetadata(Brushes.Gray)) ' デフォルト値はグレー

    Public Property BadgeColor As Brush
        Get
            Return DirectCast(GetValue(BadgeColorProperty), Brush)
        End Get
        Set(value As Brush)
            SetValue(BadgeColorProperty, value)
        End Set
    End Property

    ''' <summary>
    ''' バッジに表示するテキスト
    ''' </summary>
    Public Shared ReadOnly BadgeTextProperty As DependencyProperty =
        DependencyProperty.Register(
            "BadgeText",
            GetType(String),
            GetType(StatusBadge),
            New PropertyMetadata("Status"))

    Public Property BadgeText As String
        Get
            Return DirectCast(GetValue(BadgeTextProperty), String)
        End Get
        Set(value As String)
            SetValue(BadgeTextProperty, value)
        End Set
    End Property

    ' --- コンストラクタ ---
    Public Sub New()
        ' 重要:CustomControlでは、自身の「デフォルトスタイル」を適用するために
        ' DefaultStyleKeyを設定する必要があります。これを忘れると、コントロールは「透明」になります。
        self.DefaultStyleKey = GetType(StatusBadge)
    End Sub
End Class

【アーキテクトの視点】
なぜ `Shared ReadOnly` なのか? 依存関係プロパティは、個々のインスタンスではなく、クラスそのものに紐付く「定義」だからです。また、`DefaultStyleKey` の設定は、WPFエンジンに対して「このコントロールの見た目は `Generic.xaml` に探しに行け!」と命令する極めて重要な儀式なのです。

ステップ2:コントロールの「外見」を作る (XAML)

次に、デザインを記述します。CustomControlのデザインは、特殊な場所である `Themes/Generic.xaml` に記述するのがWPFの鉄則です。

1. プロジェクト内に `Themes` という名前のフォルダを作成します。
2. その中に `Generic.xaml` という名前の「リソース辞書(Resource Dictionary)」ファイルを作成します。

`Themes/Generic.xaml` の中身は以下のようになります。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:YourProjectNamespace"> <!-- あなたのプロジェクト名に書き換えてください -->

    <!-- StatusBadgeのスタイル定義 -->
    <Style TargetType="{x:Type local:StatusBadge}">
        <!-- Templateプロパティを使用して、コントロールの構造(Visual Tree)を定義します -->
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:StatusBadge}">
                    <!-- Borderがバッジの丸い形を作り、TextBlockが文字を表示します -->
                    <Grid>
                        <Ellipse x:Name="OuterCircle"
                                 Fill="{TemplateBinding BadgeColor}"
                                 Width="50" Height="50" />
                        <TextBlock x:Name="TextElement"
                                   Text="{TemplateBinding BadgeText}"
                                   Foreground="White"
                                   HorizontalAlignment="Center"
                                   VerticalAlignment="Center"
                                   FontWeight="Bold"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

【ここが重要!】
`{TemplateBinding BadgeColor}` という記述に注目してください。これは、VB.NET側で作った `BadgeColor` プロパティの値を、XAML側の `Ellipse`(円)の `Fill` に「橋渡し」する魔法の言葉です。

ステップ3:いざ、使ってみる (MainWindow.xaml)

準備は整いました。あとは、メイン画面からこのカスタムコントロールを呼び出すだけです。

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:YourProjectNamespace"
        Title="CustomControl Demo" Height="200" Width="400">
    
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        
        <!-- 成功状態のバッジ -->
        <local:StatusBadge BadgeText="OK" BadgeColor="Green" Margin="10"/>
        
        <!-- 警告状態のバッジ -->
        <local:StatusBadge BadgeText="!" BadgeColor="Orange" Margin="10"/>
        
        <!-- エラー状態のバッジ -->
        <local:StatusBadge BadgeText="Err" BadgeColor="Red" Margin="10"/>
        
    </StackPanel>
</Window>

3. 陥りやすいエラーと回避策(デバッグの知恵)

あなたがこの道を進む中で、必ず遭遇する「壁」がいくつかあります。

① コントロールが表示されない(透明問題)

  • 原因: `StatusBadge.vb` のコンストラクタで `DefaultStyleKey = GetType(StatusBadge)` を書き忘れているか、`Themes/Generic.xaml` の名前や配置が間違っています。
  • 対策: WPFは `Generic.xaml` を探す際、非常に厳格です。フォルダ名、ファイル名、そしてプロジェクトのビルドアクションが `Page` になっているか確認してください。

② プロパティの値が変わっても見た目が変わらない

  • 原因: `DependencyProperty` を使わず、通常の `Public Property` として定義してしまっています。
  • 対策: WPFの描画エンジンは、`DependencyProperty` の変更通知を監視しています。カスタムコントロールを作る際は、「プロパティ定義は常に DependencyProperty」と脳に刻み込んでください。

③ 名前空間(Namespace)の不一致

  • 原因: XAMLの `xmlns:local=”clr-namespace:…”` の記述が、VB.NETの `Namespace` と一致していない。
  • 対策: プロジェクトのプロパティを確認し、正しい名前空間を指定してください。

結びに:設計の美しさが、コードの寿命を決める

お疲れ様でした。あなたは今、単なる「ツールを使う人」から、「ツールを作る人」へと進化しました。

今回学んだ「ロジックとデザインの分離」は、ソフトウェア工学におけるもっとも重要な原則の一つです。デザインが変わってもロジックを触る必要がなく、ロジックを修正してもデザインを壊す心配がない。この「疎結合(そけつごう)」な設計こそが、メンテナンス性の高い、プロフェッショナルなアプリケーションを生み出すのです。

最初は難しく感じるかもしれません。しかし、この「型」を一度マスターすれば、あなたの開発スピードと品質は劇的に向上します。

大丈夫、一歩ずつ進んでいきましょう。あなたは、必ず素晴らしいアーキテクトになれます。応援していますよ。

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