[[20170518215425]] 『二つの文字を交互に表示』(外部コーチ) ページの最後に飛ぶ

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

 

『二つの文字を交互に表示』(外部コーチ)

お世話になります。

お蔭様で何とかプログラムが出来ました。私のVBA初自力プログラムです。
試行錯誤の末動作は希望通りに表示していると思います。

シート1の入力条件は
「B6〜P6」= 指示項目の入力(今回は使いません)
「U4」= 指示回数の入力
「U5」= 表示時間の入力
「S6」= 間奏回数(下限)の入力
「U6」= 間奏回数(上限)の入力

表示画面は「A1」と 動作確認の為「A10」に表示回数(ランダムの為)

何か問題点や修正点などは有りますでしょうか?
GoTo Labelを使用したのですが、調べるとあまり使わない方が良さそうですが・・・。

Sub 間奏時間の交互表示()

'変数宣言

   Dim oc As Integer    ' order count = 指示回数…セル("U4")…間奏不用
   Dim dt As Single     ' display time = 表示時間…セル("U5")
   Dim ics As Integer   ' Interval count S = 間奏回数(下限)…セル("S6")
   Dim icr As Integer   ' Interval count R = 間奏回数(上限)…セル("U6")
   Dim ic As Integer    ' Interval count R = 間奏回数…Int/Rnd関数
   Dim tdt As Single    ' total display time = 累計表示時間…Timer関数

'数値の取得

   oc = Sheet1.Range("U4").Value    ' 指示回数をセルから取得…間奏不用
   dt = Sheet1.Range("U5").Value    ' 表示時間をセルから取得
   ics = Sheet1.Range("S6").Value   ' 間奏回数(下限)をセルから取得
   icr = Sheet1.Range("U6").Value   ' 間奏回数(上限)をセルから取得

'ランダム数値取得

   Randomize                                  ' 乱数の初期化
   ic = Int((icr - ics + 1) * Rnd + ics)      ' 間奏回数をランダムに取得
   Sheet1.Range("a10").Value = ic             ' 仮に間奏回数を「A10」に表示…削除予定

'表示プログラム

   For tic = 1 To ic                          ' ループ処理開始 : tic = 累計間奏回数
      If tic Mod 2 = 0 Then                   ' 間奏回数を偶数・奇数判定

   '表示回数が偶数判定の処理
      tdt = Timer                             ' tdt値の上書き
Label1:
         If (Timer - tdt) <= dt Then          ' 条件=累計表示時間が表示時間以下
            Sheet1.Range("A1").Value = "▽"   '  「A1」 に 「▽」 を表示
GoTo Label1
         End If
      Else
   '表示回数が奇数判定の処理
      tdt = Timer                             ' tdt値の上書き
Label2:
         If (Timer - tdt) <= dt Then          ' 条件=累計表示時間が表示時間以下
            Sheet1.Range("A1").Value = "△"   '  「A1」 に 「△」 を表示
GoTo Label2
         End If
      End If
   Next

   MsgBox "ランダム表示終了", vbInformation   ' ネットから引用:動作は理解 コード不理解

End Sub

ご採点およびご指導宜しくお願い致します。

< 使用 Excel:Excel2013、使用 OS:Windows7 >


どこで見つけたのか知りませんが、Goto文は、初心者のうちは使用禁止です。初期のBASIC言語から存在する基本命令ですが、簡単に無限ループを作り込んでしまったり、飛んではいけないところに飛ばしてロジックを破綻させてしまう、危険な命令だからです。

次に、Goto文のせいでもありますが、条件成立するまで、同じセルに同じ値を何度も代入し続けています。1回代入すればずっとそれが表示されているのに、何度も代入するのは、文字がちらついて見えたり、CPU負荷を増大させる、無駄な行為です。ステップ実行し、どう動いているか追ってみてください。(修正時には、DoEventsを実行しつつループするようにしてください)

更に、これは意味を理解して利用しているのなら構わないのですが、Sheet1.Range("U4").Value というようなシートオブジェクトの指定は、あまり使いません。これは、Sheet1というオブジェクトはシートをコピーしてもリネームしても不変であり、別なシートで実験、とかしにくいからです。 間違いではないし、無駄はないので、このままでも構いませんが、Sheets("Sheet1")というシート名で指定する方法がよく使われます。更には、シート名を変えた場合に変更しやすいよう、これをWorkSheetオブジェクトの変数に代入しておく場合が多いですね。
(???) 2017/05/19(金) 09:31


