自分用の「置時計」を作ってみましょう。

当ページのリンクには広告が含まれています。

 こんにちは、皆さん。今日は、本格的なアプリケーションを、作ってみましょう。概要だけでは、中々使い方も分からないし、「目標」も定まらないと思います。

 どんな使い方が出来るのかという、一つの参考にして貰えれば良いと思います。

 とは言え、まぁ本格的とはいっても、まだまだ難しいとまでは行きません。しかし、自分で使うものとしては、これくらいの方が、実用的かも知れません(笑)。

 また、非常に「基本的」で「必要不可欠」なことが、ふんだんに含まれているので、是非この機会に習得してみましょう。

 今回作るのは、自分専用の「置時計」です。何の変哲もない、家にあるような「アナログ時計」です。特に、装飾もしていませんし、「付加機能」も付けていません。

 それらは各個人で、独自に追加・実装してください。

 それでは、早速、見て行きましょう。可なり難しい部分もあるので、焦らずにゆっくりと進みましょう。Let’s get started !

目次

「MyTableClock プロジェクト」を作成します。

 先ず、「Visual Studio Community」を起動して、「Windows フォーム アプリケーション (.NET Framework)」を作成します(通常)。

 この時の「プロジェクト名」を、「MyTableClock」にします。

 作成に関する詳しいことは、以下の記事を参照してください。

次に、基本のフォーム(Form)を、作成しましょう。

 前回の記事、「TabControl コントロールの使い方について。」で、詳しく説明していますが、「BaseTabForm.cs(本体の Form)」を、コピー&ペーストしましょう。

 当サイトで扱うアプリケーションは、殆どがこの形式なので、是非習得しておきましょう。一度作れば、何度でも再利用できるので、効率的です。

 詳しくは、以下の記事を参照してください。

 尚、当サイトの記事は、初心者の方が途中から見ても、中々分らない事が多いと思います。

 ですから、トップページにある「オススメ記事」を、最初から確りと、身に付けておきましょう。難しい事は何もありませんが、「 」で出来るようになるには、大変かも知れません。

 考える必要はありません。只管愚直に行う方が、良いかも知れません。というのは、頭で覚えるよりも、体で覚えた方が実用的だと思います。

 意味は、後から徐々に分かってきます。まぁ、言葉は悪いのですが、「下手の考え休むに似たり」という諺もあります(汗)。

 逆に、スキルのある方は、途中からでも「十分」理解できると思います。

 そして、基本のフォーム「BaseTabForm.csコピー&ペーストして、その名前を「TableClockForm.cs」に変更します。

 ここ迄を、完全に実行しましょう。ここ迄が上手く行かなければ、その後も上手く行きませんので。

 もし失敗したのなら、一からやり直しましょう。難しいことではなく、簡単な操作なので時間は掛からないと思います。

「諺」の付箋

 まぁ今日は、男の本懐についての諺を、一つ紹介します(大袈裟)。(^_^;)
 
 それは、「男は敷居を跨げば七人の敵あり」、という諺です。
 
 意味は、男は社会に出て仕事をすると、どんな人でも七人の敵がいるということです。これは、本当にそうですよね。
 
 どんなに良い事をしても、そうなります(笑)。まぁしかし、敵ばかりではなく、称賛してくれる人や、助けてくれる人も沢山います(good)。
 
 そして、まぁ処世術としては、前回の記事にも書きましたが、「批判」しかない所に、「発展」もないですからね、「本質」や「良い所」を見て行きましょう!。
 
 ファイトと、Take it easy ! ですよ。先ずは、自分で見聞きした感性を、信じましょう。

それでは、先ず「完成図」を見ておきましょう。

 今回は、この「アナログ時計」を、一から作ります。この事は、単に「アプリケーションを一つ作る」、という事ではありません。

 このアプリケーションを通じて、「グラフィックスの仕組み」や「グラフィックスの移動」、そして「タイマー(Timer)クラスの使い方」を、習得するという試みです。

 特に、グラフィックスに関しては、色んな方法がありますが、今回の手法もその一つであり、大小に関わらずアプリケーションの基本になります(一応)。

 ですから今回は、簡単ではありますが、後で確りと、意味を理解しておきましょう。

 それでは、以下が完成図です。

 この「置時計」を、これから作成します。先ずは、「確認事項」からです。

次に、コピー&ペーストした、フォームを確認します。

 先ず、以下の状態になっているかを、確認してください。一応、デスクトップを基本としているので、ノートパソコンの場合は、それなりに工夫してください。m(_ _)m

 確認する部分は、

  1. ソリューションエクスプローラーで、「TableClockForm.cs」になっているか?
  2. 名前空間(namespace)が、「MyTableClock」になっているか?
  3. エラーは、出ていないか?

 という事です。「置時計のデザイン」は、この状態から始めます。初心者の方は、ここ迄が出来ていないと、先には進めないと思います(多分、意味的に)。

 逆に、スキルのある方は、意味が分かっているので、何処からでも進めると思います。

