それわVBA案件ですね

エクセルVBAネタを書いています

学習時間管理ツールを作る

オリンピックイヤー最初の記事更新です。

このように書くと、年明けすぐに記事を更新したかのようですが、違います。

こんちくわ 壁|ω・)ノ


さて今日の記事は、日ごろのコソ勉の累積時間をカウントするツールを作ろう~なオハナシです。


でわいきます ̄▽ ̄)ノ



目次




背景

私が所属させていただいております#ノンプロ研の御大、通称タカーシさんが英語の学習に1000時間を捻出すると宣言されてました。その後学習を継続しつつ、累積時間を頻繁にツイされているのを見てふと思いました。

塵も積もれば山となるを見える化すれば、継続のチカラになりそう

ならば、

  • コソ勉始めたら開始ボタンポチー(時計スタート)
  • 終わったら終了ボタンポチー(時計止まる)
  • 勉強時間と累積時間の表示と記録


みたいなツールがあればいいなぁ~
と、なりまして、早速作ることにしました。


VBAで時間をカウントする

時間のカウント機能は言うまでもなくこのツールの御本尊様であるわけですけれども、コレをVBAでどう作るかが案外悩むところかと思います。
myコソ勉にはVBAも含まれるワケでして、時間記録マクロの実行中は他のVBAのコーディングができないというのもイマイチなので。。。

もちろん、

  1. 開始ボタンポチーでその時の時刻を変数 a に代入
  2. 終了ボタンポチーしたときの時刻から変数aの時刻を引いて計算した時間を表示

みたいな仕掛けもいいんですけど・・
それではなんだかつまらないし、折角ならリアルタイムでの継続時間が見たいってのが世の情けですよね



で、どうやるの?

指定の時刻にマクロの予約実行を行う OnTime メソッドを利用します。
このメソッドは指定の時刻だけでなく、例えば○○分xx秒後に実行ということも設定できますのでこの機能を利用します。

まずはこのメソッドの概要は以下の通りです

Application.OnTime(EarliestTime, Procedure, [LatestTime], [Schedule])
(Applicationは省略できません)

引数 省略 説明
EarliestTime プロシージャを実行したい時刻 Date
Procedure 実行するプロシージャ名(ダブルクオーテーションで囲みます)
(文字列を引数として渡す形での設定は可能だが、変数を渡す形での設定はできないよもよう)
String
LatestTime (指定のプロシージャが実行できない状態のとき)実行できる状態になるまで待つ時刻
省略した場合は指定のプロシージャが実行できるまで待ちます
Date
Schedule 設定済みのOnTimeメソッドの実行を取りやめるか否か Boolean



それで○○分xx秒後に実行は、Now + TimeValue(指定の間隔)で設定します。

例えば以下の"十秒後に挨拶" マクロを実行すると、


Sub 十秒後に挨拶()
  Application.OnTime Now + TimeValue("0:00:10"), "Greet"
End Sub

Sub Greet()
  MsgBox "こんちくわ~"
End Sub



実行から10秒後(Now + TimeValue("0:00:10"))に

f:id:FukuCyndiP:20200119171431p:plain:w200

が出力されます。


このメソッドを利用すれば目的のカウントアップタイマーが作れそうな気がしますよね。




カウントアップタイマー

1項目分ですが、下記の様なカウントアップタイマーのコードを紹介します

f:id:FukuCyndiP:20200119171648p:plain

