WPFでアナログクロックを作る
開発環境: 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
動きがないと悲しいので、アニメーションを追加します。
ここまでで、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 します。
すると、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
実行すると、次のようなアプリケーションが動作します。
4. まとめ
ちょっとした 3D アプリケーションでも、デザインセンスがものをいいますね。WPF/XAML
の勉強だけでなく、デザインの勉強もしたほうがよさそうです。
なんか、2002年に初めて C# でアナログクロックを作ったころから、ちっとも進歩していなんですけど orz ・・・