Excel VBA - GotFocus/LostFocus event handler for dynamically added ActiveX Object - excel

I have created a tool using Excel to gather inputs from a user and use it to do some processing of data. I have created a UI on a worksheet with a bunch of ActiveX controls (TextBox, ListBox, ComboBox).
Part of the ActiveX controls are dynamic - they are added at run time based on "metadata" that the tool admin creates on a second worksheet. Metadata contains the field name, type of ActiveX control, position of the control, ListRange to populate values, Multi-Text/Multi-Select flag, etc.
I am able to successfully add the ActiveX controls to the UI worksheet. However, now I want to add functionality for ActiveX TextBox controls to show a default text, when the control gets focus - default text gets removed, when the control loses focus - if user has entered any data it remains otherwise the default text shows up again.
Public Sub df_segment_GotFocus()
Dim wb As Workbook
Set wb = ThisWorkbook
Set form_sheet = Worksheets(Sheet1.Name)
If form_sheet.OLEObjects("df_segment") Is Nothing Then
Else
'When user begins to type, remove the help text and remove Italics
Dim seg_val As String
seg_val = form_sheet.OLEObjects("df_segment").Object.Value
If seg_val = "e.g. Desktop-Mac,Desktop-Win,Mobile-OSX" Then
form_sheet.OLEObjects("df_segment").Object.Font.Italic = False
form_sheet.OLEObjects("df_segment").Object.Value = ""
Else
form_sheet.OLEObjects("df_segment").Object.Value = seg_val
End If
End If
End Sub
Public Sub df_segment_LostFocus()
Dim wb As Workbook
Set wb = ThisWorkbook
Set form_sheet = Worksheets(Sheet1.Name)
If form_sheet.OLEObjects("df_segment") Is Nothing Then
Else
'Incase user doesn't enter any values, show the help text again
Dim seg_val As String
seg_val = form_sheet.OLEObjects("df_segment").Object.Value
If seg_val = "" Then
form_sheet.OLEObjects("df_segment").Object.Font.Italic = True
form_sheet.OLEObjects("df_segment").Object.Value = "e.g. Desktop-Mac,Desktop-Win,Mobile-OSX"
Else
form_sheet.OLEObjects("df_segment").Object.Value = seg_val
End If
End If
End Sub
In the sample code above, you can see that I am using the exact name of the control to setup the GotFocus and LostFocus event handlers. However, since my UI is metadata driven, the controls will be added/removed dynamically and I wouldn't know the name of the controls to explicitly add the event handlers.
I looked up the forums and implemented this:
a.) Implemented a Class Module
Public WithEvents df_TextBox As MSForms.TextBox
Public df_TextBox_Name As String
Private Sub df_TextBox_Change()
Dim wb As Workbook
Set wb = ThisWorkbook
Set form_sheet = Worksheets(Sheet1.Name)
Set metadata_sheet = Worksheets(Sheet2.Name)
Dim obj_name As String
obj_name = df_TextBox_Name
obj_val = form_sheet.OLEObjects(obj_name).Object.Value
MsgBox "Change in TextBox" & obj_val
End Sub
b.) Created objects for the Class where I instantiate the control objects
ElseIf d_Type = "TextBox" Then
df_obj.Object.Value = d_def_val
df_obj.Object.Font.Italic = True
If d_Multi = 1 Then
df_obj.Object.MultiLine = True
End If
'--------------------------------------------------------------
'part where we add the custom events for GotFocus and LostFocus
'--------------------------------------------------------------
ReDim Preserve TextBox_Event_Array(1 To i)
Set TextBox_Event_Array(i).df_TextBox = df_obj.Object
TextBox_Event_Array(i).df_TextBox_Name = df_obj.Name
Problem Statements
1.) When I create the class module, I don't see the GotFocus and LostFocus events available. Only Change, KeyDown/Press/Up, MouseDown/Move/Up
2.) I created a Change event handler just to test the Class Module but I do not see it getting triggered.
Any suggestions on how can I fix the problem or any alternate solutions?

Related

How to link to a macro-enabled Excel file from an Access Unbound Objectframe

