How to open a userform through a custom excel ribbon - excel

I am trying to open a userform through a custom excel ribbon. When I click the button in the ribbon it begins initializing and on the workbooks.open function it sends the code to the queryclose sub. The show userform code is below:
Sub RemoveFixture_onAction(control As IRibbonControl)
SelectedCompType = Fixture
Set EditComp = New ufUpdateComp
With EditComp
.Top = Application.Top + 125
.Left = Application.Left + 25
.Show
End With
End Sub
When the code begins the userform_Initialize code it ends up moving to the query_close sub. The code for that is below:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
'If wb Is Nothing Then UserForm_Initialize
wb.Close False
End Sub
As seen above, in the commented out section, I tried to return to the initialize sub when the code moved to the queryclose function. It moves to the queryclose sub when it runs the workbooks.open code and it says that the wb is nothing. I have tried opening the workbook separately and then setting the workbook as ActiveWorkbook. I also tried:
do while wb is nothing
set wb = ActiveWorkbook
loop
This loop ran endlessly until I had to manually cancel it.
It was originally set wb = workbooks.open(Test)
Private Sub UserForm_Initialize()
Workbooks.Open Test, , , , , DynoCompPassword, True
Set wb = ActiveWorkbook
Set ws = wb.Worksheets("Info")
Set ws = wb.Worksheets("Info")
Set wsC = wb.Worksheets("Calipers")
Set wsF = wb.Worksheets("Fixtures")
Set wsW = wb.Worksheets("Wheel Sims")
ws.Visible = True
wsC.Visible = True
wsF.Visible = True
btnCreate.Enabled = False
Dim rng As Range
lblLocation.Visible = False
tbLocation.Visible = False
Me.cbOut.AddItem "Sent To"
Me.cbOut.AddItem "Scrapped"
Me.cbOut.AddItem "Returned"
Me.btnCreate.Enabled = True
For Each rngprojectcode In ws.Range("ProjectCode")
Me.cbProjectCode.AddItem rngprojectcode.Value
Next rngprojectcode
Set ProjCodeDictionary = New Dictionary 'Create the dictionary
Dim i As Integer
Dim j As Integer
Dim ProjCodeString As String
Dim AssociatedCodes As ProjectCodeList
If ws Is Nothing Then Exit Sub
ProjCodeDictionary.CompareMode = vbTextCompare 'Make the .exists method case insensitive in an attempt to avoid duplicate values
Set AssociatedCodes = New ProjectCodeList 'create the class module which will split up the associated codes into individual values
i = 1
While ws.Range("F1").Offset(i, 0) <> ""
With AssociatedCodes
.SetCodes = CStr(ws.Range("F1").Offset(i, 0).Value)
For j = 1 To .NumCodes
ProjCodeDictionary.Add .ProjCode(j), i 'key, item
Next j
End With
i = i + 1
Wend
If SelectedCompType = Fixture Then
Me.lblCompNum.Caption = "Fixture ID"
Me.btnCreate.Caption = "Update Fixture"
'Automation Error occurs here
Me.Caption = "Edit Fixture"
Me.frChangeFrame.Height = 65
Me.frChangeFrame.Caption = "Bolt Circle"
Me.cbPartNum.Text = "FIX"
For Each rng In wsF.Range("FixtureNum")
Me.cbPartNum.AddItem rng.Value
Next rng
Set tbNumStuds = frChangeFrame.Controls.Add("Forms.TextBox.1", , "True")
To clarify, the queryclose sub should only be activated when the red box with the X is pressed in the userform. It is a built in function of the userform.

The only time the queryclose sub should run is when the X button is pressed on the userform.
But that's not how QueryClose works. The UserForm.QueryClose event is fired whenever the form is about to be closed, and its parameters give you means to cancel it, depending on what prompted it to close.
What you want is to run wb.Close False conditionally, when the CloseMode parameter value is vbFormControlMenu (the X button - see QueryClose constants):
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = vbFormControlMenu Then
wb.Close False
End If
End Sub
I tried to return to the initialize sub when the code [...]
Don't do this. Event handlers are meant to be invoked by VBA itself, not user code. If you need to invoke logic that you've implemented in an event handler, refactor the code out of the handler and into its own procedure instead:
Private Sub UserForm_Initialize()
DoInitializationStuff
End Sub
Private Sub DoInitializationStuff()
'...
End Sub
Lastly, the UserForm.Initialize event is fired well before the form is shown.
Set EditComp = New ufUpdateComp ' <~ initialize handler runs before this instruction returns
With EditComp
.Top = Application.Top + 125
.Left = Application.Left + 25
.Show
End With
Note that you don't need to declare the local variable if you're only using it in a With block - have the block hold the object reference instead:
With New ufUpdateComp ' <~ initialize handler runs before this instruction returns
.Top = Application.Top + 125
.Left = Application.Left + 25
.Show
End With
If you want DoInitializationStuff to run after the form is shown, invoke it in the Activate event.

Related

VBA Show / Hide Images or Shapes on Command Button Click Event To Sort Data

