VBA-excel: How to check if any of controls were changed - vba

Im new at VBA and wonder if there any way to check if any of UserForm controls were changed? Maybe there is some global event handler for all of controls?
For example I have a UserForm1 with some data and Controls on it. Also there is some kind of global Boolean B_bool which is False by default.
If User changes any text or click any of radio buttons, or check any of checkboxes B_bool becomes True.
I tried to create some sort of event handler and a Classmodule for each type of controls but only textbox and optionbuttons classes work fine. Other classes (checkboxes, comboboxes) swears at OnChange event (Object or class doesn't support the set of events)
Following code mostly same for every type of control, so i see no reason to flood a question
Private WithEvents MyTextBox As MSForms.TextBox
Public Property Set Control(tb As MSForms.TextBox)
Set MyTextBox = tb
End Property
Private Sub MyTextBox_Change()
B_bool = True
End Sub
and then UserForm code
Public tbCollection As Collection
Private Sub Userform_Initialize()
Set tbCollection = New Collection
For Each ctrl In Me.Controls
If TypeOf ctrl Is MSForms.TextBox Then
Set obj_tb = New clsTextBox
Set obj_tb.Control = ctrl
tbCollection.Add obj_tb
End If
Next ctrl
End Sub

You can detect if a workbook has unsaved changes with:
ActiveWorkbook.Saved
An example of how you could use this:
'Here is the code where your userform is prepared for the user...
'maybe you set default values or something like that. Once you're
'finished the changes YOU (or your VBA) are making to the workbook,
'SAVE it. This will mark a "starting point" to watch for changes...
ActiveWorkbook.Save
'And here would be your code (if any) that runs when the user is doing
'he does. blah blah blah
'When it's time to check if the user made any changes, find out with:
If ActiveWorkbook.Saved then
'no changes have been made
Else
'changes have been made
'do whatever you need to do to save your data.
End If
See the links below to learn how AutoSave may or may not affect this functionality.
More Information:
MSDN : Workbook.Saved Property (Excel/VBA)
MSDN : Workbook.Save Method (Excel)
MSDN : Workbook.AutoSaveOn Property (Excel)
MSDN : How AutoSave impacts add-ins and macros

Related

Use VBA to create a dynamic form with a button using "vbModeless"

I want to create a vbModeless dynamic user form during run-time. The user form has just one button, that's it. The form works fine using vbModal but unfortunately with vbModeless I can't get the click event for the button to work. Clicking the button does not call the event. I'm using the following steps/code:
Created an empty user form named UserForm1
Created a module named Modul1 with the following code:
Sub CreateFormControls()
'Create Command Button
Dim Button01 As MSForms.CommandButton
Set Button01 = UserForm1.Controls.Add("Forms.CommandButton.1", "dynButton01", False)
Button01.Visible = True
'Reference click event
Dim ClickEvents As New Class1
Set ClickEvents.ButtonEvent = Button01
'Show User Form
UserForm1.Show vbModeless '-> THIS DOES NOT WORK, only vbmodal works
End Sub
Created a class module named Class1 with the following code:
Public WithEvents ButtonEvent As MSForms.CommandButton
Private Sub ButtonEvent_Click()
MsgBox "Test"
Unload UserForm1
End Sub
Is there a way to get this working with vbModeless or is there a different work around?
Note: I haven't used dynamic forms very much yet, and I copied/modified the shown implementation using an existing code snippet without completely understanding how the button object references the click event e.g. why a separate class is needed and I can't do it within the procedure in Modul1. I assume within lies the reason why it doesn't work opening the form non-modal. A little light on this issue would be appreciated as well.
ClickEvents should be declared at the module level...
Option Explicit
Dim ClickEvents As New Class1 'declared at the module level
Sub CreateFormControls()
'etc
'
'
End Sub

Excel - Change BackColor of UserForm TextBoxes and ComboBoxes with VBA

I am just learning VBA and have used some code from an older book (Excel 2010). It could be that Excel 2016 had some changes that make this code not work anymore.
I do not get a compile error for the class or the Subs. The behavior is that NOTHING happens. What is supposed to happen is that the BackColor of either a ComboBox or a TextBox should change color as if is in focus or leaves focus.
As I said, for some reason when I run the code nothing happens. No errors or warnings appear so it's as if the code is running and then just doing nothing.
Here is my code. The comments should make it clear. I am hoping someone can explain to me what is going on and why this code results in no color changes as the focus changes when I tab through the UserForm.
This first block of code is a stand alone Class Module called "clsCtlColor"
Public Event GetFocus()
Public Event LostFucus(ByVal strCtrl As String)
Private strPreCtr As String
'Base Class for chaging Backcolor of ComBoxes and TextBoxes when focus is changed.
Public Sub CheckActiveCtrl(objForm As MSForms.UserForm)
With objForm
If TypeName(.ActiveControl) = "ComboBox" Or _
TypeName(.ActiveControl) = "TextBox" Then
strPreCtr = .ActiveControl.Name
'On Error GoTo Terminate
Do
DoEvents
If .ActiveControl.Name <> strPreCtr Then
If TypeName(.ActiveControl) = "ComboBox" Or _
TypeName(.ActiveControl) = "TextBox" Then
RaiseEvent LostFucus(strPreCtr)
strPreCtr = .ActiveControl.Name
RaiseEvent GetFocus
End If
End If
Loop
End If
End With
Terminate:
Exit Sub
End Sub
The following Subs are in the UserForm Code
Option Explicit
Private WithEvents objForm As clsCtlColor
'*********************************************************************************************************************
'*Subs for managing the BackColor of comboxes and TextBoxes depending on focus.***************************************
'*********************************************************************************************************************
'initializes the Userform with the clsCtlColor class
Private Sub UserForm_Initialize()
Set objForm = New clsCtlColor
End Sub
'Changes the BackColor of the Active Control when the form is activated.
Private Sub UserForm_Activate()
If TypeName(ActiveControl) = "ComboBox" Or _
TypeName(ActiveControl) = "TextBox" Then
ActiveControl.BackColor = &H99FF33
End If
objForm.CheckActiveCtrl Me
End Sub
'Changes the BackColor of the Active Control when it gets the focus.
Private Sub objForm_GetFocus()
ActiveControl.BackColor = &H99FF33
End Sub
'Changes the BackColor back to white when the control loses focus.
Private Sub objForm_LostFocus(ByVal strCtrl As String)
Me.Controls(strCtrl).BackColor = &HFFFFFF
End Sub
'Clears the objForm when the form is closed.
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Set objForm = Nothing
End Sub
In the Class Module the is an On Error Statement that terminates the Sub when an error occurs. However, I commented it out and still, I see no compile errors. So, I can only conclude it is a runtime issue.
Any help would be much appreciated.
UPDATE:
If I use these two subs on a TextBox I get the effect I'm looking for:
Private Sub TextBox1_Enter()
TextBox1.BackColor = RGB(153, 255, 51)
End Sub
Private Sub TextBox1_Exit(ByVal Cancel As MSForms.ReturnBoolean)
TextBox1.BackColor = RGB(255, 255, 255)
End Sub
What I hate about this is that my UserForm has over one hundred TextBoxes and I would need to write these two subs for each TextBox - so like 200++ Subs!
I am still trying to get the above more general approach to work.
One thing I noticed is that if I change the RGB values in the two subs above to Hex values, they no longer work. I tried changing the hex color values in the more general approach to RGB but it made no difference.
Yet Another Update:
It was pointed out that I had a typo in the class LostFucus. I changed that in two places to LostFocus. However, the code still does not work. Then the question was whether or not my code is in the userform module. It is. Then I tried an experiment. I created a new Workbook and imported the code into a brand new class and userform. I added three textboxes. Abracadabra! It worked! However, it does not work in the form I want it to work in. I have scoured the properties for the form itself and the text boxes and I can see nothing different between my form and the dummy form.
This must be something very simple I am over looking!
After a great deal of head scratching and screaming at my poor monitor I finally found the solution but as of now, I am totally disappointed in Microsoft for the weirdness of working with UserForms. Here is what fixed the problem:
I had not yet set the tab order!
I realized the tab order had my form opening with the first tab stop being set for a TextBox in a MultiPage on my form. I set the tab order so that the first TextBox is active on the UserForm and everything works with the coloring on the main body of the form.
Here is where the weirdness begins, in my opinion.
When the last TextBox on the main body of the form is reached and tab is pressed, the multi-page itself is selected. Only after you hit tab a second time is the first TextBox within the MultiPage selected and then the colors are not applied as they are in the main body of the form at all. The same scenario holds true for Frames as well. Also, there does not appear to be a good way to simply tab from the end of page 1 to the beginning of page 2.
It's very disappointing to me because I would have thought that this is not the way it is. I ASSUMED I could set up 1000 TextBoxes, use the Frames and the Multipage to organize things (SO I COULD MAINTAIN THE WINDOW AT ONE SIZE AND NOT HAVE TO SCROLL THE FORM UP AND DOWN) and then set a tab order that would navigate ALL of the TextBoxes regardless of what organizing container they are in. I assumed it would be this way because it MAKES SENSE! I want to click into the first TextBox and simply never touch my mouse until the form is completely filled out. Otherwise, there really is no point in this effort of making a UserForm! I could point and click around in the spreadsheet without the hassle of designing a form and writing code!
What a bummer!
I suppose I can "make it so!" by writing a bunch of code to jump the selection from container to container...MICROSOFT - It should not be this wonky and stupid!

Use button caption as variable excel

This has to be an obvious thing to do, but I want to use the name of a command button that is pressed in excel as a variable in a macro. The macro would be as simple as setting the value of a cell to the name of the button; So if button captioned "10" is pressed the cells value would be "10", the same macro needs to work for all numeral button captions/names.
Again, sorry if this is obvious!
Try this, works with Forms buttons but not ActiveX buttons.
Sub Button1_Click()
If Not IsError(Application.Caller) Then
Dim obj2 As Object
Set obj2 = ActiveSheet.Shapes.Item(Application.Caller)
Debug.Print obj2.AlternativeText
End If
End Sub
but your question asked about command buttons (the ActiveX variety) and this is more involved, we need to find the shape and then drill in via OLEFormat and two layers of IDispatch to get a reference to the command button, then we use WithEvents to fire event handler.
Option Explicit
'* Inside Sheet module
Private WithEvents mcmd As MSForms.CommandButton
Private Sub Initialise()
Dim obj As Object
Set obj = Me.Shapes.Item("CommandButton1")
Set mcmd = obj.OLEFormat.Object.Object
End Sub
Private Sub mcmd_Click()
Debug.Print mcmd.Caption
End Sub
Sadly you need to initialise code like this for every command button I think.

VBA: how to use class module to change value of checkbox in userform

I have a userform with a lot of emailaddresses. I want the user to be able to select who to send an email to. I do so with checkboxes which are created on run time. To make it easier to use, I have also added a checkbox which allows the user to select of deselect all checkboxes.
This works perfectly as I want it to, but there is one problem I'm breaking my head over. If all checkboxes are checked and one gets unchecked, I want the "Select all" checkbox to be unchecked as well - and vice versa, if not all checkboxes were checked and the final checkbox is being checked by the user I want the "Select all" checkbox to be checked as well.
I try to do this using a class module. My overall knowledge of vba is pretty okay, but class modules are new territory for me, so excuse me if my language gets a bit fussy now.
In the initialize event of the userform I create a new collection and I assign clicks to these specific checkboxes. That works perfectly, as it doesn't give any errors in the initialize event of the userform and an event is triggered once I click one of these checkboxes. The problem I'm having is that I can't get a grip on the "Select all" checkbox (chkSelAll) in the userform. I've tried creating a public object for this checkbox in the userform (Public objSelAll As MSForms.CheckBox), but still it gives me the "Variable not defined" error once I click one of the checkboxes.
Here's the code for the class module (cls_RIRI):
Private WithEvents chkBox As MSForms.CheckBox
Public Sub AssignClicks(ctrl As Control)
Set chkBox = ctrl
End Sub
Private Sub chkBox_Click()
If chkBox.Value = False Then objSelAll.Value = False
'^This is where the error occurs: variable not defined
End Sub
And here's the relevant part of the Userform_Initialize event:
Private colTickBoxes As Collection
Public objSelAll As MSForms.CheckBox
Private Sub UserForm_Initialize()
Dim ChkBoxes As cls_RIRI
Dim ctrl As Control
Set objSelAll = Me.Controls.Item("chkSelAll")
Set colTickBoxes = New Collection
For Each ctrl In Me.Controls
If TypeName(ctrl) = "CheckBox" And Left(ctrl.Name, 1) = "M" Then
Set ChkBoxes = New cls_RIRI
ChkBoxes.AssignClicks ctrl
colTickBoxes.Add ChkBoxes
End If
Next ctrl
Set ChkBoxes = Nothing
Set ctrl = Nothing
End Sub
As you can see I didn't get to the point yet where I let the code check if all checkboxes are checked so that the select all checkbox can be checked as well. I'm not really looking for this code, I'll probably manage it once I get grip on the select all checkbox from the class module, so please don't worry about this part! :)
Private WithEvents chkBox As MSForms.CheckBox
private strParentFormName as string
Public Sub AssignClicks(ctrl As Control,strFormName as string)
strParentFormName=strFormName
.....
end sub
Private Sub chkBox_Click()
dim f as userform
set f=userforms(0) <--- or loop the userforms to get form name
If chkBox.Value = False Then f.controls("objSelAll").Value = False
'^This is where the error occurs: variable not defined
End Sub
and something like this
Public Function GET_USERFORM(strUserform As String) As UserForm
Dim i As Integer
For i = 0 To UserForms.Count - 1
If UserForms(i).Name = strUserform Then
Set GET_USERFORM = UserForms(i)
Exit For
End If
Next i
End Function
You'll need to pass in the form also, as the variable doesnt exist in the class. Add a property for the formname or if you'll only have 1 form open, then use the open form or reference the form if multiples are open. Your class exists on it's own as a check box. I am not sure, but you may be able to get the parent object from the checkbox. Hope this helps.
private strFormName as string
Public Property Let ParentForm(value as string)
strFormname=value
End Property
then...
userforms(strFormname).controls("objSelectAll").value=true