Do文を使わず、Goto文を使わず。この条件で、時間待ちする1つの答えが、以前最初に教えたOnTimeを使う方法です。 今回は別解として、Timer関数を覚えたので、これを利用して一定時間待つ例を書いてみます。

まず、以下のtest1を実行してみてください。私のノートPCでは、結果は9秒程になります。

 Sub test1()
    Dim i As Long
    Dim St As Single

    St = Timer
    For i = 0 To 999999
        DoEvents
    Next i
    MsgBox Timer - St
 End Sub

つまり、判定条件を間違えても、このループ回数ならば、10秒くらいで抜けてきて、無限ループしない訳ですね。 応用して、3秒で抜けるように変えてみた例です。

 Sub test2()
    Dim i As Long
    Dim St As Single

    St = Timer
    For i = 0 To 999999
        If 3 < Timer - St Then
            Exit For
        End If
        DoEvents
    Next i
    MsgBox Timer - St
 End Sub

test1で計った時間より長く待つ事はできませんので、もし足りないようならば、ループ回数を1桁増やしてみてください。
(???) 2017/05/19(金) 14:29


(???)さん
 引き続きご指導ありがとう御座います。

すみません。返信が遅くなりました。
只今1回目に頂いたご教授の対応に追われています。

>どこで見つけたのか知りませんが・・・

 どこからなんでしょう? 繰り返し処理をネットで調べているうちに辿り着きました。
 やはり使わない方が良さそうですね。変更します。

>同じセルに同じ値を何度も代入

 「 Sheet1.Range("A1").Value = "▽" 」を「 tdt = Timer 」の前に移動(偶数・奇数 共)で対応
 ループ処理の前に設置で何度も代入されない???と思います。
 どの道「GoTo」文を訂正するのでその時考慮します。

>修正時には、DoEventsを実行しつつループ

 とりあえず「GoTo Label1」と「GoTo Label2」の前に設置しました。これも訂正により無くなるのかな?

>意味を理解して利用しているのなら構わない

 意味は理解していないです。
 今は構文をシート2に仮に製作して後でコード入力画面にコピペしています。
 その時シート2を開いたままマクロを実行すると書いていた構文に何かしらが表示されてしまい戻せないとい
 う私にとって大惨事が発生してしまいシートも指示しておけばと言う単純発想からです。おそらくその様な
 命令文や何かは在るのだろうと思いましたが今はいっぱいいっぱいです。「 WorkSheetオブジェクト 」この
 辺を理解せずに来てしまったのが原因かと・・・。今後の課題とします。

>Do文を使わず、Goto文を使わず。

 すみません。まだ手が付けられません。「 OnTime 」は意識はしたのですが、表示を見ていてトレーニング
 を想像するとけっこう1秒って長いなぁ と。それであの様になりました。とりあえず「 OnTime 」もふま
 えて考えてみます。「DoEvents」奥が深そうです。ちょっと色々試して見ます。

ちょっと勉強が必要そうです。がんばってみます。
ご教授ありがとう御座います。

(外部コーチ) 2017/05/19(金) 16:15


1秒だと長い、という場合は、Timer関数利用で進めるのが良いでしょうね。

DoEvents についてちょっと説明しておきますと、これは自分がCPUを使っている事を一旦中断することで、他のプロセスにCPUを使わせるきっかけを与えてあげる命令です。 この一文自体は、なにも処理するものではありません。

無限ループまたはそれに近いくらい一生懸命マクロが計算している場合、表示更新する隙が無いと、例えばセルの値を変えたとしても表示が変わらないので、何もしていないように見えます。カウントダウンしていても、見た目は最初と最後しか見えない、という事になります。

この辺りはコンピュータとOSの動作原理の理解があれば簡単なのですが、まぁ実際に体験して困ってみれば判ると思います。(DoEventsをコメントアウトして実行してみるだけです) そして、時間経過を待つ間にDoEventsした意味ですが、待っている間にマクロを強制停止しようとした場合、キー入力をチェックする隙すらないと、止める事ができないのです。DoEventsを入れておけば隙ができるので、キー入力できるし、表示更新もできる、という訳です。ヘルプの説明だけだと意味不明かもしれませんので、その時は私の砕いた説明を思い出してみてください。
(???) 2017/05/19(金) 17:36


(???) さん
 お世話になります。

