SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

キャプション付きの画像表示を行うサンプル

マウスオーバー時に、アニメーションしながらキャプション表示を行うControlTemplateを作ってみました。

f:id:minami_SC:20140116012436p:plain:w400

仕組み

ContentControlのControlTemplateとして作ってるので、画像表示に限らず任意のコントロールに対してキャプション表示を行えます。(そんな用途あるのか、、、って感じですが。)
また、キャプション表示用の文字列は、ContentControlのTagプロパティを使って表示します。
キャプション表示時のアニメーションは、MouseEnterイベントとMouseLeaveイベントをトリガーとして、キャプションの表示/非表示を切り替えるアニメーションのStoryboardを動かしています。

キャプション表示を行うContentControl

画像の準備

以下のようにContentTemplateを適用したContentControlの内部に、Imageコントロールを配置しておきます。

        <ContentControl Width="300"
                        Height="200"
                        Margin="20"
                        HorizontalAlignment="Left"
                        VerticalAlignment="Top"
                        Tag="キャプションのテスト"
                        Template="{DynamicResource CaptionImageControlTemplate1}">
            <Image HorizontalAlignment="Center"
                   VerticalAlignment="Center"
                   RenderOptions.BitmapScalingMode="HighQuality"
                   Source="Images/9.jpg"
                   Stretch="UniformToFill" />
        </ContentControl>
キャプションをふわっと表示する

以下のようなControlTemplateを前述のContentControlに適用すると、こんなアニメーションでキャプション表示できます。

        <ControlTemplate x:Key="CaptionImageControlTemplate1" TargetType="{x:Type ContentControl}">
            <ControlTemplate.Resources>
                <Storyboard x:Key="OnMouseEnter">
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="textBlock" Storyboard.TargetProperty="(UIElement.Opacity)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="1" />
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
                <Storyboard x:Key="OnMouseLeave">
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="textBlock" Storyboard.TargetProperty="(UIElement.Opacity)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0" />
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </ControlTemplate.Resources>
            <Grid>
                <ContentPresenter />
                <TextBlock x:Name="textBlock"
                           VerticalAlignment="Bottom"
                           Background="#88000000"
                           Foreground="White"
                           Opacity="0"
                           Padding="5"
                           Text="{TemplateBinding Tag}" />
            </Grid>
            <ControlTemplate.Triggers>
                <EventTrigger RoutedEvent="Mouse.MouseLeave">
                    <BeginStoryboard Storyboard="{StaticResource OnMouseLeave}" />
                </EventTrigger>
                <EventTrigger RoutedEvent="Mouse.MouseEnter">
                    <BeginStoryboard Storyboard="{StaticResource OnMouseEnter}" />
                </EventTrigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

キャプションを下部からスライドインさせる

次はこんな感じ

        <ControlTemplate x:Key="CaptionImageControlTemplate2" TargetType="{x:Type ContentControl}">
            <ControlTemplate.Resources>
                <Storyboard x:Key="OnMouseEnter">
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="textBlock" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="0">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <CubicEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
                <Storyboard x:Key="OnMouseLeave">
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="textBlock" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="25">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <CubicEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </ControlTemplate.Resources>
            <Grid ClipToBounds="True">
                <ContentPresenter />
                <TextBlock x:Name="textBlock"
                           VerticalAlignment="Bottom"
                           Background="#88000000"
                           Foreground="White"
                           Padding="5"
                           RenderTransformOrigin="0.5,0.5"
                           Text="{TemplateBinding Tag}">
                    <TextBlock.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform />
                            <SkewTransform />
                            <RotateTransform />
                            <TranslateTransform Y="25" />
                        </TransformGroup>
                    </TextBlock.RenderTransform>
                </TextBlock>
            </Grid>
            <ControlTemplate.Triggers>
                <EventTrigger RoutedEvent="Mouse.MouseLeave">
                    <BeginStoryboard Storyboard="{StaticResource OnMouseLeave}" />
                </EventTrigger>
                <EventTrigger RoutedEvent="Mouse.MouseEnter">
                    <BeginStoryboard Storyboard="{StaticResource OnMouseEnter}" />
                </EventTrigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

