How do I prevent control passing to the next Userform textbox? - vba

I have a Userform with multiple Textboxes. All the Textboxes are in tab-order.
Each subsequent Textbox is back colored yellow to indicate to the user which is the next Textbox to complete.
If the Textbox Value is determined to be invalid I want the control to SetFocus back on that particular Textbox. However, control is automatically handed over to the next Textbox in the tab-order.
When I try to focus back on the required Textbox with the mouse, this fires an event on the next Textbox, which follows the rules of my program and requests the user to enter a valid value.
Below is a sample of two Textboxes, if the user fails to enter a first name I want the control to return to the tbxCustomerFirstName Textbox, however, the control is handed over to the tbxCustomerSurName Textbox, even though I've "tbxCustomerFirstName.SetFocus".
AstFlag = 2 means there has to be a valid value in the Textbox.
AstFlag = 1 means the Textbox value can be blank.
I stepped through the program and AstFlag does indeed = 2, and the set focus code is executed.
'====================================================================================
'
' Customer First Name
'
Private Sub tbxCustomerFirstName_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Call tbxValues(3)
If tbxCancel = True Then
Cancel = True
End If
If AstFlag = 2 Then
tbxCustomerFirstName.SetFocus
End If
End Sub
'====================================================================================
'
' Customer Surname
'
Private Sub tbxCustomerSurName_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Call tbxValues(4)
If tbxCancel = True Then
Cancel = True
End If
If AstFlag = 2 Then
tbxCustomerSurName.SetFocus
End If
End Sub