I want to link a "TimePicker" housed on an Excel macro enabled spreadsheet (it has three textboxes for hrs, min, AM/PM with associated SpinnerButtons [SBs] operating with SB_Change events code) to an unbound Objectframe (OLE1) of an Access form. The user will select the time settings which will then be transferred to the appropriate Access form textbox. I'm using a command button click routine to perform the link to the Objectframe. Ideally I would like to use the TimePicker within OLE1. On running the code the textboxes appear in OLE1, but not the SBs. Also the code quits running as soon as it hits the .Action line.
I like the idea of using the OLE1 object, but it is such a "black box" with all the different properties specified with only strings. What am I missing? Thank you for any and all suggestions. The code:
Private Sub GetTimePicker_Click()
Dim xlApp As Excel.Application
Dim xlWB As Excel.Workbook
Dim obOLE1 As ObjectFrame
Set xlApp = CreateObject("Excel.Application")
Set xlWB = xlApp.Workbooks.Open(" [filepath] \TimePicker2.xlsm") ' specify file
Set obOLE1 = Me.OLE1
xlApp.Visible = True
With obOLE1
.SourceDoc = " [filepath] /TimePicker2.xlsm"
.Class = "Excel.SheetMacroEnabled.12"
.OLETypeAllowed = acOLEEither
.AutoActivate = 0 'vbOLEActivateManual
.SetFocus
.SourceItem = "Shapes.Range(Array('txtHr', 'txtMin', 'txtAPM', 'SP1', 'SP2', 'SP3', 'Label1'))"
.Verb = acOLEVerbOpen
.Action = acOLELinked
End With
End Sub

Excel ControlButton Pasteface