フォームのデザインを、置時計用に変更します。

 置時計の場合は、少しフォームを小さくして、大きさが変更出来ないようにします。それに伴って、不要なコントロールとイベントを削除します。

 ところが、削除する場合は、簡単だと思うかも知れませんが、「削除する順番」を間違えると、初心者の方にとっては、クリティカル(致命的)なエラーになる場合があります。

 というのは、イベントが絡んでいるので、デザイナー画面に進めない、という事になります。勿論、スキルのある場合には、簡単にエラーを修正出来るのですが(笑)。

 基本的に、イベントが絡んでいる場合は、先ずそのコントロールから削除します。その後で、ソースコードを削除します(飽くまでも、基本です)。

 それでは、次の「順番通り」に、確実に進めてください。

STEP
「StatusStrip」の StatusLabel コントロールを、削除します。

 正確には、「ToolStripStatusLabel コントロール」の一部を削除します。

 今回は、フォームの大きさを小さくして、大きさを変更出来ないようにするので、右下にある「ペインの開閉」は、不要になります。それを削除します。

  1. フォームの「デザイン画面」で、一番下にある「StatusStrip」をクリックします。
  2. 右側のプロパティグリッドで、「Items: (コレクション)」の三点リーダーをクリックします。
  3. 「項目コレクションエディター」が現れるので、六個の「A StatusLabel」の内、下から三個を削除(×)します。
  4. 残すのは、上から「toolStripStatusLabel3」までです。
  5. 右下の「OK」をクリックします。
STEP
右側の「TabControl」の TabPage コントロールを、削除します。

 今回は、本当に小さなアプリケーションなので、「TabPage コントロール(SideTabControl)」は、2ページで充分です。

  1. フォームの「デザイン画面」で、右側(ペイン)の「TabControl」をクリックします。右上の「小さな三角形」付近をクリックすると、選択出来ます。
  2. 右側のプロパティグリッドで、中程にある「TabPages: (コレクション)」の三点リーダーをクリックします。
  3. 「TabPage コレクションエディター」が現れるので、「tabPage3」を削除します。
  4. 右下の「OK」をクリックします。
  5. 次に、「tabPage1」 をクリック(右ペインの真ん中辺り)して、プロパティグリッドの表示を変更します。
  6. そしてプロパティを、「(Name) : AddTabPage」、「Text: 付加機能」に変更します。
STEP
フォーム(Form)を小さくして、大きさを固定にします。

 フォーム(Form)の一番上をクリックして、Form のプロパティを表示します。そして、以下のように変更します。変更部分だけです。

MaximizeBoxFalse最大化ボタン
(Name)TableClockForm名前(既)
Size650, 570サイズ
FormBorderStyleFixedSingle境界線スタイル
TextMyTableClockテキスト

 今回の場合は、「DoubleBuffered プロパティ」は、どちらでも良いです。

STEP
SplitContainer の大きさを変更します。

 これを、デザイナー上で選択するのは、結構難しいのですが(笑)、「Panel1」の右側にある境界線(8 pixel)をクリックすると、選択できます。

 確実に選択する場合は、右側のプロパティグリッドの上部に、「コンボボックス」があるので、そこから「太字の SplitContainer」を、選択します。

 そして、プロパティの「SplitterDistance」を、「370」に変更します。

ソースコードから、イベントを削除します。

 「ペインの開閉」のイベントは、今回は必要ないので、削除します。そして、初期設定を、少しだけ変更します。それでは、「ソースコード」のタブをクリックします。

 先ずは、「#region ● イベント ~ #endregion」の中にある、

「private void PaneStatusLabel_Click(object sender, EventArgs e) {……}」を、コメントも含めて、バッサリと削除します。

 そして、「● 初期設定」の部分を、

SplitContainer.SplitterDistance = 370;
MessageStatusLabel.Text = “大きさは、変更出来ません。”;

 に変更します。以上です。これで、「置時計」用のフォームに成ったので、動かしてみましょう。「 開始」をクリックするか、または「F5」を押下しましょう。

 以下のように成れば、大成功です。この状態から、「置時計」を作成します。ここ迄を、「  」で出来るようになれば、基本は卒業です(笑)。

「冗談(By way of a joke)」で一息。

 「馬の耳に念仏」に、成らないようにしましょう(笑)。それには、何かに「興味」を持つことが、重要でしょうか?。
 
 それによって、「進むべき道」というものが出来るからです。「全ての道はローマに通ず」という格言もあります。
 
 或いは、「鶏口となるも牛後となるなかれ」、ですかね。ワンランク落とすと、楽になりますからね!(オススメ)。
 
 まぁ、とは言っても、向上心も重要です(笑)。

それでは、コントロールを追加して行きます。

 今回は、「アナログ時計」を作成するので、重要なのは「PictureBox コントロール」になります。グラフィックスを描画する場合は、殆どこのコントロールを使用します。

 勿論、「Form コントロール」に、直接描画することも出来ますし、汎用的で「出来る事」も「付加価値」も高いのですが、再利用という点からすると、難しいです。

 ですから、特別な機能を必要としない場合は、「PictureBox コントロール」で充分です。それでは、左側の「Panel1」から、デザインをして行きましょう。

 その前に、プロパティグリッドで、「SplitContainer.Panel1」の「BackColor」を、「White(白)」にしておきましょう(重要)。

