Excel VBA質問箱 IV

当質問箱は、有志のボランティア精神のおかげで成り立っています。
問題が解決したら、必ずお礼をしましょうね。
本サイトの基本方針をまとめました。こちら をご一読ください。

投稿種別の選択が必要です。ご注意ください。
迷惑投稿防止のため、URLの入力を制限しています。ご了承ください。


831 / 13645 ツリー ←次へ | 前へ→

【78064】イベント処理について かず 16/3/26(土) 11:09 質問[未読]
【78065】Re:イベント処理について かず 16/3/26(土) 11:31 回答[未読]
【78067】Re:イベント処理について β 16/3/26(土) 12:52 発言[未読]
【78072】Re:イベント処理について かず 16/3/27(日) 2:25 回答[未読]
【78073】Re:イベント処理について かず 16/3/27(日) 14:15 質問[未読]
【78074】Re:イベント処理について かず 16/3/27(日) 14:16 発言[未読]

【78064】イベント処理について
質問  かず  - 16/3/26(土) 11:09 -

引用なし
パスワード
   2つの表(リスト)を突き合わせる方法について有識者、
経験者の方のご意見をきかせてください。

1.業務要件
(1)建設プロジェク(=案件と呼ぶ。)の案件名、担当社、売上、利益
 などを1行にまとめて月次でメンテしています

(2)大元のマスタリストがあり、それを月に一回、10人の担当者に配布。
担当者は自分の担当案件の追加や、案件の売上や利益の変化を、配布
された表に記載して返信。

(3)各担当から返信されたリストを大元のマスタに反映させています

2.担当者が案件情報をリストに反映する際の記載ルール

▼(更新):
リストの各行に対し変更ががある場合、行の1列目に▼印をつける。

 削除は行削除ではなく、案件の進捗を示すセルを用意して、
 失注として表す。リストの行の削除は考慮不要。

★(挿入):
リストに対し、案件=行を追加する場合1列目に★印をつけて、行
ごと追加する


3.現状のマクロの仕様
1)マクロ本体のボタンを押す。
2)ダイアログが開いて、担当者の作成したリストのブックを指定する。
3)ダイアログが開いて、マスタのリストのブックを指定する

4)担当者のリストのシートを読み込み Find 文で1列目のセルを順に
読込む

5)1列目に▼を見つけたら、プロジェクトの区分、案件名、担当者を複合キー
として、マスタのリストからキーが合致する行を探す

6)1列目が★印の場合、その行の前の行を見る。
 前行の1列目のセルが空白か▼だったら、前行のキーで、マスタリストを検索
 し、挿入先の行を特定しその後ろに該当行をマスタリストのに挿入する。

7)キーがマッチする行や挿入先の行が風名などの場合は、マスタリストの一番後
 ろにその行を挿入し、1列目にエラー理由を書き込む。


4.問題
上の方法は、各担当者が作成する修正元リストのキーの一部の、
プロジェクト区分、案件名、担当者名 が変わるとマスタリストと突合わせがで
きません。

そこで次善の策として、キー項目を修正する場合は、修正元リストの1列目に▽
印を書き
同じ行の使われていない備考欄に、変更前のキーの項目を残しておき、マスター
のリスト
と突き合わで使っています。

この方法は、
・各担当者にとって ▼、★、▽の場合は、備考欄に元のキーを残す必要があるな
ど記載
 ルールが面倒らしく▼や★の取り違えや記載漏れ、▽の場合の備考欄の記載漏れ
が発生します。
・またキー項目を文字列としているため、変更元とマスタ側で文字の全角、半角
の違い等の場合も不一致が発せしやすいルールとなっています。

5.改善案

1.マスタ側で大本のマスタリスト作成時、従来のリストの外の右端余白に作業列
を追加
 1列目 そのリストを作成した日時のミリ秒単位でタイムスタンプを格納
 2列目 記号1文字 挿入I(最初はすべてI) or 変更C
     +リストの各行を識別するための項番(シーケンシャル)を記載
 3列目 行の修正箇所
 例 mm/dd/mm/ss△I,1

