アナログ時計を作成中

タグの編集
投稿者 胡麻  (高校生) 投稿日時 2009/11/5 06:23:29
ビジュアルベーシックを使って今のところこのようにプログラムしているのですが時計の針が一本廻って動くだけです。どうしたら三本ちゃんと動かせるのでしょうか?
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim g As Graphics = PictureBox1.CreateGraphics
        Dim Houp As New Pen(Color.Black)
        Dim Minp As New Pen(Color.Black)
        Dim Secp As New Pen(Color.Black)
        Dim CX As Integer
        Dim CY As Integer
        Dim EX As Integer
        Dim EY As Integer
        Dim R As Integer
        Dim Deg As Double
        Dim Sec As Integer
        Dim Min As Integer
        Dim Hour As Integer



        R = 100 '針の長さ
        Deg = Deg + (1 / 30)

        CX = 250
        CY = 248

        EX = CX - (Math.Cos(Math.PI * Deg) * R) + R
        EY = CY - (Math.Sin(Math.PI * Deg) * R) - R

        g.DrawEllipse(Pens.Blue, 100, 100, 300, 300) '外側の円
        g.DrawEllipse(Pens.Blue, 243, 243, 10, 10) '内側の円
        Houp.Width = 3
        g.DrawLine(Houp, CX, CY, EX, EY) '短針
        Minp.Width = 3
        g.DrawLine(Minp, CX, CY, EX, EY) '長針
        g.DrawLine(Secp, CX, CY, EX, EY) '秒針




        Deg = 1 / 2
        For I = 0 To 12

            For J = 0 To 60

                For K = 0 To 60
                    For L = 0 To 100000

                    Next L
                Next K

                Deg = Deg + (1 / 30)
                EX = CX - (Math.Cos(Math.PI * Deg) * R)
                EY = CY - (Math.Sin(Math.PI * Deg) * R)
                g.Clear(Color.White)
                g.DrawEllipse(Pens.Blue, 100, 100, 300, 300) '外側の円
                g.DrawEllipse(Pens.Blue, 243, 243, 10, 10) '内側の円
                g.DrawLine(Secp, CX, CY, EX, EY) '秒針
                g.
            Next J




            g.DrawLine(Minp, CX, CY, EX, EY) '長針
        Next I


        g.DrawLine(Houp, CX, CY, EX, EY) '短針
    End Sub

    Private Sub PictureBox1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles PictureBox1.Click

    End Sub
End Class
投稿者 (削除されました)  () 投稿日時 2009/11/5 06:47:44
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/5 07:07:49
> 時計の針が一本廻って動くだけです。どうしたら三本ちゃんと動かせるのでしょうか?

まず、長針/短針/秒針の座標が、いずれも CX, CY, EX, EY ですよね。
つまり、すべて同じ座標であるため、重なって 1 本のように表示されてしまっています。
(本来は、それぞれ違う座標になるはずです)


また、たとえ座標が正しかったとしても、ループ内の処理が良くありません。
提示されたコードでは、最内部のループ(J)内で画面をクリアしてから秒針を描いていますが、
その他の針は最内部では描かれていません。J を脱出して I のループになれば長針が描かれますが、
せっかく描いた長針も、次の g.Clear でまた消されてしまい、秒針のみの状態に戻ってしまいます。


まずは、その他の針の座標を正しく計算する事から始めてみてください。
あとは画面を Clear するたびに、毎回、3本の針を DrawLine すれば良いかと。



ただ、それとは別の問題も数多く見受けられます。

・使用したペンが破棄されていません。自分で作成した Pen は、使用後に必ず
 Houp.Dispose() / Minp.Dispose() / Secp.Dispose() するようにしてください。

・CreateGraphics は、基本的に使わないでください。描画処理は、Button1 の Click で
 行うのではなく、PictureBox1 の Paint イベントで e.Graphics に対して行うようにします。

・空のループ処理は避けてください。待機時間が CPU 性能に依存してしまいますし、
 CPU 負荷が跳ね上がってしまい、フォームの移動等すら行えない状態になってしまいます。
 このような場合には、ループではなく Timer コントロールを使うようにします。Tick イベントで、
 PictureBox1.Invalidate を呼び出して、PictureBox1 の Paint イベントが
 定期的に呼び出されるようにすると良いでしょう。
投稿者 失礼よこはいり  (社会人) 投稿日時 2009/11/5 18:31:28
魔界の仮面弁士さまにお尋ねします。



>CreateGraphics は、基本的に使わないでください。描画処理は、Button1 の Click で
> 行うのではなく、PictureBox1 の Paint イベントで e.Graphics に対して行うようにします。

