アナログ時計を作ろう! (プログラミングの知識がある方向け)

更新日: 2011 年 10 月 26 日

ここでは XAML (ザムル) の特性を活かしたアナログ時計を作ってみたいと思います。

アナログ時計は文字盤、長針、短針、秒針で構成されています。文字盤は、時間の数字をテキストで描画して作る方法もありますが、今回はあらかじめ用意した画像ファイルを読み込むことにします。針についても線 (ライン) を使うこともできますが、見た目も良くなるので画像を使います。

皆さんは、過去に Windows などでアナログ時計を作ったことがありますか?
経験のある方でしたら、針の座標を計算するのに三角関数を使って、時間から角度を求めて描画・・といったイメージが浮かぶかもしれません。
ところが、XAML を使う場合は、三角関数などを使わなくてもよいのです。タイマーも For ループなども必要ありません。
XAML では、描画するモノ (ここでは針の画像) について、回転の中心座標を設定し、角度を指定することでモノを回転させることができるのです。
また、StoryBoard という機能を使うと、一定時間ごとに描画したモノの角度を設定してアニメーション動作をさせることもできます。
具体的には、秒針を動かす場合は、時計の中心点を針の中心座標として 1 分間で 360 度の回転を行うようにアニメーションを設定するということです。


プロジェクトを作ってみよう!

まずは、Windows Phone のプロジェクトを作ってみましょう。
プロジェクトの作り方は、[初めての Windows Phone プログラミング] のコンテンツを参照してください。

今回は、アプリケーション名は [Analog Clock] にしましょう。

ページのトップへ


とりあえず、動かしてみよう!

画面は、以下のようになっているはずです。

図 1

この画面は、XAML (ザムル) 編集画面です。主に画面回りを作ることができます。
左ペインが Windows Phone で実際に表示される画面、右ペインはこの描画を行うための XAML です。

とりあえず、実行してみましょう。[デバッグ] メニューから [デバッグ開始] を選択します。
すぐにエミュレーター (Windows Phone Emulator) が起動します。しばらく待っていると以下のような画面が表示されます。エミュレーターが起動しないときは、Visual Studio のツール バーに [Windows Phone Device] という文字が表示されていないか確認します。もし表示されていたときは、その部分を [Windows Phone Emulator(JA)] に変更して、再度実行します。

図 2

タイトルが表示されるだけのプログラムですが、正しく実行できましたか?
終了するときは、[デバッグ] メニューから [デバッグ停止] を選択します。  

ページのトップへ


ツールボックスを出そう!

まずは、開発をするのに便利なウィンドウを出しましょう。
[表示] メニューから [その他のウィンドウ] [ツールボックス] を選択します。

図 3

ツールボックス ウィンドウが開き、Windows Phone コントロールが表示されたと思います。

図 4

これらのコントロールを Windows Phone のデザイン画面に貼り付けることで、簡単に XAML を作成することができます。

ページのトップへ


ドキュメント アウトラインを出そう!

続いてドキュメント アウトライン ウィンドウを出しましょう。
このウィンドウは、メニューが基本設定の場合だとメニューに現れません。
簡単に出すには、キーボードで Ctrl+W, U と連続で押します。
メニューから表示する場合は、[ツール] → [設定] [上級者用設定] としてから、[表示] → [その他のウィンドウ] [ドキュメント アウトライン] と選択します。
ドキュメント アウトラインの画面は以下のようになっています。

図 5

白い三角形をクリックすると展開します。

図 6

この画面を利用すると、デザイン画面でコントロールがどのように構成されているのかが、分かりやすいです。
ここでは、PhoneApplicationPage というベースの上に、レイアウト用のグリッド (Grid) やスタックパネル (StackPanel)、テキストボックス (TextBox) などが配置されているのが分かります。

コントロールやグリッドなどを選択する際には、デザイン画面でコントロールをクリックしてもできますが、コントロールが重なっていたり、複雑だった場合は、ドキュメント アウトラインから選択する方がやりやすい場合があるでしょう。