STEP
日時用の「Label コントロール」を貼ります。

 「ツールボックス」から「Label」をクリックして、ドラッグ&ドロップで SplitContainer の 「Panel1」上に貼ります(場所は、どこでも構いません)。

 そのまま、以下のプロパティを設定します。

(Name)DateLabel名前
AutoSizeFalse自動サイズ
Location20, 20位置
Size320, 30サイズ
FontYu Gothic UI, 12ptフォント
TextAlignMiddleCenterテキストの位置
STEP
時計用の「PictureBox コントロール」を貼ります。

 「ツールボックス」から「PictureBox」をクリックして、ドラッグ&ドロップで「Panel1」上に貼ります(場所は、どこでも構いません)。

 そのまま、以下のプロパティを設定します。

(Name)ClockPictureBox名前
Location20, 60位置
Size320, 320サイズ
BackColorWhite背景色

 後で、「Paint イベント」を設定します。

STEP
時刻用の「Label コントロール」を貼ります。

 「ツールボックス」から「Label」をクリックして、ドラッグ&ドロップで「Panel1」上に貼ります(場所は、どこでも構いません)。

 そのまま、以下のプロパティを設定します。

(Name)TimeLabel名前
AutoSizeFalse自動サイズ
Location20, 400位置
Size320, 30サイズ
FontYu Gothic UI, 12ptフォント
TextAlignMiddleCenterテキストの位置

「AddTabPage」に、付加機能を付けます。

 一応、「時計」なので、「付加機能」を付けておくと、便利に使えると思います。

 とは言っても当サイトでは、「何が便利なのか?」は、各個人によって異なるので付けません。それは、今後各個人で「自由に」実装してください。

 ただし、「音を鳴らす」にしても結構難しいので、スキルが上がってからにしましょう(笑)。

 当サイトでは、一つだけ「Form の便利機能」を、紹介しておきます。実は、「Form」には、通常のコントロールには出来ない「機能」が、含まれています。

 それらを使うと、本格的な「置時計」も、夢ではありません(笑)。今回紹介するのは、その一つです。非常に簡単なのですが、実用的かも知れません。

STEP
見出し用の、「Label コントロール」を貼ります。

 「ツールボックス」から「Label」をクリックして、ドラッグ&ドロップで「AddTabPage」上に貼ります(場所は、どこでも構いません)。

 そのまま、以下のプロパティを設定します。

Location20, 330位置
Size144, 20サイズ(AutoSize)
Text不透明度( 50 – 100 ):テキスト
TextAlignMiddleLeftテキストの位置
STEP
「不透明度」用の、「Label コントロール」を貼ります。

 「ツールボックス」から「Label」をクリックして、ドラッグ&ドロップで「AddTabPage」上に貼ります(場所は、どこでも構いません)。

 そのまま、以下のプロパティを設定します。

(Name)OpacityLabel名前
Location170, 330位置
Size45, 20サイズ(AutoSize)
Text100%テキスト
TextAlignMiddleCenterテキストの位置
STEP
「不透明度」用の、「TrackBar コントロール」を貼ります。

 「ツールボックス」から「TrackBar」をクリックして、ドラッグ&ドロップで「AddTabPage」上に貼ります(場所は、どこでも構いません)。

 そのまま、以下のプロパティを設定します。

(Name)OpacityTrackBar名前
LargeChange10大きい変化量
Maximum100最大量
Minimum50最小量
Value100現在の量
Location24, 370位置
Size200, 56サイズ(AutoSize)
TickFrequency10目盛りの量

 これは、「TrackBar コントロール」を使って、「Form の不透明度」を、「50% ~ 100%」まで変化させます。

 注意することは、「0%」にすると「完全に消える」ので、捕捉する手段がありません(激汗)。ですから、「50%」に留めています。

最も重要な、「Timer コントロール」を貼ります。

 今回は、この「Timer コントロール」を使って、日時(日付、時分秒)を刻みます。

 「C#」には、「時間」や「時刻」に関するクラスは、他にも幾つか有りますが、最も簡単で手軽に使えるという意味では、今回の使用に適しています。

 それでは、 「ツールボックス」から「Timer」をクリックして、ドラッグ&ドロップで「Panel1」上に貼ります(場所は、どこでも構いません)。

 一番下の欄に、表示されます。そして、プロパティグリッドで、「(Name): ClockTimer」、「Interval: 1000」にします。

 以上で、デザインは、ほぼ終了です。後は、「イベント」を四つ作成すれば、終わりです。

イベントを四つ作成します。

 それでは、「デザイン」タブから、イベントを作成します。この「イベントの設定」を、「デザイン」タブから、自由自在に作成出来るようになれば、「基本的」なことは卒業です。

 しかし、本来は、「ソースコード」から「イベント」を、設定出来るようになれば、「イベントを完全に理解できた」という事になります。

 尚、「デザイン」タブから、「イベントを設定」するという事は、ソースコード上に、イベントを処理する「空のメソッド」を、作成するという事です。

STEP
「ClockTimer_Tick イベント」を作成します。

 デザイン画面の下にある、「ClockTimer」をクリックします。プロパティグリッドの上部にある、「雷(§)のアイコン」をクリックします。

 「Tick」を、ダブルクリックします。これで、ソースコードに「ClockTimer_Tick イベント」が、作成されました。