「 DoEvents 」について
ほぼ理解出来ていない状態かと…。調べて解った事はプログラムの処理を何か別の所(パソコン本体?)で処理をさせる。その為プログラムで動かしていないので無限ループ状態に陥っても操作できる。また、起動中でも他の作業ができる。程度です。頂いたテストコードも試してみましたが、解った事はループ中に他の作業を行っていると終了時間が遅くなる。位で私にはかなり難問です。Sub test1() は、なぜループを抜けるのか理解できません。今の所単純に無限ループ対策と理解しています。

「 OnTime 」について
やはりこれも完全には理解してません。時刻を指定してマクロを実行する みたいな感覚で認識していますが、プログラムとしてどう使えば良いのか悩んでいます。Applicationオブジェクトのメソッドという解説を見ましたが、やはり「オブジェクト」や「メソッド」の意味をしっかり理解しないと難しいと思いました。

>Timer関数利用で進めるのが良い
とりあえずこちらの方で進めようと思います。多少は悩んだ分理解できましたのでその範囲で作ってみようとおもいます。ただ、ループ処理に関してですが「Doループ = 条件」「Forループ = 回数」「GoTo =無条件」以外でどの様な関数があるのか教えて頂けないでしょうか?
私の中では「Doループ」が有力なのですが…。

呑み込みの悪い頭ですみません。少しの間お付合いとご指導宜しくお願いします。

(外部コーチ) 2017/05/20(土) 00:04


(???) さん
 お世話になります。

訂正:Sub test1()の件
999999の処理を約10秒ほどで終わらせているのですね。
Sub test1()は処理に3秒以上かかれば処理回数に関係なく繰返しを抜けるって事では。

つまらない事ですみません。
(外部コーチ) 2017/05/20(土) 08:42


>Sub test1()は処理に3秒以上かかれば処理回数に関係なく繰返しを抜けるって事では。
間違えました。 Sub test1() → Sub test2() です。
(外部コーチ) 2017/05/20(土) 10:06

test1は、単純に100万回のループが終わればループ完了するだけです。何秒で終わるかは、PC性能によって大きく変わります。

test2は、そのループの最中に経過時間をチェックし、3秒経っていたならループから強制的に抜けることで、3秒カウントを実現しています。3秒判定しているので、PCの性能が違っても、3秒を計れます。何回回るのかはどうでも良いことであり、時間を計るのが主目的です。Do文でループしても同じことができますが、Do文は判定を間違えると無限ループになるので、これをFor文で防いでいるのです。

応用の仕方としては、これをたとえば1秒か0.5秒で抜けるようにしておき、抜けてきたら数字をカウントダウンして表示、また時間を待って、更にカウントダウン…、というコーディングにすれば良いでしょう。
(???) 2017/05/20(土) 19:25


(???) さん
 お世話になります。

すみません。
夢中で作っていたら返信が遅くなってしまいました。教えて頂いた事を応用して製作していたらほぼほぼ完成してしまいました。

一応動作確認は問題無さそうですが如何でしょうか?
ご確認宜しくお願いします。