I am developing a program that has multiple columns of Data that can be sorted by several of the columns. For aesthetics I am using Command Buttons click event to toggle sorting in ascending or descending order. My code is pretty simple. I am using images of "up" arrows and "down" arrows as ascending / descending indicators. All of the images are on the worksheet and depending on the sort method, the click event shows or hides the appropriate image.The coding is working correctly with one issue that I wasnt thinking about. When a user clicks the button to sort, that arrow shows and hides correctly for that column however the other columns still show an arrow which can confuse the user. I would like to hide the other images / arrows except for the images / arrows in the column that is being sorted.
See Attached Image For Clarification
In the photo above if the Player ID Command Button is pressed again the up arrow will hide and a down arrow will be visible but the other arrows will stay exactly as they are. I would like only the column being sorted to show the arrows.
The code below is used in the worksheet module using the command button click event.
Private Sub cmbAgentID_Click()
If ActiveSheet.Shapes.Range(Array("picAgentIDUp")).Visible = False Then
Call SortByAgentAsc 'sort ascending
ActiveSheet.Shapes.Range(Array("picAgentIDUp")).Visible = True
ActiveSheet.Shapes.Range(Array("picAgentIDDown")).Visible = False
Else
Call SortByAgentDes 'sort descending
ActiveSheet.Shapes.Range(Array("picAgentIDDown")).Visible = True
ActiveSheet.Shapes.Range(Array("picAgentIDUp")).Visible = False
End If
End Sub
Private Sub cmbAllHands_Click()
If ActiveSheet.Shapes.Range(Array("picAllHandsUp")).Visible = False Then
Call SortByHandsAsc 'sort ascending
ActiveSheet.Shapes.Range(Array("picAllHandsUp")).Visible = True
ActiveSheet.Shapes.Range(Array("picAllHandsDown")).Visible = False
Else
Call SortByHandsDes 'sort descending
ActiveSheet.Shapes.Range(Array("picAllHandsDown")).Visible = True
ActiveSheet.Shapes.Range(Array("picAllHandsUp")).Visible = False
End If
End Sub
Private Sub cmbCashHands_Click()
If ActiveSheet.Shapes.Range(Array("picCashUp")).Visible = False Then
Call SortByCashAsc 'sort ascending
ActiveSheet.Shapes.Range(Array("picCashUp")).Visible = True
ActiveSheet.Shapes.Range(Array("picCashDown")).Visible = False
Else
Call SortByCashDes 'sort descending
ActiveSheet.Shapes.Range(Array("picCashDown")).Visible = True
ActiveSheet.Shapes.Range(Array("picCashUp")).Visible = False
End If
End Sub
Private Sub cmbEmbers_Click()
If ActiveSheet.Shapes.Range(Array("picEmbersUp")).Visible = False Then
Call SortByEmbersAsc 'sort ascending
ActiveSheet.Shapes.Range(Array("picEmbersUp")).Visible = True
ActiveSheet.Shapes.Range(Array("picEmbersDown")).Visible = False
Else
Call SortByEmbersDes 'sort descending
ActiveSheet.Shapes.Range(Array("picEmbersDown")).Visible = True
ActiveSheet.Shapes.Range(Array("picEmbersUp")).Visible = False
End If
End Sub
Private Sub cmbFees_Click()
If ActiveSheet.Shapes.Range(Array("picFeeUp")).Visible = False Then
Call SortByFeeAsc 'sort ascending
ActiveSheet.Shapes.Range(Array("picFeeUp")).Visible = True
ActiveSheet.Shapes.Range(Array("picFeeDown")).Visible = False
Else
Call SortByFeeDes 'sort descending
ActiveSheet.Shapes.Range(Array("picFeeDown")).Visible = True
ActiveSheet.Shapes.Range(Array("picFeeUp")).Visible = False
End If
End Sub
Any Suggestions? I have been looking at ShapeRange which is new to me and Shape Arrays, but havent found what I am looking for yet.
-------Updated Code Below with Suggested Improvements Not Working-------
Created "Rotate It" Sub and assigned macro to a single arrow.
Sub RotateIt()
Dim s As Shape: Set s = ActiveSheet.Shapes(Application.Caller)
If s.Rotation = 0 Then
s.Rotation = 180
Else
s.Rotation = 0
End If
End Sub
Created 1 sub for Sorting and I think my problem is here...
Sub SortByEverything(sortKey As Range, Optional boolAsc As Boolean)
Dim sh As Worksheet: Set sh = ActiveSheet
Dim lastrow As Long: lastrow = Cells(Rows.Count, 2).End(xlUp).Row
Dim rng As Range: Set rng = sh.Range("B3:M" & lastrow)
If boolAsc Then
With rng 'your existing code for ACENDING sorting type, but using supplied sortKey...
.Sort Key1:=sortKey, Order1:=xlAscending, Header:=xlYes
End With
Debug.Print "Sort Ascending..."
Else
With rng 'your existing code for ACENDING sorting type, but using supplied sortKey...
.Sort Key1:=sortKey, Order1:=xlAscending, Header:=xlYes
End With
Debug.Print "Sort Descending..."
End If
End Sub
Created Class Module ButtonName
Option Explicit
Public WithEvents cmdButton As MSForms.CommandButton
Public Sub cmdButton_Click()
Dim sArr As Shape: Set sArr = ActiveSheet.Shapes("Arrow")
sArr.Top = cmdButton.Top: sArr.Left = cmdButton.Left + cmdButton.Width
If sArr.Rotation = 0 Then
SortByEverything cmdButton.TopLeftCell, True
sArr.Rotation = 180
Else
SortByEverything cmdButton.TopLeftCell
sArr.Rotation = 0
End If
End Sub
Created the worksheet activate sub
Option Explicit
Private arrEvents As Collection
Private Sub Worksheet_Activate()
Dim ActXButEvents As ButtonName, shp As Shape
Set arrEvents = New Collection
varSplitCol = 0
varSplitRow = 4
Call EnhancePerformance
Call FreezeSheetPanes
For Each shp In Me.Shapes
If shp.Type = msoOLEControlObject Then
If TypeOf shp.OLEFormat.Object.Object Is MSForms.CommandButton Then
Set ActXButEvents = New ButtonName
Set ActXButEvents.cmdButton = shp.OLEFormat.Object.Object
arrEvents.Add ActXButEvents
End If
End If
Next
Call NormalPerformance
End Sub
Try the next approach, please. Create a Sub to be called by all buttons Click event:
Sub HideArrows(sh As Worksheet)
Dim s As Shape
For Each s In sh.Shapes
If Right(s.Name, 2) = "Up" Or _
Right(s.Name, 4) = "Down" Then s.Visible = msoFalse
Next
End Sub
Then use your existing code in this way:
Private Sub cmbAgentID_Click() 'proceed in a similar way to all the other click events
Dim sh As Worksheet: Set sh = ActiveSheet
HideArrows sh
If sh.Shapes.Range(Array("picAgentIDUp")).Visible = False Then
Call SortByAgentAsc 'sort ascending
sh.Shapes.Range(Array("picAgentIDUp")).Visible = True
Else
Call SortByAgentDes 'sort descending
sh.Shapes.Range(Array("picAgentIDDown")).Visible = True
End If
End Sub
Edited: Try the next different approach, please. It is very compact. The whole necessary code will be the next one, in a standard module:
Create a single (Up) arrow shape and name it "Arrow"
Each (Form type) button will target the same Sub, so assign to all of them the next code. For an ActiveX button I will show the approach (a little more complicated, but not too much) at the end:
Sub Button_Click()
Dim s As Shape: Set s = ActiveSheet.Shapes(Application.Caller)
Dim sArr As Shape: Set sArr = ActiveSheet.Shapes("Arrow")
sArr.Rop = s.top: sArr.left = s.left + s.width
If sArr.Rotation = 0 Then
SortByEverything s.TopLeftCell, True 'ascending
sArr.Rotation = 180
Else
SortByEverything s.TopLeftCell 'descending
sArr.Rotation = 0
End If
End Sub
Use the sorting Subs built in the next way. They will receive the sorting key according to each pressed button position:
Sub SortByEverything(sortKey As Range, Optional boolAsc As Boolean)
Dim sh As Worksheet
Set sh = ActiveSheet
If boolAsc Then
'your existing code for ACENDING sorting type, but using supplied sortKey...
'....
Debug.Print "Sort Ascending..."
Else
'your existing code for ACENDING sorting type, but using supplied sortKey...
'....
Debug.Print "Sort Descending..."
End If
End Sub
In order to change the arrow orientation/sorting type, please assign the next code to the "Arrow" shape:
Sub RotateIt()
Dim s As Shape: Set s = ActiveSheet.Shapes(Application.Caller)
If s.Rotation = 0 Then
s.Rotation = 180
Else
s.Rotation = 0
End If
End Sub
This approach philosophy would be the next: When press a button the "Arrow" shape will be moved to its right side. According to its rotation property, the sorting will be done ascending or descending. Then the arrow rotation will be adapted. If it remains down oriented and next time, for a different column, you need to sort descending, just click the arrow shape and it will rotate for the appropriate sorting type. You need only a single sorting Sub being informed about the sorting key and sorting type...
In case of ActiveX button, Application.Coller does not return the shape calling the sub name and a Class Events Wrapper is necessary...
a) Insert a class module, name it ButtonName and copy the next code:
Option Explicit
Public WithEvents cmdButton As MSForms.CommandButton
Public Sub cmdButton_Click()
Dim sArr As Shape: Set sArr = ActiveSheet.Shapes("Arrow")
sArr.top = cmdButton.top: sArr.left = cmdButton.left + cmdButton.width
If sArr.Rotation = 0 Then
SortByEverything cmdButton.TopLeftCell, True
sArr.Rotation = 180
Else
SortByEverything cmdButton.TopLeftCell
sArr.Rotation = 0
End If
End Sub
Note: No click events for all ActiveX buttons are necessary (for this specific task)!
b) Create a Private variable at the sheet level module. On top of it, in the declarations area:
Public arrEvents As Collection
c) Use Worksheet_Activate event (of course in the sheet keeping the buttons), in order to initialize the class for all ActiveX type buttons:
Private Sub Worksheet_Activate()
Dim ActXButEvents As ButtonName, shp As Shape
Set arrEvents = New Collection
For Each shp In Me.Shapes
If shp.Type = msoOLEControlObject Then
If TypeOf shp.OLEFormat.Object.Object Is MSForms.CommandButton Then
Set ActXButEvents = New ButtonName
Set ActXButEvents.cmdButton = shp.OLEFormat.Object.Object
arrEvents.aDD ActXButEvents
End If
End If
Next
End Sub
Note: When you have the code, it is not possible to press a button on the working sheet, without triggering the sheet Activate event. But, during your code preparation, it is necessary to activate another sheet and then reactivate it. Just in order to trigger the before mentioned event.
Check it please, if interested, and send some feedback.
I figured it out. Thank you to FaneDuru for helping me. I used the coding that FaneDuru provided but I separated the Up arrows and down Arrows to still leave the majority of them grouped but had to individually hide the other ones. For example in the Player ID column. In order for me to toggle the up and down arrows I had to at least have 2 arrows available. In FaneDuru code it only left me with 1 arrow to work with since the rest were not visible. The only way i could figure to to it was:
if down arrow was visible before the click event then I could hide all down arrows and all up arrows except for the arrow in that column. When the click event happens the up arrow becomes visible and the down arrow is hidden.
It was more work because i had to put the other shapes in an array individually
This is now fixed but there is always room to improve it.
Code for worksheet module
Private Sub cmbAgentID_Click()
Dim sh As Worksheet: Set sh = ActiveSheet
If sh.Shapes.Range(Array("picAgentIDUp")).Visible = msoFalse Then
hidedownarrows sh
Call SortByAgentAsc 'sort ascending
With sh.Shapes
.Range(Array("picAgentIDUp")).Visible = msoTrue
.Range(Array("picCashUp", "picAllHandsUp", "picEmbersUp", "picRBAmtUp", "PicRBUp", "picFeeUp", "picIDUp")).Visible = msoFalse
End With
Else
HideupArrows sh
Call SortByAgentDes 'sort descending
With sh.Shapes
.Range(Array("picAgentIDDown")).Visible = msoTrue
.Range(Array("picCashdown", "picAllHandsdown", "picEmbersdown", "picRBAmtdown", "PicRBdown", "picFeedown", "picIDdown")).Visible = msoFalse
End With
End If
End Sub
Private Sub cmbAllHands_Click()
Dim sh As Worksheet: Set sh = ActiveSheet
If sh.Shapes.Range(Array("picAllHandsUp")).Visible = msoFalse Then
hidedownarrows sh
Call SortByHandsAsc 'sort ascending
With sh.Shapes
.Range(Array("picAllHandsUp")).Visible = msoTrue
.Range(Array("picCashUp", "picAgentIDUp", "picEmbersUp", "picRBAmtUp", "PicRBUp", "picFeeUp", "picIDUp")).Visible = msoFalse
End With
Else
HideupArrows sh
Call SortByHandsDes 'sort descending
With sh.Shapes
.Range(Array("picAllHandsDown")).Visible = msoTrue
.Range(Array("picCashdown", "picAgentIDdown", "picEmbersdown", "picRBAmtdown", "PicRBdown", "picFeedown", "picIDdown")).Visible = msoFalse
End With
End If
End Sub
Private Sub cmbCashHands_Click()
Dim sh As Worksheet: Set sh = ActiveSheet
If sh.Shapes.Range(Array("picCashUp")).Visible = msoFalse Then
hidedownarrows sh
Call SortByCashAsc 'sort ascending
With sh.Shapes
.Range(Array("picCashUp")).Visible = msoTrue
.Range(Array("picAllHandsUp", "picAgentIDUp", "picEmbersUp", "picRBAmtUp", "PicRBUp", "picFeeUp", "picIDUp")).Visible = msoFalse
End With
Else
HideupArrows sh
Call SortByCashDes 'sort descending
With sh.Shapes
.Range(Array("picCashDown")).Visible = msoTrue
.Range(Array("picAllHandsDown", "picAgentIDdown", "picEmbersdown", "picRBAmtdown", "PicRBdown", "picFeedown", "picIDdown")).Visible = msoFalse
End With
End If
End Sub
Private Sub cmbEmbers_Click()
Dim sh As Worksheet: Set sh = ActiveSheet
If sh.Shapes.Range(Array("picEmbersUp")).Visible = msoFalse Then
hidedownarrows sh
Call SortByEmbersAsc 'sort ascending
With sh.Shapes
.Range(Array("picEmbersUp")).Visible = msoTrue
.Range(Array("picAllHandsUp", "picAgentIDUp", "picCashUp", "picRBAmtUp", "PicRBUp", "picFeeUp", "picIDUp")).Visible = msoFalse
End With
Else
HideupArrows sh
Call SortByEmbersDes 'sort descending
With sh.Shapes
.Range(Array("picEmbersDown")).Visible = msoTrue
.Range(Array("picAllHandsDown", "picAgentIDdown", "picCashDown", "picRBAmtdown", "PicRBdown", "picFeedown", "picIDdown")).Visible = msoFalse
End With
End If
End Sub
Private Sub cmbFees_Click()
Dim sh As Worksheet: Set sh = ActiveSheet
If sh.Shapes.Range(Array("picFeeUp")).Visible = msoFalse Then
hidedownarrows sh
Call SortByFeeAsc 'sort ascending
With sh.Shapes
.Range(Array("picFeeUp")).Visible = msoTrue
.Range(Array("picAllHandsUp", "picAgentIDUp", "picCashUp", "picRBAmtUp", "PicRBUp", "picEmbersUp", "picIDUp")).Visible = msoFalse
End With
Else
HideupArrows sh
Call SortByFeeDes 'sort descending
With sh.Shapes
.Range(Array("picFeeDown")).Visible = msoTrue
.Range(Array("picAllHandsDown", "picAgentIDdown", "picCashDown", "picRBAmtdown", "PicRBdown", "picEmbersDown", "picIDdown")).Visible = msoFalse
End With
End If
End Sub
Code for Standard Module
Sub HideupArrows(sh As Worksheet)
Dim s As Shape
For Each s In sh.Shapes
If Right(s.Name, 2) = "Up" Then
s.Visible = msoFalse
End If
Next
End Sub
Sub hidedownarrows(sh As Worksheet)
Dim s As Shape
For Each s In sh.Shapes
If Right(s.Name, 4) = "Down" Then
s.Visible = msoFalse
End If
Next
End Sub