回転しながらキャプション表示を行う

↓の記事で紹介されてたものも真似してみました。
http://coliss.com/articles/build-websites/operation/css/css-incontent.html

        <ControlTemplate x:Key="CaptionImageControlTemplate3" TargetType="{x:Type ContentControl}">
            <ControlTemplate.Resources>
                <Storyboard x:Key="OnMouseEnter">
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="360">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.Opacity)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
                <Storyboard x:Key="OnMouseLeave">
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="(UIElement.Opacity)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1.1">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <SineEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                        <EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="1.1">
                            <EasingDoubleKeyFrame.EasingFunction>
                                <QuarticEase EasingMode="EaseInOut" />
                            </EasingDoubleKeyFrame.EasingFunction>
                        </EasingDoubleKeyFrame>
                    </DoubleAnimationUsingKeyFrames>
                </Storyboard>
            </ControlTemplate.Resources>
            <Grid ClipToBounds="True">
                <Viewbox HorizontalAlignment="Center"
                         VerticalAlignment="Center"
                         Stretch="UniformToFill">
                    <ContentPresenter x:Name="contentPresenter" RenderTransformOrigin="0.5,0.5">
                        <ContentPresenter.RenderTransform>
                            <TransformGroup>
                                <ScaleTransform ScaleX="1.1" ScaleY="1.1" />
                                <SkewTransform />
                                <RotateTransform />
                                <TranslateTransform />
                            </TransformGroup>
                        </ContentPresenter.RenderTransform>
                    </ContentPresenter>
                </Viewbox>
                <Border x:Name="border"
                        Background="#88000000"
                        RenderTransformOrigin="0.5,0.5">
                    <Border.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform ScaleX="0" ScaleY="0" />
                            <SkewTransform />
                            <RotateTransform />
                            <TranslateTransform />
                        </TransformGroup>
                    </Border.RenderTransform>
                    <TextBlock x:Name="textBlock"
                               HorizontalAlignment="Center"
                               VerticalAlignment="Top"
                               Foreground="White"
                               Padding="5"
                               Text="{TemplateBinding Tag}" />
                </Border>
            </Grid>
            <ControlTemplate.Triggers>
                <EventTrigger RoutedEvent="Mouse.MouseLeave">
                    <BeginStoryboard Storyboard="{StaticResource OnMouseLeave}" />
                </EventTrigger>
                <EventTrigger RoutedEvent="Mouse.MouseEnter">
                    <BeginStoryboard Storyboard="{StaticResource OnMouseEnter}" />
                </EventTrigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

まとめ

前述したすべてのアニメーションで、画像ギャラリーみたいなのを作るとこんな感じ。

その他作ってた時のメモ

最初は、UserControlとして作ろうかと考えてました。ImageSourceとキャプション用のstringの依存関係プロパティを持ったユーザーコントロールとして。
でも、よくよく考えたら、UserControlにするまでもなく、ContorolTemplateのカスタマイズで対応できそうだったので、書き換えました。
あと、ImageコントロールはTemplateプロパティを持っていないので、ControlTemplateの設定ができず、ContentControlに対するテンプレートを作成しました。
この方が、いろんなコントロールに対して汎用的に使えるし。


あと、最初はVisualStateManagerを使ってホバー時の画面表示を定義しようとしてました。
でも、VisualStateを自分で定義したら、VisualStateの変更をXAML上で制御しようとした時に、GoToStateActionを使わなければならず、Blend SDKのdllへの参照が必要になってしまいます。
それでも、別にいいっちゃいいけど、何となくBlendのSDK抜きでできる方法を使いたくて、イベントトリガーを使って実装しました。