STEP
「ClockPictureBox_Paint イベント」を作成します。

 デザイン画面に戻って、左ペインにある「ClockPictureBox」をクリックします。プロパティグリッドの上部にある、「雷(§)のアイコン」をクリックします。

 この場合は、既になっています。

 イベント項目の一番下にある「Paint」を、ダブルクリックします。これで、ソースコードに「ClockPictureBox_Paint イベント」が、作成されました。

STEP
「OpacityTrackBar_ValueChanged イベント」を作成します。

 デザイン画面に戻って、右ペインにある「OpacityTrackBar」をクリックします。プロパティグリッドの上部にある、「雷(§)のアイコン」をクリックします。

 イベント項目の上部にある「ValueChanged」を、ダブルクリックします。これで、ソースコードに「OpacityTrackBar_ValueChanged イベント」が、作成されました。

STEP
「TableClockForm_FormClosed イベント」を作成します。

 デザイン画面に戻って、「フォーム(Form)」の一番上をクリックします。プロパティグリッドの上部にある、「雷(§)のアイコン」をクリックします。 

 イベント項目の下の方にある「FormClosed」を、ダブルクリックします。これで、ソースコードに「TableClockForm_FormClosed イベント」が、作成されました。

 これは、独自に設定した「リソース」を、開放するために行います。

 以上です。これで、全ての「デザイン」が終わりました。ご苦労様でした。m(_ _)m

 尚、「雷(§)のアイコン」の左側にある「アイコン」をクリックすると、「プロパティの選択」に戻ります。

諺の付箋。
 
 「旅の恥は掻き捨て」、という諺があります。これは、旅に出ると顔見知りも居ないので、普段なら出来ない事でもしてしまう、という意味です。
 
 これは、良い意味にも、悪い意味にも使えます。良い意味としては、「大胆」になれるという事です。悪い意味としては、色々ありますね(汗)。
 
 しかし、基本的には、「良い意味」で使うことが多いですね。せっかく旅に来たのだから、遠慮せずに楽しもう!。ということだと思います。
 
 それともう一つ、重要な諺があります。それは、
 
 「聞くは一時の恥、聞かぬは一生の恥」、という諺です。
 
 これは、「世間体」を気にして、「本質」を見逃すという事でしょうか?!。自分にもこういう所があるので、気を付けたいと思います。

