[[20190110173222]] 『Exsit Do でタイマーを止めたい。』(OREO) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『Exsit Do でタイマーを止めたい。』(OREO)

お世話になっております。OREOです。

今ユーザーフォームで表示しているタイマー(カウントダウン)で困っていることがあります。

ひとまず前提を話しておきます。2択の問題をフォームに表示して、正解だと思う選択肢を選べば次の問題が表示され・・・一定数の問題を解き終わるとおしまい。ざっくりといえばこのようなものを作成しています。そこで問題1問ごとに制限時間を設けようと思い、今はカウントダウン式で0になればタイムオーバーとなるようにしています。
つまり、制限時間が0になった時にメッセージボックスで「タイムオーバー」と表示しています。

ここで問題が発生しました。2択問題は例えば3回続けて解くとしましょう。制限時間内に3問全て解いて、ユーザーフォームを閉じると、「タイムオーバー」のメッセージボックスが3回(制限時間内に答えられた問題数分だけ)立て続けに表示されてしまいます。
問題の選択肢はコマンドボタンのCaptionで表示していて、コマンドボタンを押せば正解か不正解かが分かるというものになっています。問題が表示されるのは、ユーザーフォームがアクティブになった時です。

もう少し詳しく必要でしたらおっしゃって頂ければと思います。

ひとまず簡単にしたものでコードを提示します。問題は一切関係ないようにしています。ユーザーフォーム1つと、ラベルを2つご用意お願いします。
この状態で、ユーザーフォームを表示するとカウントダウンが始まるわけですが、0秒になる前にユーザーフォームを閉じるとします。そしたらカウントダウンは止まらずに(フォームを閉じているので目では確認でませんが)、0秒になったころに「タイムオーバー」とメッセージボックスの表示がされてしまいます。Exsit Do を用いればカウントダウンを止められるような気はするのですが、どのように書けばよいでしょうか?何かいい案があれば教えて頂きたいです。よろしくお願いします。

ちなみに本来は、コマンドボタンをクリックして問題が変わるごとにカウントダウンを止めたいのですが、もしよろしければこちらの状況でうまくいく案があればありがたいです。※問題が表示されるユーザーフォームは1つなので、「ユーザーフォームを閉じたらExit Do」みたいなコードじゃない方がありがたいかもしれないです。それでもうまくいくのだったらそれで大丈夫なのですが。もしコードやもっと詳しい説明が必要だったらお申し付け下さい。
以前にこちらの掲示板でも質問しておりまして、こちらをご覧になればある程度分かるかもしれません。ただし、かなり変更はあるのであまり深く見て頂かなくて大丈夫です。

http://www.excel.studio-kazu.jp/kw/20181210221944.html

以下、ユーザーフォームに記述しているコードです

 Private Sub UserForm_Activate()

 Dim myStartTime As Long 'タイマーの変数
 Dim iTime As Long 'タイマーの変数

            iTime = Worksheets("Sheet1").Range("A1").Value 'シート1のA1セルの数字がタイムリミット
            myStartTime = iTime '秒数指定
            myEndTime = DateAdd("s", myStartTime, Time)

                Do Until myStartTime <= 0

                    myStartTime = DateDiff("s", Time, myEndTime)

                        UserForm1.Label1.Caption = myStartTime
                        UserForm1.Label2.Caption = "sec"

                            If myStartTime <= 3 Then
                                  UserForm1.Label1.ForeColor = RGB(255, 0, 0)
                            Else: UserForm1.Label1.ForeColor = RGB(0, 0, 0)
                            End If

                    DoEvents

                Loop

            If myStatTime = 0 Then 'タイムオーバーの時・・・

                MsgBox "タイムオーバー"

            End If

 End Sub

< 使用 Excel:Excel2016、使用 OS:Windows10 >


余計なお世話でしょうけど、気になったので一言。
[[20181210221944]] 『VBA 英語の前置詞を学習する教材の開発。』(OREO)
↑で
別の掲示板の方は名前を変えられないので(もちろん登録し直せば別かもしれませんが)、これからはOREOで基本的には投稿しようと思います。 ただし、今この掲示板にてアイス名義で立ち上げたスレッドについてはアイスのまま進めさせていただきます。
(OREO) 2018/12/30(日) 23:31

とコメントされてますよね。使い分けたいなら勝手にすればいいけど、せめて別名義でも投稿しているトピックがあることは書いておきましょうよ。
後から発覚したら、それはそれで荒れるチックになっちゃいますよ。(たぶん)
一言おわり。

今回の質問については、深く読み込めてないですが、本筋ではないところで気になる点がいくつか。

1.タイムオーバーになったときどのようにしたいのですか?
たとえば、出題数が3問で1問目がタイムオーバーだったときに、2問目を表示する処理にいくのか、それ以降の出題はしないのかということです。