2.各担当者は、リストの行に変更を加えた場合のwork_sheet_Changeイベントプロシジャ内
 で以下を記載
 Private Sub WorkShhet_Change(ByVal Target As Range)
 If Not Application.Intersect(Target,Range(担当者リストの全領域)
 ’ここでTargetでを見てどこが参照されたか判定し
 ’タイムスタンプを作業列1、修正範囲を作業列3に書く
 End Sub

 a) 変更
 ・1列目に  ▼
 ・作業列1 タイムスタンプ
 ・作業列2 C+項番、
 ・作業列3 修正箇所のセル範囲

 例
 1列目 作業列1   作業列2 作業列3
 ▼   mm/dd/mm/ss C,10   $B$10,$C$10:$E$10,$F$10, $G$10 ...サイズ可変
         
 b) 挿入 自作のイベントを定義し、タイマーで監視し、イベントプロシージャで処理をする
  ・リストの1列目 ★格納
  ・作業列1 I+項番 
  2行追加の例

 1列目   作業列1   作業列2
 ★・・・  mm/dd/mm/ss I,10   ・・・元の行、挿入時のターゲット
 ★・・・  mm/dd/mm/ss I,10-1  ・・・挿入行1
 ★・・・  mm/dd/mm/ss I,10-2  ・・・挿入行2

 マスタ側から見て、担当者1、担当者2の挿入行が重なった時は以下。
 1列目  作業列1   作業列2
 ★・・・ 03/22/10/60 I,10
 ★・・・ 03/23/12/12 I,10-1  ・・・ 担当者1による挿入
 ★・・・ 03/23/50/13 I,10-1-1 ・・・ 担当者2による挿入
 ★・・・ 03/23/50/13 I,10-1-2 ・・・ 担当者2による挿入
 ★・・・ 03/23/12/12 I,10-2  ・・・ 担当者1による挿入 
 
 行挿入時ののイベントプロシジャの例は以下。
 「井川はるき VBA裏ワザ技大辞典」Sample31_1」のコード参照

 Private Sub myRowsInsertEventClass_RowsInsert(Cancel As Boolean)
  Cancel = MsgBox("行が挿入されました。" & vbCrLf _
    & "キャンセルしますか?", vbInformation Or vbYesNo) = vbYes
  ’★
 End Sub

 ★の部分に 担当者リストに行挿入がされた場合に
 1列目へ★記載や、作業列1、作業列2 作成の処理を書く

3.更新行処理 (1列目▼)
 作業列1、2を見る
 作業列1のタイムスタンプがマスタのタイムスタンプより新しい かつ
 作業列2の先頭文字がC
 作業列2の後半の、修正行の項番をキーとしてマスタのリストを検索し
 作業列3のセル範囲を修正する
 
 
4.挿入行処理 (1列目★)   
 作業列1のタイムスタンプがマスタのタイムスタンプより新しい かつ
 作業列2の先頭文字が Iの時 作業列2の, から枝番までの数字をキー
 にして、マスター側のリストを検索し、見つかったらそのうしろに枝番の
 ついた行を枝番の順序で挿入する
 

 注 井川はるき Excel VBA裏ワザ大辞典の TIPS 31 を参考にする
   添付 Sample031.xls では 行を挿入した時 キャンセルしますかという
   ウィンドウを表示させている

5.質問

 Q1 担当者リストには、イベントハンドラ処理を入れることによって悪影響
   を少なくする方法について教えてください

   例えばESCキーなどが押されマクロの実行=イベントの監視が行われなくなる
   だけでなく、担当者にとって意味不明のメッセージが出るなどを想定。

   こういった場合でもエラーが発生しました、ブックを閉じて再度開きなおして
   ください等のメッセージを出すか、どこかで イベント監視の機能を動かないように
   するApplication.EnableEvents = False を発行するよにしておき
   従来の処理を正常に実行できるようにしておきたいです

   どのような方法があるでしょうか

 Q2 Worksheet_Changeイベントは行の挿入時にも発生するようで
    自作するRowsInsertイベントと処理が重複して不具合がおきないか
    どのような方法があるでしょうか?
    

 よろしくお願い致します。

以上

【78065】Re:イベント処理について
回答  かず  - 16/3/26(土) 11:31 -

引用なし
パスワード
   ▼かず さん:

サンプルコードの解説を補足します

 では、1.'から順に見ていきましょう。
 まず、1行目でEventステートメントを使ってイベントを宣言しています。このクラスは
