たとえば、ワークシートにスイッチ画像を貼り付けて、それをクリックするごとに ON/OFF が反転し、そのタイミングで何かの仕事をさせたい、ということがよくあります。
しかも、それらのスイッチは、その時の状況に応じて動的に配置したい、ということがよくあります。

たとえばこんな感じ。だいたい、1個ということはなくて、たいていの場合複数のスイッチが並びますね。それに、たいていグルーピングされています。図のA、B、Cのように。
これを混乱しないように実現するには、どうすればよいか。考えてみました。
実際に使うスイッチは、これらをコピーして名前を変えながら配置することになります。
一種類のスイッチに対して、ON/OFF 用にふたつの画像が必要です。ここに書いたような組み合わせでカップルになっているわけです。
オリジナルを貼り付けたワークシートのオブジェクトをパブリックな変数 IconSheet に代入しておきます。たとえば
Set IconSheet = ThisWorkbook.Worksheets("ICONs")そのうえで、次のようなクラスを用意します。
このクラスは、最初に Init で条件を設定しておいて、Show で実際に所定の位置にスイッチを表示します。表示されたスイッチがクリックされるごとに Value を切り替えてやれば、状態に応じたアイコンに切り替わります。
現在の状態は、プロパティ Value を参照すれば取得できます。
Value を切り替える関数は、Init 時に procedureName で指定しておきます。この関数は、Value を切り替えるだけでなく、当然ついでにほかの仕事もできます。
(Init と Show は合体できるでしょうが、とりあえず頭の整理をしやすいように分割しました)
クラスモジュール YesNoSwClass
'----- private member
Private swValue 'その時、ON/OFF どちらのアイコンが見えているかの値
Private swTop, swLeft 'アイコンを表示する位置
Private coupleName 'スイッチの名前
Private swOnName, swOffName '各アイコンの Shape 名
Private targetSheet 'スイッチを配置するワークシート
Private onIcon, offIcon 'オリジナルのアイコン画像名
Private procName 'スイッチをクリックしたときに実行される関数名
'----- constant
Private Const showMargin = 3 'セル内の余白
'----- public property
Public Property Let Value(sw) 'スイッチの値を設定
swValue = sw
changeIconVisibility
End Property
Public Property Get Value() 'スイッチの現在の値を取得
Value = swValue
End Property
'----- 初期化
Public Sub Init( _
switchRange, _
groupName, switchNumber, _
onIconName, offIconName, _
procedureName)
'スイッチを表示するセル
'グループ名、グループ内での番号
'それぞれ ON,OFF で表示するアイコンのオリジナル画像名
'スイッチがクリックされた時に実行すべきサブルーチン名
With switchRange
'セルの左上から showMargin の余白をとって表示される
swTop = .top + showMargin
swLeft = .left + showMargin
Set targetSheet = .Worksheet
End With
onIcon = onIconName
offIcon = offIconName
procName = procedureName
coupleName = groupName & "-" & switchNumber
swOnName = coupleName & "-" & "ON"
swOffName = coupleName & "-" & "OFF"
End Sub
'----- 表示
Public Function Show()
On Error Resume Next
targetSheet.Unprotect
setIcon onIcon, swOnName, True
setIcon offIcon, swOffName, False
Application.CutCopyMode = False
Value = True
'↓いちいちこれが無いと、追加された Icon が編集可能になってしまうので
' クリックしづらい
targetSheet.Protect
Show = coupleName
End Function
'1個のアイコンの表示
Private Sub setIcon(originIconName, coupleName, swValue)
Const waitTime = 50 / 86400000 'Copy の待ち時間
On Error Resume Next
'IconSheet から所定の画像をコピー
'(Shapes.Copy はよくエラーとなるので、念には念をいれて)
Do
Do
IconSheet.Shapes(originIconName).Copy
DoEvents
Application.Wait [now()] + waitTime
DoEvents
Loop While Err.Number <> 0
Loop While Application.ClipboardFormats(1) = False
'targetSheet の所定の位置にペースト
With targetSheet
.Cells(1, 1).Select
.Paste
With .Shapes(originIconName)
.top = swTop
.left = swLeft
.Name = coupleName
.Locked = True
.OnAction = procName
.Visible = swValue
End With
End With
End Sub
'----- swValue に応じてアイコンの表示を切替
Private Sub changeIconVisibility()
On Error Resume Next
With targetSheet
.Shapes(swOnName).Visible = swValue
.Shapes(swOffName).Visible = Not swValue
End With
Err.clear
End Sub
'----- スイッチを表示/隠蔽
Public Property Let Visible(sw)
On Error Resume Next
If sw Then
changeIconVisibility
Exit Property
End If
With targetSheet
.Shapes(swOnName).Visible = False
.Shapes(swOffName).Visible = False
End With
End Property
<注意> Sleep などの API をかませると、とたんに Copy、Paste が
安定して機能しなくなるので、API は使用しないようにした方がよい
と思います。これがわかるまでに、大変苦労しました。オリジナルのアイコン画像を Book 内に保管するようにしたので、少し煩雑なクラスになってしまいました。画像をファイルから挿入するようにすれば、もっと簡潔になると思いますが、システムが複数のファイル構成になるのがいやで、こうしました。
標準モジュール InstallSw()
Public IconSheet
Private mySw
'----- "B14"のセルにスイッチを設置する
Public Sub InstallSw()
Set IconSheet = ThisWorkbook.Worksheets("ICONs")
Set mySw = New YesNoSwClass
With mySw
.Init ActiveSheet.Range("B14"), _
"mySwitch", 1, _
"VisibleIcon", "InVisibleIcon", _
"MySwClick"
.Show
End With
End Sub
'----- スイッチをクリックするごとに実行するマクロ
Public Sub MySwClick()
With mySw
.Value = Not .Value ‛これだけで、表示が変わります
'必要であれば
' スイッチが切り替わったら実行する仕事を以下に
' たとえば
If .Value Then ButtonChick
End With
End SubYesNoSwClass のインスタンスを複数作成して、並べておくこともできます。その際には、上の MySwClick はほんのちょっと複雑になりますが、クリックされたスイッチの名前を Caller で取得して、必要な切替を行うようにします。
各クラスのインスタンスを Install 時に、coupleName を Key とした連想配列に入れておいて、上の mySw の替わりに使うことになります。
たとえば、こんな感じ。
InstallSw2()
Public SwitchList As Object
'----- "B2" から下に10個のスイッチを設置する
Public Sub InstallSw2()
Dim n, mysw, coupleName
Set IconSheet = ThisWorkbook.Worksheets("ICONs")
Set SwitchList = CreateObject("Scripting.Dictionary")
For n = 1 To 10
Set mysw = New YesNoSwClass
With mysw
.Init ActiveSheet.Range("B2").Offset(n - 1, 0), _
"mySwitch", n, _
"VisibleIcon", "InVisibleIcon", _
"mySwClick2"
coupleName = .Show
End With
Set SwitchList(coupleName) = mysw
Next
End Sub
'----- スイッチをクリックするごとに実行するマクロ
Private Sub mySwClick2()
Dim mysw, coupleName, s
s = Split(Application.Caller, "-")
coupleName = s(0) & "-" & s(1)
Set mysw = SwitchList(coupleName)
With mysw
.value = Not .value
'スイッチ "mySwitch-n" が切り替わったら実行する仕事をここに
'たとえば
If .value Then ButtonChick
End With
End Subここで、グループ名として s(0) が、その中の番号として s(1) が取得されていますので、必要に応じてそれを判別しながらクリック後の処理を行います。
クリック直後に行う必要がなければ、後で SwitchList(coupleName).value で現在の状態が取得できます。
※注意> このままでは SwitchList() は保存されませんので、終了時にすべてのスイッチの Shape を削除しておくこと、毎回あらためてスイッチの描画を行うことが必要です。