ページのトップへ


タイトルを変更しよう!

現在は、デザイン上で [マイ アプリケーション] [ページ名] と書かれています。これを変更してみましょう。

まず、プロパティ ウィンドウを出します。[表示] メニューから [その他のウィンドウ] [プロパティ ウィンドウ] を選択します。
次に、[ページ名] と書かれたところをクリックします。(または、ドキュメント アウトラインで [TextBlock(PageTitle)] をクリックします。) すると、プロパティ ウィンドウで、テキスト ブロックのプロパティが変更できるようになります。
ここで、[テキスト] というプロパティがありますので、これを [Analog Clock] に変更します。

図 7

同様に [マイ アプリケーション] と書かれた部分も [Windows Phone Sample] に変更します。
これでタイトルの変更は完了です。実行すると以下のような画面が表示されます。

図 8

ページのトップへ


画像を用意しよう!

ダウンロード画像

  • 【clock 01】 Stylish (Zip、142 KB) (もはや利用できます)
  • 【clock 02】 Cat (Zip、63.3 KB) (もはや利用できます)
  • 【clock 03】 Claudia (Zip、192 KB) (もはや利用できます)

こちらでは、3 種類の時計の画像ファイルを用意しています。ダウンロードした圧縮ファイルの中に、clock01、clock02、clock03 というフォルダーがあり、それぞれのフォルダーの中に 4 種類の画像ファイルが入っています。どの時計を使っても構いませんが、ここでは、clock01 を利用して説明します。

画像は、以下を利用します。

clock01.png: 文字盤
clock01_sec.png: 秒針
clock01_min.png: 長針
clock01_hour.png: 短針

ページのトップへ


文字盤を設定しよう!

では、アナログ時計の文字盤を作りたいと思います。

まずデザイン画面にイメージ (Image) コントロールを配置します。ツールボックスから [Image] をドラッグしてデザイン画面の中央にドロップします。
次に、プロパティ ウィンドウに、Source という項目がありますので、ここで […] ボタンをクリックします。

図 9

すると、イメージ選択ウィンドウが表示されますので、追加ボタンをクリックします。

図 10

ファイルを開くウィンドウが表示されますので、先ほど用意した clock01.png を指定します。選択が終わったら OK ボタンをクリックします。

(ここで、デザイン画面に文字盤が出ないかもしれません。その場合は、[デバッグ] メニューから [デバッグの開始] を選択して、一度実行してみてください。以後表示されるようになります)

次にサイズを変更します。文字盤をクリックして選択し、プロパティ ウィンドウから [Height] と [Width] を作成した画像のサイズにします。この画像はどちらも 480 です。設定すると、以下のような画面になります。

図 11

サイズは正しいのですが、位置が悪いです。左右の余白を設定する [Margin](マージン) というプロパティがありますので、ここに [0] を入力します。

図 12

[Margin] プロパティは、コントロールの左、上、右、下の余白のサイズを指定します。コンマで区切って 4 つの数字で指定できます。単純に 0 とだけ設定すると、全ての余白が無くなります。

0 を設定すると以下のような画面になります。

図 13

まだ、少しずれています。Windows Phone の横幅は 480 ピクセルなので本当は綺麗に収まるはずなのですが。実は、この Image コントロールは、Grid の上に貼り付けているので Grid のマージンによって使える領域が狭くなっているのです。ドキュメント アウトラインを確認すると、関係がよく分かります。

図 14

1 つ上の Grid (ContentPanel) をクリックしてからプロパティ ウィンドウで [Margin] を見てみましょう。以下のようになっています。

図 15

12 ピクセルの余白がありますね。これを 0 にします。

図 16

今度は綺麗に文字盤が配置されました。Grid コントロールは、レイアウトを設定するのにとても便利です。Grid と [Margin] プロパティを使えば、複雑な画面も分かりやすくデザインしていくことができます。