Rowslnsertというイベントを発行して、そのイベントプロシージヤは、CancelというBoolean d
の引数を参照渡しで受け取りますよ、といった宣言になります。
 CheckRowsInsertメソッドは、この後解説するタイマー処理によって繰り返し実行され、行
が挿入されたタイミングでイベントを発行するメソッドです。ここで重要なのは、Static変数
「myRow」が最終行を表すRangeオブジェクトへの参照を保持していることです。初めて実行
されるときだけは変数「myRow」が「Nothing」なので、その場合には最終行を表すRange オ
ブジェクトへの参照を格納するだけの処理を行いますが、2回目以降にはいよいよチェックを行
います。
 このチェックには、On Error Resume Nextステートメントを使って、実行時エラーを無視す
るようにしてから、変数「myRow」に参照が格納されているRangeオブジェクトの何らかのプ
ロパティを取得してみるのが簡単です。ここではRowプロパティを取得していますが他のプロ
パティでもかまいません。
 このとき、行が挿入されていれば最終行は存在しなくなっているのでエラーが発生します、
ですから、「エラーの発生=行が挿入された」と判断できるわけです。エラーの発生の有無は、
Err関数を使って参照を取得できるErrObjectオブジェクトのNumberプロパティで判断でき
ます。エラーが発生していなければNumberプロパティの値が「O」となるからです。
 そして、行が挿入されたと判断したらRaiseEventステートメントを使ってRowslnsertイベン
トを発行します。引数Cancelには変数[myCancel]を指定し、イペントプロシージヤによって、
変数.[myCancel」の値が「True」に変更される(引数Cancelに「True」が設定される)と、
ApplicationオブジェクトのUndoメソッドを使って、行挿入の操作を元に戻します。

 次に、2.のコードをご覧ください。これはタイマープロシージヤと呼ばれるプロシージヤで、
この後解説するWin32API関数のSetTimer関数の引数IpTimei'Funぺこ、このプロシージヤの
アドレスを指定すると、繰り返し非同期で実行されるようになります。
 宣言部については決まり事として覚えておいてください。ここで行っている処理は、
clsRowsInsertEventオブジェクトのCheckRowsInscrtメソッドを実行するといったものですI
clsRowsInsertEventオブジェクトへの参照は、3.のコードで定義しているThisWorkbookク
ラスのRowsInsertEventClassプロパティを使って取得します。

 最後に3.・のコードの要点をまとめます。
 まず、このブックモジュールでclsRowsInsertEventオブジェ列ヽが発行するイベントをハンド
ルするために、モジュールレベルのオブジェクト変数「mvRowsInsertEventClass」を、

 WithEventsキーワードを付けて、cIsRowsInsertEvent型で宣言します。
 そして、そのclsRowsInsertEventオブジェ列ヽのRowslnsertイペントプロシージャに、
行挿入時に実行する処理を記述します。ここでは、キャンセルするかどうかの問い合わせのみ
を行っています。
 タイマー処理の開始はブックのOpenイベントプロシージャで、終了はBeforeCloseイベント
ブロシージャで行います。
 タイマー処理を開始するのはWin32API関数のSetTimerで、引数HwndとnlDEventには0を、
uElapseにはタイマー処理を実行する問隔(ミリ秒)を、lpTimerFunc にはタイマープロシージャ
のアドレスを指定します。プロシージャのアドレスはAddressOf演算子を使って取得し
ます。なお、サンプルではnElapseに「O」を指定していますが、当然ですがOミリ秒ごとに処理
を繰り返すといった非現実的なことは不可能です。このような場合処理できる極めて微小な時間
単位で処理が繰り返されます。
 SetTimer関数の戻り値はタイマーIDと呼ばれる識別子で、
KillTimer関数のnIDEventに指定することでタイマー処理を終了できます。サンプルで、ブックの
BeforeCloseイベントプロシージャの処理に保存確認のロジックを内包しているのは、Beforecloose

イペントプロシージャの処理によってタイマー処理(イベント発行のための監視)を完了してから、
Excelの機能によって保存確認が行われた場合にキャンセルすると、イベントをハンドルできない状態
で開かれたままになるからです。
さて、3.のコードの最後で定義しているRowsInsertEventClassプロパティの内容はまったく
難しいものではありません。

モジュールレベル変数[my RowsInsertE vent Class]が「Nothing」であれば新たにインスタンスを
生成してから、その参照を返すプロパティです。しかし1つだけ重要なことがあります。
このようにして外部にclsRowsInsertEventオブジェクトヘの参照を返す(公開する)場合には、

clsRowsInsertEventクラスのInstancingがデフォルトのPrivate]のままではいけないからです。
このような場合には、あらかじめ[プロパティ]ウインドウを使ってInstancingを[PublicNotCreatable]
に設定しておきます。