Excel Userform animated dots on loading

There are a lot of tutorials in the Internet. However I was not able to find anything suitable. Is there any way to make animated dots on loading?
The idea is to make a loop of animated dots ..... on userform so they would appear one after another and then would start over after some amount of dots.
So I input a dot to Label1 and move it to left after certain time criteria?
My current code for UserForm:
Private Sub UserForm_Initialize()
HideTitleBar.HideTitleBar Me
Call loadingdots
End Sub
Code for Private Sub Workbook_Open():
Loading.Show (vbModeless)
Dim RngCom As Range
Dim RngTurb As Range
Dim RngGen As Range
Application.Wait (Now + TimeValue("00:00:06"))
ThisWorkbook.Worksheets("MAIN").ScrollArea = "$A$1:$BL$45"
Application.DisplayFormulaBar = False
ActiveWindow.DisplayHeadings = False
ActiveWindow.DisplayGridlines = False
etc...
Unload Loading
'Application.ScreenUpdating = True
End Sub
The most elegant solution would likely to be the OnTime method.
Place a label inside your UF and remove the caption. Next, in a regular module (so not that of the UF), place this subroutine:
'this function ensures the self-activating sub will stop if the UF has been closed
Public Function IsLoaded(form As String) As Boolean
Dim frm As Object
For Each frm In VBA.UserForms
If frm.Name = form Then
IsLoaded = True
Exit Function
End If
Next frm
IsLoaded = False
End Function
Public Sub loadingdots()
If IsLoaded("UserForm1") = True Then
If Len(UserForm1.Label1.Caption = 4) Then
UserForm1.Label1.Caption = "."
Else
UserForm1.Label1.Caption = UserForm1.Label1.Caption & "."
End If
Application.OnTime Now + TimeValue("00:00:01"), "loadingdots"
End If
End Sub
Next, call the self-activating sub when the UF gets initialised
Private Sub UserForm_Initialize()
Call loadingdots
End Sub
Do not forget to change the references to the UF to the right name.

