- ホーム
- Other
- controlToggleButton
ToggleButtonをなぞって一括設定
複数配置したToggleBoxをマウスの左ボタンを押した状態でなぞり、まとめてクリックしたいというお題。
最初コントロール配列化して、個々のToggleBoxのイベントを制御しようとトライしたが、どうもうまくいかないので、ToggleBoxをDisableにして、UserFormのイベントで取り扱う事を思いついた。
応用編でWorksheet上に透明UserFormを表示して、セルをなぞって着色もやってみました。(元の色と混ぜたいと思いましたが、「色」の世界は深く、ギブアップ状態です)
セルをなぞって着色(拙いお絵かきソフト?)
UserForm_MouseDownなら、OnTimeのお世話にならなくてもいけるかもとトライしてみるとうまくいきました。
動作もストレスフリーになって、実用的になったようです。
☆標準モジュール
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Public Declare Function GetActiveWindow Lib "user32" () As Long
Public Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Public Declare Function ScreenToClient Lib "user32" (ByVal hWnd As Long, lpPoint As POINTAPI) As Long
Public Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long
Public Type POINTAPI
X As Long
Y As Long
End Type
Public Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Public Const VK_LBUTTON = &H1 '[LeftClick]
Public Const VK_RBUTTON = &H2 '[RightClick]
Sub test()
UserForm1.Show
End Sub
☆UserForm1モジュール
Dim hWnd As Long
Private myToggle() As MSForms.ToggleButton
Private myRect() As RECT
Private initialColor As Long
'UserFormのハンドル取得。 ScreenToClient APIで使用。
Private Sub UserForm_Activate()
hWnd = GetActiveWindow()
End Sub
Private Sub UserForm_Initialize()
Dim myControl As Control
Dim scaleFactor As Single
scaleFactor = 96 / 72
'配列の添え字0の要素は使わない
ReDim myToggle(0 To 0)
ReDim myRect(0 To 0)
For Each myControl In Me.Controls
If TypeName(myControl) = "ToggleButton" Then
myControl.Enabled = False
myControl.Caption = ""
ReDim Preserve myToggle(0 To UBound(myToggle) + 1)
ReDim Preserve myRect(0 To UBound(myRect) + 1)
Set myToggle(UBound(myToggle)) = myControl
With myRect(UBound(myRect))
.Left = CLng(myControl.Left * scaleFactor)
.Top = CLng(myControl.Top * scaleFactor)
.Right = CLng((myControl.Left + myControl.Width) * scaleFactor)
.Bottom = CLng((myControl.Top + myControl.Height) * scaleFactor)
End With
End If
Next
initialColor = myToggle(1).BackColor
End Sub
'Private Sub UserForm_Click()だと、ボタンを離さないとEventが発生しないらしい
Private Sub UserForm_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Dim tempToggle As MSForms.ToggleButton
Dim initialState As Boolean
Dim toggleNo As Long, currentToggleNo As Long
'最初のクリック箇所を保持
If Button <> VK_LBUTTON Then Exit Sub
currentToggleNo = getToggleNo()
With myToggle(currentToggleNo)
initialState = .Value
.Value = Not (initialState)
.BackColor = IIf(initialState, initialColor, vbBlue)
End With
'無限ループでマウスのモニタ
Do
DoEvents: DoEvents: DoEvents
Sleep 10
toggleNo = getToggleNo
If toggleNo <> 0 Then
Set tempToggle = myToggle(toggleNo)
With tempToggle
.Value = Not (initialState)
.BackColor = IIf(initialState, initialColor, vbBlue)
End With
Set tempToggle = Nothing
End If
'マウスの左ボタンを離すまでループ
Loop While GetAsyncKeyState(VK_LBUTTON)
End Sub
'マウスの存在する位置のトグルボックスのNoを取得。取得失敗は0を戻す。
Private Function getToggleNo() As Long
Dim pos As POINTAPI
Dim ret As Long
Dim i As Long
'Screen座標→Client座標に変換。RECT配列内の値はUserForm座標→Client座標に変換済み。
GetCursorPos pos
ret = ScreenToClient(hWnd, pos)
For i = 1 To UBound(myRect)
With myRect(i)
If (pos.X >= .Left) And (pos.X <= .Right) And (pos.Y >= .Top) And (pos.Y <= .Bottom) Then
getToggleNo = i
Exit Function
End If
End With
Next i
getToggleNo = 0
End Function
======================================================
こちらは旧バージョンですが、UserFormからのOnTimeの使い方等の事例として残しておきます
☆ 標準モジュール toggleControlModule
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Public Declare Function GetActiveWindow Lib "user32" () As Long
Public Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Public Declare Function ScreenToClient Lib "user32" (ByVal hWnd As Long, lpPoint As POINTAPI) As Long
Public Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long
Public Type POINTAPI
X As Long
Y As Long
End Type
'追加
Public Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Public Const VK_LBUTTON = &H1 '[LeftClick]
Public Const VK_RBUTTON = &H2 '[RightClick]
Private nextTriggerTime As Date
Sub test()
UserForm1.Show
End Sub
'UserForm側でApplication.Ontimeが実行出来ないので仲立ちをする
Public Sub setOnTime()
’Webで見つけたが本当に1秒以下の指定が出来ているか不明
nextTriggerTime = [now()+"00:00:00.10"]
Application.OnTime nextTriggerTime, "onTimer"
End Sub
Public Sub onTimer()
UserForm1.ontimesub
End Sub
☆ UserForm1モジュール
'UserForm1には任意個数のToggleBoxが置いてあるものとする
Dim hWnd As Long
Private myToggle() As MSForms.ToggleButton
Private myRect() As RECT
Private initialColor As Long
Private xframe As Single, yframe As Single
Private myStartToggle As Long
'ToggleBox動的配置バージョン UserForm全体にToggleButtonを敷き詰めて操作する検証用バージョン
'Const BUTTONCOUNT As Long = 15
'Const COLUMNCOUNT As Long = 5
'Const SIDELENGTH As Long = 40
'UserFormのハンドル取得。 ScreenToClient APIで使用。
Private Sub UserForm_Activate()
hWnd = GetActiveWindow()
End Sub
Private Sub UserForm_Initialize()
Dim myControl As Control
Dim scaleFactor As Single
scaleFactor = 96 / 72
'配列の添え字0の要素は使わない
ReDim myToggle(0 To 0)
ReDim myRect(0 To 0)
For Each myControl In Me.Controls
If TypeName(myControl) = "ToggleButton" Then
myControl.Enabled = False
myControl.Caption = ""
ReDim Preserve myToggle(0 To UBound(myToggle) + 1)
ReDim Preserve myRect(0 To UBound(myRect) + 1)
Set myToggle(UBound(myToggle)) = myControl
With myRect(UBound(myRect))
.Left = CLng(myControl.Left * scaleFactor)
.Top = CLng(myControl.Top * scaleFactor)
.Right = CLng((myControl.Left + myControl.Width) * scaleFactor)
.Bottom = CLng((myControl.Top + myControl.Height) * scaleFactor)
End With
End If
Next
initialColor = myToggle(1).BackColor
End Sub
'ToggleBox動的配置バージョン
'Private Sub UserForm_Initialize()
' Dim i As Long
'
' With Me
' xframe = .Width - .InsideWidth
' yframe = .Height - .InsideHeight
' End With
'
' ReDim myToggle(1 To BUTTONCOUNT)
' For i = 1 To BUTTONCOUNT
' Set myToggle(i) = Controls.Add("Forms.ToggleButton.1")
' With myToggle(i)
' 'UserFormのイベントを用いるためにtoggleButtonはDisableにする。値の変更は可能。
' .Enabled = False
' .Width = SIDELENGTH
' .Height = SIDELENGTH
' .Left = SIDELENGTH * ((i - 1) Mod COLUMNCOUNT)
' .Top = SIDELENGTH * ((i - 1) \ COLUMNCOUNT)
' End With
' Next i
' With Me
' .Width = xframe + SIDELENGTH * COLUMNCOUNT
' .Height = yframe + SIDELENGTH * (BUTTONCOUNT \ COLUMNCOUNT)
' End With
'End Sub
'Private Sub UserForm_Click()だと、ボタンを離さないとEventが発生しないらしい
Private Sub UserForm_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
'最初のクリック箇所を保持
If Button <> VK_LBUTTON Then Exit Sub
Me.startToggle = getToggleNo()
'イベントを直ぐに抜けるために、次の処理はApplication.OnTimeで起動する
'UserFormからは直には使えないので、標準モジュールに仲立ちをしてもらう
toggleControlModule.setOnTime
End Sub
Public Sub ontimesub()
Dim tempToggle As MSForms.ToggleButton
Dim initialState As Boolean
Dim toggleNo As Long
With myToggle(Me.startToggle)
initialState = .Value
'状態変更の前にEnabled=True,変更後にEnabled=Falseにする必要があると考えたが無くてもOKだった
.Value = Not (initialState)
.BackColor = IIf(initialState, initialColor, vbBlue)
End With
'無限ループでマウスのモニタ
Do
DoEvents: DoEvents: DoEvents
Sleep 10
toggleNo = getToggleNo
If toggleNo <> 0 Then
Set tempToggle = myToggle(toggleNo)
With tempToggle
.Value = Not (initialState)
.BackColor = IIf(initialState, initialColor, vbBlue)
End With
Set tempToggle = Nothing
End If
'マウスの左ボタンを離すまでループ
Loop While GetAsyncKeyState(VK_LBUTTON)
End Sub
'マウスの存在する位置のトグルボックスのNoを取得。取得失敗は0を戻す。
Private Function getToggleNo() As Long
Dim pos As POINTAPI
Dim ret As Long
Dim i As Long
GetCursorPos pos
ret = ScreenToClient(hWnd, pos)
For i = 1 To UBound(myRect)
With myRect(i)
If (pos.X >= .Left) And (pos.X <= .Right) And (pos.Y >= .Top) And (pos.Y <= .Bottom) Then
getToggleNo = i
Exit Function
End If
End With
Next i
getToggleNo = 0
End Function
'ToggleBox動的配置バージョン
'Private Function getToggleNo() As Long
' Dim pos As POINTAPI
' Dim ret As Long
' Dim toggleId As Long
'
' GetCursorPos pos
' ret = ScreenToClient(hWnd, pos)
' With pos
' 'UserForm座標系の値に戻す
' .X = .X * 72 / 96
' .Y = .Y * 72 / 96
' getToggleNo = (.X \ SIDELENGTH) + (.Y \ SIDELENGTH) * COLUMNCOUNT + 1
' End With
'End Function
Public Property Let startToggle(toggleNo As Long)
myStartToggle = toggleNo
End Property
Public Property Get startToggle() As Long
startToggle = myStartToggle
End Property
ページのトップに戻る
======================================================================================
'セルをなぞって色を着けます。別ページの色指定ダイアログで色を選択するのも組み込んでみました。
'標準モジュール
Public Type POINTAPI
X As Long
Y As Long
End Type
Public Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Declare Function FindWindow Lib "User32.dll" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal uFlags As Long) As Long
'Windowを半透明化する
Public Declare Function SetLayeredWindowAttributes Lib "user32" (ByVal hWnd&, ByVal crKey As Long, ByVal bAlpha As Long, ByVal dwFlags As Long) As Long
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Public Declare Function GetActiveWindow Lib "user32" () As Long
Public Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Public Declare Function ScreenToClient Lib "user32" (ByVal hWnd As Long, lpPoint As POINTAPI) As Long
Public Declare Function GetAsyncKeyState Lib "User32.dll" (ByVal vKey As Long) As Long
Public Const VK_LBUTTON = &H1 '[LeftClick]
Public Const VK_RBUTTON = &H2 '[RightClick]
Public Const LWA_COLORKEY = 1
Public Const LWA_ALPHA = 2
Public Const SM_CXSCREEN As Long = 0
Public Const SM_CYSCREEN As Long = 1
Public Const GWL_STYLE = -16
Public Const GWL_EXSTYLE = -20
Public Const WS_CAPTION = &HC00000 'タイトルバーを持つウィンドウ
Public Const WS_SYSMENU = &H80000 'タイトルバー上にウィンドウメニューボックスを持つウィンドウ
Public Const WS_MINIMIZEBOX = &H20000 '最小化ボタンを持つウィンドウ
Public Const WS_MAXIMIZEBOX = &H10000 '最大化ボタンを持つウィンドウ
Public Const WS_EX_DLGMODALFRAME = &H1& '二重の境界線を持つウィンドウ
Public Const HWND_TOPMOST = -1& '常に手前に表示
Public Const HWND_TOP = 0 '手前に表示
Public Const SWP_FRAMECHANGED = &H20
Public Const WM_DROPFILES = &H233
Public Const GWL_WNDPROC = -4
Public Const WS_EX_TOOLWINDOW = &H80
Public Const WS_EX_LAYERED = &H80000
Public Const WM_KEYDOWN = &H100
'スクリーン座標とエクセルワークシート座標の変換
'出典:http://home.att.ne.jp/zeta/gen/excel/c04p06.htm
Public Const DPI As Long = 96
Public Const PPI As Long = 72
Public IsHooked As Boolean
Public lpPrevWndProc As Long
Public m_hwnd As Long
Public buttonName As String
'ワークシート上に置いた図形から起動。選択した色を図形に表示。
Sub showForm()
buttonName = Application.Caller
'非表示のUserForm2から色指定のクラスを呼んでダイアログを閉じる時にエラーが出るため安易にパス
On Error Resume Next
Load UserForm2
UserForm1.Show
End Sub
'ワークシート上のクリックより得られたスクリーン座標をセル座標に変換する
Public Function screenToCellAddress(scrnPOINT As POINTAPI) As String
Dim pointDifX As Single, pointDifY As Single
Dim startX As Single, startY As Single
Dim targetRange As Range
Dim pointX As Single, pointY As Single
Dim zoomX As Single, zoomY As Single
Dim i As Long
'左上隅セルの左上角との距離をポイントに変換
Call realZoomRate(zoomX, zoomY)
pointDifX = (scrnPOINT.X - ActiveWindow.PointsToScreenPixelsX(0)) * PPI / DPI / zoomX
pointDifY = (scrnPOINT.Y - ActiveWindow.PointsToScreenPixelsY(0)) * PPI / DPI / zoomY
startX = ActiveWindow.VisibleRange(1).Left
startY = ActiveWindow.VisibleRange(1).Top
Set targetRange = ActiveWindow.VisibleRange(1)
For i = 1 To ActiveWindow.VisibleRange(1).Column
pointX = pointX + targetRange.Width
Next i
For i = 1 To ActiveWindow.VisibleRange(1).Row
pointY = pointY + targetRange.Height
Next i
Do Until pointX > pointDifX
Set targetRange = targetRange.Offset(0, 1)
pointX = pointX + targetRange.Width
Loop
Do Until pointY > pointDifY
Set targetRange = targetRange.Offset(1, 0)
pointY = pointY + targetRange.Height
Loop
screenToCellAddress = targetRange.Address
End Function
'真のズーム倍率を求める 'by kanabunさん
Public Sub realZoomRate(ByRef zoomX As Single, ByRef zoomY As Single)
Dim c As Range
Dim dotX As Long
Dim dotY As Long
Dim dotX1 As Long
Dim dotY1 As Long
Set c = Range("a1")
With ActiveWindow
' ---------- 実際のZoom比の計算 ---------------
dotY = c.Height * DPI / PPI
dotY1 = dotY * .Zoom / 100
zoomY = dotY1 / dotY '実際に適用されているZoom率
dotX = c.Width * DPI / PPI
dotX1 = dotX * .Zoom / 100
zoomX = dotX1 / dotX
End With
End Sub
'ユーザーフォームを二個使っているのですが、どちらも「見えない・表示されない」のです。
'☆UserForm1モジュール 不透明度1の見えないフォーム
Dim R1C1Left As Long
Dim R1C1Top As Long
Private Sub UserForm_Initialize()
'フォームの不透明度 0にすると機能しなくなる。1ならほとんど判別できないが機能する。
Const opacityRatio As Long = 1
With Me
.StartUpPosition = 0
.BorderStyle = fmBorderStyleNone
.SpecialEffect = fmSpecialEffectFlat
'フォームに時刻を名前としてつける
.Caption = .Caption & Timer()
End With
'名前を手がかりとして、ユーザーフォームのハンドルを取得
m_hwnd = FindWindow("ThunderDFrame", Me.Caption)
' フォームのメニュー、最大最小化ボタン等は一切表示しない設定とする
SetWindowLong m_hwnd, GWL_STYLE, _
GetWindowLong(m_hwnd, GWL_STYLE) And _
Not (WS_SYSMENU Or WS_CAPTION Or _
WS_MINIMIZEBOX Or WS_MAXIMIZEBOX)
SetWindowLong m_hwnd, GWL_EXSTYLE, _
GetWindowLong(m_hwnd, GWL_EXSTYLE) And _
Not WS_EX_DLGMODALFRAME
SetWindowLong m_hwnd&, GWL_EXSTYLE, _
GetWindowLong(m_hwnd&, GWL_EXSTYLE) Or WS_EX_LAYERED
SetLayeredWindowAttributes m_hwnd&, 0, opacityRatio, LWA_ALPHA
R1C1Left = ActiveWindow.PointsToScreenPixelsX(0)
R1C1Top = ActiveWindow.PointsToScreenPixelsY(0)
End Sub
Private Sub UserForm_Activate()
Dim myLeft As Long, myTop As Long, myWidth As Long, myHeight As Long
Dim zoomX As Single, zoomY As Single
Dim rightBottomCell As Range
Const myFontSize = 11
'エクセルのActiveWindowのセルが表示されている範囲を覆うUserFormを表示
'ほとんど透明なので視認できない
realZoomRate zoomX, zoomY
With Application.ActiveWindow.VisibleRange
Set rightBottomCell = .Cells(.Rows.Count, .Columns.Count)
End With
myLeft = R1C1Left
myTop = R1C1Top
myWidth = zoomX * (rightBottomCell.Left + rightBottomCell.Width) * DPI / PPI
myHeight = zoomY * (rightBottomCell.Top + rightBottomCell.Height) * DPI / PPI
'フォームの表示順(Zオーダー)、サイズ指定
SetWindowPos m_hwnd, HWND_TOP, myLeft, myTop, myWidth, myHeight, SWP_FRAMECHANGED
errHandle:
End Sub
Private Sub UserForm_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Dim pos As POINTAPI
Dim currentCell As Range
'左ボタンのみ対象
If Button <> VK_LBUTTON Then Exit Sub
'無限ループでマウスのモニタ
Do
DoEvents: DoEvents: DoEvents
Sleep 10
GetCursorPos pos
'ドロップされた位置に該当するセルをActivate
Set currentCell = ActiveSheet.Range(screenToCellAddress(pos))
'元の色と混ぜたいなと思ってRGB、CMYK、加色混合、減色混合、補色等色々調べて試行錯誤しましたがちっともうまくいきません
currentCell.Interior.color = Sheets(1).Shapes(buttonName).Fill.ForeColor.RGB
'マウスの左ボタンを離すまでループ
Loop While GetAsyncKeyState(VK_LBUTTON)
End Sub
'やめたくなったときは何かキーを押すと停止
Private Sub UserForm_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
Unload Me
End Sub
'☆UserForm2モジュール
'色設定ダイアログを呼ぶためだけの、表示しないフォーム(はっきり言って手抜き)
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function DestroyWindow Lib "user32" (ByVal hndw As Long) As Boolean
Dim myPalette As Class1
Dim ccHwnd As Long
Private Sub UserForm_Initialize()
'Class1は別のページに載せてあるダイアログからの色選択クラス
4-34 色指定のダイアログを表示して色を取得、カスタムカラー保存機能つき
Set myPalette = New Class1
Set myPalette.parent = Me
Set myPalette.dataSheet = ThisWorkbook.Sheets(2)
Sheets(1).Shapes(buttonName).Fill.ForeColor.RGB = myPalette.colorCode
Unload Me
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Dim lngRet As Long
'Dialogが消えないままUserFormを消した時の対策
'Dialogが消える時まで,Class1のterminateが実行されない様である
ccHwnd = FindWindow(vbNullString, "色の設定")
If ccHwnd Then DestroyWindow (ccHwnd)
Set myPalette = Nothing
End Sub