ポイント
 タイマー処理中に実行時エラーが発生すると、Excelが即座に落ちてしまいます。万が一にもそのよう
なことがないように、絶対に実行時エラーが発生しない処理でない限り、必ずOn Error Resume Next
ステートメントを付けるようにしましょう。

--

【78067】Re:イベント処理について
発言  β  - 16/3/26(土) 12:52 -

引用なし
パスワード
   ▼かず さん:

回答ではありません。

>7)キーがマッチする行や挿入先の行が風名などの場合は

風名 とは?

>サンプルコードの解説を補足します

これを見るために、井川さんの書式を購入しなければいけないのですか?

【78072】Re:イベント処理について
回答  かず  - 16/3/27(日) 2:25 -

引用なし
パスワード
   ▼β さん:
>▼かず さん:
>
>>7)キーがマッチする行や挿入先の行が風名などの場合は
>風名 とは?

 申し訳ありません。風名ではなく不明でした。申し訳ありません。
 
言いたかったのは、以下の点です。

7)更新の場合に変更元とキーがマッチする行がマスターリストに存在しない
 挿入の場合に、更新元リストで挿入位置を示す行に該当する行が、マスター
 リストに存在しない等、イレギュラー状態の場合は、挿入対象行はマスター
 リストの一番最後の部分に挿入し、1列目にエラーの理由を書き込む。


>>サンプルコードの解説を補足します
>これを見るために、井川さんの書式を購入しなければいけないのですか?