You want to set the Cancel parameter to True when you mean to cancel exiting the control.
I don't know what your tbxCancel flag logic is, but...
If AstFlag = 2 Then
theControlYouAreExiting.SetFocus
End If
Should be
If AstFlag = 2 Then
Cancel.Value = True
End If
That will prevent exiting the control, so there's no need to SetFocus on anything. Now again I don't know what tbxCancel is supposed to be doing, but it doesn't look right.
In fact I would scrap all this painfully duplicated conditional logic, and implement some Validate method that returns True if the field is valid (and thus can be exited) and False if the field is invalid (and thus Cancel.Value must be True and focus remains on the control).
Cancel.Value = Not Validate(args)
Where args could be theControlYouAreExiting.Tag, and then you could use the controls' Tag property to hold the metadata you're likely currently hard-coding in tbxValues.
Ultimately the problem you're solving is going to create a mess no matter how you put it, because everything is happening in the form's code-behind: you want to separate the View (the form/UI) from the Model (the data it's manipulating).
The solution is to create a class module that represents your model. This model exposes a property that each control on your form manipulates, and then the model itself knows how to validate each property.
Option Explicit
Private model As CustomerModel
Private Sub UserForm_Initialize()
Set model = New CustomerModel
End Sub
Public Property Get CustomerModel() As CustomerModel
Set CustomerModel = model
End Property
Public Property Set CustomerModel(ByVal value As CustomerModel)
Set model = value
End Property
Private Sub tbxCustomerSurName_Change()
model.Surname = tbxCustomerSurName.Text
End Sub
Private Sub tbxCustomerSurName_Exit(ByVal Cancel As MSForms.ReturnBoolean)
Cancel.Value = Not model.IsValidSurname
End Sub
And with that you don't need to maintain flags and state switches to keep track of what the metadata is for the control you're in.

Related

How to develop a class module for a keyboard shortcut

I have a custom button on a ribbon, and I want to create a keyboard shortcut using a class module. I have managed to start it, but I am not able to take the next step, which would be to perform the action. I give you the code:
`
Option Compare Database
Option Explicit
Private WithEvents Form As Access.Form
Private mPresionarTecla As Boolean
Public Property Get PresionarTecla() As Boolean
PresionarTecla = mPresionarTecla
End Property
Public Property Let PresionarTecla(ByVal vNewValue As Boolean)
mPresionarTecla = vNewValue
End Property
Public Sub InitalizeAutokeys(FName As Form)
Set Form = FName
mPresionarTecla = True
If mPresionarTecla = True Then
FName.OnKeyDown = "[Event Procedure]"
End If
End Sub
Private Sub frmCustomForm_KeyDown(KeyCode As Integer, Shift As Integer)
If mPresionarTecla = True Then
rbPegarSinFormato (Screen.ActiveControl)
End If
End Sub
`
I want to create a keyboard shortcut to execute a button on a ribbon. The ribbon button is a procedure created by me, which applies only to formatted text fields, to paste without formatting.
So that you understand me, in the toolbar you have a button, which can be executed by means of the combination of letters control + v. I want to achieve something similar.
Now if I do this through a macro, it's very limited because I can only do a few things, and with VBA I can do a lot more. That is, if I want to add conditions or, most importantly, execute a procedure, not a function, a macro is not worth it. Also, I can select which fields I want to apply it to, that is, since the function I'm using is to copy without formatting, it doesn't make sense for numeric fields to be applied

Find open subform

I have a subform with a button that opens another form.
On the secondary form, the user can select an address.
The selected address should be applied to the calling form.
I pass the window handle when opening the child form.
But when it tries to find the calling form in the Forms collection, it isn't there.
I suspect that is because the calling form is actually a subform.
I don't know where to go from here.
Calling the form, passing the windows handle
OpenCCCustAddr [CustFID], "CCInt", Me.hWnd
In the Form Close event, I try to set the address values on the calling form,
but GetFormByHWND returns null.
Set frm = GetFormByHWND(Me!txtCallingHWND)
// Me!txtCallingHWND here is populated and looks reasonable
frm!BillStreet = strAddr // This blows up since frm is null
frm!HolderZipCode = strZip
frm!AddressUpdated = -1
Set frm = Nothing
Public Function GetFormByHWND(lngHWND As Long) As Form
Dim frm As Form
Dim nm As String
Select Case lngHWND
Case 0
Case Else
For Each frm In Forms
nm = frm.NAME // the name of the parent form shows, but not my calling subform
If frm.hWnd = lngHWND Then
Set GetFormByHWND = frm
Exit For
End If
Next
End Select
End Function
For Each and For I=0 to Count-1 both give the same results. The form just isn't in Forms. It's possible that it is because it is a subform.
I tried searching the subforms, but this blows up when I check ctl.hWnd with "Object doesn't support this property"
Public Function GetFormByHWND(lngHWND As Long) As Form
Dim frm As Form
Dim ctl As Access.Control
Dim nm As String
Select Case lngHWND
Case 0
Case Else
For Each frm In Forms
nm = frm.NAME
If frm.hWnd = lngHWND Then
Set GetFormByHWND = frm
Exit For
End If
Next
Rem If we didn't find the form, check for a subform
If GetFormByHWND Is Nothing Then
For Each frm In Forms
nm = frm.NAME
For Each ctl In frm.Controls
If ctl.Properties("ControlType") = acSubform Then
nm = ctl.NAME
If ctl.hWnd = lngHWND Then // Error: "Object doesn't support this property"
Set GetFormByHWND = ctl
Exit For
End If
End If
Next
Next
End If
End Select
End Function
As #June7 pointed out, my mistake was assuming that the control was the form. Instead is has a form.
So the proper solution is
Rem If we didn't find the form, check for a subform
If GetFormByHWND Is Nothing Then
For Each frm In Forms
For Each ctl In frm.Controls
If ctl.Properties("ControlType") = acSubform Then
If ctl.Form.hWnd = lngHWND Then // note the change here
Set GetFormByHWND = ctl.Form
Exit For
End If
End If
Next
Next
End If
First, it not at all clear why all that code and hwn stuff is required?
We assume that you have a form.
On that form, you have a button, and it launches the 2nd form.
so, in first form, we have this:
' write data to table before launching form
If Me.Dirty = True Then Me.Dirty = False
DoCmd.OpenForm "formB"
Ok, now in formB on-load event, we have this:
Option Compare Database
Option Explicit
Dim frmPrevious As Form
Dim frmPreviousSub As Form
Private Sub Form_Load()
Set frmPrevious = Screen.ActiveForm
Set frmPreviousSub = frmPrevious.MySubFormControl.Form
' do whatever
End Sub
So now we have both a reference to the previous form, and also the sub form.
Say the user selects some address and hits the ok button.
The code then does this:
frmPreviousSub!AddressID = me!ID ' get/set the PK address ID
docmd.Close acForm, me.name
So no need for all that world poverty, grabbing and looping hwnd or any such hand stands.
Just a few nice clean lines of code.
Now, I DO HAVE a recursive loop that will return the form handle ALWAYS as a object reference, and you thus don't even have to hard code the form(s) name.
So, say that main form had 2 sub forms, and on those to sub forms, you have
A Company address, and a ship to address. So, you want to launch form B from EITHER of these two sub forms, and when you select a address, you return that value, and thus two or even potential 3 sub forms could in fact call this way call pop up address selector form.
The way you do this is similar to the above code, but we do NOT need to hard-code the sub form.
The code will now look like this:
Private Sub Form_Load()
Dim f As Form
Set f = Screen.ActiveForm ' pick this up RIGHT away -
' previous active form only valid
' in open/load event
' we have the previous active form, get the sub form.
Set frmPrevous = GetSubForm(f)
End Sub
Note that the sub form can be 3 or even 5 levels deep. This routine is "recursive". It grabs the previous form, then checks if a sub form has focus. If the sub form has focus, then it gets that control, and if that control is a sub form, then it just keeps on going until such time we drill down this rabbit hole and NO MORE drilling down can occur.
This routine should be placed outside of the form and placed in your standard "global" module of routines.
Public Function GetSubForm(f As Form) As Form
Static fs As Form
If f.ActiveControl.Controltype = acSubform Then
GetSubForm f.ActiveControl.Form
Else
Set fs = f
End If
Set GetSubForm = fs
End Function
So note how if it find that a sub form has the focus? Well then it just calls itself again with that form and keeps on drilling down. As a result, it don't matter if the form is 1 or 5 levels deep. The resulting "frmPrevous" will be a valid reference to that sub form, and thus after you select or do something in the supposed popup form? You can set the value of some PK or whatever and then close the form.
There is no hwnd, very clean code, and the recursion trick means that even for nested sub forms more then one deep, your frmPrevious is in fact a reference to the form that launch the form we pop up for the user to select whatever.
If you don't have a common Address ID column? Then our popup form should ASSUME that you always have a public variable defined in the calling form.
Say ReturnAddressID as long
Make sure you dim the value as public, say like this:
Public ReturnAddressID as long
So, now in our popup form, we can do this:
frmPrevious.ReturnAddressID = me!PD
frmPrevous.MyUpdate
(we assume that all forms that call the popup also have a public function names MyUpdate.
Thus, now we have a generalized approach, and 2 or 10 different address forms, even as sub forms can now call the one address picker. As long as any of those forms adopts that public ReturnAddressID and a public function MyUpdate, then we can pass back the values, and MyUpdate will shove/take/set the ReturnAddressID into whatever column and value you use for the Address ID in that sub form.
And of course if there are no sub forms, the routine will just return the top most form that called the pop up form.

Checkbox in UserForm on startup not working

I have a userform with 2 dropdowns which I normally not need so I build a checkbox and hide the dropdown menu and the text label. The checkbox is checked by default. But the dropdown and label isn't hidden when I start the UserForm. When I manually uncheck and check the checkbox after UserForm is started it's working. So I dont know why the checkbox seems to work but I need to uncheck/check it manually after every start of the UserForm.
I think I have to do something at the initial start of the userform?
Private Sub SortCheckBox(blnChecked As Boolean)
Private Sub CheckBox1_Click()
ActiveDocument.Bookmarks("KurbeitragKinder").Range.Font.Hidden =
CheckBox1.Value
If CheckBox1.Value = True Then
Label8.Enabled = False
Label8.Visible = False
Label9.Enabled = False
Label9.Visible = False
ComboBox6.Enabled = False
ComboBox6.Visible = False
ComboBox7.Enabled = False
ComboBox7.Visible = False
Else
Label8.Enabled = True
Label8.Visible = True
Label9.Enabled = True
Label9.Visible = True
ComboBox6.Enabled = True
ComboBox6.Visible = True
ComboBox7.Enabled = True
ComboBox7.Visible = True
End If
End Sub
The state of the controls on a UserForm when it is first displayed will be, by default, whatever the design-time state is; let's call this the "default state".
You can define what that default state is by configuring each control's individual properties, using the designer's properties toolwindow (F4).
If the form's default instance is shown, then its state will be preserved between calls:
UserForm1.Show
...unless the instance gets reset - which can easily happen if you Unload that form, or if the user clicks the red "X" button to close it: the instance is destroyed, and since forms have a predeclared ID (aka default instance), the object is automatically re-created the next time it's referenced - with whatever the default (design-time) state is. If you handle the QueryClose event and programmatically Hide the form when CloseMode is VbQueryClose.vbFormControlMenu (and set the Cancel parameter to True, to prevent destroying the form instance and its state), then the state will be preserved ...and this can lead to unexpected or inconsistent behavior.
The solution is to make sure you always display a fresh new instance of the form, instead of the default one:
With New UserForm1
.Show
End With
That way the form's state is guaranteed to always be the default/intended design-time state every time it's displayed, and you can access the form's state between .Show and End With. All you need to do is to handle QueryClose and cancel the form's destruction when the user clicks the "form control menu" aka "the X button".
Initializing a form will raise the Initialize event; if you're using the form's default instance (i.e. UserForm1.Show), then you can't really control exactly when this happens, but if you show a fresh new instance every time (i.e. With New UserForm1), then you are certain that this event will be raised exactly once, every time you need to show the form.
The Initialize event is raised as soon as the object is created, and that happens before the first member call is made against it (i.e. when the New UserForm1 returns, the event has already executed). If you need to check a box and then initialize the form accordingly, then you might want to handle the Activate event instead, which will be raised when the form is actually displayed (i.e. when the .Show method is called):
With New UserForm1 'UserForm_Initialize runs
.CheckBox1.Value = foo 'form state is accessible here
.Show 'UserForm_Activate runs
'UserForm_QueryClose runs
foo = .CheckBox1.Value 'form state is accessible here
End With 'UserForm_Terminate runs
Looks like you want to run that CheckBox1_Click handler before the form is shown - problem is, event handlers aren't Public, and you don't want them to be.
The solution is to pull the logic into a Public Sub, invoke that procedure from the client code, and invoke it from the checkbox':
Public Sub InitializeFormState()
Dim isChecked As Boolean
isChecked = CheckBox1.Value
ActiveDocument.Bookmarks("KurbeitragKinder") _
.Range.Font.Hidden = isChecked
Label8.Enabled = isChecked
Label8.Visible = isChecked
Label9.Enabled = isChecked
Label9.Visible = isChecked
ComboBox6.Enabled = isChecked
ComboBox6.Visible = isChecked
ComboBox7.Enabled = isChecked
ComboBox7.Visible = isChecked
End Sub
Private Sub CheckBox1_Click()
InitializeFormState
End Sub
And now your client code can look like this:
With New UserForm1
.InitializeFormState
.Show
'consume form state here
End With
Or, you can invoke InitializeFormState from the form's Initialize or Activate handler, as needed:
Private Sub UserForm_Initialize()
InitializeFormState
End Sub
Or
Private Sub UserForm_Activate()
InitializeFormState
End Sub
In which case the procedure should probably be made Private, and there's no need to invoke it from the client code before the .Show method.

Return a value from CommandButton1_Click() Sub in Outlook VBA

Please note this question is related to Outlook
I am an amateurish programmer in VB in outlook
I want to have a custom Message Box with button captions as 'Send Anyway' and 'Don't Send'.
But with the existing message box changing text is not possible.
So I made a custom form. Now I want to return a Boolean value from the CommandButton1_Click() Sub
This is my main sub which call the form:
Public Result1 As Boolean
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Const MAX_ITEM_SIZE As Long = 5242880
Result1 = True
Dim FileSize As Long
For Each Item In Item.Attachments
FileSize = FileSize + Item.Size
Next
If FileSize > MAX_ITEM_SIZE Then
UserForm1.Show
'Cancel = True
Cancel = Result1
End If
End Sub
This is my code for click event handler:
Private Sub CommandButton1_Click()
Unload Me
End Sub
Please advise on how to achieve custom captions on MsgBox Buttons in Outlook
I'll use the Tag property of the UserForm object to pass back a value from it to its calling sub
this means you'll want to use UserForm Hide() method rather then Unload it from within its code pane, not to loose its state, i.e. all its properties values (and methods calling),
So I'd go like follows:
give meaningful names to your Userform1 button
for instance, let's rename
SendBtn, the button that has the "Send Anyway" caption
DoNotSendBtn, the button that has the "Don't Send" caption
you can actually use whatever name you want (even CommandButton1 and CommandButton2 would do), but be consistent with chosen names for their corresponding event handlers names
assign them the following click event handlers
Private Sub DoNotSendBtn_Click() '<--| change "DoNotSendBtn" to your actual chosen button name
Me.Tag = "True" '<--| store in userform 'Tag' property the value that will be read to cancel the email sending
Me.Hide '<-- this will hide the userform, thus not loosing its "state" -> 'Tag' property will still be available to the calling sub
End Sub
Private Sub SendBtn_Click()'<--| change "SendBtn" to your actual chosen button name
Me.Tag = "False" '<--| store in userform 'Tag' property the value that will be read to let the email sending go on its way
Me.Hide '<-- this will hide the userform, thus not loosing its "state" -> 'Tag' property will still be available to the calling sub
End Sub
finally, change your ItemSend event handler like follows
Option Explicit
Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean)
Const MAX_ITEM_SIZE As Long = 5242880
Dim FileSize As Long
For Each Item In Item.Attachments
FileSize = FileSize + Item.Size
Next
If FileSize > MAX_ITEM_SIZE Then
UserForm1.Show '<--| show the userform
Cancel = UserForm1.Tag = "True" '<--| 'Cancel' will be set to 'True' if the userform TAG property value is "True", otherwise it'll be set to 'False'
Unload UserForm1 '<--| now unload the Userform (and loose its "state", which you don't need any more)
End If
End Sub

