Need help, VBA, Stoping a private sub event from another private sub - vba

I have private sub textbox1_keydown and private sub textbox1_afterupdate. If im finish filling up textbox1 then clicking the combobox still populates it with lists. But when im finish filling up textbox1 then pressing the enter key the combobox populates a abnormal lists. cmbo4 is a sub event where it populates the combobox based on what i input in the textbox1. My codes in on a userform
Heres my code:
Private sub TextBox1_KeyDown(ByVal KeyCode As_
MSForms.ReturnInteger,ByVal Shift As Integer)
If KeyCode=13 Then
ComboBox4.Clear
Call cmbo4
ComboBox4.DropDown
ComboBox4.SetFocus
End If
End Sub
Now i have this textbox1_afterupdate event, im adding this event to avoid errors to my program when the user wants to use the mouse to click the next combobox and not by pressing enter.
Private Sub TextBox1_AfterUpdate()
ComboBox4.Clear
Call cmbo4
ComboBox4.DropDown
ComboBox4.SetFocus
End Sub
My problem is to stop the event textbox1_afterupdate to perform when the user press the enter key so that my combobox wont have abnormal lists

Option explicit
Dim InProgress as boolean 'module level variable
Private sub TextBox1_KeyDown(ByVal KeyCode As MSForms.ReturnInteger,ByVal Shift As Integer)
If KeyCode=13 Then
ComboBox4.Clear
Call cmbo4
ComboBox4.DropDown
ComboBox4.SetFocus
InProgess = true
End If
End Sub
Private Sub TextBox1_AfterUpdate()
if inProgress then 'we just updated it so don't repeat
inprogress = False 'just clear the event
else
ComboBox4.Clear
Call cmbo4
ComboBox4.DropDown
ComboBox4.SetFocus
inprogress = False 'and clear the event
End IF
End Sub

Related

VBA raising events from modeless Userfrom

I'm trying to raise events from a modeless userform. My starting point is this excellent example. When I show the form modeless, the code raising the event executes, but the event handler never runs (I don't get the expected MsgBox when the Cancel button is clicked.) When I show the form modal, the events are handled as desired, but the form is no longer modeless as desired.
The userform named FormWithEvents has an OKButton and a CancelButton; here's the code behind:
Option Explicit
Public Event FormConfirmed()
Public Event FormCancelled(ByRef Cancel As Boolean)
Private Function OnCancel() As Boolean
Dim cancelCancellation As Boolean
RaiseEvent FormCancelled(cancelCancellation)
If Not cancelCancellation Then Me.Hide
OnCancel = cancelCancellation
End Function
Private Sub CancelButton_Click()
OnCancel
End Sub
Private Sub OKButton_Click()
Me.Hide
RaiseEvent FormConfirmed
End Sub
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = Not OnCancel
End If
End Sub
Here's the code for the Presenter class that shows the form:
Option Explicit
Private WithEvents myModelessForm As FormWithEvents
Public Sub Show()
Set myModelessForm = New FormWithEvents
' COMMENT OUT ONE OF THE FOLLOWING TWO LINES TO TOGGLE MODELESS OR MODAL
myModelessForm.Show vbModeless ' Modeless, but events don't get handled (no msgbox on clicking cancel button)
' myModelessForm.Show vbModal ' Events get handled, but no longer modal
End Sub
Private Sub myModelessForm_FormCancelled(Cancel As Boolean)
' Setting cancel to True will leave the form open
Cancel = MsgBox("Cancel this operation?", vbYesNo + vbExclamation) = vbNo
If Not Cancel Then
' Modeless form was cancelled and is now hidden
' ...
Set myModelessForm = Nothing
End If
End Sub
Private Sub myModelessForm_FormConfirmed()
' Form was okayed and is now hidden
Set myModelessForm = Nothing
End Sub
And here's the code in the main module:
Option Explicit
Public Sub RunForm()
With New Presenter
.Show
End With
End Sub
Any ideas on where I've gone wrong?
Maybe this instead so you can keep your Presenter instance in scope:
Dim pres as Presenter
Public Sub RunForm()
Set pres = New Presenter
pres.Show
End Sub
Avoid using RaiseEvent. Declare a public instance of the Presenter class in the standard module.
Option Explicit
Public Preter As New Presenter
Public Sub RunForm()
With New Presenter
.Show
End With
End Sub
Convert the event procedure to a sub with public scope.
Sub FormCancelled(Cancel As Boolean)
' Setting cancel to True will leave the form open
Cancel = MsgBox("Cancel this operation?", vbYesNo + vbExclamation) = vbNo
If Not Cancel Then
' Modeless form was cancelled and is now hidden
' ...
Set myModelessForm = Nothing
End If
End Sub
The call the sub within the class.
Private Function OnCancel() As Boolean
Dim cancelCancellation As Boolean
Preter.FormCancelled cancelCancellation
If Not cancelCancellation Then Me.Hide
OnCancel = cancelCancellation
End Function
I'm not sure but your code line Set myModelessForm = Nothing in the class module bother me. I wonder whether the form should be hidden instead so that the code can continue to the Show procedure where the form can then be put to rest.