Overwriting values in a range which is sourcerange of a listbox

I have a listbox on a userform which has a sourcerange which I am trying to overwrite by providing values from a userform but as soon as I overwrite a particular cell the event ListBox1_Click() fires up which is undesirable as it repopulates the data on the userform.
Private Sub ListBox1_Click()
Application.EnableEvents = False
Dim i As Long, fRow As Long
For i = 0 To ListBox1.ListCount - 1
If ListBox1.Selected(i) Then
If i > 0 Then
HSht.Range("cRow").Value = i + 1
fRow = HSht.Range("cRow").Value
Call getData(fRow)
HSht.Range("LRow").Value = getlastRow()
Me.ItemLbl.Caption = "Item number :" & HSht.Range("cRow").Value - 1 & " of " & HSht.Range("LRow").Value - 1
End If
Exit For
End If
Next i
Application.EnableEvents = True
End Sub
Here is the update button code:
Private Sub cmdUpdate_Click()
Application.EnableEvents = False
'Update
Dim fRow As Long, i As Long
fRow = HSht.Range("cRow").Value
Call updateData(fRow)
HSht.Range("LRow").Value = getlastRow()
Me.ItemLbl.Caption = "Item number :" & HSht.Range("cRow").Value - 1 & " of " & HSht.Range("LRow").Value - 1
'MsgBox "Data updated successfully"
Application.EnableEvents = True
End Sub
E.g let's you have 10 fields and you have ten textbox on a userform to view/modify the data but you also have multicolumn listbox to view and scroll the data in a table format, when I scroll up or down I get the specific rows data in the textboxes on userform, I also have a button which says "overwrite" in case I want to modify the data on the worksheet through userform. But as soon it modifies one cell in the worksheet the event "Listbox1_click" triggers and it overwrites the data on the userform.
Application.EnableEvents = false won't affect UserForms. You have to create a property and check it's value at event start and exit event sub if events disabled like:
' Top of UserForm-Class
Public EnableEvents As Boolean ' if Private code outside the userform can't change value.
'One should add a Letter/Getter to have more control over the property (exposing the variable that stores a property-value isn't recommended I think, with Get/Let we can perform checks or just make the Letter private, but the Getter public)
Private Sub UserForm_Initialize()
Me.EnableEvents = True
End Sub
Private Sub ListBox1_Click()
If Me.EnableEvents = False Then 'the first three lines of code suppress the events-code execution if EnableEvents = False and must be on top of every event that you want to have disabled.
Exit Sub
End If
'Me.EnableEvents = False should be set on top of button code and Me.EnableEvents = True at buttom if other events of from should be suppressed.
Dim i As Long, fRow As Long
For i = 0 To ListBox1.ListCount - 1
...
End Sub
Private Sub cmdUpdate_Click()
If Me.EnableEvents = False Then 'the first three lines of code suppress the events-code execution and must be on top of every event that you want to have disabled.
Exit Sub
End If
Me.EnableEvents = False 'disable Form-Events
... 'Button-Code
Me.EnableEvents = True 'reenable events
End Sub