ページのトップへ


秒針を作ろう!

今度は、イメージ (Image) コントロールを使って、針を配置したいと思います。

ツールボックスから Image コントロールをドラッグ アンド ドロップで大体 0 時の位置に配置します。大雑把でいいです。

図 17

細かい座標設定はプロパティ ウィンドウで行います。
秒針の画像サイズは、22 × 240 です。プロパティ ウィンドウで Width と Height に 22 と 240 をセットします。
あとは、[Margin] プロパティで位置を決定するのですが、秒針の根元は文字盤の中心点より少し飛び出した方が見映えがいいので、40 ピクセルほど飛び出すことにして計算しましょう。

文字盤の画像が 480 × 480 なので、中心点は (240, 240) です。まず秒針の左上の X 座標を考えます。秒針の幅は 22 ピクセルなので、中心点より 11 ピクセル左から描画すればよさそうです。つまり、229 ですね。次に Y 座標ですが、中心点より 40 ピクセル飛び出すということは、秒針の底辺の Y 座標は 240 + 40 = 280 です。秒針の高さが 240 ピクセルですので、上辺の Y 座標は 40 ということになります。
つまり、Margin は、229,40,0,0 ということになります。

次に、Source プロパティを使って秒針の画像を設定します。秒針の画像は clock01_sec.png です。設定の仕方は文字盤のときと同じです。

最後に、この秒針に名前を付けましょう。プロパティ ウィンドウ の最上部に Image2 と書いてあるところがありますので、これを [秒針] にします。

図 18

修正項目

Width: 22
Height: 240
Margin: 229,40,0,0
Image名: 秒針

以下のようになりましたか? もし、秒針が表示されない場合は、一度実行してから確認してください。

図 19

ページのトップへ


秒針を回転できるようにしよう!

続けて、秒針を回転できるようにします。まずは回転の中心点を設定します。この座標は Imageコントロールをオフセットとして指定します。例えば、画像の左上の場合は 0,0 となります。
秒針の中心点は、X 座標は中央なので、画像の横幅 22 ピクセルの半分の 11 です。 Y 座標は 40 ピクセル飛び出すことにしたので、画像の高さ 240 から 40 を引いて 200 となります。

さて、これまで、プロパティしか設定していませんでしたが、いよいよ、XAML に直接記述をします。
XAML のコードの中で、秒針は以下のように記述されています。

<Image Height="240" HorizontalAlignment="Left" Margin="229,40,0,0" Name="秒針" Stretch="Fill" VerticalAlignment="Top" Width="22" Source="/Analog%20Clock;component/Images/clock01_sec.png" />

先ほど設定した、[Width] や [Height]、[Margin] などが設定されているのが分かります。

これを以下のように変更します。

<Image Height="240" HorizontalAlignment="Left" Margin="229,40,0,0" Name="秒針" Stretch="Fill" VerticalAlignment="Top" Width="22" Source="/Analog%20Clock;component/Images/clock01_sec.png" > <Image.RenderTransform> <RotateTransform CenterX="11" CenterY="200"></RotateTransform> </Image.RenderTransform> </Image>

 

[RotateTransform] というのは、回転の設定をするものです。[CenerX]、[CenterY] で中心座標を指定します。
試しに実験してみましょう。プログラム中で、[RotateTransform] と書いたところをクリックしてカーソルを置くと [RotateTransform] のプロパティ ウィンドウが表示されます。

図 20

ここで [Angle] と書いてあるところに、0 ~ 360 の適当な数字を入れてみましょう。針が回転するのが確認できましたか?

ページのトップへ


秒針を回転させよう!

いよいよ秒針を回転させます。
以下のように太字の部分のコードを追加してください。

<phone:PhoneApplicationPage
    x:Class="Analog_Clock.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <phone:PhoneApplicationPage.Resources>
        <Storyboard x:Name="SecondMove" RepeatBehavior="Forever">
            <DoubleAnimation
                Duration="0:1:0" From="0" To="360"
                Storyboard.TargetName="秒針"
                Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
                d:IsOptimized="True" />
        </Storyboard>
    </phone:PhoneApplicationPage.Resources>