How to dynamically edit a excel vba form label and tab label?

I have an excel vba form which has some tabs(pages).What I need is, if I double click on label, it should be editable. same is the case for the tabs.
I tried to add some things into the double click function, but not showing any change.
Labels are not editable by the end user
you may adopt this workaround
'change "Label1" occurrences to your actual label name
Private Sub Label1_DblClick()
Me.Label1.Caption = Application.InputBox("enter label text", "label editing", "")
End Sub
You can have a TextBox and disable it. The side effect is you will have textbox edit cursor over the label.
Have two subroutines, one called Labelize and another Textboxize. The former locks the textbox, makes it tranparent, and sets the 3D effect to flat. The latter unlocks it, makes it opaque, and sets the 3D to sunken (the default).
' Makes a textbox look like a label
Private Sub Labelize(txtbox As MSForms.TextBox)
txtbox.Locked = True
txtbox.BackStyle = fmBackStyleTransparent
txtbox.SpecialEffect = fmSpecialEffectFlat
End Sub
' Makes a textbox look like a textbox
Private Sub Textboxize(txtbox As MSForms.TextBox)
txtbox.Locked = False
txtbox.BackStyle = fmBackStyleOpaque
txtbox.SpecialEffect = fmSpecialEffectSunken
End Sub
In the form initialize event set call the Labelize method passing in your textboxes.
Private Sub UserForm_Initialize()
Labelize Me.txtExample
End Sub
In the double-click event per textbox, call the Textboxize method passing in your textbox again.
Private Sub txtExample_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Textboxize Me.txtExample
End Sub
Then in each KeyDown event, check if the key pressed was enter then call the Labelize method.
Private Sub txtExample_KeyDown(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
If KeyCode = 13 Then Labelize Me.txtExample
End Sub

VBA Input Value From Another UserFormB into TextBox From UserFormA

I have a userForm (mappingGuide) that allows user to pick a smartyTag from a list of more user-friendly names.
I have a second user-form (conditionalBuilder) that I would like to call this userForm upon double-clicking a text field so that a user can lookup which smartyTag to apply (in case they don't know).
So logic, is:
open conditionalBuilder
double-click Field text box
mappingGuide opens
pick a smartytag from listbox
fill smartytag value into field text-box in conditionalBuilder
unload mappingGuide
The issue I think I having with completing the requirement is that when I load the forms themselves I cannot find a way to set the text of the fieldName textbox of the loaded instance of conditionalBuilder (see last code block below). I've been searching around, but cannot figure it out.
Here is relevant code:
conditionalBuilder loads from Custom UI ribbon
Sub RunCode(ByVal Control As IRibbonControl)
Select Case Control.ID
Case Is = "mapper": LoadMappingGuide
Case Is = "conditional": LoadConditionalBuilder
End Select
End Sub
Sub LoadConditionalBuilder()
Dim conditionalForm As New conditionalBuilder
conditionalForm.Show False
End Sub
double-click event of fieldName then loads mappingGuide
Private Sub fieldName_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
Me.hide
Dim pickField As New mappingGuide
pickField.Show False
End Sub
smartTag listbox click event then attempts to place selection into fieldName (or selection if form not loaded)
Private Sub smartTagList_Click()
If smartTagList.ListIndex > -1 And smartTagList.Selected(smartTagList.ListIndex) Then
Dim smartyTag As String
smartyTag = smartTagList.List(smartTagList.ListIndex, 2)
If isUserFormLoaded(conditionalBuilder.Name) Then
'*** ---> below is my issue how to reference instance of form
conditionalBuilder.fieldName.Text = smartyTag
conditionalBuilder.Show
Else
Selection.Range.Text = smartyTag
End If
End If
Unload Me
End Sub
If there is a better set-up that would be great to know too. I have the forms separate because there's a couple of levels a user can create tags with.
This is how I would do it, a bit of overkill but in case of multiple forms it will be beneficial.
Module 1:
Option Explicit
Sub test()
frmMaster.Show False
End Sub
Form 1 : frmMaster:
Option Explicit
'/ Declare with events
Dim WithEvents frmCh As frmChild
Private Sub TextBox1_DblClick(ByVal cancel As MSForms.ReturnBoolean)
handleDoubleClick
End Sub
Sub handleDoubleClick()
If frmCh Is Nothing Then
Set frmCh = New frmChild
End If
frmCh.Show False
End Sub
'/ Handle the event
Private Sub frmCh_cClicked(cancel As Boolean)
Me.TextBox1.Text = frmCh.bChecked
End Sub
Form 2: frmChild:
Option Explicit
Event cClicked(cancel As Boolean)
Private m_bbChecked As Boolean
Public Property Get bChecked() As Boolean
bChecked = m_bbChecked
End Property
Public Property Let bChecked(ByVal bNewValue As Boolean)
m_bbChecked = bNewValue
End Property
Private Sub CheckBox1_Click()
Me.bChecked = Me.CheckBox1.Value
'/ Raise an event when something happens.
'/ Caller will handle it.
RaiseEvent cClicked(False)
End Sub
You can do this with a presenter class which controls userform instances and pass values between them. I mocked up something similar to give you an idea.
Presenter. This is a class module which creates the userforms, controls their scope, and catches the event thrown by the
ConditionalBuilder. It makes it super easy to pass values between
userforms.
Private WithEvents CB As ConditionalBuilder
Private MG As MappingGuide
Public Sub ShowCB()
Set CB = New ConditionalBuilder
CB.Show vbModal
End Sub
Private Sub CB_ShowMappingGuide()
Set MG = New MappingGuide
MG.Show vbModal
CB.UpdateTB1 Value:=MG.SmartTag
End Sub
ConditionalBuilder.
This has a simple function to update your textbox and also an event which raises actions in the presenter.
Public Event ShowMappingGuide()
Public Function UpdateTB1(Value As String)
TextBox1.Value = Value
End Function
Private Sub TextBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean)
RaiseEvent ShowMappingGuide
End Sub
MappingGuide.
The Type and Property could be overkill since we just want one value from the mapping guide but it's still good practice.
Private Type TView
Tag As String
End Type
Private this As TView
Public Property Get SmartTag() As String
SmartTag = this.Tag
End Property
Private Sub UserForm_Initialize()
Tags.List = Array("a", "b", "c")
End Sub
Private Sub Tags_Click()
this.Tag = Tags.List(Tags.ListIndex, 0)
Me.Hide
End Sub
I have one final Standard Module which creates the Presenter. This is what you'd hook up to your ribbon.
Public Sub ShowProject()
With New Presenter
.ShowCB
End With
End Sub
Step 1 (double click text field)
Step 2 (selecting "b")
Step 3 (result)
I actually solved it by placing the below block inside the IF where I check for the form being loaded and I will leave open for better answers, if there are any.
Dim uForm As Object
For Each uForm In VBA.UserForms
If uForm.Name = conditionalBuilder.Name Then
uForm.fieldName.Text = smartyTag
uForm.Show
End If
Next

An instance of an Excel-VBA form opens with an error, if it was closed from the top right red `X`

Prehistory
I have read the best practises for creating a form, concerning the fact that one should always refer to an object of the form and not the form itself. Thus, I have decided to build a boiler-plate form for myself.
The problem
Everything ran smoothly, until the moment I have decided to close the form with the top right red X. It closes ok. But then, when I try to open the form again, I get this runtime error:
The error is on objPresenter.Show (see the code below). Obviously, it does not enter in the if above. But the problem is that the closing from the X does not work fine. When I close the form from the End button, anything works. And even, if I copy the code for the closing from the btnEnd to UserForm_QueryClose it still does not work the same.
The form
Thus, I have a modMain, frmMain and clsSummaryPresenter, which all take care of the form. I start the code from modMain
My form looks like this:
It has btnRun, btnExit, lblInfo. The name of the class is frmMain.
The code
In frmMain:
Option Explicit
Public Event OnRunReport()
Public Event OnExit()
Public Property Get InformationText() As String
InformationText = lblInfo.Caption
End Property
Public Property Let InformationText(ByVal value As String)
lblInfo.Caption = value
End Property
Public Property Get InformationCaption() As String
InformationCaption = Caption
End Property
Public Property Let InformationCaption(ByVal value As String)
Caption = value
End Property
Private Sub btnRun_Click()
RaiseEvent OnRunReport
End Sub
Private Sub btnExit_Click()
RaiseEvent OnExit
End Sub
Private Sub UserForm_QueryClose(CloseMode As Integer, Cancel As Integer)
If CloseMode = vbFormControlMenu Then
Cancel = True
Hide
'Even if I change the two lines above with this the error happens:
'RaiseEvent OnExit
'However, if I simply write END in stead of those two lines
'anything works quite ok...
'but that is a bit brutal.
End If
End Sub
In clsSummaryPresenter
Option Explicit
Private WithEvents objSummaryForm As frmMain
Private Sub Class_Initialize()
Set objSummaryForm = New frmMain
End Sub
Private Sub Class_Terminate()
Set objSummaryForm = Nothing
End Sub
Public Sub Show()
If Not objSummaryForm.Visible Then
objSummaryForm.Show vbModeless
Call ChangeLabelAndCaption("Press Run to Start", "Starting")
End If
With objSummaryForm
.Top = CLng((Application.Height / 2 + Application.Top) - .Height / 2)
.Left = CLng((Application.Width / 2 + Application.Left) - .Width / 2)
End With
End Sub
Public Sub Hide()
If objSummaryForm.Visible Then objSummaryForm.Hide
End Sub
Public Sub ChangeLabelAndCaption(strLabelInfo As String, strCaption As String)
objSummaryForm.InformationText = strLabelInfo
objSummaryForm.InformationCaption = strCaption
objSummaryForm.Repaint
End Sub
Private Sub objSummaryForm_OnRunReport()
MainGenerateReport
Refresh
End Sub
Private Sub objSummaryForm_OnExit()
Hide
End Sub
Public Sub Refresh()
With objSummaryForm
.lblInfo = "Ready"
.Caption = "Task performed"
End With
End Sub
In modMain
Option Explicit
Private objPresenter As clsSummaryPresenter
Public Sub MainGenerateReport()
objPresenter.ChangeLabelAndCaption "Starting and running...", "Running..."
GenerateNumbers
End Sub
Public Sub GenerateNumbers()
Dim lngLong As Long
Dim lngLong2 As Long
tblMain.Cells.Clear
For lngLong = 1 To 4
For lngLong2 = 1 To 1
tblMain.Cells(lngLong, lngLong2) = lngLong * lngLong2
Next lngLong2
Next lngLong
End Sub
Public Sub ShowMainForm()
If (objPresenter Is Nothing) Then
Set objPresenter = New clsSummaryPresenter
End If
objPresenter.Show
End Sub
The question
Once again, why I cannot close the form with the red X? I can substitute the code in UserForm_QueryClose with End but that is a bit brutal. Any ideas?
Changing the form's mode from vbModeless to vbModal gives you an earlier and more informative failure:
The problem seems to be because the Cancel = True assignment in the QueryClose handler, isn't working for some reason.
The signature for the QueryClose handler is as follows:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Yours is:
Private Sub UserForm_QueryClose(CloseMode As Integer, Cancel As Integer)
You should never type these handler signatures manually yourself - instead, use the drop-down in the codepane's top-right corner, and have the VBE generate the handler stubs for you:
That way your handler signatures will always match the interface they're for.
VBA doesn't really care about parameter names in handlers: the way the runtime matches a handler signature is by matching the parameter indices and their types, against the expected ones. Since both QueryClose parameters are Integer values, inverting them compiles just fine - except when you set Cancel = True, what the runtime sees is that you've assigned CloseMode = -1 and left the Cancel parameter alone.
Which means your form doesn't cancel its close, and thus the object gets destroyed every time.
Invert the parameters in your QueryClose handler, and everything works perfectly fine and exactly as intended.
Calling the form like so works just fine for me:
Option Explicit
dim mfrmMain as ufMain
Sub ShowMainForm2()
If ufMain Is Nothing Then
Set ufMain = New mfrmMain
End If
mfrmMain.Show vbModeless
End Sub

Determine which command button was clicked to open userform

I have two command buttons (cmd1 and cmd2) on userform1, when clicked each show the same userform (userform2). Within the initialize or load sub is it possible to determine which command button was clicked on userform1 and therefore show the form differently? I imagine the code in either the initialize or load sub on userform2 to have the following skeleton:
if (cmd1 was clicked)
' do stuff relating to 1
elseif (cmd2 was clicked)
' do stuff relating to 2
else
' error handling
End if
The respective "stuff" could be moved into the event handler for cmd1 and cmd2 however, if the method described above can be used it will be a lot simpler and cleaner.
Use a Public Variable in UserForm1 and then test it in UserForm2_Initialize Event.
Something like this in UserForm1:
Public whatsclicked As String
Private Sub CommandButton1_Click()
whatsclicked = "CommandButton1"
UserForm2.Show
End Sub
Private Sub CommandButton2_Click()
whatsclicked = "CommandButton2"
UserForm2.Show
End Sub
And then in UserForm2:
Private Sub UserForm_Initialize()
Select Case UserForm1.whatsclicked
Case "CommandButton1": MsgBox "CommandButton1 loaded form."
Case "CommandButton2": MsgBox "CommandButton2 loaded form."
Case Else 'Do something else
End Select
End Sub
you can do it also without public variable.
i won't show an example where you can simply write something in a cell or hidden sheet, instead i just pass the wanted info directly .
this time whatsclicked is the name of a label in userform2,
in userform1, before calling userform2:
Private Sub CommandButton1_Click()
load UserForm2
with UserForm2
.whatsclicked.caption= "CommandButton1"
.Show
end with
End Sub
Private Sub CommandButton2_Click()
load UserForm2
with UserForm2
.whatsclicked.caption= "CommandButton2"
.Show
end with
End Sub
Of course, you will have to hide the text of whatsclicked to the user (same color as font or background...)
You can use ActiveControl for this as when you click a control it becomes active/focussed:
Private Sub CommandButton1_Click()
Debug.Print Me.ActiveControl.Name
End Sub
Private Sub CommandButton2_Click()
Debug.Print Me.ActiveControl.Name
End Sub
So more in line with your example:
Private Sub CommandButton1_Click()
Call DoStuff
End Sub
Private Sub CommandButton2_Click()
Call DoStuff
End Sub
Private Sub DoStuff()
select case Me.ActiveControl.Name
case "CommandButton1"
' do stuff relating to 1
case "CommandButton2"
' do stuff relating to 2
case else
'error etc.
end select
End Sub