2.「コマンドボタンを押せば正解か不正解かが分かる」ですから、1問ごとに成否判定するんですよね
そうなると正解か不正解かをどのように通知するのか、また、次の問題に行くのは、何をきっかけにするんでしょうか?

(もこな2) 2019/01/10(木) 18:47


もこな2様ありがとうございます。

ご指摘の通り、「アイス」という名義でファイルのパスについて伺っていたことがありました。

>1.タイムオーバーになったときどのようにしたいのですか?
タイムオーバーになると、その問題は間違えたことになって、次の問題へ移るようにしたいです。それ以降の出題はされるようにしたい、ということです。

>2.「コマンドボタンを押せば正解か不正解かが分かる」ですから、1問ごとに成否判定するんですよね
1問ごとに判定します。

正解の時・・・正解と分かるユーザーフォームを表示(絵で〇を表示するか、文字で「正解」のようにするかは迷っていますが、今回は関係ないでしょう)。
不正解の時・・・正解の選択肢が分かるように、ユーザーフォームにラベルを一つ用意し、そこへ正解の選択肢を出力する。
正解と不正解の場合のどちらにおいても、成否判定がされたユーザーフォームを閉じれば次の問題へ移ります。ちなみに、設定された問題数に達すると、次の問題へは移らずに、問題を表示していたユーザーフォームが閉じられます。

正解の時と不正解の時のユーザーフォームは別です。しかし、不正解の時は、選択肢1(コマンドボタン1)をクリックして表示されるユーザーフォームも、選択肢2(コマンドボタン2)をクリックして表示されるユーザーフォームも同じものを使用しています。
不正解だった時に表示されるユーザーフォームは同じものですが、選択肢1で間違えた時は選択肢2(つまり正解の選択肢)のテキストがラベルに表示され、表示位置が選択肢2のコマンドボタンを隠すように表示されます(ユーザーフォームが表示された時に、1.問題の絵、2.選んだ選択肢(不正解の選択肢)3.正解の選択肢を示すユーザーフォームが分かるようにするためです)。
選択肢2が不正解の時で選択し2を選んでしまった時は、同様に選択肢1を隠すように、ラベルに正解の選択肢が書かれたユーザーフォームを表示します。

不正解の時にわざわざ正解の選択肢を隠して、正解の選択肢をユーザーフォームに表示するのは、
1.不正解という文字を表示したくない(モチベーションが落ちる可能性を考えてのことです)。
2.そのために間違えたことが分かるように、「正解の選択肢は・・・(正解の選択肢のテキスト)」としたい。
3.「正解の選択肢は・・・」だけを位置調整して、選択肢の横へ表示することも考えましたが、「確認」のコマンドボタン(ユーザーフォームを閉じて次の問題へ移るためのものです)も表示したかったためです。
以上の3点が理由です。

(OREO) 2019/01/10(木) 20:20


UserForm_Activateなイベント中に長時間ループするコードを書くべきではありません。 それは、条件成立するまで、フォームがアクティブになり終わらないという事になってしまいますから。

そもそも、Excelは表計算アプリなので、リアルタイムな処理には向きません。 普通のVBなら使えるTimerコントロールも使えませんし。 Excelではなく、VB.NETでも使うべきでしょう。(Excelと違い、無料で使えるし、配布も許可されています)

なので、選択しないまま時間が経過した場合に何か処理する、というインターフェースは止めた方が良いです。(どうしてもやりたい、というならば、孤独な茨の道を覚悟してください) 選択した後に、時間が過ぎていたか判定し、例えば時間内の正解なら、時間外より得点が多くなるとか、いっそ短時間なら得点が高くなるというだけの単純計算ロジックの方が簡単でしょう。 よく考えてみてください。

また、経過時間を計算、判定するならば、Timer関数が便利だと思うので、ヘルプを調べてみてください。 0時基準なので、日またがりして実行されるとマイナスになる欠点がありますが、対応するロジックを書かなくとも、そもそも0時に実行するようなプログラムではないと思うので、問題ないでしょう。
(???) 2019/01/11(金) 09:36


 とりあえず解決案だけ

 Dim X As Boolean 'モジュールレベルで判定用変数

 Private Sub UserForm_Activate()

 Dim myStartTime As Long 'タイマーの変数
 Dim iTime As Long 'タイマーの変数

 X = False '初期化

 iTime = Worksheets("Sheet5").Range("A1").Value 'シート1のA1セルの数字がタイムリミット
 myStartTime = iTime '秒数指定
 myEndTime = DateAdd("s", myStartTime, time)

 Do Until myStartTime <= 0

   myStartTime = DateDiff("s", time, myEndTime)

   UserForm1.Label1.Caption = myStartTime

   If myStartTime <= 3 Then

     UserForm1.Label1.ForeColor = RGB(255, 0, 0)

   Else

     UserForm1.Label1.ForeColor = RGB(0, 0, 0)

   End If

   If X Then Exit Do 'Trueならループ抜け。Exit Subでも

   DoEvents

 Loop

 If myStartTime = 0 Then 'タイムオーバーの時

   MsgBox "タイムオーバー"

 End If

 End Sub

 Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)

   X = True

 End Sub

 Private Sub CommandButton1_Click()
   '勝手に追加しました。
   X = True

 End Sub

 次の問題行ったらCall UserForm_Activateとかでいいんじゃ?
 実際はどんなコードか知らんけど。