次は最も難しい、「ソースコード」の作成ですが、・・・

 「ソースコード」というのは、実は、これが「正解」というものはありません。100人のプログラマーが居れば、100通りの「ソースコード」があって当たり前です。個性ですかね。

 ただし多分、結果は全て同じでしょうが(笑)。

 ですから、今回紹介するのは、飽くまでも「私が信じている基本」です。オススメはしていますが、何の保証もしていません(汗)。

 まぁ通常は、一通り自分で作ってから、「以下のソースコードを見て下さい」という流れなんですが、今回は違います。

 何故なら、初心者の方が、「完成されたデザイン」だけで、ソースコードを作成することは、「多分、殆ど不可能」だと思うからです。

 勿論、マニュアルを駆使して、ネット検索を駆使して、AIを活用すれば、出来るかも知れませんが、それはそれなりに難しいと思います(笑)。

 ですから、今回は、そのまま「見て覚えて下さい」。先ずは、手入力で「コメント」も含めて、「ソースコード」を打ち込みましょう。

 まぁ、プロの方が、「動作確認」をする為に、「コピペ」するのは構いませんが、初心者の方が、「身に付く方法」ではありません。

 最も良い習得方法は、ソースコードを打ち込みながら、「疑問点」をコメント(//)にしておく事です。そうしておくと、後で「疑問点」を解決し易くなります。

 また、コメントに「TODO: 又は、TODO」を先頭に付けると、「タスク一覧:表示(V)の中にあります」に、表示されます。

 逆に、プロの方は、「ノールック」で作成してから、「コンペアルック」をすると、面白いと思います。

 尚、今回の「ソースコード」は、「難しい」という程ではありませんが、「簡単」という程でもありません。数学の「回転」という概念が、必要になります。

 或いは、「数学の座標系」と「C#の座標系」は、「Y軸」の方向が異なるので、それなりに考慮が必要になります。

 ですから、「ソースコード」の説明はしませんが、作成に必要な「概念」を、次に説明したいと思います。尚、「ソースコード」には、ある程度「説明」を入れています。

「置時計」を作る場合に、必要な概念。

 先ず、グラフィックスを描画する場合に、最も重要な事は、「数学の座標系」と「C#の座標系」が、異なっているという事です。

 ですから、この事に慣れることが重要です。何故なら、計算や概念は全て、「数学の公式」をそのまま使います。しかし、その値が、画面上に表示される場合は、数学上とは異なります。

 それは、「Y軸」の方向が、異なっているからです。それに伴って、角度(θ)の方向も異なります。

 例えば、( X, Y ) = ( 0, 0 )、( 100, 100 )、( 200, 200 )、・・・、( 500, 500 ) の点を描画する場合、数学上では、「右( X+ )上( Y+ )」に伸びて行きます。

 しかし、C#の画面上では、「右( X+ )下( Y+ )」に伸びて行きます。回転の場合も、それぞれ「Y軸」の(+)方向に回転して行きます。

 C#の描画に関しては、主に「Graphics オブジェクト」が担当していますが、そう成っています。ですから、これ等に慣れることが重要になります。

 ただし、頭で覚える「事」や「必要」は、何もありません。「概念」として、「あッ、そう成っているんだ!」という事を、理解して置きましょう。

 というのは、頭で覚えていても、直ぐに忘れます(爆笑)。また、「プログラム言語」毎に、「状況」が異なる場合もあります。

 ですからそれよりも、プログラムを組む直前に、小さなプログラムを作って、「特性」や「特徴」を確認しましょう。

 例えば、以下のようなプログラムを、「PictureBox 等の Paint イベント」で走らせて確認します。

    e.Graphics.DrawRectangle(Pens.Red, new Rectangle(0, 0, 250, 250));
    e.Graphics.DrawArc(Pens.Green, new Rectangle(0, 0, 250, 250), 0, 300);

 これは、どんな言語においても、多分そんな感じです。

「三角関数」の概念が、必要になります。

 まぁ、日常において、「三角関数」を使うことは、殆ど無いかも知れません。しかし、知らず知らずの内に、「三角関数」を使っている場合があります。

 例えば、「車のハンドル」や「船の舵」ですよね。あれは「円」ですから、角度(θ)が重要になります。或いは、これから作る「置時計」ですね。これも、「円」です。

 或いは、「星」を描く場合に、「分度器」を使いますよね(72 度毎に)。

 しかし、もう少し顕著なのが、仕事となると結構「三角関数」は、直ぐに必要になります。「三角関数」というのは、「高校数学IA」で習得します。

 ですから、原理さえ確りと理解しておけば、決して難しい事ではありません。今回、使用するのも、本当に簡単な「公式」だけです。この機会に、是非、習得しておきましょう。

STEP
「C#の回転」を使うと、ソースコードが簡単になります。

 実は、「C#」の「Graphics オブジェクト」には、非常に実用的な機能が、豊富に揃っています。描画を美しく見せるメソッドや、「移動・回転」に関するメソッドです。

 これらは、独自に実装しようとすると、非常に難しいのですが、C#の場合は、たった一行で実現出来たりします。

 今回使うものは、以下の通りです。

  • 移動」に関する、「e.Graphics.TranslateTransform(原点にしたいX、Y座標)」。
  • 回転」に関する、「e.Graphics.RotateTransform(回転角:Degree)」。

 です。特に回転は、「rad: ラジアン」ではなく、「deg: 度(ディグリー)」で扱えるので、便利です。

 これらの変換は、「対象物が、移動・回転する」というよりも、「座標系が、移動・回転する」と考えた方が、考え易いかも知れません。

 この場合、プログラマーは、難しい「数学計算」が必要ないので、非常に楽です。

STEP
「C#の回転」が、使えない場合があります。

 これは、主に、「文字の描画」ですね。まぁ、文字自体を「回転」させたい場合は良いのですが、逆に「回転」させたくない場合があります。今回のように。

 この時は、「数学の三角関数」を使って、表示位置を計算するしかありません!?(多分)。

 とは言っても、非常に簡単です。次の「二つの公式」から、表示位置を計算します。

  • X座標は、= 中心のX座標 + 半径 × COS(角度:ラジアン)
  • Y座標は、= 中心のY座標 + 半径 × SIN(角度:ラジアン)

 これだけです。これを使って、文字を正確に描画します。

 
 一応、「数学上の概念」を、表示しておきます。

 これが、「三角関数」の基本です。

STEP
「Reset」や「Restore」が、必要になります。

 描画する場合は、常に上書きですから、後から描画したものが上になります。

 ですから、「C#の移動・回転」を使う場合と、使わない場合に対して、一旦「Graphics オブジェクト」を、元に戻す必要があります(Reset)。

 或いは、必要な「状況」に戻すことになります(Restore)。この辺りが、難しい所です。

STEP
Timer クラスの「Tick イベント」は、1秒毎に発生します。

 この事は、一見、当たり前のように思うかも知れませんが、重要なことが含まれています。

 それは、「Paint イベント」で描画する範囲は、「その1秒に起きる事」だけで充分だという事です。それ以外のことは、何もする必要がありません。

 つまり、過去の描画を、消したりする必要がないという事です。これは、本当に良い事なんです(笑)。

 尚、「Paint イベント」は、この「Tick イベント」から呼び出します。

 まぁ、この辺りを突き詰めると、「最適化」という事になりますが、スキルが上がってから、考えましょう(笑)。

STEP
今回の「ソースコード」は、スマートではありません。

 本来なら、最もスマートな「ソースコード」を提供するのが、世の常だと思うのですが、実は、最もスマートな「ソースコード(最適化)」は、将来自分で実装してください。

 何故なら、それは、当サイトの目的では無いからです。

 今回は、「Graphics オブジェクト」の「移動・回転・Reset・Restore」を焦点に、ソースコードを作成しています。

STEP
「ガベージコレクション」は、飽くまでも「基本」です。

 「マネージドコード」を扱う場合は、「ガベージコレクション」にお任せで大丈夫なんですが、「グラフィックス」等を扱う場合は、そうは行きません。

 というのは、描画に関する「リソース」は、Windows で提供されている「GDI/GDI+」を、使用しています(WPF は、DirectX)。

 これ等は、「アンマネージド・リソース」を含んでいるので、「ガベージコレクション」の対応だけでは、間に合いません。

 ですから、「グラフィックス」や「大きなメモリー」を扱う場合は、プログラマーが明示的に、リソースを開放する必要があります。

 今回も、それに準じています。 

それでは、ソースコードを公開します。

 例によって、一応、「アコーディオン」にしています(笑)。非常に長いので、覚悟しておいてください!。習得したい場合は、「手入力」で打ち込みましょう。

 その為にも、「良いキーボード」は、必須ですか?!。私の感覚としては、「打鍵感」がある方が、良いですね(人其々ですが)。

 私自身は、日常は、結構安い物を使っています。高級なものも持っていますし、以前は使っていました(笑)。

 「ソースコード」は、常に、実際に動いているものを、提供しています。ですから、もし、動かない場合は、「デザインの作成」の方を、確認して見てください。

TableClockForm のソースコードです。
using Microsoft.SqlServer.Server;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using static System.Windows.Forms.DataFormats;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.ProgressBar;

namespace MyTableClock
{
    /// <summary>
    /// 自分用の置き時計を提供します。
    /// </summary>
    public partial class TableClockForm : Form
    {
        /// <summary>
        /// 新しいインスタンスを作成します。
        /// </summary>
        public TableClockForm()
        {
            // デザイナーが行う初期設定です。
            InitializeComponent();

            // 個人的な初期設定を行います。
            InitializeMyMember();
        }

        /// <summary>
        /// 終了処理を行います。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TableClockForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            // 独自に設定したリソースを開放します。
            ClockFont.Dispose();
        }

        #region ● 初期設定

        /// <summary>
        /// 初期設定を行います。
        /// </summary>
        private void InitializeMyMember()
        {
            // 正確に値を設定したい場合は、コードで設定します。
            SplitContainer.SplitterDistance = 370;

            // 時計関係を初期設定します。
            // (デザインからでも設定できますが、白と白で見えなくなるので。)
            DateLabel.BackColor = Color.White;
            TimeLabel.BackColor = Color.White;

            // フォントを設定します。
            ClockFont = new Font(this.Font.FontFamily, 14);

            // 中心位置を設定します。
            ClockCenterPt = new PointF(
                ClockPictureBox.Width / 2f, ClockPictureBox.Height / 2f);

            // 時刻の初期表示を行います。
            // (空で呼び出すことは、良くあります。)
            ClockTimer_Tick(null, null);

            // 最初のご挨拶。
            MessageStatusLabel.Text = "大きさは、変更出来ません。";
        }

        #endregion

        #region ● プロパティ

        /// <summary>
        /// 度をラジアン変換する時の値。
        /// </summary>
        /// <remarks>
        /// Radian(ラジアン)は、「C#」では必須なので、習得しましょう。
        /// </remarks>
        private const double Radian = Math.PI / 180.0;

        /// <summary>
        /// 時計のフォントを取得または設定します。
        /// </summary>
        private Font ClockFont { get; set; }

        /// <summary>
        /// 時計の中心位置を取得または設定します。
        /// </summary>
        private PointF ClockCenterPt { get; set; }

        /// <summary>
        /// 現在の日時を取得または設定します。
        /// </summary>
        private DateTime CurrentDateTime { get; set; }

        /// <summary>
        /// 一つ前の日時を取得または設定します。
        /// </summary>
        private DateTime LastDateTime { get; set; }

        #endregion

        #region ● イベント

        /// <summary>
        /// Form の不透明度を変更します。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OpacityTrackBar_ValueChanged(object sender, EventArgs e)
        {
            // 小数点を付けると、倍精度になります。
            this.Opacity = OpacityTrackBar.Value / 100.0;
            OpacityLabel.Text = this.Opacity.ToString("##%");
        }

        /// <summary>
        /// 1秒毎にタイマーが発生します。
        /// </summary>
        /// <remarks>
        /// 起動した時点から、1秒毎に発生します。
        /// </remarks>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClockTimer_Tick(object sender, EventArgs e)
        {
            // 現在日時を取得します。
            DateTime dt = DateTime.Now;
            CurrentDateTime = dt;

            if (CurrentDateTime.Date != LastDateTime.Date)
            {
                // 年月日の表示には、ToLongDateString() を使えますが、
                // もう少し見易くする為に、自前で表示します。
                DateLabel.Text = String.Format(
                    "{0:####} 年 {1:##} 月 {2:##} 日  ({3:ddd})",
                    dt.Year, dt.Month, dt.Day, dt);
            }

            // 時刻も自前で表示します。
            TimeLabel.Text = String.Format(
                "{0}  :  {1:00}  :  {2:00}",
                dt.Hour, dt.Minute, dt.Second);

            // 時計を表示します。
            // (Paint イベントを、呼び出す方法です。)
            ClockPictureBox.Invalidate();

            // 日時を保存します。
            LastDateTime = CurrentDateTime;
        }

        /// <summary>
        /// 時計を表示(描画)します。
        /// </summary>
        /// <remarks>
        /// 描画における、最も重要なイベントです。
        /// Graphics オブジェクトの移動・回転を学びましょう。
        /// </remarks>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ClockPictureBox_Paint(object sender, PaintEventArgs e)
        {
            // グラフィックの表示を高品質にします。
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

            // 時計版の目盛りを表示します(回転を使います)。
            DisplayClockMarkings(e);

            // 時計版の文字を表示します(数学計算を使います)。
            DisplayClockDial(e);

            // 時計の回転位置を、時計の中心に設定します。
            e.Graphics.TranslateTransform(
                ClockCenterPt.X, ClockCenterPt.Y);
            // この状態を保存します。
            GraphicsState transState = e.Graphics.Save();

            // 時針を表示します(回転を使います)。
            DisplayClockHour(e);

            // 変換を保存状態に戻します。
            e.Graphics.Restore(transState);
            transState = e.Graphics.Save();

            // 分針を表示します(回転を使います)。
            DisplayClockMinute(e);

            // 変換を保存状態に戻します。
            e.Graphics.Restore(transState);

            // 秒針を表示します(秒の分だけ回転させます)。
            e.Graphics.RotateTransform(
                6f * CurrentDateTime.Second);

            // 描画します。
            e.Graphics.DrawLine(Pens.Red,
                PointF.Empty, new PointF(0, -114));

            // 変換をリセットします。
            e.Graphics.ResetTransform();
        }

        /// <summary>
        /// 時計版の目盛りを表示します。
        /// </summary>
        /// <remarks>
        /// Graphics の移動・回転を使用します。
        /// </remarks>
        /// <param name="e"></param>
        private void DisplayClockMarkings(PaintEventArgs e)
        {
            // 回転位置を、時計の中心に設定します。
            e.Graphics.TranslateTransform(
                ClockCenterPt.X, ClockCenterPt.Y);

            // using() は、リソースを自動的に開放します。
            using (var sPen = new Pen(Color.Orange, 2))
            {
                // 表示位置を設定します。
                PointF sprPt = new PointF(0, -114);
                PointF norPt = new PointF(0, -120);
                PointF endPt = new PointF(0, -130);

                // 60個の目盛りを表示します。
                for (int i = 1; i <= 60; i++)
                {
                    // 6度回転させます。
                    e.Graphics.RotateTransform(6f);

                    // 5分毎の場合(% は、剰余)。
                    if (i % 5 == 0)
                    {
                        e.Graphics.DrawLine(sPen, sprPt, endPt);
                    }
                    // それ以外の場合。
                    else
                    {
                        e.Graphics.DrawLine(Pens.Black, norPt, endPt);
                    }
                }
            }

            // 変換をリセットします。
            e.Graphics.ResetTransform();
        }

        /// <summary>
        /// 時計版の文字を表示します。
        /// </summary>
        /// <remarks>
        /// 回転を使うと文字自体が回転するので、数学計算を行います。
        /// </remarks>
        /// <param name="e"></param>
        private void DisplayClockDial(PaintEventArgs e)
        {
            // 文字の表示を、上下左右の中央に設定します。
            StringFormat cdFormat = new StringFormat
            {
                Alignment = StringAlignment.Center,
                LineAlignment = StringAlignment.Center
            };

            // 時計回りに、1 から 12 まで表示します。
            for (int i = 1; i <= 12; i++)
            {
                // 角度をラジアンに変換します。
                double cdAng = (30.0 * i - 90.0) * Radian;

                // 表示位置を計算します(三角関数の基本です)。
                PointF cdPt = new PointF(
                    ClockCenterPt.X + (float)(145.0 * Math.Cos(cdAng)),
                    ClockCenterPt.Y + (float)(145.0 * Math.Sin(cdAng)));

                // 文字を表示します。
                e.Graphics.DrawString(i.ToString(), ClockFont,
                    Brushes.RoyalBlue, cdPt, cdFormat);
            }

            // 後処理をします。
            cdFormat.Dispose();
        }

        /// <summary>
        /// 時針を表示します。
        /// </summary>
        /// <remarks>
        /// Graphics の回転を使用します。
        /// </remarks>
        /// <param name="e"></param>
        private void DisplayClockHour(PaintEventArgs e)
        {
            // ペンを作成します。
            // (ペンが不変の時は、再利用する場合もあります。)
            Pen hPen = new Pen(Color.Gray, 6)
            {
                Alignment = PenAlignment.Center,
                StartCap = LineCap.Flat,
                EndCap = LineCap.Triangle
            };

            // 時針を回転させます。
            // (時針が、時分秒で何度動いたかを計算します。)
            e.Graphics.RotateTransform(
                30f * CurrentDateTime.Hour +
                0.5f * CurrentDateTime.Minute +
                0.5f * CurrentDateTime.Second / 60f);

            // 描画します。
            e.Graphics.DrawLine(hPen,
                PointF.Empty, new PointF(0, -84));

            // 使用したペンは、破棄します。
            hPen.Dispose();
        }

        /// <summary>
        /// 分針を表示します。
        /// </summary>
        /// <remarks>
        /// Graphics の回転を使用します。
        /// </remarks>
        /// <param name="e"></param>
        private void DisplayClockMinute(PaintEventArgs e)
        {
            // ペンを作成します。
            Pen mPen = new Pen(Color.Gray, 4)
            {
                Alignment = PenAlignment.Center,
                StartCap = LineCap.Flat,
                EndCap = LineCap.DiamondAnchor
            };

            // 分針を回転させます。
            // (分針が、分秒で何度動いたかを計算します。)
            e.Graphics.RotateTransform(
                6f * CurrentDateTime.Minute +
                0.1f * CurrentDateTime.Second);

            // 描画します。
            e.Graphics.DrawLine(mPen,
                PointF.Empty, new PointF(0, -104));

            mPen.Dispose();
        }

        #endregion
    }
}