I have an excel sheet that makes a custom toolbar. Within this toolbar, I have several buttons that use faceIDs, which are no problem. I have a few buttons where I wanted to use custom icons. When I use the custom icons, sometimes the PasteFace works, sometimes it does not, and I get an error. I added an On Error Resume next statement, to see what was happening. Sometimes both buttons paste ok, sometimes just one button, sometimes neither button. I can find no pattern to it working or not working.
My toolbar consists of approximately 24 buttons, and one drop down box. Eleven of the buttons use a custom icon, the remainder use a FaceID. Some buttons are toggled to display or not display based on user needs. The example below shows 2 buttons that will toggle to turn a meal penalty on or off. I only picked these 2 buttons because these are first buttons to use a custom icon.
Sub Make_PayBar()
Dim PayBar As CommandBar
Dim NewButton As CommandBarButton
'Delete existing PayBar toolbar if it exists
Call Kill_PayBar
ThisWorkbook.Activate
On Error Resume Next
'Create New PayBar
Set PayBar = Application.CommandBars.Add(Name:="Payroll Bar", Temporary:=True)
PayBar.Visible = True
PayBar.Position = msoBarTop
... Missing Code, More buttons ...
'Meal Penalty Off Button
Set NewButton = PayBar.Controls.Add(Type:=msoControlButton, Parameter:="Meal_Off")
NewButton.Caption = "Meal Penalty Off"
NewButton.OnAction = "ToggleMeal"
'NewButton.FaceId = 1254 'Backup if pasteface fails
Sheets("Pics").Shapes("Meal_Off").Copy
NewButton.PasteFace
'Meal Penalty On Button
Set NewButton = PayBar.Controls.Add(Type:=msoControlButton, Parameter:="Meal_On")
NewButton.Caption = "Meal Penalty On"
NewButton.OnAction = "ToggleMeal"
'NewButton.FaceId = 1253 'Backup if pasteface fails
Sheets("Pics").Shapes("Meal_On").Copy
NewButton.PasteFace
NewButton.Visible = False
... Missing Code, More buttons ...
End sub
If the Resume Next is not used, the error may occurs on either of the two pasteface statements.
Is there something in my code making PastFace unreliable?
If PasteFace is inherently unreliable, is there a way to check for a successful paste, and repeat if it wasn't?
Is there a better way to do this?
Worksheets have hidden collections that hold pictures and ActiveX controls. The VBA also has a hidden Picture type. Pictures have a CopyPicture method that is more consistent then `Shape.Copy.
Press F2 to open the Object Browser, right click and choose Show Hidden Members
Function picMeal_Off() As Picture: Set picMeal_Off = ThisWorkbook.Worksheets("Pics").Pictures("Meal_Off"): End Function
Function picMeal_on() As Picture: Set picMeal_on = ThisWorkbook.Worksheets("Pics").Pictures("Meal_on"): End Function
Sub Make_PayBar()
Dim PayBar As CommandBar
Dim NewButton As CommandBarButton
Set PayBar = Application.CommandBars.Add(Name:="Payroll Bar", Temporary:=True)
PayBar.Visible = True
PayBar.Position = msoBarTop
Set NewButton = PayBar.Controls.Add(Type:=msoControlButton, Parameter:="Meal_Off")
NewButton.Caption = "Meal Penalty Off"
NewButton.OnAction = "ToggleMeal"
picMeal_Off.CopyPicture
NewButton.PasteFace
End Sub
I created functions to refer to the Pictures by using this macro:
Sub PrintPitureDefs(ws As Worksheet)
Const BaseCode As String = "Function picCodeName() As Picture:Set picCodeName = ThisWorkbook.Worksheets(""WorksheetName"").Pictures(""PictureName""):End Function"
Dim Code As String
Dim Img As Picture
For Each Img In ws.Pictures
Code = Replace(BaseCode, "WorksheetName", ws.Name)
Code = Replace(Code, "PictureName", Img.Name)
Code = Replace(Code, "CodeName", Replace(Img.Name, " ", "_"))
Debug.Print Code
Next
End Sub

VBA Excel, Permanently Save/Update Textbox Input

How would you save or update a TextBox with its own value the user has inputted?
Example, I have a Userform with a TextBox for "password". Every user who opens the file will enter his own password in that TextBox, then save the file and reopen at any given time.
Note, the value should be saved permanently in that file (not only session based), so even after completely closing and reopening the file, the users password should be there in the Textbox until its changed again.
Passing the value to cell isn't a good idea, since its a password and shouldn't be visible.
I used so far below code, to no avail.
In the UF code for "Save" button:
Private Sub CommandButton1_Click()
SavePWStrings
Me.Hide
End Sub
In the standard module:
Public Sub SavePWStrings()
Dim pw As String
pw = UserForm1.TextBox1.Value
UserForm1.TextBox1 = pw
End Sub
Goal is to update the value of the TextBox programmatically as below.
Thanks
As I mentioned in the comments, here is an unusual way to achieve what you want. You can store the username/password in the module.
Module Setup
Insert a Module. Let's call it MyModule. Paste the below code there
Option Explicit
Private Sub UserDatabase()
'USER|sid_sid_sid|PASSWORD
'
'
'
End Sub
NOTE: Not that it matters but to make it look concise and manageable, ensure that there are no blank lines after End Sub.
|sid_sid_sid| is a separator that I am using to seperate the user from the password. Feel free to change that. Ensure that it is a unique text.
Userform Setup
And let's say your userform looks like this
Paste this code in the userform code area. The name of the textbox is txtPassword
Option Explicit
Dim proj As VBIDE.VBProject
Dim comp As VBIDE.VBComponent
Dim codeMod As VBIDE.CodeModule
Dim lineCount As Long
Dim i As Long
Dim doNotReEnter As Boolean
Dim oldPassword As String
Dim newPassword As String
'~~> Separator for Username / Password
Private Const MySep As String = "|sid_sid_sid|"
'~~> Userform Initialize Event
Private Sub UserForm_Initialize()
Dim currentUser As String
'~~> Get the username
currentUser = Environ("UserName")
lblUser.Caption = "USER :" & currentUser
'~~> Check if user exists
If DoesUserExist(currentUser) Then
'~~> If it does then get the password
txtPassword.Text = GetUserPassword(currentUser)
oldPassword = txtPassword.Text '<~~ Store current password in a variable
End If
End Sub
'~~> Login button
Private Sub CommandButton1_Click()
'~~> Get the password from the textbox
newPassword = txtPassword.Text
'~~> Check if they match. If they do then do not store else store
If newPassword <> oldPassword And Len(Trim(newPassword)) <> 0 Then
Dim modLine As String
modLine = " '" & Environ("UserName") & MySep & txtPassword.Text
Set proj = ThisWorkbook.VBProject
Set comp = proj.VBComponents("MyModule")
Set codeMod = comp.CodeModule
codeMod.InsertLines codeMod.CountOfLines - 1, modLine
End If
End Sub
'~~> Function to check if the user is there in the module
Private Function DoesUserExist(xlUser As String) As Boolean
Set proj = ThisWorkbook.VBProject
Set comp = proj.VBComponents("MyModule")
Set codeMod = comp.CodeModule
lineCount = codeMod.CountOfLines
For i = 1 To lineCount
If codeMod.Find(xlUser, i, 1, -1, -1) Then
DoesUserExist = True
Exit For
End If
Next i
End Function
'~~> Function to get the password for a user
Private Function GetUserPassword(xlUser As String) As String
Set proj = ThisWorkbook.VBProject
Set comp = proj.VBComponents("MyModule")
Set codeMod = comp.CodeModule
lineCount = codeMod.CountOfLines
For i = 1 To lineCount
If codeMod.Find(xlUser, i, 1, -1, -1) Then
GetUserPassword = Split(codeMod.Lines(i, 1), MySep)(1)
Exit For
End If
Next i
End Function
Basic Setup
In VBE, click on TOOLS|REFERENCES and check the Microsoft Visual Basic for Applications Extensibility 5.3 as shown below
Enable trust access to the VBA project object model. Click FILE | OPTIONS. In the navigation pane, select TRUST CENTER. Click TRUST CENTER SETTINGS. In the navigation pane, select MACRO SETTINGS. Ensure that Trust access to the VBA project object model is checked. Click OK.
Now if you notice that this will work when the VBA Project is Unprotected. So how do we make it work when the VBA project is Locked. For that, use the code from HERE. Port all that code inside the userform and do not keep that code in the module.
For additional security, you can encrypt the data before storing it in a module. Lot of examples in the web on how to encrypt/decrypt a string in VBA.
For demonstration purpose, I have VBA unlocked.
Sample File: The file can be downloaded from HERE

How to assign custom action to dynamically created Active-X button?

I'm setting up an Excel worksheet where I need to dynamically generate Active-X buttons and set up a different action for each one.
I get
run time error 459 "Object or class does not support the class of events"
when I launch it.
I've seen similar questions but the solutions have been given for userforms.
My current solution.
I have a custom class module WoExp_FSelect_Btn:
Public WithEvents btn As OLEObject
Public id As Integer
Dim iCount As Long
' Action to handle button click
Private Sub btn_Click()
'*** just for debug: show msgbox with id
MsgBox ("ID: " & id) 'Debug
End Sub
A collection is created with global scope to fit this kind of objects:
Public WoExp_DFileSel_Buttons As New Collection
Then I dynamically create the buttons running the following function inside a loop, i being the loop iteration:
Private Sub WoExp_AddFileSel_Btn(i As Integer)
Dim cmdbtn As OLEObject
Dim FselBtnWithEvents As WoExp_FSelect_Btn
Set cmdbtn = Worksheets("Word Report Gen").OLEObjects.Add(ClassType:="Forms.CommandButton.1", _
Link:=False, DisplayAsIcon:=False, Left:=50, Top:=80, Width:=75, _
Height:=30)
cmdbtn.Left = Worksheets("Word Report Gen").Cells(13 + i, 3).Left
cmdbtn.Top = Worksheets("Word Report Gen").Cells(13 + i, 3).Top
cmdbtn.Name = "WoExpDFileSel_Btn_" + CStr(i)
Set FselBtnWithEvents = New WoExp_FSelect_Btn
Set FselBtnWithEvents.btn = cmdbtn
FselBtnWithEvents.id = i
WoExp_DFileSel_Buttons.Add FselBtnWithEvents
End Sub
All buttons are properly shown if I comment the Set FselBtnWithEvents.btn = cmdbtn line, so I think that the problem is that OLEobject class and WithEvents don't go along.
As the error states, the OLEObject object does not support the Click event. If you go to your class module, select btn from the Object dropdown menu, and then click on the Procedure dropdown menu, you'll see that it only supports GotFocus and LostFocus.
However, when I replaced the Click event with either GotFocus or LostFocus, the same error occurred. So maybe there's some sort of bug.

Userform combobox not populating on initialize

I have a user form with three comboboxes and one text box that I would like to populate with named ranges or public variables. I am working in Excel 2010 on Windows. Here is what I have:
First I run through some code that converts one worksheet into a new configuration. I then call the userform which will set up the variables necessary to update the worksheet further. I have done this on a Mac and it works, but I am transitioning this from the mac to a windows server. I thought this would be the easy part but it in not working for some reason.
Here is the code for the userform.
Public KorS As String
Public ActivityID As String
Public Stage As String
Public varsaveme As String
Private Sub ufStageDt_Initialize()
AD = varsaveme & ".xls"
duh = KorS
Set Me.tbAdName.Text = duh
Set UserForm1.Caption = AD
'Set Me.cmbLowDt.List = "AnnDt"
Set Me.cmbHighDt.List = "AnnDt"
Set Me.cmbStage.List = "Stage"
Me.cmbLowDt.List = "AnnDt"
End Sub
The public variables are present in the code on the worksheet.
Here is the code that I used on the Mac.
Private Sub UserForm_Initialize()
Ad = varsaveme & ".xls"
duh = KorS
tbAdName.Text = varsaveme
UserForm.Caption = Ad
cmbLowDt.List = Range("AnnDt").Value
cmbHighDt.List = Range("AnnDt").Value
cmbStage.List = Range("Stage").Text
End Sub
Any assistance would be greatly appreciated. I am using the ufStageDt.Show command in the vba script to bring up the userform.
Set won't work, so eliminate that. Also, List expects an array. For a single hard-coded item, use Additem.
Me.cmbHighDt.Additem "AnnDt"
EDIT: "AnnDt" is a named range:
Me.cmbHighDt.List = Application.Transpose(ActiveSheet.Range("AnnDt"))
EDIT2: For dates:
Private Sub UserForm_Initialize()
Dim i As Long
With Me.cmbHighDt
.List = Application.Transpose(ActiveSheet.Range("AnnDt"))
For i = 0 To .ListCount - 1
.List(i) = Format(.List(i), "yyyy-mm-dd")
Next i
'to get it back to a date
ActiveSheet.Range("B1") = DateValue(.List(0))
End With
End Sub

Resources