(フォームコントロールで作成したボタンにタイマー開始(TimerStart)と終了(StopTimer⦆を割り当てます


Option Explicit

Private Const timeCount = "0:00:02"  '時間カウントの間隔を設定する
Private flagTimerRegulation As Boolean
Private isInitial As Boolean


Public Sub TimerStart()
  isInitial = True
  flagTimerRegulation = True
  MsgBox "始めます", vbInformation, "がんばれ~"
  Call ClockCounter
End Sub

Public Sub stopTimer()
  'タイマーを止めるときは "今回の学習時間" 列のみ時間をリセットする
  flagTimerRegulation = False
  MsgBox "記録終了します", vbInformation, "終わったのかな? ん?"
  ws時間記録.Range("B2").Value = "0:00:00"
  Call ClockCounter
End Sub


Private Sub ClockCounter()

  'モジュールレベル変数として宣言した flagTimerRegulation を利用して
  '時計を動かすかどうかを決定する
  If flagTimerRegulation Then
    With ws時間記録  
      If Not isInitial = True Then
        .Range("B2").Value = .Range("B2").Value + TimeValue(timeCount)
        .Range("C2").Value = .Range("C2").Value + TimeValue(timeCount)
      End If
      'ClockCounterご本尊
      Application.OnTime Now() + TimeValue(timeCount), "ClockCounter"
      isInitial = False
    End With
  End If

End Sub

(サンプルコードなので、時間表示部分はアドレス決め打ちで書きました)

プロシージャ "ClockCounter" は 設定した時間が経過したら自分自身を呼び出して時計を進める 仕組みになっています。これは放っておくと永久に時間を刻み続けることになりますので、経過時間記録と時計部分の実行はブール型変数flagTimerRegulation で制御 しています。


実行するとこんな感じで動きます

f:id:FukuCyndiP:20200119191148g:plain:w450
(2秒間隔設定です)

ちゃんと思惑通りの動きになっていますね

  • 開始ボタンポチー(時計スタート)
  • 終了ボタンポチー(時計止まる)
  • その間の活動時間の表示と累積時間を記録




時間カウントしながら他のVBAコードは実行できるの?

コレメッチャ大事ですよね。

確認した範囲では概ね大丈夫でした。

他のコードの実行:ほとんど影響されない

上に書いたように、Application.OnTimeメソッドは、引数 LatestTime を設定しない場合は指定のプロシージャが実行可能になるまで待ちます。逆に言うと、実行可能状態になれば自動で再開しますので、他のコードを実行したとしてもその間は止まりますが、終われば再び時を刻み始めます。

したがって、別のコードの実行中はその間時計は止まりますが、よほど実行時間が大きなコードでない限りは、時間計測に影響はほとんどないと思います。


VBEでのコード編集:可能だけれども、テストランなどでエラーが発生すると時計も止まる

時計はVBE画面で他のコード編集中でも律義に動いてくれます。時計と同じプロジェクト上でのコーディングも可能です。時計が動いた瞬間に入力中コードが強制的に入力確定されしまいますが、時計を動かす間隔を広げておけば、ほとんど気になることはないと思います。

ただし、テストランなどでコード実行後に何らかの'エラーが発生したときには、時計も一緒に止まってしまう`点に注意してください(時計を動かすために再度開始ボタンポチが必要)。



まとめ

今回の記事ではコソ勉時間を見える化するためのツール作成を紹介しました。

リアルタイムで経過時間を表示するためのポイントは

  • マクロ予約実行メソッドOnTimeを利用する
  • 設定時間後に自分自身を呼び出す

でした。

ただし、自分自身を呼び出す仕組みとしましたので、停止のための仕組みを必ず準備することが注意点です。

時計を動作させながらVBAコーディングを含めた他の作業ができるので、目的通りのツールを作ることができました。

現在使っていますが、実際にかかった時間、これまでの累積時間の見える化は、思った通りコツコツコソ勉のモチベ維持に良いと思いました。




因みに

現在私が使っているのは以下の様なものです。

f:id:FukuCyndiP:20200119173604p:plain:w500

ActiveXコントロールを利用した5項目ものです(ゼロが多いのはご愛敬ということでw)。

今回紹介したのは1項目分のコードでしたが、以下の仕組みを追加すれば複数項目にも対応できるかと思います

  • ​各項目にセットしたボタンに時計動作開始/終了のプロシージャを仕込む
  • それぞれの項目行に経過時間を表示させる


当初はクラスを使って、それぞれの項目での経過時間を同時にカウントできるようにしようと考えたのですが、Application.Ontimeメソッドを使ったプロシージャの呼び出しはオブジェクトモジュール上では実行できないようです。
複数の学習を同時進行することはないので、そもそも必要ないですけどね。


でわまた~  ̄▽ ̄)ノシ


追記

この記事で紹介したコードでは時間のカウントに不具合が出てしまうことがわかりました。
原因を特定してコードを修正したので、コードはこちらの記事を参考にしてください

fukucyndip.hatenablog.com