Having an MS Office UserForm detect which subroutine called it

In a VBA project of mine I am/will be using a series of reasonably complex userforms, many of which are visually identical but have different subroutines attached to the buttons. As a result I'm not overly keen on the idea of duplicating them multiple times in order to get different functionality out of the same layout. Is it possible to have a userform detect which subroutine called it and use this in flow control? I would like to be able to do something like this:
Private Sub UserForm_Initialize()
If [the sub that called the userform is called "foo"] then
Call fooSub
else
Call barSub
End If
End Sub
My backup plan is to have the calling subroutine set a global variable flag and have the userform check that, but that seems like a rather crude and clumsy solution.
Thanks everyone,
Louis
You can use the tag property of the form. Load the form, set the property, then show the form:
Sub PassCallerToForm()
Load UserForm1
UserForm1.Tag = "foo"
UserForm1.Show
End Sub
Now that the property is set, you can determine what to do in the form:
Private Sub UserForm_Activate()
If Me.Tag = "foo" Then
Call fooSub
Else
Call barSub
End If
End Sub
You can also use public variables:
' in userform
Public Caller As String
Private Sub UserForm_Click()
MsgBox Caller
Caller = Now()
Me.Hide
End Sub
' in caller
Sub callUF()
Dim frm As New UserForm1
frm.Caller = "Test Caller"
frm.Show
MsgBox frm.Caller ' valid after Me.Hide
Set frm = Nothing
End Sub
Personally, I would not have one userform doing two disparate activities. The code would get hard to read pretty quickly, I think. Copying the layout of a userform is pretty trivial.
To copy a userform: Open a blank workbook. In the Project Explorer, drag the userform to the new workbook. Rename the userform in the new workbook. Now drag it back to the original workbook. Change the code in the userform copy.
If you absolutely don't want separate userforms, I recommend setting up a property of the userform. Userforms are just classes except they have a user interface component. In the userform module
Private mbIsFoo As Boolean
Public Property Let IsFoo(ByVal bIsFoo As Boolean): mbIsFoo = bIsFoo: End Property
Public Property Get IsFoo() As Boolean: IsFoo = mbIsFoo: End Property
Public Sub Initialize()
If Me.IsFoo Then
FooSub
Else
BarSub
End If
End Sub
I always write my own Initialize procedure. In a standard module:
Sub OpenForm()
Dim ufFooBar As UFooBar
Set ufFooBar = New UFooBar
ufFooBar.IsFoo = True
ufFooBar.Initialize
ufFooBar.Show
End Sub