[StoryBoard] は、アニメーションの設定が行えます。[x:Name] は、このアニメーションの名前です。後でこのアニメーションを実行させるコードを記述するときに使います。ここでは、[SecondMove] という名前を付けました。

[RepeatBehavior] は、アニメーションを何回繰り返すかの設定です。[Forever] にするとずっと動き続けることになります。

[DoubleAnimation] の中で実際にどういうアニメーションをさせるのかを設定します。
[Duration] は、1 回のアニメーションの時間設定です。日数.時:分:秒で指定します (日数は省略できます)。ここでは 1 分間で 1 周するようにすればいいので、0:1:0 となります。
長針の場合は 1:0:0、短針は 12:0:0 となりますね。
[From] と [To] は、[Duration] で指定した間にどのくらい数値を変化させるかを設定します。ここでは 1 回転させればいいので、0 から 360 を指定すればよいでしょう。

[Storyboard.TargetName] は、どのコントロールをアニメーションさせるかを指定します。秒針を回転させたいので [秒針] と指定します。
[Storyboard.TargetProperty] は、どのプロパティを変更すればよいかを指定します。先ほど秒針を回転させたときに設定したのは [Angle] ですので、以下のように指定します。

(Image.RenderTransform).(RotateTransform.Angle)

XAML の階層をみると Image コントロール (秒針) の下の [Image.RenderTransform] の下にある [RotateTransform] のプロパティが [Angle] ということが分かります。

これで、アニメーションの設定は整いました。最後にアニメーション開始のプログラムを記述します。

ドキュメント アウトラインで最上位にある [PhoneApplicationPage] をクリックし、プロパティ ウィンドウを出します。上部にあるタブを [イベント] に切り替え [Loaded] をダブルクリックします。

図 21

ここで初めて C# の画面が開きました。以下のように太字の部分を 1 行追加します。

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
    SecondMove.Begin();
}

 

ページが開いたときに、先ほど名前を付けた StoryBoard の実行をするという意味です。

それでは、実行してみましょう。秒針が回転すれば成功です。

図 22

ページのトップへ


長針と短針を作ろう!

続けて長針と短針を作りましょう。

作り方は、ほとんど秒針と変りません。
変更する部分は [DoubleAnimation] の [Duration] で長針の場合は、[1:0:0] (1 時間で 1 周)、短針の場合は [12:0:0] (12 時間で 1 周) と指定するところだけです。

ここで、設定に必要な座標データをまとめておきます。

秒針

Width: 22
Height: 240
Margin: 229,40,0,0
CenterX: 11
CenterY: 200

長針

Width: 100
Height: 234
Margin: 190,16,0,0
CenterX: 50
CenterY: 224

短針

Width: 68
Height: 180
Margin: 206,70,0,0
CenterX: 34
CenterY: 170

XAML ソースは、以下のようになります。太字は追加した部分です。