サンプルコード(私が追記した部分は★印を記載

' 井川はるき さんのサンプルコード

’***************************************
' クラスモジュール clsRowsInsertEvent
' ***************************************
Public Event RowsInsert(Cancel As Boolean, InsRow As Long) ’★ InsRow を引数に追加 by かず

Public Sub CheckRowsInsert(ByVal mySht As Worksheet)
  Static myRow As Range
  Dim myInsRow As Long         ' ★ 行を挿入した行番号を格納するための変数
  Dim myCancel As Boolean
  Dim w_FndRng As Range
  Const TopRow As Integer = 29     ' ★ 作業列2 行を挿入した行番号を格納するための変数
  Const SeqCol As Integer = 65     ' ★ 作業列2の列番号の変数
  Const EdaNum As Integer = 66     ' ★ 作業列2の拡張  枝番を格納するための行
  Dim i As Long            ’★ 制御変数
  
  If mySht Is Nothing Then Exit Sub
  With mySht
    If Not myRow Is Nothing Then
      On Error Resume Next
      myInsRow = myRow.Row

      If Err().Number <> 0 Then        
        ’単純な行の挿入の場合、SeqCol列の空白を探す
        Set w_FndRng = Range(Cells(TopRow, SeqCol), Cells(Rows.Count, SeqCol).End(xlUp)).Find("", , xlValues, xlWhole, xlByRows, xlNext)
 
        If Not w_FndRng Is Nothing Then
          ' 単純に行挿入された場合 SeqCol列のどこかには""空白のセルがある
          myInsRow = w_FndRng.Row
        Else
          ' コピーして挿入 の場合、作業列2には 元の行の値が入っている 
          i = TopRow
          Do While (i < Rows.Count)
            If Cells(i, EdaNum) > 1 Then
              myInsRow = i
              Exit Do
            End If
            i = i + 1
          Loop
        End If
        
        RaiseEvent RowsInsert(myCancel, myInsRow)
        If myCancel Then
          Application.Undo
        End If
      End If
    End If
    Set myRow = .Rows(.Rows.Count)
  End With
End Sub


’***************************************
' ' ThisWorkbook
' ***************************************


Private Declare Function SetTimer Lib "user32" ( _
  ByVal Hwnd As Long, ByVal nIDEvent As Long _
  , ByVal uElapse As Long, ByVal lpTimerFunc As Long) _
  As Long
Private Declare Sub KillTimer Lib "user32" ( _
  ByVal Hwnd As Long, ByVal nIDEvent As Long)

Private WithEvents myRowsInsertEventClass As clsRowsInsertEvent
Private myTimerId As Long

Private Sub myRowsInsertEventClass_RowsInsert(Cancel As Boolean, myInsRow As Long)
  '
  '★ 作業列1 時刻取得して タイムスタンプをとる(予定)
  '★ 作業列2 シーケンシャル番号を格納する(予定)
  MsgBox myInsRow & "行に挿入されました" ’★ 挿入位置を確認するためのMsgBoxを出力
  
  Cancel = MsgBox("行が挿入されました。" & vbCrLf _
    & "キャンセルしますか?", vbInformation Or vbYesNo) = vbYes
End Sub

Private Sub Workbook_BeforeClose(Cancel As Boolean)
  Dim myRes As VbMsgBoxResult
  If Not Saved Then
    myRes = MsgBox("'" & Name & "' への変更を保存しますか?" _
      , vbExclamation Or vbYesNoCancel)
    If myRes = vbYes Then
      Save
    ElseIf myRes = vbNo Then
      Saved = True
    Else
      Cancel = True
      Exit Sub
    End If
  End If
  KillTimer 0&, myTimerId
  Set myRowsInsertEventClass = Nothing
End Sub

Private Sub Workbook_Open()
  myTimerId = SetTimer(0&, 0&, 0&, AddressOf TimerProc)
End Sub

Public Property Get RowsInsertEventClass() As clsRowsInsertEvent
  If myRowsInsertEventClass Is Nothing Then
    Set myRowsInsertEventClass = New clsRowsInsertEvent
  End If
  Set RowsInsertEventClass = myRowsInsertEventClass
End Property

'***********************************************************
' 標準モジュール
'***********************************************************
Sub TimerProc(ByVal Hwnd As Long, ByVal uMsg As Long _
  , ByVal idEvent As Long, ByVal dwTime As Long)
  On Error Resume Next
  ThisWorkbook.RowsInsertEventClass.CheckRowsInsert Sheet3 ’★井川さんのオリジナルは 引数Sheet1
End Sub

【78073】Re:イベント処理について
質問  かず  - 16/3/27(日) 14:15 -

引用なし
パスワード
   ▼かず さん:
>▼β さん:

質問を再度整理しました。
コピーしたセルの挿入時、挿入行の行番号
を把握するための方法について教えてください。

1.業務要件
(1)建設プロジェク(=案件と呼ぶ。)の案件名、担当社、売上、利益
 などを1行にまとめて月次でメンテしています

(2)大元のマスタリストがあり、それを月に一回、10人の担当者に配布。
担当者は自分の担当案件の追加や、案件の売上や利益の変化を、配布
された表に記載して返信。

(3)各担当から返信されたリストを大元のマスタに反映させています

2.担当者が案件情報をリストに反映する際の記載ルール

▼(更新):
リストの各行に対し変更ががある場合、行の1列目に▼印をつける。

 削除は行削除ではなく、案件の進捗を示すセルを用意して、
 失注として表す。リストの行の削除は考慮不要。

★(挿入):
リストに対し、案件=行を追加する場合1列目に★印をつけて、行
ごと追加する

2.ワークシートのイメージ

(1) 最初にマスタ側リスト作成時、リスト右端余白で今は使われていない
 部分に以下の作業列を追加

  列    作業列1  作業列2 作業列3

  番号   日時時刻   シーケンスNo 枝番
  A/1 (略) BL/64     BM/65  BN/66  現時点の枝番号の計算式
行  --------------------------------------------------------------------------------
10 &#9251; (略) 2016/3/26/ 18:23 1   1  =COUNTIF($BM$10:$BM709,BM10) 
11 &#9251;  略
12 &#9251;  略
13 ★ (単純な空行の挿入) ・&#9251;・  1  =COUNTIF($BM$11:$BM709,BM11)   
14 &#9251;
15 ・・ ・2016/3/26/ 18:23・・6・・1  =COUNTIF($BM$15:$BM15,BM15) (A)
16 ★・(15行目をコピー挿入)  6・・2  =COUNTIF($BM$16:$BM16,BM16) (A)
17 &#9251;  略
18 &#9251;  略
19 ★・(20行目をコピー挿入)・ 9・・2  =COUNTIF($BM$18:$BM709,BM18) (B)
20 &#9251;・・・・・・・・・・・・ 9・・2  =COUNTIF($BM$19:$BM709,BM19) (B) 
709 ・・・・・・・・・・・・・697・ 1  =COUNTIF($BM$10:$BM709,BM709)

【説明】
 行の挿入には、単純な空行の追加とコピーした行の挿入の2種類がある。
 どちらの場合も、井川はるき著 VBA裏ワザ大辞典Sample31_1のコードを
 参考にして行が挿入されたことは添付のコードで検知可能。

 業務要件から1列目に★印をつけたいが、コピー元はそのままで
 コピー先にだけ★印をつけたい。

 ⇒ 単純空行の挿入の場合は、本来はシーケンスNoとしているBM列に空白が
できるのでそこをFind文で探して 行番号を取得可能。
   
 ・ただしコピーして挿入の場合は、BM列の値は同じ値の行が複数できてしまう。
 BN列に枝番号や重複有無を示す計算式を入れる方法、(上記(A)(B)あるがいずれも
 うまくいかない)では重複していることはわかるがどの行が挿入されたかわからない。

 ・コピーして挿入なので タイムスタンプではコピー前とコピー後の区別がつかない
 
【質問事項】
 Q1 コピーした行を挿入のイベントが発生した場合に、イベント処理の中で
  どの行が挿入されたか枝番やタイムスタンプか、他の方法で特定する方法
  をご存知の方は教えてください。

 Q2 上記のイベント処理では、ブックを開いて閉じるまでのイベントをタイマ
  監視しているが何かの理由でイベントの監視ができなくなった場合、
  イベント処理をあきらめて手動でワークシートを記載してもらえればよい
  ようにするにはどうしたらよいでしょうか

  既定のイベントでは Application.EnableEvents = False を発行すれば
  イベントが発生しなくなるので、自作イベントでもそのようにするにはど
  すればいいいでしょうか?
 
  イベント監視処理は中止します。修正が終了したら1列目に▼や★を書くの
  を忘れず記入してくださいとメッセージがだせれば十分です。
 
3.イベントプロシジャを使うコード
  井川はるきさんおサンプルコードを一部改変して作成しました。
  私には少し難しい方法のようです

' 井川はるき さんのサンプルコード
' ★印の行は 投稿者 かずが改変した部分
’***************************************
' サンプル解説の?@ クラスモジュール clsRowsInsertEvent
' ***************************************
Public Event RowsInsert(Cancel As Boolean, InsRow As Long) ’★ InsRowを追加 by かず

Public Sub CheckRowsInsert(ByVal mySht As Worksheet)
Static myRow As Range
Dim myInsRow As Long     '★ 行を挿入した行番号を格納するための変数
Dim myCancel As Boolean
Dim w_FndRng As Range

Const TopRow As Integer = 29 '★ 作業列2の最初の行番号の
Const SeqCol As Integer = 65 '★ 作業列2の列番号
Const EdaNum As Integer = 66 '★ 作業列2 枝番を格納するための行
Dim i As Long         '★ 制御変数
  
If mySht Is Nothing Then Exit Sub

With mySht
 If Not myRow Is Nothing Then

  On Error Resume Next
  myInsRow = myRow.Row

  If Err().Number <> 0 Then 

  ’★単純な行の挿入の場合、SeqCol列の空白を探す
  Set w_FndRng = Range(Cells(TopRow, SeqCol), Cells(Rows.Count, SeqCol).End(xlUp)).Find("", , xlValues, xlWhole, xlByRows, xlNext) '★
  
  If Not w_FndRng Is Nothing Then ’★
    ' ★単純に行挿入された場合 SeqCol列は""空白のセルがある
    myInsRow = w_FndRng.Row   ' ★
  Else
    '★ コピーして挿入の場合、作業列2には元の行と同じ値が入る 
    i = TopRow            '★
    Do While (i < Rows.Count)    '★
      If Cells(i, EdaNum) > 1 Then '★
        myInsRow = i       '★
        Exit Do          '★
      End If            '★
      i = i + 1           '★
    Loop               '★
  End If                '★
        
  RaiseEvent RowsInsert(myCancel, myInsRow)
  If myCancel Then
     Application.Undo
  End If
 End If
 End If
 Set myRow = .Rows(.Rows.Count)
End With
End Sub

'***********************************************************
' ?A 標準モジュール
'***********************************************************
Sub TimerProc(ByVal Hwnd As Long, ByVal uMsg As Long _
  , ByVal idEvent As Long, ByVal dwTime As Long)
  On Error Resume Next
  ThisWorkbook.RowsInsertEventClass.CheckRowsInsert Sheet3 ’★引数Sheet3をかずの環境にあわせて設定
End Sub

’***************************************
' ?B ThisWorkbook
' ***************************************

Private Declare Function SetTimer Lib "user32" ( _
  ByVal Hwnd As Long, ByVal nIDEvent As Long _
  , ByVal uElapse As Long, ByVal lpTimerFunc As Long) _
  As Long
Private Declare Sub KillTimer Lib "user32" ( _
  ByVal Hwnd As Long, ByVal nIDEvent As Long)

Private WithEvents myRowsInsertEventClass As clsRowsInsertEvent
Private myTimerId As Long

Private Sub myRowsInsertEventClass_RowsInsert(Cancel As Boolean, myInsRow As Long)
  '
 '★ 作業列1 時刻取得して タイムスタンプをとる(予定)
 '★ 作業列2 シーケンシャル番号を格納する(予定)
  MsgBox myInsRow & "行に挿入されました" ’★ 現状 挿入行を確認するため MsgBoxを出力
  
  Cancel = MsgBox("行が挿入されました。" & vbCrLf _
    & "キャンセルしますか?", vbInformation Or vbYesNo) = vbYes
End Sub

Private Sub Workbook_BeforeClose(Cancel As Boolean)
  Dim myRes As VbMsgBoxResult
  If Not Saved Then
    myRes = MsgBox("'" & Name & "' への変更を保存しますか?" _
      , vbExclamation Or vbYesNoCancel)
    If myRes = vbYes Then
      Save
    ElseIf myRes = vbNo Then
      Saved = True
    Else
      Cancel = True
      Exit Sub
    End If
  End If
  KillTimer 0&, myTimerId
  Set myRowsInsertEventClass = Nothing
End Sub

Private Sub Workbook_Open()
  myTimerId = SetTimer(0&, 0&, 0&, AddressOf TimerProc)
End Sub

Public Property Get RowsInsertEventClass() As clsRowsInsertEvent
  If myRowsInsertEventClass Is Nothing Then
    Set myRowsInsertEventClass = New clsRowsInsertEvent
  End If
  Set RowsInsertEventClass = myRowsInsertEventClass
End Property

【78074】Re:イベント処理について
発言  かず  - 16/3/27(日) 14:16 -

引用なし
パスワード
   ▼かず さん:
>▼かず さん:
>>▼β さん:

サンプルコードに誤字があったので修正しました。

4.サンプルコードの解説 (井川春樹さんの本から抜粋)

'*************************************************************
' 井川さんのサンプルコードの解説
'*************************************************************
では(1)から順に見ていきましょう。
 まず、1行目でEventステートメントを使ってイベントを宣言しています。このクラスは
Rowslnsertというイベントを発行して、そのイベントプロシージヤは、CancelというBoolean d
の引数を参照渡しで受け取りますよ、といった宣言になります。

CheckRowsInsertメソッドは、この後解説するタイマー処理によって繰り返し実行され、
行が挿入されたタイミングでイベントを発行するメソッドです。

ここで重要なのは、Static変数「myRow」が最終行を表すRangeオブジェクトへの参照を保持していることです。

初めて実行されるときだけは変数「myRow」が「Nothing」なので、その場合には最終行を
表すRange オブジェクトへの参照を格納するだけの処理を行いますが、2回目以降には
いよいよチェックを行います。

このチェックには、On Error Resume Nextステートメントを使って、実行時エラーを
無視するようにしてから、変数「myRow」に参照が格納されているRangeオブジェクトの
何らかのプロパティを取得してみるのが簡単です。

ここではRowプロパティを取得していますが他のプロパティでもかまいません。
このとき、行が挿入されていれば最終行は存在しなくなっているのでエラーが発生します。
ですから、「エラーの発生=行が挿入された」と判断できるわけです。エラーの発生の
有無はErr関数を使って参照を取得できるErrObjectオブジェクトのNumberプロパティ
で判断できます。

エラーが発生していなければNumberプロパティの値が「O」となるからです。
そして、行が挿入されたと判断したらRaiseEventステートメントを使ってRowslnsert
イベントを発行します。引数Cancelには変数[myCancel]を指定し、イペントプロシー
ジヤによって、変数.[myCancel」の値が「True」に変更される(引数Cancelに「True」
が設定される)と、ApplicationオブジェクトのUndoメソッドを使って、行挿入の操作
を元に戻します。

次に(2)のコードをご覧ください。これはタイマープロシージヤと呼ばれるプロシー
ジヤで、この後解説するWin32API関数のSetTimer関数の引数lpTimeerFuncにこのプロ
シージヤのアドレスを指定すると、繰り返し非同期で実行されるようになります。

宣言部については決まり事として覚えておいてください。ここで行っている処理は、
clsRowsInsertEventオブジェクトのCheckRowsInscrtメソッドを実行するといった
ものです。

clsRowsInsertEventオブジェクトへの参照は、?Bのコードで定義している
ThisWorkbookクラスのRowsInsertEventClassプロパティを使って取得します。

最後に(3)のコードの要点をまとめます。
まず、このブックモジュールでclsRowsInsertEventオブジェ列が発行するイベント
をハンドルするために、モジュールレベルのオブジェクト変数
「mvRowsInsertEventClass」を、WithEventsキーワードを付けて
、cIsRowsInsertEvent型で宣言します。

そして、そのclsRowsInsertEventオブジェ列のRowslnsertイペントプロシージャに、
行挿入時に実行する処理を記述します。ここでは、キャンセルするかどうかの
問い合わせのみを行っています。

タイマー処理の開始はブックのOpenイベントプロシージャで、終了はBeforeCloseイベント
ブロシージャで行います。

タイマー処理を開始するのはWin32API関数のSetTimerで、引数HwndとnlDEventには0を、
nElapseにはタイマー処理を実行する問隔(ミリ秒)を、lpTimerFunc にはタイマー
プロシージャのアドレスを指定します。プロシージャのアドレスはAddressOf演算子を
使って取得します。

なお、サンプルではnElapseに「O」を指定していますが、当然ですがOミリ秒ごとに処理
を繰り返すといった非現実的なことは不可能です。このような場合処理できる極めて
微小な時間単位で処理が繰り返されます。

SetTimer関数の戻り値はタイマーIDと呼ばれる識別子で、
KillTimer関数のnIDEventに指定することでタイマー処理を終了できます。
サンプルでは、ブックのBeforeCloseイベントプロシージャの処理に保存確認の
ロジックを内包しているのは、BeforeCloseイペントプロシージャの処理によって
タイマー処理(イベント発行のための監視)を完了してから、Excelの機能によって
保存確認が行われた場合にキャンセルすると、イベントをハンドルできない状態で
開かれたままになるからです。

さて(3)のコードの最後で定義しているRowsInsertEventClassプロパティの内容
はまったく難しいものではありません。モジュールレベル変数
[my RowsInsertE vent Class]が「Nothing」であれば新たにインスタンスを生成
してから、その参照を返すプロパティです。

しかし1つだけ重要なことがあります。
このようにして外部にclsRowsInsertEventオブジェクトヘの参照を返す(公開する)
場合には、clsRowsInsertEventクラスのInstancingがデフォルトのPrivate]のまま
ではいけないからです。

このような場合には、あらかじめ[プロパティ]ウインドウを使ってInstancingを
[PublicNotCreatable]に設定しておきます。

ポイント
タイマー処理中に実行時エラーが発生すると、Excelが即座に落ちてしまいます。
万が一にもそのようなことがないように、絶対に実行時エラーが発生しない処理
でない限り、必ずOn Error Resume Nextステートメントを付けるようにしましょう。
--

831 / 13645 ツリー ←次へ | 前へ→
ページ:  ┃  記事番号:
2610219
(SS)C-BOARD v3.8 is Free