VBA - Apply same MouseMove code to all Labels (Event Handling Collections) - vba

I have a few Labels on my worksheet, and each one has the following code to display on the Status Bar the Range they're currently at (as the mouse moves over them):
Private Sub Label1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
rng = ActiveSheet.Shapes("Label1").TopLeftCell.Address
Application.StatusBar = rng
End Sub
Is there any way I can apply this same code to ALL the labels instead of rewriting it over and over again?

I added a new class called LabelHandler:
Option Explicit
Public WithEvents lbl As msforms.Label
Private Sub lbl_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Dim rng As String
rng = ActiveSheet.Shapes(lbl.Name).TopLeftCell.Address
Application.StatusBar = rng
End Sub
In a New Module I added the following:
Public myLabels As Collection 'Of LabelHandler
Sub init()
Dim ws As Worksheet
Dim myLabel As LabelHandler
Set myLabels = New Collection
For Each l In ActiveSheet.OLEObjects
Set myLabel = New LabelHandler
Set myLabel.lbl = l.Object
myLabels.Add myLabel
Next
End Sub
Now when I run my cursor over the label, I get $F$11 in the status window
EDIT
You will want to edit your For Each loop to only add the label objects you want to the collection. Perhaps by their Name property
For Each l In ActiveSheet.OLEObjects
If Left(l.Name,5)="Label" Then
Set myLabel = New LabelHandler
Set myLabel.lbl = l.Object
myLabels.Add myLabel
End If
Next
Or for all Labels:
For Each l In ActiveSheet.OLEObjects
If l.progID = "Forms.Label.1" Then
Set myLabel = New LabelHandler
Set myLabel.lbl = l.Object
myLabels.Add myLabel
End If
Next

Related

How to reference an element in a class collection

I have a collection of Toggle Buttons along with the object name of the toggle button, as is shown in the watch screen shot
I have tried to reference the sName for the second item in the collection with this line itbcollection.Item(2).sName but it gives me Object does not support this property or method error. What is the appropriate code to get to the sName?
There is a class that is used to capture Mouse Down events, assigned to several ToggleButtons.
Option Explicit
Private WithEvents cTB As MSForms.ToggleButton
Private sName As String
Public Property Let aTB(iTB As ToggleButton)
Set cTB = iTB
sName = cTB.Name
End Property
Private Sub cTB_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
If Button = 1 Then Exit Sub
Module1.RightClickDay cTB.Caption
End Sub
and at form load a loop finds all ToggleButtons and loads the collection
Dim tbEvent As cObjectArray
Dim TB(42) As Object
x = 0
For Each o In UFShows.Controls
If o.Tag = "T" Then
Set TB(x) = o
x = x + 1
o.Visible = False
Set tbEvent = New cObjectArray
tbEvent.aTB = o
iTBcollection.Add tbEvent
End If
Next o
Please, use the next adapted class code and name it "TGButClass":
Option Explicit
Public WithEvents cTB As MSForms.ToggleButton 'if not Public, it will not be exposed!
Public sName As String 'if not Public, it will not be exposed!
Public Property Set aTB(iTB As MSForms.ToggleButton)'you need Set here (nothing to be extracted, you need to Set)
'and As TooggleButtin means a sheet control
Set cTB = iTB
sName = cTB.Name
End Property
Private Sub cTB_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As Single, ByVal Y As Single)
If Button = 1 Then Exit Sub
Module1.RightClickDay cTB.Caption
End Sub
A testing called sub RightClickDay should look like:
Sub RightClickDay(strName As String)
MsgBox strName
End Sub
Then, in the form code module you should paste the next declarations and UserForm_Initializeevent:
Option Explicit
Private objMyEventClass As New Collection
Public tbEvents As TGButClass
Private Sub UserForm_Initialize()
Dim o As Control, x As Long
For Each o In Me.Controls
If o.Tag = "T" Then
x = x + 1
Set tbEvents = New TGButClass
Set tbEvents.aTB = o
objMyEventClass.Add tbEvents
Debug.Print objMyEventClass(x).sName 'to see the (now) exposed sName
End If
Next o
End Sub
Please, send some feedback after testing it.
If something not clear enough, do not hesitate to ask for clarifications.
A similar result can be obtained using an array instead of a collection, too.

One event sub for multiple textboxes instead of declaring it multiple time?

