WPFカスタムコントロールの開発

WPFにおけるカスタムコントロールの実装方法

WPF(Windows Presentation Foundation)では、カスタムコントロールをStyle要素を使用して外観と動作を定義します。StyleはWPFの強力なリソース機能であり、開発者は一連のプロパティ値を定義して、単一のコントロールまたは複数のコントロールに適用できます。

カスタムコントロールにおけるStyleの役割

1. 視覚的デザイン

  • Setter要素を使用して背景色(Background)、前景色(Foreground)、フォントサイズ(FontSize)、枠線スタイル(BorderBrushBorderThickness)などの各種プロパティを設定できます。
  • ControlTemplateを含めることで、コントロールのレイアウトと外観を完全に再定義できます。

2. インタラクティブ動作

  • Triggers要素により、マウスホバーやボタンクリックなどのプロパティ変化時にコントロールの外観や動作を変更できます。
  • 動的効果(マウスホバー時の色変更、クリック時のアニメーションなど)を追加できます。

3. リソースの再利用

  • リソースディクショナリ(ResourceDictionary)にStyleを定義することで、複数の場所で同じスタイル定義を再利用できます。
  • x:Key属性を使用してスタイルに一意の識別子を指定できます。

4. スタイルの継承

  • BasedOn属性を使用して他のStyleをベースにすることで、継承機能を活用できます。
  • 親スタイルのプロパティとトリガーを継承し、独自のプロパティを追加または上書きできます。

実装例

例1:Setterを使用した基本プロパティ設定

カスタムのEnhancedButtonコントロールにスタイルを適用する例:

<Window.Resources>
    <Style x:Key="EnhancedButtonTheme" TargetType="local:EnhancedButton">
        <Setter Property="Background" Value="#FFE6F3FF"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="FontSize" Value="18"/>
        <Setter Property="Padding" Value="12,6"/>
    </Style>
</Window.Resources>

<local:EnhancedButton Style="{StaticResource EnhancedButtonTheme}" Content="実行"/>

例2:Triggersを使用した動的効果

マウスホバー時に外観を変更するトリガーの実装:

<Style x:Key="InteractiveButtonTheme" TargetType="local:EnhancedButton">
    <Setter Property="Background" Value="#FFE6F3FF"/>
    <Setter Property="Foreground" Value="White"/>
    <Setter Property="BorderThickness" Value="2"/>
    <Setter Property="BorderBrush" Value="Transparent"/>
    
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="#FF0066CC"/>
            <Setter Property="BorderBrush" Value="#FF004499"/>
        </Trigger>
        <Trigger Property="IsPressed" Value="True">
            <Setter Property="Background" Value="#FF0055AA"/>
            <Trigger.EnterActions>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" 
                                         To="0.95" Duration="0:0:0.1"/>
                        <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" 
                                         To="0.95" Duration="0:0:0.1"/>
                    </Storyboard>
                </BeginStoryboard>
            </Trigger.EnterActions>
        </Trigger>
    </Style.Triggers>
</Style>

例3:ControlTemplateを使用した完全なカスタマイズ

コントロールの構造を完全に再定義する例:

<Style x:Key="ModernButtonTemplate" TargetType="local:EnhancedButton">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:EnhancedButton">
                <Grid x:Name="MainGrid">
                    <Border x:Name="OuterBorder"
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            CornerRadius="8"
                            Padding="{TemplateBinding Padding}">
                        <Border.Effect>
                            <DropShadowEffect x:Name="ShadowEffect" 
                                              BlurRadius="8" 
                                              ShadowDepth="2" 
                                              Opacity="0.3"/>
                        </Border.Effect>
                        <ContentPresenter x:Name="ContentPresenter"
                                          HorizontalAlignment="Center"
                                          VerticalAlignment="Center"
                                          Content="{TemplateBinding Content}"/>
                    </Border>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="OuterBorder" Property="Background" Value="#FF0078D7"/>
                        <Trigger.EnterActions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetName="ShadowEffect"
                                                     Storyboard.TargetProperty="BlurRadius"
                                                     To="12" Duration="0:0:0.2"/>
                                </Storyboard>
                            </BeginStoryboard>
                        </Trigger.EnterActions>
                    </Trigger>
                    <Trigger Property="IsPressed" Value="True">
                        <Setter TargetName="OuterBorder" Property="Background" Value="#FF106EBE"/>
                    </Trigger>
                    <Trigger Property="IsEnabled" Value="False">
                        <Setter TargetName="OuterBorder" Property="Opacity" Value="0.5"/>
                        <Setter TargetName="ShadowEffect" Property="Opacity" Value="0"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