<phone:PhoneApplicationPage
    x:Class="Analog_Clock.MainPage"
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True" Loaded="PhoneApplicationPage_Loaded">

    <phone:PhoneApplicationPage.Resources>
        <Storyboard x:Name="SecondMove" RepeatBehavior="Forever">
            <DoubleAnimation
                Duration="0:1:0" From="0" To="360"
                Storyboard.TargetName="秒針"
                Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
                d:IsOptimized="True" />
        </Storyboard>
        <Storyboard x:Name="MinutesMove" RepeatBehavior="Forever">
            <DoubleAnimation
                Duration="1:0:0" From="0" To="360"
                Storyboard.TargetName="長針"
                Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
                d:IsOptimized="True" />
        </Storyboard>
        <Storyboard x:Name="HourMove" RepeatBehavior="Forever">
            <DoubleAnimation
                Duration="12:0:0" From="0" To="360"
                Storyboard.TargetName="短針"
                Storyboard.TargetProperty="(Image.RenderTransform).(RotateTransform.Angle)"
                d:IsOptimized="True" />
        </Storyboard>

    </phone:PhoneApplicationPage.Resources>

 

    <!--LayoutRoot は、すべてのページ コンテンツが配置されるルート グリッドです-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel は、アプリケーション名とページ タイトルを格納します-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Windows Phone Sample" Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Analog Clock" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - 追加コンテンツをここに入力します-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="0">
            <Image Height="480" HorizontalAlignment="Left" Margin="0" Name="image1" Stretch="Fill" VerticalAlignment="Top" Width="480" Source="/Analog%20Clock;component/Images/clock01.png" />
            <Image Height="240" HorizontalAlignment="Left" Margin="229,40,0,0" Name="秒針" Stretch="Fill" VerticalAlignment="Top" Width="22" Source="/Analog%20Clock;component/Images/clock01_sec.png" >
                <Image.RenderTransform>
                    <RotateTransform CenterX="11" CenterY="200" Angle="0"></RotateTransform>
                </Image.RenderTransform>
            </Image>
            <Image Height="234" HorizontalAlignment="Left" Margin="190,16,0,0" Name="長針" Stretch="Fill" VerticalAlignment="Top" Width="100" Source="/Analog%20Clock;component/Images/clock01_min.png">
                <Image.RenderTransform>
                    <RotateTransform CenterX="50" CenterY="224" Angle="0"></RotateTransform>
                </Image.RenderTransform>
            </Image>
            <Image Height="180" HorizontalAlignment="Left" Margin="206,70,0,0" Name="短針" Stretch="Fill" VerticalAlignment="Top" Width="68" Source="/Analog%20Clock;component/Images/clock01_hour.png">
                <Image.RenderTransform>
                    <RotateTransform CenterX="34" CenterY="170" Angle="0"></RotateTransform>
                </Image.RenderTransform>
            </Image>

        </Grid>
    </Grid>

    <!--ApplicationBar の使用法を示すサンプル コード-->
    <!--<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>-->

</phone:PhoneApplicationPage>

プログラムを実行すると以下のようになります。

図 23

ページのトップへ


現在時刻を設定しよう!

このままでは、常に 0 時 0 分から時計が動いてしまいます。
起動時に現在の時刻を指定するようにしましょう。

[PageLoaded] のイベントに以下のように太字の部分のプログラムを追加します。

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
    SecondMove.Begin();
    MinutesMove.Begin();
    HourMove.Begin();

    int second = DateTime.Now.Second;
    SecondMove.Seek(new TimeSpan(0, 0, second));
    int minutes = DateTime.Now.Minute;
    MinutesMove.Seek(new TimeSpan(0, minutes, second));
    int hour = DateTime.Now.Hour;
    HourMove.Seek(new TimeSpan(hour, minutes, second));

}

必要な処理は、開始時にアニメーションを一定時間進めるだけです。DateTime.Now は現在の時刻を返します。DateTime.Now.Second、DateTime.Now.Minutes、DateTime.Now.Hour は、それぞれ、現時刻の秒、分、時を返します。

TimeSpan は、引数に時、分、秒を与えるとその時間を返します。StoryBoard.Seek でその時間分進行させれば、現在の時刻までそれぞれのアニメーションが進行するということです。

これでプログラムは完成しました。

ページのトップへ


おわりに

このアナログ時計は、針を動かす部分は XAML に全て任せてしまう作りになっています。Windows Phone アプリケーションの開発では、こういった発想、閃きが大切になってくるのではないでしょうか。

今回紹介したサンプルの他にも、Windows Phone のサンプル プログラムは沢山あります。

MSDN の Code Recipe というサイトも是非ご覧になってください。沢山の便利なプログラムが用意されています。最初は、こういったサンプル プログラムを書き換えながら理解を深めていくのもよい学習方法です。

ページのトップへ