【実務・中級編】【上級プロ】カスタムイベントとWindows APIの非同期タイマーを融合した、アセンブリ自動ビルド中のリアルタイム進捗&中断UIの実装 – SolidWorks VBA解析バイブル

スポンサーリンク

序文:なぜあなたのツールは「フリーズ」するのか

SolidWorks VBAで大規模なアセンブリを自動構築する際、多くの開発者が陥る罠がある。それは、数千行のループの中で `AddComponent` と `AddMate` を連打し、画面が白く固まり、「応答なし」のタイトルバーを眺めながら処理が終わるのを祈るという、あまりにも原始的な実装だ。

「`DoEvents` を入れているから大丈夫だ」と反論するかもしれない。だが、それは甘い。`DoEvents` はOSに制御を戻すだけであり、ユーザーが「キャンセル」ボタンを押した際のイベントを確実に拾い、かつSolidWorksの背後で動くジオメトリエンジンを適切に制御するには力不足だ。

真のプロフェッショナルは、Windows APIのシステムタイマーとカスタムイベントクラスを融合させ、VBAというシングルスレッドの限界を超えた「擬似非同期UI」を構築する。本稿では、アセンブリ構築という重負荷処理において、滑らかな進捗表示と完璧な中断制御を両立させる極限の設計論を伝授する。

—

1. アーキテクチャの設計思想:疎結合とメッセージポンプ

まず、UI(UserForm)とロジック(AssemblyBuilder)を切り離せ。UIの中にSolidWorksのAPIを直接書き込むのは、保守性の観点から「三流」の所業だ。

構成要素

1. `frmProgress` (View): 進捗バーとキャンセルボタンのみを持つ。
2. `clsAssemblyBuilder` (Model): SolidWorksの演算を担当。進捗を「イベント」として発行する。
3. `modTimerBridge` (Controller): Windows API `SetTimer` を制御し、VBAの実行権限をOSと協調させる。

この設計の肝は、「Windowsから一定周期で『次のパーツを組め』という合図(WM_TIMER)をもらう」という点にある。これにより、1パーツ組むごとに関数が一旦終了し、OSがUIの描画やクリック判定を行う「空き時間」を強制的に作り出すのだ。

—

2. 実装:Windows APIによる非同期制御の心臓部

標準モジュールに、タイマーの制御ロジックを実装する。ここが「コマンドセンター」となる。

Standard Module: `modTimerBridge`

Option Explicit

‘ Windows API Declarations
Public Declare PtrSafe Function SetTimer Lib “user32” (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr, ByVal uElapse As Long, ByVal lpTimerFunc As LongPtr) As LongPtr
Public Declare PtrSafe Function KillTimer Lib “user32” (ByVal hwnd As LongPtr, ByVal nIDEvent As LongPtr) As Long

Public TimerID As LongPtr
Public Builder As clsAssemblyBuilder

‘ タイマーコールバック関数
‘ この関数がWindowsから一定間隔(1ms等)で呼び出される
Public Sub TimerProc(ByVal hwnd As LongPtr, ByVal uMsg As Long, ByVal idEvent As LongPtr, ByVal dwTime As Long)
On Error Resume Next

‘ タイマーを一旦停止(二重起動防止)
StopStepTimer

‘ ビルダーに次のステップを実行させる
If Not Builder Is Nothing Then
Builder.ExecuteNextStep
End If
End Sub

Public Sub StartStepTimer()
If TimerID = 0 Then
‘ 1ミリ秒後にTimerProcを呼ぶ(実質的にスタックを空にしてから実行)
TimerID = SetTimer(0, 0, 1, AddressOf TimerProc)
End If
End Sub

Public Sub StopStepTimer()
If TimerID <> 0 Then
KillTimer 0, TimerID
TimerID = 0
End If
End Sub

—

3. 実装:イベント駆動型アセンブリビルダー

次に、SolidWorksの操作をカプセル化したクラスを構築する。

Class Module: `clsAssemblyBuilder`

Option Explicit

‘ カスタムイベントの定義
Public Event OnProgress(ByVal current As Long, ByVal total As Long, ByVal message As String)
Public Event OnComplete()
Public Event OnError(ByVal description As String)

Private swApp As SldWorks.SldWorks
Private swModel As SldWorks.ModelDoc2
Private swAssy As SldWorks.AssemblyDoc

Private m_CurrentStep As Long
Private m_TotalSteps As Long
Private m_IsCanceled As Boolean

‘ 外部(Form)からキャンセルを通知するプロパティ
Public Property Let IsCanceled(ByVal value As Boolean)
m_IsCanceled = value
End Property

‘ 初期化
Public Sub Initialize(ByRef swInstance As SldWorks.SldWorks, ByVal totalCount As Long)
Set swApp = swInstance
Set swModel = swApp.ActiveDoc
Set swAssy = swModel
m_TotalSteps = totalCount
m_CurrentStep = 0
m_IsCanceled = False
End Sub