(X) 2019/01/11(金) 10:56

Xさんが直してくれましたが、現状案だと、UserForm_Activate内でCPUがブン回るので、このやり方は好かないのですよ。

一応、ExcelのOnTimeメソッドを使えば、タイマと似た動作はできます。(タイマが一定時間毎にイベントを発生させるのに対して、OnTimeは指定した時間になると指定のプロシジャを呼び出してくれる、スケジュール起動みたいな違いがあります) ただ、リアルタイム処理に向かないExcelなので、時間間隔は最小1秒もあるし、正確性もないのです。 使い方にコツがあるし、タイマコントロールより面倒なんですよね。 だから茨の道です。

とりあえず、最終的に実現したいであろう処理を想像し、茨の道の入り口だけサンプルを書きます。 新しいブックで、フォームにボタンを1つ貼り(タイマのサンプルなので、QA部分は書きません)、標準モジュールとフォームモジュールに、以下のコードを貼ってから、フォームをデバッグ実行してみてください。 これをどう応用しても構いませんが、変えた事で動かなくなっても、自己責任で解決する事を心がけてください。

 【標準モジュール】
 Public Const TIMEOUT = 10
 Public Const COUNTMAX = 3
 Public dNext As Date
 Public sStart As Single
 Public iCount As Long

 Public Sub sTimer()
    Dim sw As Single

    sw = sStart + TIMEOUT - Timer
    If sw < 0 Then
        iCount = iCount + 1
        If COUNTMAX <= iCount Then
            dNext = 0
            Unload UserForm1
            MsgBox "End.", vbInformation
            Exit Sub
        End If
        sStart = Timer
        sw = TIMEOUT
    End If

    UserForm1.Caption = iCount + 1 & " " & String(Int(sw), "■")
    DoEvents
    dNext = DateAdd("s", 1, dNext)
    Application.OnTime dNext, "sTimer"
 End Sub

 Public Sub sStop()
    If dNext <> 0 Then
        Application.OnTime dNext, "sTimer", , False
        dNext = 0
    End If
 End Sub

【UseerForm1】

 Private Sub CommandButton1_Click()
    iCount = iCount + 1
    If COUNTMAX <= iCount Then
        Call sStop
        MsgBox "End", vbInformation
        Unload Me
        Exit Sub
    End If
    sStart = Timer
 End Sub

 Private Sub UserForm_Initialize()
    iCount = 0
    sStart = Timer
    dNext = Now()
    Call sTimer
 End Sub

 Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    Call sStop
 End Sub
(???) 2019/01/11(金) 12:01

???様、X様、ありがとうございます。

エクセルはリアルタイムでの処理は不向きなのですね。じっくりと読んでコードもいろいろ試してみようと思います。

VB.NETというものもあるのですね。プログラミング系のソフトはエクセルしか使ってない(勉強したことが無い)ので存在すら知りませんでした。今回のはエクセルで作り上げてきたぶん、今から変えるのは少ししんどいので、勉強も兼ねてエクセルでやってみようと思います。必要であればVB.NETへ移すことにします。

すごくいいと思ったものがありました。
>選択した後に、時間が過ぎていたか判定し、例えば時間内の正解なら、時間外より得点が多くなるとか、いっそ短時間なら得点が高くなるというだけの単純計算ロジックの方が簡単でしょう。
この方法はとてもいいなと思いました。確かにこれならカウントダウンが0になっても次の問題へ移るのはコマンドボタン(選択肢)を押した時ですし、得点を2倍にするのも難しくないですね。なにより、タイムオーバーということにとらわれすぎてその1問を0点にすることばかり考えていましたので、制限時間外でも得点をGETできるというのは非常にいい案だと感じました。

今後どうなるかはわかりませんが、正直な所を申しますと、制限時間内なら得点が2倍という案に流されそうですね。

どちらにせよ、まずはアドバイスで頂いたコード等でいい方法を模索しながら頑張ります。

???様、X様、ありがとうございます、重ねてお礼申し上げます。
(OREO) 2019/01/11(金) 16:03


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.