C# Programming

WPFWPFでアナログクロックを作る

開発環境: Visual Studio 2008 

1.目的

WPF を使って、アナログクロックを作ってみます。

2.参考書

(1) XAMLプログラミング
(2) WPF のブラシの概要

3.ZAM 3D

WPF では、3D オブジェクトを扱うことができ、3D のアプリケーションが組めます。しかし、WPF の3Dオブジェクトを手書きで作るのは地獄です。はっきりいって、手に負えません。Expression Blend は 3D オブジェクトを作成することができません。もちろん、Visual Studio 2008も 3D オブジェクトを自由に扱うことはできません。このため、お手軽に3DのWPFアプリケーションを作成できるかというと大間違いです。

3Dオブジェクトを作成するのであれば、ZAM 3Dなどのデザインツールを購入するしかありません。ここでは、ZAM 3Dを利用して、3D WPF を作成し、それをプログラムで動かす方法で アナログ時計を作ってみます。

3.1 ZAM 3Dで、時計の3D モデリングを行う

ZAM 3D の Extrusion Editor で針をデザインします。

時計の針

時計の針を時間、分、秒の3つを作成します。このとき、針の名前にそれぞれ HourHand, MinHand, SecHand という名前を付けます。ここで名前をつけておかないと、あとで時間に合わせて針を動かす際に、針のオブジェクトの名前がわからなくなります。

次に、それぞれマテリアルの色を設定します。またもや、デザインの壁ですね・・・ダサいというコメントは却下w

時計の針

 動きがないと悲しいので、アニメーションを追加します。

ZAM 3D

ここまでで、XAML-Clock.xaml ができます。

このままでは、時計として機能していません。そこで、1秒ごとに、針を動かしてあげればよいことになります。

3.2 ZAM 3Dから、Visual Studio のプロジェクトにExport

ZAM 3Dから、Visual Studio 2005 のプロジェクトに WPF をExportすることができます。

File→Export で、次のExport Options ダイアログが表示されるので、Microsoft Visual Studio 2005 Project を指定して、Export します。

Export

すると、VS 2005 形式のプロジェクトフォルダーが作成されます。Visual Studio 2008 で開くと、自動的に VS 2008 に合う形でインポートしてくれます。

3.3 C# で針を動かすコードを書く

針を動かす実装は次の通りです。

Form_Loaded イベントを追加します。

注意: フォームをダブルクリックして、Windows_Loaded イベントを追加すると、Windows1.xaml.cs に 追加されます。しかし、ZAM 3D から Visual Studio のプロジェクトをExport するたびにファイルがオーバーライトされてしまいます。そこで、時計を動かす部分はWindows1 の Partial Class のファイルを追加して、そこに実装するとオーバーライトされずに済みます。

class1.cs というファイルを追加して、Window1 のパーシャルクラスとして、フォームがロードされたときにタイマーを追加してあげます。

注意: WPF ではツールボックスにタイマーコントロールがないので、System.Windows.Forms.Timer を参照設定に追加します。手書きで追加します。

タイマーは、1秒に1回TickEventHandler を呼び出します。TickEventHandler では、各針の角度をGetAngle() で求めて、各針のオブジェクトに、時間に対応する回転を加えてあげます。

このとき、各針のオブジェクトは、ZAM3D で、SecHand、MinHand、HourHand と名前を付けましたが、自動生成される際にそれぞれ SecHandOR37 のように、名前が変更されており、適切に修正しないといけません。ただし、Visual Studio のインテリセンス機能で、比較的簡単に名前を見つけることができます。このため、オブジェクトにわかりやすい名前を付けることが重要です。

注意: XY座標でいうと右方向が0度です。このため、針の角度は90度左に回転させています。また、座標系は左手系であり、AxisAngleRotation3DのVector は Z方向ベクターはマイナスにする必要があります。 

注意: どうやって回転されたら良いのかと思ってあれこれ調べてみたところ、Transformマトリックスが4つの配列に回転成分、トランスレート成分など、自動的に構成しているようです。針の回転には、Transform マトリックスのChildren[2] を置き換えていますが、もうちょっとましな方法があるかもしれません。このあたりの座標変換は厄介ですね。

using System;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media.Media3D;


namespace clock
{

    public partial class Window1 : System.Windows.Window
    {
        System.Windows.Forms.Timer timer = new Timer();

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            this.ZAM3DViewport3D.Visibility = Visibility.Hidden;
            TickEventHander(null, null);
            this.ZAM3DViewport3D.Visibility = Visibility.Visible;

            timer.Interval = 1000;
            timer.Tick += new EventHandler(TickEventHander);
            timer.Enabled = true;
        }
        
        private void GetAngle(ref double hourAng, ref double minAng, ref double secAng)
        {
            DateTime time = DateTime.Now;
            secAng = 360 * time.Second / 60.0 - 90f;
            minAng = 360 * (time.Minute + time.Second / 60.0) / 60.0 - 90f;
            hourAng = 360 * (time.Hour + time.Minute / 60.0) / 12.0 - 90f;
        }

        private void TickEventHander(object obj, EventArgs arg)
        {        
            double hourAng = 0f, minAng = 0f, secAng = 0f;

            GetAngle(ref hourAng, ref minAng, ref secAng);

            Transform3DGroup tg = (Transform3DGroup)this.SecHandOR37.Transform;
            tg.Children[2] = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, -1), secAng));
            this.SecHandOR37.Transform = tg;


            Transform3DGroup tgMin = (Transform3DGroup)this.MinHandOR27.Transform;
            tgMin.Children[2] = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, -1), minAng));
            this.MinHandOR27.Transform = tgMin;


            Transform3DGroup tgHour = (Transform3DGroup)this.HourHandOR32.Transform;
            tgHour.Children[2] = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 0, -1), hourAng));
            this.HourHandOR32.Transform = tgHour;
        }
    }
}

3.4 実行イメージ

以上で、WPF のアナログクロックの完成です。

Click Once で以下の場所から実行することができます。

http://uchukamen.com/WPF/XAML-AnalogClock/Clock/publish.htm

実行すると、次のようなアプリケーションが動作します。

AnalogClock

4. まとめ

ちょっとした 3D アプリケーションでも、デザインセンスがものをいいますね。WPF/XAML の勉強だけでなく、デザインの勉強もしたほうがよさそうです。

なんか、2002年に初めて C# でアナログクロックを作ったころから、ちっとも進歩していなんですけど orz ・・・