Sub トレーニング1()

 '表示シートの条件
   '「B2」= 指示/間奏の表示画面          '「T7」= 指示表示時間の入力
   '「R4」= カウントダウンの表示画面     '「T8」= 間奏回数(下限)の入力
   '「B10〜P10」= 指示項目の入力         '「T9」= 間奏回数(上限)の入力
   '「T6」= 指示回数の入力               '「T10」= 間奏表示時間の入力
 '変数宣言
    Dim oic As Integer   ' order item count = 指示項目数
    Dim oil As Integer   ' order item last = 指示項目の最終列番号
    Dim oin As Integer   ' order item number = 指示項目の列番号
    Dim odc As Integer   ' order display count = 指示表示回数…セル("T6")
    Dim odt As Single    ' order display time = 指示表示時間…セル("T7")
    Dim odst As Single   ' order display start time = 指示表示開始時刻…Timer関数
    Dim ics As Integer   ' Interval count small = 間奏回数(下限)…セル("T8")
    Dim icl As Integer   ' Interval count large = 間奏回数(上限)…セル("T9")
    Dim idc As Integer   ' Interval display count = 間奏表示回数…Int/Rnd関数
    Dim idt As Single    ' Interval display time = 間奏表示時間…セル("T10")
    Dim idst As Single   ' Interval display start time = 間奏表示開始時刻…Timer関数
    Dim toc As Integer   ' total order count = 累計指示回数…Int/Rnd関数
    Dim tic As Integer   ' total Interval count = 累計間奏回数…Int/Rnd関数
    Dim i As Long                                    ' ループ発生用変数(任意)

 '数値の取得
    odc = Sheet1.Range("T6").Value                      ' 指示表示回数をセルから取得
    odt = Sheet1.Range("T7").Value                      ' 指示表示時間をセルから取得
    ics = Sheet1.Range("T8").Value                      ' 間奏回数(下限)をセルから取得
    icl = Sheet1.Range("T9").Value                      ' 間奏回数(上限)をセルから取得
    idt = Sheet1.Range("T10").Value                     ' 間奏表示時間をセルから取得
    oil = Sheet1.Range("b10").End(xlToRight).Column     ' 表示項目行の最終列数番号を取得

 'トレーニングプログラム
       Randomize                                  ' 乱数の初期化
    For toc = 1 To odc                            ' 指示表示ループ処理開始
       Sheet1.Range("R4").Value = odc + 1 - toc   '「R4」に「指示表示回数」をカウントダウン表示

    '間奏表示プログラム
       idc = Int((icl - ics + 1) * Rnd + ics)     ' 間奏表示回数をランダムに取得
       Sheet1.Range("T3").Value = idc             ' 間奏表示回数を「T3」に表示…ランダム値確認(削除予定)
       For tic = 1 To idc                         ' 間奏表示ループ処理開始
          If tic Mod 2 = 0 Then                   ' 条件=累計間奏回数の偶数・奇数判定
             '累計間奏回数が偶数判定の処理
             Sheet1.Range("B2").Value = "▽"      '「B2」に「▽」を表示
             idst = Timer                         ' 間奏表示開始時刻の取得
                For i = 0 To 999999               ' 間奏表示ループ処理開始
                   If idt < Timer - idst Then     ' 条件=累計間奏時間の判定
                Exit For                          ' ループ命令
                   End If                         ' 累計間奏時間判定の終了
                   DoEvents                       ' 無限ループ回避
                Next i                            ' 間奏表示ループ終了
          Else
             '累計間奏回数が奇数判定の処理
             Sheet1.Range("B2").Value = "△"      '「B2」に「△」を表示
             idst = Timer                         ' 間奏表示開始時刻の取得
                For i = 0 To 999999               ' 間奏表示ループ処理開始
                   If idt < Timer - idst Then     ' 条件=累計間奏時間の判定
                Exit For                          ' ループ命令
                   End If                         ' 累計間奏時間判定の終了
                   DoEvents                       ' 無限ループ回避
                Next i                            ' 間奏表示ループ終了
          End If                                  ' 条件:偶数・奇数判定終了
       Next                                       ' 間奏表示ループ終了

    '指示表示プログラム
       oin = Int((oil - 1) * Rnd + 1)                             ' 表示項目の列番号をランダムに取得
       Sheet1.Range("B2").Value = Sheet1.Cells(10, oin).Value     '「B2」に「表示項目」を表示
       odst = Timer                                               ' 指示表示開始時刻の取得
          For i = 0 To 999999                                     ' 間奏表示ループ処理開始
             If odt < Timer - odst Then                           ' 条件=累計指示時間の判定
          Exit For                                                ' ループ命令
             End If                                               ' 累計指示時間判定の終了
             DoEvents                                             ' 無限ループ回避
          Next i                                                  ' 間奏表示ループ終了

        DoEvents                                                  ' 無限ループ回避
    Next                                                          ' 指示表示ループ終了

    Sheet1.Range("R4").Value = ""                                 '「R4」を「空白」表示
    Sheet1.Range("B2").Value = "終了"                             '「B2」に「終了」表示

 End Sub

宣言の位置や変数の文字が一般とは違うような気もしますが・・・。
宜しくお願いします。
(外部コーチ) 2017/05/21(日) 22:30


ちゃんと動くものを作り上げましたね。このほうがどこで何をやっているか、理解できた事でしょう。

宣言が先頭に固まっているのは、これで良いです。BASIC言語はFortran言語の派生なので、宣言は先頭にする方が良いです。変数名が小文字オンリーなのは、そういう考え方もあるので、問題ありません。(打ちやすく、更に予約語との違いが一目で判るから)
変数名を短くして意味が判りにくくなる点は、各行にコメント付けているので、十分判りやすいです。Exit For の段付けは、If文より深くしましょう。あとは、Integer型は特に意味が無ければLong型にする癖を付けるべきでしょうか。(Integer型の方が桁が少ないのでメモリを有効に使う、と考えがちですが、32bit長の型のほうが値の範囲が大きい以外にも、処理が速かったりするため)

まぁ、細かい事は少しずつ覚えていけば良くて、動くコードを「完成」させた、という点が重要です。自信を持ってください。