入門書には、pictureBox1.CreateGraphics()を使用されているサンプルが多く使われているようですが使わないほうがよいとおっしゃる理由をおしえてください。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/5 20:13:02
「CreateGraphics で無ければならない状況」の方が特殊だと思いますよ。
理解して使う分には構いませんが、通常は Paint の方が良好な;充分な結果を得られます。

たとえば、DoubleBuffered プロパティや SetStyle を用いて
ダブルバッファリングを用いた場合、その影響を受けられるのは
Paint イベント(あるいは、OnPaint メソッド)で得られる
PaintEventArgs 型の引数を用いた場合だけです。

一応、CreateGraphics で得た Graphics に対して、ダブルバッファリングを
行うためのクラスも用意されてはいますが、この場合、バッファ内の描画内容を
サーフェイスにレンダリングするタイミングは自前で管理する事になります。


また、CreateGraphics で得たサーフェイスはあくまで一時的な描画となります。
最小化したり他のウィンドウの下に隠れてしまうと、その部分は消えてしまいますので、
そのたびに消えた部分を再描画しなければなりません。

そして本来、その再描画の仕組みを担っているのが Paint イベントです。この引数
e.Graphics を使えば、自分で Graphics を生成する必要も Dispose する必要も
ありませんし、描画すべきタイミングを自分で推し量る必要もありません(プログラムから
再描画を要請するための Invalidate / Update / Refresh メソッドも用意されています)。

この「再描画通知を受け取り、そのタイミングで描画する」という手法は、
Windows フォームの描画原則となっています。この原則は .NET が登場する前から
変わっていません。VB6 しかり、MFC しかり。


なお、書き換え頻度の少ない描画(背景画像など)を表示するのが目的であれば、
CreateGraphics メソッドも Paint イベントも使う必要はありません。あらかじめ
固定画像を用意しておくか、または Graphics.FromImage(Bitmap) に対して描画して、
その画像(Bitmap)を Image/BackgroundImage に割り当てる方が効率的です。
投稿者 cupid  (社会人) 投稿日時 2009/11/5 23:37:37
> この「再描画通知を受け取り、そのタイミングで描画する」という手法は、(以下略)

Paintイベントが発生しない状況で、書き換え必要が生じた場合はどうするんですか?
今回のように1分に一度自動的に書き換える場合、ただ見ているだけではPaintイベントは
発生しないでしょ。
投稿者 は?  (その他) 投稿日時 2009/11/6 00:03:14
> 投稿者 cupid   (社会人)   投稿日時 2009/11/05 14:37:37  
> > この「再描画通知を受け取り、そのタイミングで描画する」という手法は、(以下略)

> Paintイベントが発生しない状況で、書き換え必要が生じた場合はどうするんですか?
> 今回のように1分に一度自動的に書き換える場合、ただ見ているだけではPaintイベントは
> 発生しないでしょ。
 
Timerイベントで1分に一度書き換えればいいのではないかという考えはダメなのかなぁ・・・
投稿者 neptune  (社会人) 投稿日時 2009/11/6 00:05:35
お邪魔します。

秒針付きの時計なら1秒以内での再描画になるから
>ただ見ているだけではPaintイベントは発生しないでしょ。
ってのは発生しないのでは?

もしかして分針だけ再描画って事ですか?
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/6 00:28:22
> Paintイベントが発生しない状況で、書き換え必要が生じた場合はどうするんですか?

書き換える必要が生じた場合には、先述したように
再描画を要請するための Invalidate メソッドも呼び出します。

>> プログラムから再描画を要請するための Invalidate / Update / Refresh メソッドも用意されています


これによって、適切なタイミングで Paint イベントが呼び出されます。
これはたとえば、
 ・グラフ描画アプリで、グラフデータが変更された場合
 ・アニメーション描画で、次のフレームを描画したい場合
などに使う事ができます。


もし、Invalidate による「再描画依頼」で発生するタイミングでは足りないという場合には、
Update メソッドを併用することで、任意のタイミングで「強制的に再描画」させる事もできます。
投稿者 cupid  (社会人) 投稿日時 2009/11/6 03:34:16
> PictureBox1.Invalidate を呼び出して、PictureBox1 の Paint イベントが
> 定期的に呼び出されるようにすると良いでしょう。 

ああ、これは済みません。上の方で書かれてあったのですね。
Invalidateメソッドと言うものを知らなかったので、.....今後は使いましょう。
投稿者 聖帝  (小学生) 投稿日時 2009/11/6 05:20:26
VBで時計とゆうことですね。
はい。
ペイントイベントの中で処理するとDispose()とか必要ないです。
いろんな方法がありますねぇ
タイマーコンポーネントを3つ使う方法もあるし。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/11/6 05:59:52
一応、VB.NET 2002 による実装サンプルを紹介しておきます。
http://msdn.microsoft.com/ja-jp/library/aa289159.aspx