カスタムコントロールの作成手順

1. コントロールクラスの作成

using System.Windows;
using System.Windows.Controls;

namespace CustomControls
{
    public class EnhancedButton : Button
    {
        public static readonly DependencyProperty HoverIntensityProperty = 
            DependencyProperty.Register(
                "HoverIntensity", 
                typeof(double), 
                typeof(EnhancedButton), 
                new PropertyMetadata(1.0));

        public double HoverIntensity
        {
            get { return (double)GetValue(HoverIntensityProperty); }
            set { SetValue(HoverIntensityProperty, value); }
        }

        static EnhancedButton()
        {
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(EnhancedButton), 
                new FrameworkPropertyMetadata(typeof(EnhancedButton)));
        }
    }
}

2. デフォルトスタイルの定義

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:CustomControls">
    
    <Style TargetType="{x:Type local:EnhancedButton}">
        <Setter Property="Background" Value="#FFE6F3FF"/>
        <Setter Property="Foreground" Value="White"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="BorderBrush" Value="#FF0078D7"/>
        <Setter Property="Padding" Value="16,8"/>
        <Setter Property="FontWeight" Value="SemiBold"/>
        <Setter Property="Cursor" Value="Hand"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:EnhancedButton}">
                    <Border x:Name="ButtonBorder"
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            CornerRadius="4"
                            Padding="{TemplateBinding Padding}">
                        <ContentPresenter HorizontalAlignment="Center"
                                          VerticalAlignment="Center"/>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="ButtonBorder" Property="Background" Value="#FF0078D7"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="True">
                            <Setter TargetName="ButtonBorder" Property="Background" Value="#FF005A9E"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

3. コントロールの使用

<Window x:Class="WpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:custom="clr-namespace:CustomControls">
    
    <Grid>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <custom:EnhancedButton Content="標準ボタン" 
                                 Width="200" 
                                 Height="40" 
                                 Margin="10"/>
            <custom:EnhancedButton Content="カスタム強度ボタン" 
                                 Width="200" 
                                 Height="40" 
                                 Margin="10"
                                 HoverIntensity="1.5"/>
            <custom:EnhancedButton Style="{StaticResource ModernButtonTemplate}" 
                                 Content="テンプレート適用" 
                                 Width="200" 
                                 Height="40" 
                                 Margin="10"/>
        </StackPanel>
    </Grid>
</Window>

x:Keyの使用について

リソース定義時のx:Keyの使用は、リソースの再利用方法によって決まります:

  • 明示的キーが必要な場合
    • 複数の場所で同じリソースを参照する場合
    • スタイルを特定のコントロールにのみ適用する場合
    • マージされたリソースディクショナリ内のリソース
  • 暗黙的キーで十分な場合
    • 特定の型のコントロールすべてに自動適用するスタイル
    • 一度だけ使用するリソース

ベストプラクティス

  1. 依存関係プロパティの実装
    • プロパティ変更コールバックを適切に実装
    • デフォルト値とメタデータを明確に定義
  2. テンプレートの設計
    • テンプレート内の要素に名前を付けて、トリガーから参照可能にする
    • テンプレートバインディングを適切に使用
  3. パフォーマンス最適化
    • 不要なアニメーションや効果を避ける
    • 仮想化を適切に実装
  4. アクセシビリティの配慮
    • キーボードナビゲーションをサポート
    • スクリーンリーダー対応

タグ: WPF XAML CustomControl C# ControlTemplate

6月27日 17:53 投稿