動かしてみて気になったのは、変数tic は常に1から始まるので、最初は必ず"△"になる点。終わりは回数が奇数か偶数かで変わってくるので、最後が "△" だった場合、次も "△" になるのは良いのかなぁ?、とか思いました。 向きの連続はアリとしても、開始が上下どちらかなのかもランダムにする必要は?、とか。
(???) 2017/05/22(月) 09:54


(???) さん
 お世話になります。

ご評価有難うございます。
お陰様で何とか合格ラインって所でしょうか?(まだまだ甘いか…)

>宣言が先頭に固まっているのは、これで良いです。
 有難うございます。整理と見やすさを意識していたらこうなりました。

>Exit For の段付けは、If文より深くしましょう。
 解りました。

 For i = 0 To 999999 ' 間奏表示ループ処理開始
  If odt < Timer - odst Then ' 条件=累計指示時間の判定
   Exit For ' ループ命令
  End If ' 累計指示時間判定の終了
 DoEvents ' 無限ループ回避
 Next i ' 間奏表示ループ終了

 こんな感じですね! 「Else」も同じでしょうか?

>Integer型は特に意味が無ければLong型に
  これは色々調べている時に思いました。16bit時代の名残りで今はわざわざ32bitに
  変更してるとか何とか。
  とりあえずLong型に統一します。(Integer型にするメリットって有るのでしょうか?)

>変数tic は常に1から始まるので、最初は必ず"△"になる点
  大丈夫です。
   △ ▽ △ 表示中 = その場で全力足踏み
   指示表示(例えば 「上」) = 上方向に全力ジャンプ!
   △ ▽   表示中 = その場で全力足踏み
   指示表示(例えば 「下」) = 床に手をつく様にしゃがんで立ちあがる!
   の様に"△"の向きはどちらでも特に問題ありません。
   ただ少し気になるのですが「変数ticは常に1から始まる」の動作はその様にしているつもり
   なのですが、実際その様になっているのでしょうか?動作確認では大丈夫なのですが、
   「変数tic」を For文の前に宣言している為、値はループするたびに加算されているのでは?
   と思いました。全体のループの時「変数tic」が初期化されないと"△"の向きが逆から表示さ
   れるときが有るのでは?
   使用には問題無いのですが今後の為にご教授宜しくお願いします。
   あともう一つ、「For i〜next i」と「For i〜next」で「next」の後に「i」を入れる時と
   入れない時の違いは何か有りますか。こちらもご教授宜しくお願いします。
(外部コーチ) 2017/05/22(月) 12:12


16bit型が遅いのは、32bitCPU以降は、内部的には32bitで処理されるので、例えば -1 の場合、Integer型では上位半分を &HFFFF でいちいち埋めないといけない等、残り16bitを判定して埋める処理が動くからです。 Integerの &HFFFF と Longの &HFFFFFFFF はどちらも数値は -1 。つまり、FFFF の最上位bitが0ならば、上位半分は0にする。1ならば、上位半分はFFFFにする。このように判定と代入が行われる分、無駄な時間がかかるわけですね。(これを理解するのは、情報処理技術者試験を受けるような人なので、聞き流してもらっても構いません)

Integer型を使うのは、データの塊を定義し、バイナリアクセスで一括読みとかする際、16bitで1変数にするような場合くらいですかねぇ。通常は常に整数型はLongでOKです。

ループ変数は、For文に入ったときに初期値になるので、事前クリアとか要りません。現状で、必ず1始まりでしたよ。
(ちなみに、ループ完走して抜けると、終端値の次の値になる特徴があるので、完走したのか Exit For したのかを判断できます)

Next の際、ループ変数名を記述するのは、見易くするためだけかと思います。 以前のBASICでは、無指定の方がごくわずかだけど速い、とかありましたが、誤差程度だったし、なるべく省略しないほうが良いでしょうね。
(???) 2017/05/22(月) 13:13


(???) さん

本当に長い期間お世話になりました。
最初はエクセル関数で出来るのかな?という発想から質問投稿させて頂きましたが、何となくVBA へ移行し「マルチポスト」のご指摘さらに VBA 完全初心者の私にとても親身なご指導頂き恐縮ながら少し VBA が扱えるようにまでさせて頂きました。今後も少しづつでは有りますが VBA に慣れていこうと思いますので、また質問させて頂いた時にはご指導宜しくお願い致します。

お陰様で今回の質問はこれにて締めさせて頂きます。

(???)さん 本当に有難う御座いました。

(外部コーチ) 2017/05/22(月) 13:46


コメント返信:

[ 一覧(最新更新順) ]


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