‘ 1ステップ(1パーツ追加+合致)の実行
Public Sub ExecuteNextStep()
If m_IsCanceled Then
Cleanup
Exit Sub
End If

m_CurrentStep = m_CurrentStep + 1

‘ — SolidWorks 操作の実装エリア —
‘ ここで AddComponent5 や AddMate5 を実行
‘ 注:大量のパーツを扱う場合は FeatureManager の更新を一時停止すること
‘ swModel.FeatureManager.EnableFeatureTree = False

Call SimulateSolidWorksWork(m_CurrentStep) ‘ 本来はここにAPIを記述

‘ ———————————-

‘ 進捗をUIに通知
RaiseEvent OnProgress(m_CurrentStep, m_TotalSteps, “パーツ ” & m_CurrentStep & ” を配置中…”)

If m_CurrentStep >= m_TotalSteps Then
Cleanup
RaiseEvent OnComplete
Else
‘ 次のステップを予約(タイマー再始動)
StartStepTimer
End If
End Sub

Private Sub SimulateSolidWorksWork(step As Long)
‘ 実際のAPI操作の代わり。重い処理をシミュレート
‘ 実際の実装では、ここで合致エラーのトラップを厳密に行うこと
End Sub

Private Sub Cleanup()
StopStepTimer
‘ swModel.FeatureManager.EnableFeatureTree = True
‘ swModel.ForceRebuild3 True
End Sub

—

4. UI:ユーザーへの窓口

最後に、これらを統合するUserFormの実装だ。

UserForm: `frmProgress`

Option Explicit

Private WithEvents m_Builder As clsAssemblyBuilder

‘ フォーム起動
Public Sub RunBuild(ByRef swApp As SldWorks.SldWorks, ByVal count As Long)
Set m_Builder = New clsAssemblyBuilder
m_Builder.Initialize swApp, count

Set modTimerBridge.Builder = m_Builder
Me.Show vbModeless ‘ モードレスで表示し、操作を許可する

StartStepTimer ‘ 処理開始
End Sub

‘ キャンセルボタン
Private Sub btnCancel_Click()
m_Builder.IsCanceled = True
Me.lblStatus.Caption = “中断しています…”
Me.btnCancel.Enabled = False
End Sub

‘ ビルダーからのイベント:進捗更新
Private Sub m_Builder_OnProgress(ByVal current As Long, ByVal total As Long, ByVal message As String)
Me.lblStatus.Caption = message
‘ プログレスバーの更新(ラベルの幅などで調整)
Me.imgBar.Width = (current / total) 200
DoEvents ‘ UI描画を確定させる
End Sub

‘ ビルダーからのイベント:完了
Private Sub m_Builder_OnComplete()
Unload Me
MsgBox “アセンブリの構築が正常に完了しました。”, vbInformation
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
btnCancel_Click
Cancel = True
End If
End Sub

—

5. 現場を掌握するためのテクニカル・アドバイス

① SolidWorks API特有の「重み」を逃がす

アセンブリの自動構築で最もCPUを食うのは、合致(Mate)の解決だ。1パーツ追加するたびに `ForceRebuild` をかけるのは素人の仕事だ。

  • `swModel.FeatureManager.EnableFeatureTree = False` でツリー更新を止める。
  • 構築中は `swApp.DocumentVisible False, swDocASSEMBLY` で画面描画を完全に殺すか、あるいはこのタイマー手法を用いて、10パーツごとに1回だけ描画更新を入れるといった「間引き」を行うのが上級者の定石だ。

② データベース・ファイル連携の注意点

外部のCSVやSQL Serverから構成リストを読み込む場合、`Initialize` 時に全てのデータをメモリ(CollectionやDictionary)に展開しておけ。`ExecuteNextStep` の中で毎回ファイルI/Oを発生させるのは、Windows APIタイマーの周期を乱し、UIのガタつきの原因になる。

③ エラーハンドリングの極意

VBAのタイマー処理中に実行時エラーが発生すると、ExcelやSolidWorksごとクラッシュするリスクがある。`ExecuteNextStep` 内は完璧な `On Error` トラップを張り、エラー発生時には必ず `KillTimer` を呼び出す設計を徹底せよ。

結論

`SetTimer` と `Custom Event` の融合は、VBAにおけるUI実装の到達点の一つだ。
この手法を使えば、ユーザーは数千の部品が組み上がるのをプログレスバーで優雅に眺め、必要があればいつでも安全に中断できる。この「安心感」こそが、業務ツールの品質を決定づける。

コードをコピーして満足するな。この「制御をOSに一時的に返す」という思想を、あなたの全ての自動化設計に組み込め。それが、伝説的なエンジニアへの第一歩だ。

タイトルとURLをコピーしました