結構難しい、「精度」や「付加機能」について。

 今回は、特に「精度」に関しては、殆ど気にしていませんが、もし正確に時を刻みたい場合は、少し工夫が必要になります。

 というのは、このアプリケーションの「1秒」は、起動した時点からの「1秒刻み」です。ですから、「Windows の時刻」と、同期している分けではありません。

 従って、最大で「1秒以内」の誤差が、発生します。この誤差を解消しようとすると、可なり難しいと思います。さらに、「最適化」等も必要になって来ます。

 ですから、スキルが上がってから、ゆっくりと解決しましょう。

 それと、「付加機能」として、「ストップウォッチ機能」を実装した、プロトタイプを作成したのですが、一応動くことは動くのですが、「UI」が非常に複雑になります。

 また、「Timer クラス」を2つ使う事になりますし、「表示」も専用にカスタマイズするのが、難しくなります。

 ですから、初心者の方には、お勧めしません。「ストップウォッチ機能」は、別のアプリケーションとして、「単独」で作った方がより「実用的」です。

最後に、「最適化」について。

 描画を行う場合に「最も重要」なのが、「速度」です(私の独断ですが)。しかし、この「速度」という概念には、色んな方法があります。

 今回は、「力業」ではなく、「知恵を絞って」考えてみましょう。そういう意味で、思い付くままを列記してみます。