Error when passing workbook object to VBA form

I am working on a VBA script with Microsoft Word that allows the user to select text that will be copied to the clipboard so that it can exported to an Excel file. The user will make a number of selections and finally indicate he/she is done when the contents of the clipboard will be copied to a template Excel file.
The form (frmModeLessForInput) is a modeless form. The form has two buttons "Continue" and "Done". The user is allowed to navigate the document and place the cursor anywhere in the document. Then when "Continue" is pressed the form will call a subroutine (Highlight_Sentence) to copy the selected text to a "clipboard" variable. When "Done" is pressed control will be passed to called subroutine which will then copy the clipboard to the Excel file.
The problem I am having is that I don't seem to have correct code needed to pass workbook object in created in the MAIN MODULE to the form using a form property.
Below is the code. I have inserted comments in the code in the Select_Sentence_Form_Test and cmdDone subroutines where I am having the problem passing the workbook object.
Any assistance in resolving these problems is appreciated. As VBA programming is a sideline for me I am still learning.
Option Explicit
Public str_clipboard As String
Public txt_active_document As String
Public i_how_many_sentences As Integer
Public str_active_document
Public str_Excel_Filename As String
Public i_starting_row_ID As Integer
Public i_rows_of_data As Integer
MAIN MODULE
Sub Master_Module_Test()
Dim oExcel As Object
Dim wb As Excel.Workbook 'Object
Set oExcel = New Excel.Application
str_Excel_Filename = "C:\Test\Excel_Template.xlsx"
Documents.Open ("C:\Test\Doc_to_process.docx")
oExcel.Workbooks.Open str_Excel_Filename
i_how_many_sentences = 0
i_starting_row_ID = 1
Set wb = ActiveWorkbook
Call Select_Sentence_Form_Test(wb)
End Sub
Sub Select_Sentence_Form_Test(wb_open As Workbook)
Dim frm As frmModelessForInput
Set frm = New frmModelessForInput
' At this point wb_open is instantiated, i.e. I get "Excel_Template.xlsx" for wb_open.name
' But this code gives me an error "Object or block not set". If I leave this statement out then "Me.Book1.ActiveSheet.PasteSpecial..." in cmd_Done in the frmModelessForInput module gives the same error
Set frmModelessForInput.Book1 = wb_open
With frmModelessForInput
.str_word_doc_filename = str_active_document
.str_no_copied = "0"
.Show False
End With
Set frm = Nothing
End Sub
Below is the code for the frmModelessForInput form
Option Explicit
Private m_Book1 As Workbook
Public Property Get Book1() As Workbook
Set Book1 = m_Book1
End Property
Public Property Set Book1(ByRef Value As Workbook)
Set m_Book1 = Value
End Property
Private Sub UserForm_Activate()
'Position the form near the top-left of the window
'So that the user can work with the document
Me.Top = Application.ActiveWindow.Top + 15
Me.Left = Application.ActiveWindow.Left + 15
End Sub
Private Sub cmdContinue_Click()
Dim str_clipboard, str_clipboard_line As String
Call Highlight_Sentence(str_clipboard_line)
i_how_many_sentences = i_how_many_sentences + 1 'Problem: VBA reports i_how_many_sentences as undefined even though it is a Public variable
frmModelessForInput.str_no_copied = i_how_many_sentences 'Same Problem
str_clipboard = str_clipboard + str_clipboard_line 'Problem: each time I select a new text/sentence str_clipboard does not contain the contents of the previous selection
End Sub
Private Sub cmdCancel_Click()
b_abort = True
Unload frmModelessForInput
End Sub
Private Sub cmdContinue_Click()
Dim str_clipboard_line As String
Dim i_row_number As Integer
i_row_number = i_how_many_sentences + i_starting_row_ID
i_how_many_sentences = i_how_many_sentences + 1
Call Highlight_Sentence_Test(str_clipboard_line, i_row_number)
frmModelessForInput.str_no_copied = i_how_many_sentences
str_clipboard = str_clipboard + str_clipboard_line
End Sub
Private Sub cmdDone_Click()
' When the "Done" button is pressed the contents of the str_clipboard variable is copied to the Windows clipboard
Dim str_filename As String
str_filename = getName(str_Excel_Filename)
MsgBox "I will now copy the data to the Excel file."
Call SendToClipboard(str_clipboard) 'This sub puts the contents of str_clipboard into the Windows clipboard
' Uses the Book1 property --- this is the code line that produces the error "Object or block variable not set. Activeworkbook.name shows that Excel_Template.xlsx is active
Me.Book1.ActiveSheet.PasteSpecial Format:="Text", Link:=False, DisplayAsIcon:=False, NoHTMLFormatting:=True
Unload frmModelessForInput
MsgBox "Done pasting data to: " & getName(str_Excel_Filename)
End Sub
Private Sub Highlight_Sentence(clipboard As String, i_row_no As Integer)
Dim txt_sentence, txt_page_no As String
Dim txt_section_index As String
With Selection
' Collapse current selection.
.Collapse
' Expand selection to current sentence.
.Expand Unit:=wdSentence
End With
clipboard = i_row_no & vbTab & txt_sentence & vbCrLf
End Sub

