2009年09月26日

セルの変更イベント(2)

関連記事 : セルの変更イベント

「どのセル」で変化があったのかを知るには、イベント定義範囲内の
ひとつひとつのセルに対して個別にリスナーを登録する必要があります。

ということで、ひとつひとつのセルにリスナーを登録するサンプルです。

「ひとつひとつのセル」に登録するからといって【リスナー】をセルの数だけ
生成する必要はありません。リスナーは【ひとつ】です。

ひとつの同じリスナーを「ひとつひとつのセル」に登録するから、どのセル
の変化に対しても【ひとつのイベントプロシジャー】で対処できるという事です。

下記のイベントマクロでは、監視範囲セルに同時に複数の変化(関数など
による)が起きた場合には、変化した全てのセルについて順にイベント
プロジャーが起動します。

Global oModifyTarget As Object
Global oModifyTargetCellsItem() As Object
Global oModifyListener As Object

Sub ModifyListener_Register
Dim lngCellsCount As Long
Dim i As Long
 
  oModifyTarget = ThisComponent.CurrentSelection    '[ B2:B6 ]
  lngCellsCount = GetCellsItem(oModifyTarget, oModifyTargetCellsItem)

  oModifyListener = CreateUnoListener("AnyModify_","com.sun.star.util.XModifyListener")

  For i = 1 to lngCellsCount
    oModifyTargetCellsItem(i).addModifyListener(oModifyListener)
  Next i
End Sub

Sub ModifyListener_unRegister
Dim i As Long

  For i = 1 to UBound(oModifyTargetCellsItem)
    oModifyTargetCellsItem(i).removeModifyListener(oModifyListener)
  Next i
End Sub

'================[ Modify Event Procedure ]====================
Sub AnyModify_modified( oEvent As com.sun.star.lang.EventObject )
Dim strAddr As String
Dim vntAddr As Variant

  strAddr = oEvent.Source.AbsoluteName
  strAddr = Join(Split(strAddr,"$"),"")
  vntAddr = Split(strAddr,".")
 
  With ThisComponent.Sheets(0).getCellRangeByName("C10")
    .String = .String & ";" & vntAddr(1)
  End With
  Exit Sub
End Sub

Sub AnyModify_disposing( oEvent As com.sun.star.lang.EventObject )
End Sub



(注意事項 1)
下記のマクロの作り方では『リスナーの解除』が出来ませんので注意
してください。違いはセル範囲をひとつひとつのセルに分解する方法です。

〜上記マクロ〜 (正しい)
  Register で分解したセルオブジェクト配列を保存(Global)して
  unRegister でも、同じものをそのまま使用している。

〜下記マクロ〜 (間違い)
  Register と unRegister の各々でセル範囲を分解している。



Global oModifyTarget As Object
Global oModifyListener As Object

Sub ModifyListener_Register
Dim oCellsItem() As Object
Dim lngCellsCount As Long
Dim i As Long
 
  oModifyTarget = ThisComponent.CurrentSelection    '[ A1:A10 etc ]
  lngCellsCount = GetCellsItem(oModifyTarget, oCellsItem)

  oModifyListener = CreateUnoListener("AnyModify_","com.sun.star.util.XModifyListener")

  For i = 1 to lngCellsCount
    oCellsItem(i).addModifyListener(oModifyListener)
  Next i
End Sub

Sub ModifyListener_unRegister
Dim oCellsItem() As Object
Dim lngCellsCount As Long
Dim i As Long

  lngCellsCount = GetCellsItem(oModifyTarget, oCellsItem)
  For i = 1 to UBound(oModifyTargetCellsItem)
    oCellsItem(i).removeModifyListener(oModifyListener)
  Next i
End Sub

関連記事 : Is 演算子

With ThisComponent
    oCell1 = .Sheets(0).getCellByPosition(0,0)
    oCell2 = .Sheets(0).getCellByPosition(0,0)
End With
MsgBox EqualUnoObjects(oCell1, oCell2)