Keeping track of last used ListBox - VBA

I'm currently working with forms in VBA, and want to use one form to modify some values in another form. So I have Form1, which has three ListBoxes that hold a bunch of items each. Then I have Form2, which has a non-modifiable TextBox that will contain the value of the selected ListBox item that the user wants to edit.
However, since I have multiple ListBoxes, I want to know which ListBox I last clicked on, so that I can draw the selected item from that list box and edit that item when the user clicks "Apply" in Form2.
I've looked created a property that will keep track of the name of the last ListBox. Thing is, I'm having trouble using it. Here is my code:
Public Property Get LastClicked() As ListBox
LastClicked = LastListBox
End Property
Public Property Let LastClicked(boxName As ListBox)
LastListBox = CStr(boxName)
End Property
Private Sub FirstNameTextBox_Change()
If (FirstNameTextBox.ListIndex <> -1) Then
EditButton.Enabled = True
Else
EditButton.Enabled = False
End If
End Sub
Private Sub FirstNameTextBox_Click()
LastClicked (FirstNameTextBox)
End Sub
Private Sub LastNameTextBox_Click()
LastClicked (LastNameTextBox)
End Sub
When I attempt to set the property with the name of the listbox, it brings back an error:
"Invalid use of property"
I assume this means i'm passing in the wrong value, but I don't know what other value to pass in. I'd appreciate any help I can get on this.
2 problems. First, LastClicked is a property, not a method. That means you need to assign it a value, not pass it a parameter. Second, properties that expose Objects need to use Property Set instead of Property Let. Property Let is only for primatives. Try something like this:
Option Explicit
Private LastListBox As ListBox
Public Property Get LastClicked() As ListBox
Set LastClicked = LastListBox
End Property
Public Property Set LastClicked(boxName As ListBox)
Set LastListBox = boxName
End Property
Private Sub FirstNameTextBox_Change()
If (FirstNameTextBox.ListIndex <> -1) Then
EditButton.Enabled = True
Else
EditButton.Enabled = False
End If
End Sub
Private Sub FirstNameTextBox_Click()
Set LastClicked = FirstNameTextBox
End Sub
Private Sub LastNameTextBox_Click()
Set LastClicked = LastNameTextBox
End Sub