Excel VBA how to link a class and a control?

I am using Excel 2003 with VBA, I am dynamically creating check box controls on a sheet and want to link the VBA controls to a class so that when a user clicks on a checkbox an event is fired so I can do something.
From what I've read it would seem that creating a user class is the solution, but having tried this I can't get it to work.
My user class looks like this:
Option Explicit
Public WithEvents cbBox As MSForms.checkbox
Private Sub cbBox_Change()
MsgBox "_CHANGE"
End Sub
Private Sub cbBox_Click()
MsgBox "_CLICK"
End Sub
My code to create the checkboxes:
For Each varExisting In objColumns
'Insert the field name
objColumnHeadings.Cells(lngRow, 1).Value = varExisting
'Insert a checkbox to allow selection of the column
Set objCell = objColumnHeadings.Cells(lngRow, 2)
Dim objCBclass As clsCheckbox
Set objCBclass = New clsCheckbox
Set objCBclass.cbBox = ActiveSheet.OLEObjects.Add( _
ClassType:="Forms.CheckBox.1" _
, Left:=300 _
, Top:=(objCell.Top + 2) _
, Height:=10 _
, Width:=9.6).Object
objCBclass.cbBox.Name = "chkbx" & lngRow
objCBclass.cbBox.Caption = ""
objCBclass.cbBox.BackColor = &H808080
objCBclass.cbBox.BackStyle = 0
objCBclass.cbBox.ForeColor = &H808080
objCheckboxes.Add objCBclass
lngRow = lngRow + 1
Next
The checkboxes are visible in the sheet, but when I click on them, no message box is displayed so the link to the class doesn't seem to be working.
Why?
Edit...If after adding the checkboxes I go into the VB IDE and select one of the created checkboxes from the list of controls, then select Click from the Procedure drop down list, it will insert the code for a call back which if I add a message box to this, works when I click on the same checkbox...so how can I achieve this in code? I've tried recording a macro to do this, nothing was recorded.
Edit by S.Platten, jump to the bottom for how this helped me fix the problem...
Due to some weird reason, VBA doesn't hook up the events for Sheet's ActiveX control in the same execution cycle in which they were added. So, we need to come out of the cycle which added the controls and then invoke the event adding proc in next cycle. Application.OnTime helps here.
Its seems a bit of overkill but it works :)
Option Explicit
Dim collChk As Collection
Dim timerTime
Sub master()
'/ Add the CheckBoxes First
Call addControls
'<< Due to some weird reason, VBA doesn't hook up the events for Sheet's ActiveX control in the same
'execution cycle in which they were added. So, we need to come out of the cycle which added the controls
'and then invoke the event adding proc in next cycle. >>
'/ Start Timer. Timer will call the sub to add the events
Call StartTimer
End Sub
Sub addControls()
Dim ctrlChkBox As MSForms.CheckBox
Dim objCell As Range
Dim i As Long
'Intialize the collection to hold the classes
Set collChk = New Collection
'/ Here Controls are added. No Events, yet.
For i = 1 To 10
Set objCell = Sheet1.Cells(i, 1)
Set ctrlChkBox = Sheet1.OLEObjects.Add( _
ClassType:="Forms.CheckBox.1" _
, Left:=1 _
, Top:=(objCell.Top + 2) _
, Height:=objCell.Height _
, Width:=100).Object
ctrlChkBox.Name = "chkbx" & objCell.Row
Next
End Sub
Sub addEvents()
Dim ctrlChkBox As MSForms.CheckBox
Dim objCBclass As clsCheckBox
Dim x As Object
'Intialize the collection to hold the classes
Set collChk = New Collection
'/ Here we assign the event handler
For Each x In Sheet1.OLEObjects
If x.OLEType = 2 Then
Set ctrlChkBox = x.Object
Set objCBclass = New clsCheckBox
Set objCBclass.cbBox = ctrlChkBox
collChk.Add objCBclass
Debug.Print x.Name
End If
Next
'/ Kill the timer
Call StopTimer
End Sub
Sub StartTimer()
timerTime = Now + TimeSerial(0, 0, 1)
Application.OnTime EarliestTime:=timerTime, Procedure:="addEvents", _
Schedule:=True
End Sub
Sub StopTimer()
On Error Resume Next
Application.OnTime EarliestTime:=timerTime, Procedure:="addEvents", _
Schedule:=False
End Sub
Class Module: clsCheckBox
Option Explicit
Public WithEvents cbBox As MSForms.CheckBox
Private Sub cbBox_Change()
MsgBox "_CHANGE"
End Sub
Private Sub cbBox_Click()
MsgBox "_CLICK"
End Sub
Edit continued...
The class (clsCheckbox):
Option Explicit
Public WithEvents cbBox As MSForms.checkbox
Private Sub cbBox_Click()
MsgBox "_CLICK"
End Sub
Module1
Public objCheckboxes As Collection
Public tmrTimer
Public Sub addEvents()
Dim objCheckbox As clsCheckbox
Dim objMSCheckbox As Object
Dim objControl As Object
Set objCheckboxes = New Collection
For Each objControl In Sheet1.OLEObjects
If objControl.OLEType = 2 _
And objControl.progID = "Forms.CheckBox.1" Then
Set objMSCheckbox = objControl.Object
Set objCheckbox = New clsCheckbox
Set objCheckbox.cbBox = objMSCheckbox
objCheckboxes.Add objCheckbox
End If
Next
Call stopTimer
End Sub
Public Sub startTimer()
tmrTimer = Now + TimeSerial(0, 0, 1)
Application.OnTime EarliestTime:=tmrTimer _
, Procedure:="addEvents" _
, Schedule:=True
End Sub
Public Sub stopTimer()
On Error Resume Next
Application.OnTime EarliestTime:=tmrTimer _
, Procedure:="addEvents" _
, Schedule:=False
End Sub
The code in the sheet that adds the controls:
Dim objControl As MSForms.checkbox
For Each varExisting In objColumns
'Insert the field name
objColumnHeadings.Cells(lngRow, 1).Value = varExisting
'Insert a checkbox to allow selection of the column
Set objCell = objColumnHeadings.Cells(lngRow, 2)
Set objControl = ActiveSheet.OLEObjects.Add( _
ClassType:="Forms.CheckBox.1" _
, Left:=300 _
, Top:=(objCell.Top + 2) _
, Height:=10 _
, Width:=9.6).Object
objControl.Name = "chkbx" & lngRow
objControl.Caption = ""
objControl.BackColor = &H808080
objControl.BackStyle = 0
objControl.ForeColor = &H808080
lngRow = lngRow + 1
Next
This isn't the entire project, but enough to demonstrate the workings.
You are currently using ActiveX controls. Yet, ActiveX controls are bound to specific naming conventions. For example: if you insert an ActiveX button onto a sheet and name it btnMyButton then the sub must be named btnMyButton_Click. The same applies to checkboxes. If you insert a new checkbox with the name CheckBox2 then the sub's name must be CheckBox2_Click. In short, there cannot be a sub with the name cbBox_Change associated to any ActiveX checkbox.
So, what you really need (with ActiveX controls) is a way to change the VBA code on a sheet. But thus far I have never come across any such code (VBA code to change VBA code on a sheet).
A much easier route would be if you'd be willing to use form controls instead.
The following sub will create a (form control) checkbox and assign the macro tmpSO to it. The sub tmpSO (unlike subs for ActiveX controls) does not need to reside on the sheet but can be in any module.
Sub Insert_CheckBox()
Dim chk As CheckBox
Set chk = ActiveSheet.CheckBoxes.Add(390.75, 216, 72, 72)
chk.OnAction = "tmpSO"
End Sub
Since a from control is calling the sub tmpSO you can use Application.Caller in that sub and thereby know which checkbox has been calling this sub.
Sub tmpSO()
Debug.Print Application.Caller
End Sub
This will return the name of the CheckBox. So, you can use this one sub for all of your checkboxes any dynamically handle them based on their names (possibly using a Case Select).
Here is another example for tmpSO:
Sub tmpSO()
With ThisWorkbook.Worksheets(1).CheckBoxes(Application.Caller)
MsgBox "The checkbox " & Application.Caller & Chr(10) & _
"is currently " & IIf(.Value = 1, "", "not") & " checked."
End With
End Sub

Resources