できれば自力で解決してほしいところですが、どうしても解決できなかった場合には、
このサンプルコードを参考にしてみてください。結構細かく作りこまれていますよ。
投稿者 (削除されました)  () 投稿日時 2014/12/1 15:14:22
(削除されました)
投稿者 MineCake1.4.7  (中学生) 投稿日時 2014/12/5 19:56:07
TimerコントロールのTickイベントでもって、その度に針を描画すればいいと思います。
Pen,Brushの破棄処理はガベージコレクションに任せてます。
自作物の古いバージョンのコードの一部を抜粋
http://tabbrowser.web.fc2.com
Public Class Form1
    Dim g As Graphics
    Dim b As Bitmap
    Dim FrameCount As UInteger = 0
    Dim FPS As UInteger = 0
    Private Sub Form1_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load
        b = New Bitmap(Me.ClientRectangle.Width, Me.ClientRectangle.Height)
        g = Graphics.FromImage(b)
        Timer1.Start()
        Timer2.Start()
        Timer3.Start()
    End Sub
    Dim IsShowingF3 As Boolean = False
    Dim Color1, Color2 As Color
Private Sub Timer1_Tick(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Timer1.Tick
        '色設定 
        Color1 = Color.FromArgb(255, Now.Hour / 24 * 255, Now.Minute / 60 * 255, Now.Second / 60 * 255)
        Color2 = Color.FromArgb(Color1.ToArgb Xor &HFFFFFF)
        'Graphics.Clear(Color1) 
        g.Clear(Color1)
        '針 
        Dim Bru As New SolidBrush(Color2)
        Dim Penmsec As New Pen(Color2, 2)
        Dim Pensec As New Pen(Color2, 4)
        Dim Penmin As New Pen(Color2, 6)
        Dim Penhr As New Pen(Color2, 8)
        Dim msecx, msecy As Integer
        Dim secx, secy As Integer
        Dim minx, miny As Integer
        Dim hrx, hry As Integer
        Dim thetas As Double
        thetas = (((Now.Second / 60) * 360) - 90) * Math.PI / 180
        secx = Math.Cos(thetas) * 225
        secy = Math.Sin(thetas) * 225
        Dim thetams As Double
        thetams = (((Now.Millisecond / 1000) * 360) - 90) * Math.PI / 180
        msecx = Math.Cos(thetams) * 250
        msecy = Math.Sin(thetams) * 250
        Dim thetamin As Double
        thetamin = (((Now.Minute / 60) * 360) - 90) * Math.PI / 180
        minx = Math.Cos(thetamin) * 200
        miny = Math.Sin(thetamin) * 200
        Dim thetahr As Double
        thetahr = (((IIf(Now.Hour > 12, Now.Hour - 12, Now.Hour) / 12) * 360) - 90) * Math.PI / 180
        hrx = Math.Cos(thetahr) * 175
        hry = Math.Sin(thetahr) * 175
        g.DrawLine(Penmsec, New Point(msecx + (b.Width / 2), msecy + (b.Height / 2)), New Point(b.Width / 2, b.Height / 2)) 'ミリ秒 
        g.DrawLine(Pensec, New Point(secx + (b.Width / 2), secy + (b.Height / 2)), New Point(b.Width / 2, b.Height / 2)) '秒 
        g.DrawLine(Penmin, New Point(minx + (b.Width / 2), miny + (b.Height / 2)), New Point(b.Width / 2, b.Height / 2)) '分 
        g.DrawLine(Penhr, New Point(hrx + (b.Width / 2), hry + (b.Height / 2)), New Point(b.Width / 2, b.Height / 2)) '時 
        '外側の棒? 
        (中略)
        Dim WStr As String = Now.ToString
        g.DrawString(WStr, New Font(Me.Font.FontFamily, 24), Brushes.White, New Point(1, 1))
        g.DrawString(WStr, New Font(Me.Font.FontFamily, 24), Brushes.White, New Point(-1, 0))
        g.DrawString(WStr, New Font(Me.Font.FontFamily, 24), Brushes.White, New Point(0, -1))
        g.DrawString(WStr, New Font(Me.Font.FontFamily, 24), Brushes.White, New Point(-1, -1))
        g.DrawString(WStr, New Font(Me.Font.FontFamily, 24), Brushes.White, New Point(1, 0))
        g.DrawString(WStr, New Font(Me.Font.FontFamily, 24), Brushes.White, New Point(0, 1))
        g.DrawString(WStr, New Font(Me.Font.FontFamily, 24), Bru, New Point(0, 0)) 'デジタル 
        
        Me.BackgroundImage = b
        Me.Refresh()
        FrameCount += 1
    End Sub
(中略)
End Class

これだけでは動きませんが(w)