Is it possible to have one change event procedure for multiple textboxes in the same workbook?
If for example, I have a textbox named "textbox3" in the 1st, 2nd, and 3rd sheet of the workbook and I would like this single code below to work for all of them (the textboxes) rather than having to declare it on each sheet. Right now, I have to declare the same procedure on all the sheets but I only want to declare it once since it does the same thing on all of them.
'my procedure
Sub testObj()
Dim i As Integer, obj As oleobject
Set ac = ThisWorkbook.ActiveSheet
For Each obj In ac.oleobjects
If TypeName(obj.Object) = "TextBox" And obj.name = "TextBox3" Then
i = i + 1
ReDim Preserve TextArray(1 To i)
Set TextArray(i).TextBoxEvents = obj
End If
Next obj
Set obj = Nothing
End Sub
'My class1
Public WithEvents TextBoxEvents As MSForms.TextBox
'Public WithEvents TextBoxEvents As OLEObject
Private Sub TextBoxEvents_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = vbKeyTab Then
TextBox12.Activate
End If
End Sub
Here's an example
Class module named clsTextBox:
Option Explicit
Public WithEvents TextBoxEvents As MSForms.TextBox
Private Sub TextBoxEvents_Change()
Debug.Print TextBoxEvents.Name & ": " & TextBoxEvents.Text
End Sub
'Private Sub TextBoxEvents_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
' If KeyCode = vbKeyTab Then
' TextBox12.Activate 'unclear what you're aiming for here?
' End If
'End Sub
Regular module:
Dim ColTB As Collection
Sub testObj()
Dim i As Integer, obj As OLEObject, ac As Worksheet
Set ac = ThisWorkbook.ActiveSheet
Set ColTB = New Collection
For Each obj In ac.OLEObjects
If TypeName(obj.Object) = "TextBox" Then
ColTB.Add EventObj(obj.Object)
End If
Next obj
Set obj = Nothing
End Sub
Function EventObj(obj As MSForms.TextBox) As clsTextBox
Dim o As New clsTextBox
Set o.TextBoxEvents = obj
Set EventObj = o
End Function

Drag&drop between listboxes which are classes in a collection