この結果は False になります。参照するセルは同じでも、『オブジェクト』
としては別個のものです。

[A1:A10] のひとつのセル範囲を GetCellsItem で2回分解して、2つ
のセルオブジェクト配列を作れば、各々の対応する配列要素が指すセル
は同じでも、オブジェクトとしては別個の独立したものです。

後者のマクロは
    Object-A 内に oModifyListener を登録しておいて、
    Object-B から oModifyListener を削除する
というものです。無いものを削除しようとしてますから、削除は失敗します。
その結果、Object-A にリスナーが残ったままとなります。

Object-A に登録したリスナーを削除するには、やはり Object-A に
対して removeXXXXX をじっこうしなければいけません。したがって、
最初のマクロの構造とする必要があります。


(注意事項 2)
ただし、この方法を採っても実用的とは言えないでしょう。

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
[A1:A10]の範囲にリスナーが登録してある状態で、A4 と A5 の間に
セルを挿入(行を挿入)して 範囲を [A1:A11] へ広げた場合、新たな
[A5] に対してはリスナーが登録されていませんので [A5] ではイベント
が発生しません。イベントが発生するのは[A1:A4 , A6:A11] の範囲と
なります。

対象範囲の外側で挿入/削除した場合には、移動先で問題なくイベントが
発生しますが、対象範囲の内側で挿入が行なわれると歯抜け状態になって
使い物にはなりません。

〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
逆に、セル削除(行削除,列削除)した場合に、削除したセルに割り当てられていた
リスナーが一緒に消える訳ではないので、注意が必要です。

A4セルを削除して、A5セルが新たにA4セルに上がった場合を例にします。

A4セル自体は削除されましたが、元A4セルリスナーは残っており、リスナーに
登録されたオブジェクトが指しているセルの位置情報は[A4]のままです。

元A5セルリスナーは、セルが繰り上がったことにより、リスナーに登録された
オブジェクトが指しているセルの位置情報も[A4]に変わります。

つまり、この状況では[A4]セルを監視しているリスナーが2つ存在する事に
なります。当然、A4セルで値変化が起これば、2つのリスナーが別個に反応して、
A4セルに対する Modify イベントが2回続けて発生します。

この後で、A4セルの前に1セル挿入して、A4セルを元のA5セル位置へ
繰り下げてみます。

しかし、『ダブっていた2つのリスナーが2セルに分かれて元通りになる』
というような事にはなりません

A4セルにはリスナーが無く、2つのリスナーは、ダブったまま、A5セル位置を
監視する事となります。
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜

表計算ソフトを使うのに「挿入/削除操作を禁止」とは言えませんから、
この方法でも不完全です。


(2009/10/12 追記)
ike@九州さんからアドバイスを頂きました(感謝)

    リスナー登録セルを含め入力セルの『保護プロパティ』を
    解除して『シート保護』を行なえば、行/列/セルの挿入/削除
    を抑止できます(入力セルは保護解除してますから、当然
    そこには入力ができます)。

この対処をしておけば、上記のマクロで安定した Change イベント
を利用できますね。


(注意事項 3)
あと、意味無く広範囲なセル範囲に対して実施するのも避けた方が良いでしょう。

一応、Long変数の上限(2,147,483,647)までは理論的には動きますが、
GetCellsItem では、1セルごとに[ ReDim Preserve ]で配列を再構築
していますので、セル数が異常に多いと『セル範囲の分解』に時間が掛かる
かもしれません(CPU性能によりますけど・・・)。

10行10列で100セル、100行10列では1000セルになります。
セル範囲としては『大して広いものでもない』と言えますが、セル数で
見ると『かなり多い』です。





お役に立ちましたら拍手して貰えると嬉しいです

addinbox at 01:14コメント(0)ブック・シート・セル | マクロ・関数 

コメントする

名前
 
  絵文字
 
 
記事検索
プロフィール

角田(つのだ)




  • ライブドアブログ