Excel (VBA)

Excel VBAに関するフォーラムです。
  • 解決済みのトピックにはコメントできません。
このトピックは解決済みです。
質問

 
(Windows 10 Home : Excel 2016)
SetTimer関数を用いて一定時間ごとに処理をしたい
投稿日時: 18/07/04 23:42:28
投稿者: オットセイ

お世話になります。
 
SetTimer関数を用いて一定時間ごとに処理をしたいのですが、
うまく出来ません。
サンプルとして、5秒おきに処理を3回繰り返すコード
ですが、1回しか動きません、どこが悪いのか
教えて頂けないでしょうか。
宜しくお願い致します。
 
 
 
'Windows API宣言'
Declare Function SetTimer Lib "user32" (ByVal hWnd As Long, _
                                        ByVal nIDEvent As Long, _
                                        ByVal uElapse As Long, _
                                        ByVal lpTimerFunc As Long) As Long
Declare Function KillTimer Lib "user32" (ByVal hWnd As Long, _
                                        ByVal nIDEvent As Long) As Long
  
'定数定義'
Private Const MY_TIMER_ID As Long = 100001 'タイマーID(任意値)'
Private Const MY_TIMER_MSEC As Long = 5000 '5秒間隔'
  
'変数定義'
Private prCnt As Long '処理回数'
  
 
'サンプル処理開始'
Sub Sample1()
 
    Dim total
 
 
    prCnt = 0
     
    'ウィンドウハンドルを取得'
    Dim hWnd As Long
    hWnd = Application.hWnd 'Excelの場合
     
    'タイマー設定'
    Debug.Print "タイマー設定"
    Call SetTimer(hWnd, MY_TIMER_ID, MY_TIMER_MSEC, AddressOf TimerProc)
     
    total = total + 1
    Range("B1").Value = total
        
End Sub
  
 
 
'タイマーコールバック関数'
Private Function TimerProc(ByVal hWnd As Long, ByVal msg As Long, _
                           ByVal wp As Long, ByVal lp As Long) As Long
    Select Case wp
    Case MY_TIMER_ID
        'タイマー処理'
         
        prCnt = prCnt + 1
         
        Dim time As Long
        time = (MY_TIMER_MSEC * prCnt) / 1000
         
        Dim str As String
        str = Space(3 - Len(CStr(time))) & CStr(time) & "秒経過"
        Debug.Print str
         
        '3回まで処理'
        If prCnt > 3 Then
            'タイマー解除'
            Call KillTimer(hWnd, MY_TIMER_ID)
            Debug.Print "タイマー解除"
             
            Range("A1") = prCnt
        End If
    End Select
End Function

回答
投稿日時: 18/07/05 06:49:36
投稿者: よろずや

Excel には OnTime という機能が用意されているのに、
なんでわざわざ API なんか持ち出すのかねぇ。

回答
投稿日時: 18/07/05 07:27:53
投稿者: simple

> サンプルとして、5秒おきに処理を3回繰り返すコード
> ですが、1回しか動きません、どこが悪いのか

何を根拠に一回だけというのか不明ですが、
TimerProcは、きちんと繰り返されていますよ、
イミディエイトウインドウに書き出されているものを
よく観察して下さい。
 
ちなみに、
> total = total + 1
> Range("B1").Value = total
ここは別に繰り返されるようなコードにはなっていませんよ、
書いた通りに動いているだけです。
なお、あれば出典も書いた方がよいです。
 
----------------------------
既にご指摘がありましたように
例えば、

Dim counter As Long
Sub test()
    counter = 1
    Application.OnTime Now + TimeValue("00:00:03"), "my_Procedure"
End Sub

Sub my_Procedure()
    If counter <= 3 Then
        'counter回目の作業の例
        Range("A1").Value = counter

        '次回の予約
        counter = counter + 1
        Application.OnTime Now + TimeValue("00:00:03"), "my_Procedure"
    Else
        MsgBox "終了"
    End If
End Sub
などと普通にApplication.OnTimeで実現できる話です。
(既に書いてしまったので載せます。参考にしてください。)

投稿日時: 18/07/05 13:53:08
投稿者: オットセイ

よろずやさん、simpleさん、
 
ご回答ありがとうございます。
APIを使いたい理由は、
CPUの負荷がすごく軽減されるからです。
 
このコードだと、End Sub の下でタイマーが作動するのですが、
希望は、End Sub の上でタイマーが作動する様にしたいのです。
 
また、Sleep関数だと簡単に出来るのですが、
欠点として、タイマー処理中に制御を占有されてしまいます。
SetTimerを使うとタイマー処理中に制御を占有しない利点が
あるからです。
 
End Sub の中でタイマーが作動するようにコードを書きたい
のですがうまく出来ません。
ご教授して頂けないでしょうか よろしくお願いいたします。
 
 
 

回答
投稿日時: 18/07/05 15:27:32
投稿者: Suzu

引用:
このコードだと、End Sub の下でタイマーが作動するのですが、
希望は、End Sub の上でタイマーが作動する様にしたいのです。

 
上とか下とか。。仰る意味が判りません。。
 
引用:
SetTimerを使うとタイマー処理中に制御を占有しない利点が あるからです。

制御を返さない(処理の為に占有する) のが嫌という事?
 
 
引用:
End Sub の中でタイマーが作動するようにコードを書きたい のですがうまく出来ません。
ご教授して頂けないでしょうか よろしくお願いいたします。

 
これは、あなたがお書きになったのではないのですか?
Functionの中で 5秒を3回と言うか。。5回になるか。。を繰り返しているのですから、
Sub側では何もできないのは当然では?
 
