WPFプログラミング備忘録

WPF コンバーターを使用する

WPF

はじめに

この記事ではコンバーターの使用方法について確認します。

開発環境は以下のとおりです。

オペレーティングシステムWindows10 x64
Visual StudioMicrosoft Visual Studio Community 2019 Version 16.11.2
.NET Framework4.7.2

サンプルプログラムの動作仕様

ボタンをマウスオーバーすると時計回りにクルクルと回転させることにします。

下図はプログラムの実行結果です。実際は回転し続けています。

コンバーターとは

コンバーターを適用済みの最終形ですがXAMLファイルは以下のとおりです。

<Window x:Class="WpfRotateButton.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfRotateButton"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="760">
    <Window.Resources>
        <local:HalfSizeConverter x:Key="halfSizeConverter"/>

        <Style x:Key="rotateButton" TargetType="{x:Type Button}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                        <Grid>
                            <Grid.RenderTransform>
                                <RotateTransform x:Name="rotateGrid" 
                                                 CenterX="{Binding Width, ElementName=rect, Converter={StaticResource halfSizeConverter}}" 
                                                 CenterY="{Binding Height, ElementName=rect, Converter={StaticResource halfSizeConverter}}" 
                                                 Angle="0"/>
                            </Grid.RenderTransform>
                            
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="MouseOver">
                                        <Storyboard>
                                            <DoubleAnimation
                                                Storyboard.TargetName="rotateGrid"
                                                Storyboard.TargetProperty="Angle"
                                                By="360" Duration="0:0:4" RepeatBehavior="Forever"/>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            
                            <Border Background="{TemplateBinding Background}"
                                    CornerRadius="10">
                                <ContentPresenter HorizontalAlignment="Center"
                                                  VerticalAlignment="Center"/>
                            </Border>

                            <Rectangle x:Name="rect"
                                       Width="{TemplateBinding Width}"
                                       Height="{TemplateBinding Height}">
                            </Rectangle>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    
    <StackPanel Orientation="Horizontal">
        <Button Content="ボタン青"
                Width="100"
                Height="100"
                Margin="40"
                FontSize="18"
                Foreground="White"
                Background="Blue"
                Style="{StaticResource rotateButton}"/>

        <Button Content="ボタン赤"
                Width="100"
                Height="100"
                Margin="40"
                FontSize="18"
                Foreground="White"
                Background="red"
                Style="{StaticResource rotateButton}"/>

        <Button Content="ボタン緑"
                Width="100"
                Height="100"
                Margin="40"
                FontSize="18"
                Foreground="White"
                Background="Green"
                Style="{StaticResource rotateButton}"/>

        <Button Content="ボタン黄"
                Width="100"
                Height="100"
                Margin="40"
                FontSize="18"
                Foreground="Black"
                Background="Yellow"
                Style="{StaticResource rotateButton}"/>
    </StackPanel>
</Window>

18行目のRotateTransform要素に注目してください。

RotateTransformのClientXとClientYは回転させる中心の位置を指定するためのプロパティです。

RotateTransform Class (System.Windows.Media)
Rotates an object clockwise about a specified point in a 2-D x-y coordinate system.

今回はボタンオブジェクトのちょうど中心の位置を軸にして回転させたいのですが、どうすればよいでしょうか?

ボタンの幅と高さの半分の値をそれぞれClientXとClientYに設定すれば良さそうです。

ボタンの幅と高さは構成部品であるRectangleのWidthとHeightから取得できます。

BindingクラスのElementNameプロパティではRectangleを参照することにして、とりあえずWidthとHeightをClientXとClientYにバインドすることにします。

ここで、ClientXとClientYに本当にバインドしたい値はWidthとHeightを2で割った値です。

ところがXAMLで Width/2 や Height/2 とすることはできません。

このような時にBindingクラスのConverterプロパティが使用できます。

コンバーターという名前のとおり2で割った値に変換するのです。

コンバーターの実装

コンバーターの実装を記述したコードビハインドは以下のとおりです。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfRotateButton
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }

    public class HalfSizeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if(targetType == typeof(double))
            {
                return (double)value / 2;
            }
            return value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }
}

独自のコンバーターを作成するためにはIValueConverterインターフェースを実装したクラスを作成します。今回はHalfSizeConverterというクラス名にしました。

IValueConverterにはConvertとConvertBackという2つのメソッドが定義されているので、これらを実装する必要があります。

IValueConverter Interface (System.Windows.Data)
Provides a way to apply custom logic to a binding.

今回はバインディングソースからバインディングターゲットへの片方向の変換のみが必要なので、ConvertBackメソッドは使われません。

Convertメソッドには最初にvalueという名前のパラメーターがあります。

既出ですが、XAMLのとおりにClientXにHalfSizeConverterコンバーターを指定した場合、valueにはRectangleのWidthプロパティの値が渡されます。

ClientYHalfSizeConverterコンバーターを指定した場合、valueにはRectangleのHeightプロパティの値が渡されます。

ですので、Convertメソッドではvalueを2で割った値を返せばよいだけです。

この2で割った値がClientXとClientYに実際にバインドされる値になります。

プログラムでは念のため、2番目のパラメーターtargetTypeを参照して、渡ってくる値の型チェックをしています。今回の場合はWidthとHeightなのでdoubleかどうかを確認しています。

XAMLでは、コンバーターはリソースとして定義してConverterプロパティに設定します。

以上で実装は終わりです。プログラムをビルドして実行すると仕様どおりの動作をします。

今回はコンバーターの使用方法について簡単な例で確認しました。

Buttonのカスタマイズ方法については以下の記事をご確認ください。

>> WPF マウスオーバー時にボタンの配色を変える方法

>> WPF コントロールのテンプレートを簡単に編集する方法

以上です。

コメント