チュートリアル:WPF時計の作成(3)

WPFのチュートリアル、勝手に続編です。前回はこちら。

 

1.時刻読みあげ機能

こんな機能いるかと思いつつも、今後への布石も兼ねて作ってみました。WPFらしくカスタムトリガとカスタムアクションを使います。「〇〇が起きたときに、△△する」の〇〇の部分がトリガ、△△の部分がアクションです。今回は「時計の秒針が0秒、つまり正分になったときに現在時刻を読みあげる」という機能にします。

カスタムトリガ、カスタムアクションいずれもBlendの SDKを使います。これは少し前にビヘイビアの時に紹介しました。まずはカスタムトリガから。

OclockTrigger.cs

namespace Wpf2DAnalogClock
{
    using System;
    using System.Windows.Shapes;
    using System.Windows.Media;
    using System.Windows.Interactivity;

    public class OclockTrigger : TriggerBase<Path>
    {
        private RotateTransform rotransform;
        private double previous = 0;
        protected override void OnAttached()
        {
            base.OnAttached();
            rotransform = base.AssociatedObject.RenderTransform as RotateTransform;
            if (rotransform != null){
                rotransform.Changed += rotransform_Changed;
            }
        }

        protected override void OnDetaching()
        {
            if (rotransform != null){
                rotransform.Changed -= rotransform_Changed;
            }
            base.OnDetaching();
        }

        void rotransform_Changed(object sender, EventArgs e)
        {
            double current = rotransform.Angle % 360;
            if (current < previous){
                this.InvokeActions(DateTime.Now);
            }
            previous = current;
        }
    }
}

正分なのでoclockという名前はおかしいですが、それはさておき。カスタムトリガはTriggerBase<T>から派生します。Tはトリガ発生元のオブジェクト、今回は秒針の針なのでPathオブジェクトになります。特定のタイミングを判定して、いまだ!という時にthis.InvokeActions()をコールします。引数はActionへ渡すobject型の引数で、今回は現在時刻を渡しています。秒針はDoubleAnimationで動いているためchangedハンドラで取得できる値は0とか1とか整数ではありません。そのため前回角度を保持しておき、360→0の切り替わりポイントを正分と判断しています。次いでカスタムアクション。

 TimeSignalAction.cs

namespace Wpf2DAnalogClock
{
    using System;
    using System.Windows.Media;
    using System.Windows.Shapes;
    using System.Windows.Interactivity;
    using System.Speech.Synthesis;

    public class TimeSignalAction : TriggerAction<Path>
    {
        private RotateTransform rotation;
        private SpeechSynthesizer synthesizer;

        public TimeSignalAction(){
            synthesizer = new SpeechSynthesizer();
        }

        protected override void Invoke(object parameter)
        {
            DateTime currentTime = (DateTime)parameter;
            string prompt = "It is" + currentTime.Hour + "," + currentTime.Minute + ".";
            synthesizer.SpeakAsync(prompt);
        }
    }
}

WPFと同時に紹介されたSpeechAPIを使っていますので、System.Speech.dllを参照追加が必要です。イントネーションを調整したい場合はSSMLを使って調整が可能ですが時刻読みあげならこれで十分です。”It is”を”It’s”にすると”It has”と発音されたり、ピリオドがないと語尾が抜けた感じになったりするのでちょっとばかし注意。デフォルトだと女の人の声です。

これを適用するのはこんな感じです。前回のXAMLの一部を変えています。

 MainWindow.xaml(抜粋)

<Path x:Name="secondArrow" Data="M0,0 -2,15 0,70 2,15 z" Margin="98.75,30.5,155,172.5" RenderTransformOrigin="0.5,1" Stretch="Fill">
    <Path.Fill>
        <LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
            <GradientStop Color="#FFF30E0E" Offset="0"/>
            <GradientStop Color="White" Offset="1"/>
        </LinearGradientBrush>
    </Path.Fill>
    <Path.RenderTransform>
        <RotateTransform x:Name="secondTransform"/>
    </Path.RenderTransform>
    <i:Interaction.Triggers>
        <me:OclockTrigger>
            <me:TimeSignalAction/>
        </me:OclockTrigger>
    </i:Interaction.Triggers>
</Path>

 

2.光の照射方向を調整する

前回までのアナログ時計で、時計の針にはLineargradientBrushを使いました。これが光の影響を模擬したものだと考えると、時計の針が回転しているにも関わらず根元が明るいとか光の当たり方が一定になっておらずへんな感じになります。そこで回転角に応じてグラデーション方向を調整するビヘイビアを作ってみます。

SameDirectionalLightBehavior.cs

namespace Wpf2DAnalogClock
{
    using System;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Shapes;
    using System.Windows.Interactivity;

    public class SameDirectionalLightBehavior : Behavior<Path>
    {
        private RotateTransform _transform;
        protected override void OnAttached()
        {
            base.OnAttached();
            _transform = this.AssociatedObject.RenderTransform as RotateTransform;
            if (_transform == null){
                return;
            }
            _transform.Changed += transform_Changed;
        }
        protected override void OnDetaching()
        {
            if (_transform != null){
                _transform.Changed -= transform_Changed;
            }
            base.OnDetaching();
        }

        void transform_Changed(object sender, EventArgs e)
        {
            LinearGradientBrush brush = base.AssociatedObject.Fill as LinearGradientBrush;
            if (brush == null){
                return;
            }
            double radian = _transform.Angle / 180.0 * Math.PI;
            // 以下、改善余地あり
            brush.StartPoint = new Point(0.5+0.5*Math.Cos(radian), 0.5+0.5*Math.Sin(radian));
            brush.EndPoint = new Point(0.5-0.5*Math.Cos(radian), 0.5+0.5-Math.Sin(radian)); 
        }
    }
}

回転角に応じてLineargradientBrushのStartPoint, EndPointを回転させています。が、まだちょっと調整不足感があります。(0,0)-(1,1)の間に設定必要だからSin/Cosでなく45°単位で区切ってTanを使わないといけないのかなぁ、と思ったり。これを使うのは下のような感じ。

MainWindow.xaml(同部分の抜粋)

<Path x:Name="secondArrow" Data="M0,0 -2,15 0,70 2,15 z" Margin="98.75,30.5,155,172.5" RenderTransformOrigin="0.5,1" Stretch="Fill">
    <Path.Fill>
        <LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
            <GradientStop Color="#FFF30E0E" Offset="0"/>
            <GradientStop Color="White" Offset="1"/>
        </LinearGradientBrush>
    </Path.Fill>
    <Path.RenderTransform>
        <RotateTransform x:Name="secondTransform"/>
    </Path.RenderTransform>
    <i:Interaction.Behaviors>
        <me:SameDirectionalLightBehavior/>
    </i:Interaction.Behaviors>
    <i:Interaction.Triggers>
        <me:OclockTrigger>
            <me:TimeSignalAction/>
        </me:OclockTrigger>
    </i:Interaction.Triggers>
</Path>

 

今回はBlendSDKのカスタムトリガ、カスタムアクション、カスタムビヘイビアを使った続編でした。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中