イミディエイトには、5回。。
  5秒経過
 10秒経過
 15秒経過
 20秒経過
タイマー解除
 
Sub で実施したいのは、何か目的があるからなのでは?
 
最終的に何をおやりになりたいのか説明された方が目的に近づくには早いと思いますよ。

回答
投稿日時: 18/07/05 15:30:41
投稿者: Suzu

言っておきます。
 
入力を監視する為とか、、、であれば アプローチが違うと思いますよ。。
 
そこまで監視しなきゃいけない物をExcelベースで開発
セキュリティー的甘目のExcelで開発ってどうなんですかね。。。

投稿日時: 18/07/05 18:00:06
投稿者: オットセイ

Suzuさん、
ご回答ありがとうございます。
 
目的のプログラムは、スクリーンショットで
撮った画像を印刷するだけで、それを一定時間ごとに
繰り返したいものです。
 
Sleep関数を用いたプログラムは出来ていますが、
SetTimer関数を用いた方が、タイマー処理中に
制御を占有しない利点が あるからです。
 
タイマーは、印刷するまでにデータを読み込ませる
時間調整と、それを一定時間ごとに繰り返す時に
使います。
 
質問しているSetTimer関数のコードはネットで調べたものです。
 
 
作りたいコードは、下のコードのサンプル処理開始の
中でタイマーが作動して欲しいことですが、
 KillTimer との関係で、それが出来ないことです。
 
再度ご教授して頂けないでしょうか 
よろしくお願いいたします。
 
 
 
'サンプル処理開始'
Sub Sample1()
 
    prCnt = 0
    'ウィンドウハンドルを取得'
    Dim hWnd As Long
    hWnd = Application.hWnd 'Excelの場合
    'タイマー設定'
    Debug.Print "タイマー設定"
    Call SetTimer(hWnd, MY_TIMER_ID, MY_TIMER_MSEC, AddressOf TimerProc)
         
End Sub
 

回答
投稿日時: 18/07/05 19:26:53
投稿者: Suzu

APIは。。理解する事を放棄しているので改善方法については、回答できません。。
 
でも、論理的に考えた時
呼び出し元で希望の動作が実現できるという事は、APIが制御を呼び出し元に返します。
という事は、呼び出し元側で結局占有する事になるのでは?違いますかね?
 
Sub は呼び出しだけ。。だからこそ、制御占有する事が無い。
 
行いたい事は 矛盾していると思いますよ。
どうしても、その処理が必要なのであれば、Function 側で処理するしかないのでは?

回答
投稿日時: 18/07/05 20:43:30
投稿者: simple

# 横から失礼しますね。
# 日中はアクセスしませんので、どうしても遅くなります。
 
(1)
SetTimerの基本を理解していないようですね。
これは、
・インターバル期間等とコールバック関数を指定して、
・一定のインターバール期間ごとに、コールバック関数を繰り返し実行させる命令です。
 
だから、実行させたい処理はコールバック関数の中に入れないとダメです。
SetTimerの呼び出し側に繰返す処理を書きたいなどといっても、
仕様と相容れないことは百万年掛かっても無理です。
「横紙破り」というほかない。(意味が通じないだろうが)
 
そもそも私が指摘した
> TimerProcは、きちんと繰り返されていますよ、
> イミディエイトウインドウに書き出されているものを
> よく観察して下さい。
という話はどのように受け止めたのですか?教えてください。
1回じゃなく、繰り返し処理が実行されている、
ということは理解されているんでしょうか。
 
(2)
余り人の言うことに耳を貸さない人のようだから
無駄かも知れませんが一応コメントしておきます。
 
>普通にApplication.OnTimeで実現できる話です。
と申し上げ、コードも示していますが、それは確認されたでしょうか?
コメントくらいするのが礼儀というもの。
 
また、貴君は一生懸命Sleep関数と比較していますが、
誰もSleep関数のことなんかひと言も言っていないじゃないですか。
比較して欲しいのは、Application.OnTimeです。
これはSleepなんかと違って、制御を独り占めなんかしません。
確認したんでしょうか?
 
WindowsAPIは使い方を間違うとOSごと落ちたりすることもある、というものです。
一定期間の繰り返し機能のために、Excelが用意しているのがAPplication.OnTimeなんですから、
それを使うべきでしょう。(熟練者がAPIを多用することは勿論ありえますが)
 
OnTimeで指定したプロシージャ(私の例示で言えばmy_Procedure)の処理を実行して、
既定の回数に達しないうちは、処理の最後で、
自分自身(my_Procedure)を一定期間後に動作するように予約実行すれば、
結果として繰り返し実行をさせることができます。
 
また、回数に達しなくても、ボタン等に特定のコードを登録しておいて、
これをクリックすることで、予約を取り消すことも簡単にできます。
留意点が1つあるが、それはやめておきます。無駄になりそうなので。

投稿日時: 18/07/06 03:28:09
投稿者: オットセイ

Suzuさん、simple さん、
ご回答ありがとうございます。
 
ご教授を頂いたことを全て検証して
コードを書いてみましたが、
APIは、私のレベルではうまく出来ませんでしたので、
Application.OnTimeを使うことにしました。
また、WindowsAPIは使い方を間違うとOSごと落ちたりする
こともあるので、怖いのでSetTimerは使いません。
 
色々とアドバイスありがとうございました。