Being a self-taught VBA programmer I usually search the internet until I find a satisfactory solution for the problems (and the limitations of VBA) I stumble upon. I do not simply copy code of others, I really try to understand it as well, so I can learn from it. Long story short: I used code I found for dragging data from one listbox to another. Originally, the code (for 2 listboxes) was simply put in the form's code module but I wanted to use it in a class module so I don't have to copy/paste the same code for each and every d&d listbox I use on a form. The code I use:
(Code module of the form; just the listbox part)
Option Explicit
Private collection_ListBox As New collection
Private collection_ComboBox As New collection
Private collection_Textbox As New collection
Private Sub UserForm_Initialize()
Dim frm_control As Control
Set collection_ListBox = New collection
Set collection_ComboBox = New collection
Set collection_Textbox = New collection
For Each frm_control In Me.Controls
Select Case TypeName(frm_control)
Case "ListBox"
Dim obj As CfrwxDragDropList: Set obj = New CfrwxDragDropList
Set obj.FRWX_Control = frm_control: obj.Initialize
collection_ListBox.Add obj
Case "ComboBox"
Case "TextBox"
End Select
Next frm_control
'***TEMP for testing purposes***
ListBox1.List = Array("Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7")
End Sub
(Code module of the class)
Option Explicit
Private WithEvents FRWX_DragDrop As msforms.ListBox
Private Item_Source As msforms.ListBox
Public Property Get FRWX_Control() As msforms.ListBox
Set FRWX_Control = FRWX_DragDrop
End Property
Public Property Set FRWX_Control(reg_Control As msforms.ListBox)
Set FRWX_DragDrop = reg_Control
End Property
Public Sub Initialize()
'Nothing here yet!
End Sub
Private Sub FRWX_DragDrop_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
If FRWX_Control.ListIndex < 0 Then Exit Sub
If Button = 1 Then
Call SetDraggedItem(FRWX_Control)
End If
End Sub
Private Sub FRWX_DragDrop_BeforeDragOver(ByVal Cancel As msforms.ReturnBoolean, _
ByVal Data As msforms.DataObject, ByVal X As Single, ByVal Y As Single, _
ByVal DragState As Long, ByVal Effect As msforms.ReturnEffect, _
ByVal Shift As Integer)
Cancel = True
Effect = fmDropEffectMove
End Sub
Private Sub FRWX_DragDrop_BeforeDropOrPaste(ByVal Cancel As msforms.ReturnBoolean, _
ByVal Action As msforms.fmAction, _
ByVal Data As msforms.DataObject, ByVal X As Single, ByVal Y As Single, _
ByVal Effect As msforms.ReturnEffect, ByVal Shift As Integer)
Cancel = True
Effect = fmDropEffectMove
Call AddDroppedItem(FRWX_Control, Data, Y)
Call DeleteDraggedItem(Item_Source)
End Sub
Private Sub SetDraggedItem(lb As msforms.ListBox)
Set Item_Source = lb
Dim dataObj As New DataObject
dataObj.SetText lb.Text
Call dataObj.StartDrag(fmDropEffectMove)
End Sub
Private Sub AddDroppedItem(lb As msforms.ListBox, dataObj As DataObject, Y As Single)
lb.AddItem dataObj.GetText, FixDropIndex(lb, Y)
End Sub
Private Sub DeleteDraggedItem(lb As msforms.ListBox)
Dim selIndex As Long
With lb
selIndex = .ListIndex
.Selected(selIndex) = False
.RemoveItem selIndex
End With
Set Item_Source = Nothing
End Sub
Private Function FixDropIndex(lb As msforms.ListBox, Y As Single) As Long
Dim toIndex As Long
With lb
toIndex = .TopIndex + Int(Y * 0.85 / .Font.Size)
If toIndex < 0 Then toIndex = 0
If toIndex >= .ListCount Then toIndex = .ListCount
End With
FixDropIndex = toIndex
End Function
So far so good; everything works fine except for one tiny little thing: I get an error on
Call DeleteDraggedItem(Item_Source)
in the sub FRWX_DragDrop_BeforeDropOrPaste. I know WHY I get this error: when I drop the DataObject in ListBox2, Item_Source in the corresponding instance of the class will be empty for it was filled in the ListBox1 instance of the class. So I need a way to let ListBox2 know the source of the dropped text. I can think of 2 ways of fixing this.
The first one sends shivers down my spine only thinking of it, for it feels almost like blasphemy: I could send it with the DataObject along with the text itself by 'expanding' lb.Text with "|" followed by lb.Name and split the string in the receiving instance. It will work, but I don't like this kind of solutions.
I could pass the name of the source ListBox to the parent (being the form itself) from instance 1, so instance 2 can ask for it there. I haven't tried that solution yet, but I'm sure I'll get it to work.
So now for my questions:
1. is solution 2 a/the right way to go?
2. are there other/better solutions I haven't thought of yet?
Any help will be highly appreciated!
***** UPDATE *****
As mentioned below, I found another (I think better) fix myself. The events are still triggered from the class instances for each listbox, but I used a separate, single instance of a class that performs the actual actions attached to them. Here's the updated code:
(Code module of the form; just the listbox part)
Option Explicit
Private collection_ListBox As New collection
Private collection_ComboBox As New collection
Private collection_Textbox As New collection
Private Sub UserForm_Initialize()
Dim frm_control As Control
Set collection_ListBox = New collection
Set collection_ComboBox = New collection
Set collection_Textbox = New collection
Dim handler As CfrwxDragDropList_EventHandler: Set handler = New CfrwxDragDropList_EventHandler
For Each frm_control In Me.Controls
Select Case TypeName(frm_control)
Case "ListBox"
Dim obj As CfrwxDragDropList: Set obj = New CfrwxDragDropList
Set obj.FRWX_Control = frm_control: obj.Initialize
Set obj.FRWX_EventHandler = handler
collection_ListBox.Add obj
Case "ComboBox"
Case "TextBox"
End Select
Next frm_control
'***TEMP for testing purposes***
ListBox1.List = Array("Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7")
End Sub
(Code module of the listbox class "CfrwxDragDropList"
Option Explicit
Private WithEvents FRWX_DragDrop As MSForms.ListBox
Private FRWX_DragDrop_Handler As CfrwxDragDropList_EventHandler
Private Item_Source As MSForms.ListBox
Public Property Get FRWX_Control() As MSForms.ListBox
Set FRWX_Control = FRWX_DragDrop
End Property
Public Property Set FRWX_Control(reg_Control As MSForms.ListBox)
Set FRWX_DragDrop = reg_Control
End Property
Public Property Get FRWX_EventHandler() As CfrwxDragDropList_EventHandler
Set FRWX_EventHandler = FRWX_DragDrop_Handler
End Property
Public Property Set FRWX_EventHandler(handler As CfrwxDragDropList_EventHandler)
Set FRWX_DragDrop_Handler = handler
End Property
Public Sub Initialize()
'Nothing here yet!
End Sub
Private Sub FRWX_DragDrop_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
If FRWX_Control.ListIndex < 0 Then Exit Sub
If Button = 1 Then
Call FRWX_DragDrop_Handler.SetDraggedItem(FRWX_Control)
End If
End Sub
Private Sub FRWX_DragDrop_BeforeDragOver(ByVal Cancel As MSForms.ReturnBoolean, _
ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, _
ByVal DragState As Long, ByVal Effect As MSForms.ReturnEffect, _
ByVal Shift As Integer)
Cancel = True
Effect = fmDropEffectMove
End Sub
Private Sub FRWX_DragDrop_BeforeDropOrPaste(ByVal Cancel As MSForms.ReturnBoolean, _
ByVal Action As MSForms.fmAction, _
ByVal Data As MSForms.DataObject, ByVal X As Single, ByVal Y As Single, _
ByVal Effect As MSForms.ReturnEffect, ByVal Shift As Integer)
Cancel = True
Effect = fmDropEffectMove
Call FRWX_DragDrop_Handler.AddDroppedItem(FRWX_Control, Data, Y)
Call FRWX_DragDrop_Handler.DeleteDraggedItem
End Sub
(Code module of the eventhandler class "CfrwxDragDropList_EventHandler"
Option Explicit
Private Item_Source As MSForms.ListBox
Public Sub SetDraggedItem(lb As MSForms.ListBox)
Set Item_Source = lb
Dim dataObj As New DataObject
dataObj.SetText lb.Text
Call dataObj.StartDrag(fmDropEffectMove)
End Sub
Public Sub AddDroppedItem(lb As MSForms.ListBox, dataObj As DataObject, Y As Single)
lb.AddItem dataObj.GetText, FixDropIndex(lb, Y)
End Sub
Public Sub DeleteDraggedItem()
Dim selIndex As Long
With Item_Source
selIndex = .ListIndex
.Selected(selIndex) = False
.RemoveItem selIndex
End With
Set Item_Source = Nothing
End Sub
Private Function FixDropIndex(lb As MSForms.ListBox, Y As Single) As Long
Dim toIndex As Long
With lb
toIndex = .TopIndex + Int(Y * 0.85 / .Font.Size)
If toIndex < 0 Then toIndex = 0
If toIndex >= .ListCount Then toIndex = .ListCount
End With
FixDropIndex = toIndex
End Function
That's it! It works between 2 listboxes, but if you want to use more it'll work as well. You can move items between listboxes, but also change the order of items within a listbox.

Moving single selected item in 1 of 3 listboxes via a class

Apologies for the awful title, its not the easiest thing to describe. I currently have a form with 3 listbox and adjacent to each 2 command buttons which allows the selected item in the listbox to move up or down. It all works fine but I would like to reduce the number of command buttons to a single pair and operate on the listbox with the currently selected item but I do not know if its possible. The code I have is:
Form declarations(3 instances for each pair of up/down buttons):
Private m_clsListMoveUpDown1 As CListbox_UpDown
Private m_clsListMoveUpDown2 As CListbox_UpDown
Private m_clsListMoveUpDown3 As CListbox_UpDown
and then instantiating the 3 instances of the class (within the form) via:
Set m_clsListMoveUpDown1 = New CListbox_UpDown
With m_clsListMoveUpDown1
Set .MoveDownButton = Me.Btn_MoveDown1
Set .MoveUpButton = Me.Btn_MoveUp1
Set .UpDownList = Me.LB_Sheet1
End With
Set m_clsListMoveUpDown2 = New CListbox_UpDown
With m_clsListMoveUpDown2
Set .MoveDownButton = Me.Btn_MoveDown2
Set .MoveUpButton = Me.Btn_MoveUp2
Set .UpDownList = Me.LB_Sheet2
End With
Set m_clsListMoveUpDown3 = New CListbox_UpDown
With m_clsListMoveUpDown3
Set .MoveDownButton = Me.Btn_MoveDown3
Set .MoveUpButton = Me.Btn_MoveUp3
Set .UpDownList = Me.LB_Sheet3
End With
Finally, some class declarations:
Public WithEvents MoveUpButton As MSForms.CommandButton
Public WithEvents MoveDownButton As MSForms.CommandButton
Public UpDownList As MSForms.ListBox
I haven't included either of the 2No. subroutines (moving the selected item up or down) within the class but can do if it helps. In a nutshell they contain:
With Me.UpDownList
'lines of code using arrays to move items up or down
End With
I completely understand the reason why a single pair of up/down buttons currently only operates on the listbox specified by e.g. Set .UpDownList = Me.LB_Sheet1 but I do not know how to associate the other 2 listboxes so that if either one has a selected item, have the buttons operate on that listbox.
I attempted to use the Set .UpDownList = Me.LB_Sheet1 or Me.LB_Sheet2 or Me.LB_Sheet2 but the gives a type mismatch error and I'm not sure what else to try.
I used a class like so
Private frmParent As MSForms.UserForm
Private lstListBoxUnderControl As MSForms.ListBox
Public WithEvents UP As MSForms.CommandButton
Public WithEvents DOWN As MSForms.CommandButton
Public Property Set PARENT_FORM(frm As MSForms.UserForm)
Set frmParent = frm
End Property
Public Property Set LISTBOX_CONTROLLING(lb As MSForms.ListBox)
Set lstListBoxUnderControl = lb
End Property
Private Sub Class_Initialize()
'
End Sub
Private Sub DOWN_Click()
' Down Code
lstListBoxUnderControl.BackColor = vbRed
End Sub
Private Sub UP_Click()
' Up Code
lstListBoxUnderControl.BackColor = vbGreen
End Sub
Setting it up in the form like so
Dim c As New clsUpdateController
Private Sub ListBox1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Set c.LISTBOX_CONTROLLING = Me.ActiveControl
End Sub
Private Sub ListBox2_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Set c.LISTBOX_CONTROLLING = Me.ActiveControl
End Sub
Private Sub ListBox3_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Set c.LISTBOX_CONTROLLING = Me.ActiveControl
End Sub
Private Sub ListBox4_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Set c.LISTBOX_CONTROLLING = Me.ActiveControl
End Sub
Private Sub ListBox5_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Set c.LISTBOX_CONTROLLING = Me.ActiveControl
End Sub
Private Sub UserForm_Initialize()
Set c.PARENT_FORM = Me
Set c.DOWN = Me.cmdDown
Set c.UP = Me.cmdUP
Me.ListBox1.AddItem "test"
Me.ListBox2.AddItem "test"
Me.ListBox3.AddItem "test"
Me.ListBox4.AddItem "test"
End Sub

Adjusting ActiveX Controls re-Szing and Zorder in VBA when used in a Sheet (not in a Form)

I have searched extensively and can not find a complete specification for ActiveX Controls/OLE Objects and I'm confused about if they are the same thing or not and how to reference them in VBA.
The material I saw in MSDN is very light and vague and my usual cheat-sheets are silent on this cough Chip Pearson cough
I have this code in a class module called cChart:
Option Explicit
Public WithEvents ch As Chart
Private Sub ch_MouseMove(ByVal Button As Long, ByVal Shift As Long, ByVal X As Long, _
ByVal Y As Long)
Debug.Print X & Chr(9) & Y
End Sub
I have this code in a class module called cTextBox:
Option Explicit
Public WithEvents tb As msforms.TextBox
Public cht As Chart
Dim ws As Worksheet
Private Sub Class_Initialize()
Set ws = ActiveSheet
End Sub
Private Sub tb_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, _
ByVal X As Single, ByVal Y As Single)
cht.Parent.Activate
' I want to do this but I cant...
tb.ZOrder msoSendToBack
' And I'm forced to do this
ws.Shapes("TextBox1").ZOrder msoSendToBack
End Sub
Public Sub alignObjects()
On Error GoTo objectsNotSet
tb.Top = cht.Top 'at least, I would like to be able to do this line...
objectsNotSet:
Debug.Print timeStamp(Caller:=cModuleName, Context:=cMyName, message:="ERROR")
End Sub
and this in a standard module:
Option Explicit
Public chrt As cChart
Public txtB As cTextBox
Sub initChart()
Set chrt = New cChart
Set chrt.ch = ActiveChart
End Sub
Sub inittb()
Set txtB = New cTextBox
Set txtB.tb = ActiveSheet.OLEObjects("TextBox1").Object
Set txtB.cht = chrt.ch
End Sub
Its just test code and I manually select the Chart before (manually) running initChart and then I manually run inittb.
I would like to get rid of this line
ws.Shapes("TextBox1").ZOrder msoSendToBack
but there is no name or index property that I can see in the Form.TextBox Object to allow me to do this:
ws.Shapes(tb.Name).ZOrder msoSendToBack
Do I need to re-cast the Form.TextBox object as a Shape to position and size it and do other useful things like control its Z order and visibility?
And can I not use the Form.XXXX object to get a Name or Index? Do I really have to hard code these into the shape Object?
If possible, can somebody point me in the general direction of a specification on these objects?