e日記風 独り言

#気まぐれ & 気まま & 天邪鬼な老いぼれ技術屋の日々の記録のうち、主に Perl・CGI・HTMLなど Web技術に関連した記事です。
右端上端の同一カテゴリージャンプボタンで同じカテゴリーの他の記事を順番にご覧いただけます。
Access Counter:  総アクセス数

楽 天 の 商 品

-2129- VBA on PPT
= 今日は画像なし m(_ _)m =
新型コロナの話題は置いといて。
仕事で パワーポイント(PPT)上で VBA(Visual Basic Application)を使おうと思った。表示の視覚的なポジティブ/ネガティブな効果を言葉(しかも英語!)で説明してもなかなか分かってもらえない。パワーポイントのスライドならある程度は表現できるが、図形の移動などの既存のアニメーション効果は、途中で一旦止めたり、スライドインした図形を逆方向にスライドアウトさせることは出来ない。
VBAで簡単なコードを書けばそうしたことが出来るし、Excelの VBAは 1,000行程度のプログラムも作ったことがあり、それは未だに都内のとある印刷所で重宝して使っていただいているようだ。(半年に一回くらい突発的な現象があるらしく、その度にヘルプの電話が来る)
だから、安易に取り掛かったが、VBAはその時以来数年間まったく触っていないし、Excelと PPTでは基本は一緒でも、細かなところが違っているようでなかなか思ったように動いてくれない。
その最大の問題点が、図形をアニメーションする際の画面のリフレッシュ(再描画)。図形を画面上で少しずつ移動させたり拡大したりするために図形の位置やサイズを VBAで変化させてみたが、スライドショー画面でそのマクロを実行しても途中経過は表示されず、マクロ開始前の位置からいきなりマクロ終了時の位置に移動するだけ。下のサンプル文のように途中にビープ音を出すようにしたら、Beep音は聞こえるがマクロが修了するまで図形は表示も移動もしない。
困ったときの Web検索でいろいろ検索すると同じような現象で悩んで質問している人がいる。回答を見ていると Excelではいくつか方法があるらしく、それが紹介されているのでそれを PPTで試しても一向に解決しない。何より Excelとは違って PPT の VBA は利用者が少ないらしく、VBAに関する情報自体が少ない。
万策尽きて、質問掲示板で質問してみた。まず日本語の Gooで質問したところ回答があり DoEvents を使ったら?、と言うアドバイス。どうやら DoEvents と言う Windowsに処理を一旦戻す命令では、再び VBAに処理が戻される時に、その時点でのデータに基づき再描画する仕様になっている様子。だが、そのために、私の環境でこの1命令に 15mSec程度の時間がかかる。(多分、マシンの性能や並行して実行されている処理などによってもこの時間は変わる) ただ、PPTと言うプレゼンテーション用のアプリの VBAで、図形の移動などの処理をして1秒以下のタイマーで変化の速度を変えることは極めて普通の使い方だと思うのに、そうしたタイマー関数も準備されておらず
、窮余の策で Windowsの関数を使っているのに その度に DoEventsをかませて再表示させなければならないなんてどうしても納得がいかない(困った性格だ)。
そこで今度は英語の Microsoft コミュニティーに質問してみたところ、こちらでは 質問のサンプルコードでは省いたマクロコードの頭で宣言する Declare文も掲載したほうがアドバイスが得られやすいと言う指摘があった。
この英文質問のアドバイスで「えっ!?」と思った。というのも此の問題をテストするためのサンプルコードでは Declare 文はミリセカンド単位のタイマー遅延を使用するために Windows基本の Sleep関数を使うための宣言
Public Declare Sub Sleep Lib "kernel32" (ByVal Milliseconds As Long)
というような行だけ。 これは PPTの VBAには ミリ秒単位のタイマーが用意されていないので "kernel32" と言う Windowsの最下層のライブラリを呼び出すように指定する宣言文。まさかタイマー関数 Sleep文を使うと画面描画が行われない!? そんなはずないと思いつつも、VBAから Windowsの関数を呼び出すと言う想定外の使い方になっていることは事実で、念の為 Sleep() に代えて、Excel の Wait関数を使うため 最初の方に
Set xls = CreateObject("Excel.Application")
と既述しておいて、ミリ秒単位で Wait 関数を使う構文の
xls.Application.Wait Now() + 200 / 86400000
に変えてみた。
何と、これで一発解決! 例えば以下の コメント行(行頭の 「'」 がついた行)を活かすと、ビープ音だけして、Shape(2) は挿入した位置に表示されず、マクロが終了した時点の位置にいきなり表示されるが、コメント行のままだと Beep音と共に途中経過が表示されることが分かった。


Option Explicit
' Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Declare Function BeepAPI Lib "kernel32.dll" Alias "Beep" (ByVal dwFreq As Long, ByVal dwDuration As Long) As Long
Sub TEST()
Dim sld As Slide, xls As Object, i As Integer
Set sld = ActivePresentation.Slides(1)

Set xls = CreateObject("Excel.Application")
With sld
.Shapes.AddShape msoShapeRectangle, 100 + 2 * i, 200, 50, 50
' Sleep 1000
' DoEvents
Call BeepAPI(800, 500)
' xls.Application.Wait Now() + 1000 / 86400000

For i = 0 To 20
.Shapes(2).IncrementLeft 20
Call BeepAPI(1200, 20)
' Sleep 200
' DoEvents
xls.Application.Wait Now() + 200 / 86400000
Next i
xls.Application.Wait Now() + 2000 / 86400000
.Shapes(2).Delete

End With
End Sub

(上記コードを試すには、まっさらなスライド一枚だけのシートに「動作設定ボタン」を一つ挿入し、その動作設定ボタンの動作指定マクロに上記の TEST() を指定して、スライドショーを実行してそのボタンをクリックする。)
ただ苦労して導いた解決策だったが、結局は xls.Application.Wait の方は、(多分、PPT VBA から Excel VBAを呼び出して、その Excel VBAが Windows標準の Sleepなどのタイマーを呼ぶという屋上屋な処理になるためだろうか?)どうも時間間隔が Sleep 利用より正確ではなさそうで図形移動のアニメーションは若干「ギクシャク」したり、音との同期がずれやすいように感じて、最終的に Sleep 関数と DoEvents を使うほうが良さそうだという結論。
私はこの問題解決のため結局 4-5日無駄に費やしてしまったが、同じ問題で悩む人がいたら参考にしてほしい。
2020/05/12