STEP
最も「描画」が、速くなる場合とは?

 それは、「描画する数」を減らすことです。或いは、「計算する数」を減らすことです。もう少しいうと、「変化」する部分と「不変」の部分を、洗い出してみる事です。

 そうすると、「変化」する部分は、「時分秒の針」です。「不変」の部分は、時計盤の「目盛り」と「文字」です。

 ですから、時計盤の「目盛り」と「文字」を、「ビットマップ画像」に一度だけ作成して、それを「PictureBox.Image プロパティ」に設定すれば、良さそうですね。
 

 そうすると、「Paint イベント」では、「時分秒の針」だけを、描画すれば良い事になります。「時計盤」は、PictureBox が自動的に表示してくれます。

 恐らくこの方法が、最も「効率的」で「速い」、描画方法だと思います(多分)。

 実際に、初期設定で「時計盤」を作成して、「PictureBox.Image プロパティ」に設定してみましたが、体感できる速度の違いは、分かりませんでした。(^_^;)
 

 まぁしかし、非常に凝った「時計盤」や、「高精細」なものを作る場合は、この方法が「必須」だと思います。

 今回のような「小さな物」の場合は、そこまで考える必要はないと思います。

STEP
コストの掛かるリソースは、再利用する方が良い?

 実は、描画の過程において「コスト(時間)の掛かるリソース」は、再利用する方が高速になります。

 ですから、「Paint イベント」で「作成・破棄」するよりも、「アプリケーションレベル」で「作成・破棄」する方が、良いと考えますよね、普通は。

 ところが、今回のような小さなアプリケーションでは、また、描画頻度が「1秒毎」の場合は、そこまで考える必要はありません。

 何故なら、グラフィックスにおける基本は、「使い捨て」だからです(一応)。

