HeyCHのブログ

慢性疲労のへいちゃんです

【C#】時計を作ってみよう

Visual Studioの起動

Visual Studioを起動し「新しいプロジェクトの作成」をクリックします。

f:id:HeyCH:20200325234321p:plain
新しいプロジェクトの作成

Windows Formsアプリケーションの作成

次の画面で検索欄に「windows forms」と入力し、「Windows フォームアプリケーション(.NET Framework)」をダブルクリックします。

f:id:HeyCH:20200325234733p:plain
Windowsフォームアプリケーション(.NET Framework)

プロジェクトの作成

プロジェクト名に適当な名前を入力し、「作成」をクリックします。

f:id:HeyCH:20200325235114p:plain
プロジェクトの作成

Labelの配置

ツールボックスから「Label」をフォーム上へドラッグ&ドロップします。

f:id:HeyCH:20200325235543p:plain
Label
f:id:HeyCH:20200325235608p:plain
ドラッグ&ドロップ

フォントサイズの変更とテキストの設定

フォントサイズを大きく(24pt程度)して、テキストを「00:00:00」と入力します。

f:id:HeyCH:20200326000146p:plain
フォント
f:id:HeyCH:20200326000205p:plain
テキスト

適当に整える

ラベルの位置とフォームの大きさを適当に調整します。

f:id:HeyCH:20200326000415p:plain
調整

フォームをダブルクリック

ラベルではなくフォーム部分をダブルクリックすると以下のようにイベントが追加されコード入力画面になります。
Form1_Loadというのがイベントで、フォームがロードされた時に処理されます。

f:id:HeyCH:20200326000653p:plain
コード入力画面

ラベルに現在時刻を表示するコードを書く

以下のように記述して再生ボタンを押すと現在時刻が表示されます。
しかし、このままでは、フォームロード時の時刻を表示するだけで、動きません。

        private void Form1_Load(object sender, EventArgs e)
        {
            label1.Text = DateTime.Now.ToString("HH:mm:ss");
        }

延々と繰り返すようにする

延々と繰り返し処理をするには「while」を使用します。
しかし、このままでは、フォームロードイベントから抜け出せず、フォームが表示されなくなります。

        private void Form1_Load(object sender, EventArgs e)
        {
            while (true)
            {
                label1.Text = DateTime.Now.ToString("HH:mm:ss");
            }
        }

抜け出すためには

ラベルの表示処理とフォームロードのイベントを別々に処理したいわけです。
こういう場合、別Threadで処理を行う事で並列処理を行う事ができます。
今回は簡単のためにBackgroundWorkerというクラスを使います。

        private void Form1_Load(object sender, EventArgs e)
        {
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += Bw_DoWork;
            bw.RunWorkerAsync();
        }

        private void Bw_DoWork(object sender, DoWorkEventArgs e)
        {
            while (true)
            {
                label1.Text = DateTime.Now.ToString("HH:mm:ss");
            }
        }

エラーが出るんですけど

このまま実行すると「System.InvalidOperationException」というエラーが発生します。

f:id:HeyCH:20200326003355p:plain
エラー
これは、フォームと別のスレッドからフォームの内容をいじろうとした場合、特別な処理が必要になることを意味します。

Invokeを使う

これはたぶん覚えないとしょうがないと思うのですが、Invokeを使うとエラー発生を抑えることができます。
しれっとdelegateってのも出てきていますが、これも呪文みたいなものだと思ってください。

        delegate void LC();
        private void Form1_Load(object sender, EventArgs e)
        {
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += Bw_DoWork;
            bw.RunWorkerAsync();
        }

        private void Bw_DoWork(object sender, DoWorkEventArgs e)
        {
            LC labelChange = new LC(LabelChange);
            while (true)
            {
                Invoke(labelChange);
            }
        }
        private void LabelChange()
        {
            label1.Text = DateTime.Now.ToString("HH:mm:ss");
        }

もっとスマートに書けないの?

ただラベルのテキスト変更するだけでこんなにだらだらと長くなってしまうのは嫌ですよね。
C#では、ラムダ式といってdelegateを簡単に書く事ができます。

        private void Form1_Load(object sender, EventArgs e)
        {
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += delegate (object s, DoWorkEventArgs ev)
            {
                while (true)
                {
                    Invoke((MethodInvoker)delegate ()
                    {
                        label1.Text = DateTime.Now.ToString("HH:mm:ss");
                    });
                }
            };
            bw.RunWorkerAsync();
        }