STEP
最適化が、必要な場合とは?

 今回のように、自分で使う「小さなアプリケーション」では、実は、「最適化」を行っても、「五十歩百歩」だと思います。

 というのは、「PictureBox.Image プロパティ」に設定したとしても、「高速」という意味にはなりますが、何もしていない分けではありません。

 或いは、「リソース」にしても、無限大に、使える分けではありません。その辺りのことを、考慮しておきましょう。
 

 そうですね、最適化が必要な場合は、デスクトップの広い画面一杯に、一秒間に十数回以上描画する、「ゲーム」とか「CAD」ですかね?。(・_・;)

 まぁしかし、これらは「DirectX」とか、「OpenGL」の世界なので、当サイトで、解説できる範囲ではありません。m(_ _)m

 まぁ、兎に角、私から言えることは、「余り考え過ぎない」ことですね。でないと、「最適化」のことばかりが気になって、「本来の良さ(本質)」が失われます。

まとめ。

 如何でしたでしょうか、上手く動きましたか?!。一度や二度では、中々理解もできないし、身に付かないと思います。

 まぁしかし、色々と失敗を重ねたり、付加価値を付けることによって、少しずつ進歩して行くと思います。頑張ってみて下さい。

 今後も、「コーヒーブレイク」として、「ことわざ」や「格言」や「偉人の名言」等を、入れて行こうと思います。

 それでは、この辺でごきげんよう。(^_^)/

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次