m getting an error while using
Text1.SetFocus
the error is
invalid procedure call or argument
If you're calling this from a module, i.e. not from the form code, you need to reference the form object where text1 lays.
The other thing you may need to do is to get some persistency pills, hit F1 more often and use words in abundance when asking for help.
You need to reference Text1.SetFocus from Form_Activate and no from Form_Load -
http://www.vb6.us/tutorials/understanding-forms-vb6-tutorial
Form_Load vs. Form_Activate
In the Form_Load event, you would typically perform initialization-type tasks, as you should. However, certain types of actions cannot be performed in the Load event, due to the fact that the form is fully loaded only after the Load event completes. For one thing, printing to the form will not work when done in the Load event. In addition, if you try to set focus to a particular control on the form during the Load event, you will get the message Run-time error '5': Invalid procedure call or argument. For example, assume you had a textbox called Text1 on the form. The following code would result in that error:
Private Sub Form_Load()
' other initialization stuff
Text1.SetFocus ' causes an error
End Sub
The reason for the error is that since the form is not fully loaded, neither are any of the controls on it – and you can't set focus to a control that is not yet available.
To remedy this problem, you should use one of the other Form events, such as the Activate event. (When VB loads a form, it actually cycles through a number of events, such as: Initialize, Load, Resize, Activate, GotFocus, and Paint. Of these, Load and Activate are probably the ones most commonly used. ) Placing the code to set focus to a control will work in the Form_Activate event:
Private Sub Form_Activate()
' other statements
Text1.SetFocus ' no problem here
End Sub
A caution about the activate event: it will fire whenever your application switches to that form. For example, if you switch back and forth between Form1 and Form2, be aware that any code you might have in the Activate events for these forms will be executed when you switch to that form. Therefore, if you have code in the Activate event that you only want to execute "the first time", you will need to control execution with a Boolean switch. For example, in the General Declarations of your form you could define the following variable:
Private mblnFormActivated As Boolean ' will be initialized to False by default
You can then use this switch in the Activate event as follows:
Private Sub Form_Activate()
If mblnFormActivated Then Exit Sub
' statements you only want to execute once, including the following
' statement to turn the switch on:
mblnFormActivated = True
End Sub
Related
How can I stop Access from prompting "Do you want to save changes to the layout of query" when I try to close a form that has a subform in datasheet view?
I have a form with a subform in Datasheet view with the .SourceObject set to a temporary pivot / crosstab query (no actual form object). If the user changes the width of a column and closes the window with the built-in Access close button, the user (and annoyed developer) is always presented with the "Do you want to save changes to the layout of query" prompt.
I am able to avoid this prompt by setting MySubform.SourceObject = "" in the click of my Close button but anyone clicking the [x] button or pressing CTRL+F4 or CTRL+W gets the prompt.
I have put breakpoints in the Form_Close and Form_Unload events but this prompt appears before they fire.
I want to clarify further that this subform object is Unbound and not based on a form object. I am building a dynamic Crosstab SQL statement, creating a QueryDef with the SQL, and then setting MySubform.SourceObject = "query.qry_tmp_Pivot" This is a neat technique for a crosstab/pivot query because the columns can vary and I don't think a form object would support that.
I am able to use the Watch window on the MySubform object and I can see that a MySubform.Form object exists. It has a Controls collection containing a control for all of the columns in my query. I have an optional routine that I can run that will loop through all of the controls and set their .ColumnWidth = -2, which will auto-size the column width based on the widest data of the visible rows in the datasheet. When this routine was running I was noticing that every time I closed the form (not using my Close button) I was getting the save prompt. I have disabled this routine for debugging but a user will still get the prompt if they manually adjust any column width.
I feel like I need to explain this extra detail so you realize this is not an Access 101 issue. You probably know that if you've read this far. Here's another thought I had: Maybe I could trap the Unload event in the subform control before the prompt happens. Because there is no true Form object to put test code in, I created a class object and passed MySubform to it. The class uses WithEvents and creates events like OnClose and OnCurrent on the class module's mForm object. Sample class code is below.
Private WithEvents mForm As Access.Form ' This is the form object of the Subform control
Public Sub InitalizeSubform(Subform As Access.Subform)
Set mForm = Subform.Form
Debug.Print Subform.Name, mForm.Name, mForm.Controls.count
' Create some events to intercept the default events.
mForm.OnClick = "[Event Procedure]"
mForm.OnClose = "[Event Procedure]"
mForm.OnUnload = "[Event Procedure]"
mForm.OnCurrent = "[Event Procedure]"
End Sub
Private Sub mForm_Click()
Debug.Print "Clicking " & mForm.Name
End Sub
Private Sub mForm_Current()
Debug.Print "On Current " & mForm.Name, "Record " & mForm.CurrentRecord & " of " & mForm.RecordsetClone.RecordCount
End Sub
Private Sub mForm_Unload(Cancel As Integer)
Debug.Print "Unloading " & mForm.Name
End Sub
Private Sub mForm_Close()
Debug.Print "Closing " & mForm.Name
End Sub
The VBE Watch window shows my new events on the mForm object but unfortunately they never fire. I know the class works because I used it with a bound subform and all of the events are intercepted by the class. I'm not sure what else to try.
Events on the subform never fire because it's a lightweight form (without a module). See this Q&A and the docs. Lightweight forms don't support event listeners, but do support calling public functions from an event, e.g. mForm.OnClick = "SomePublicFunction()"
Note that the workaround described in this answer also opens up the possibility of displaying a crosstab query in a form without saving it at all.
Alternatively, you could try capturing the event on your main form, and suppress saving there.
I gave answer credit to #ErikA for this question. He directed me to an answer to a somewhat related question here which is what I ended up implementing and it works. His instructions were very straightforward and easier to implement than I first anticipated.
The answer to my question seems to be that it may not be possible to avoid the save prompt when using a lightweight form object. If someone comes along with a solution I'd still like to hear it.
What I learned from this experience:
An unbound subform that has its .SourceObject set to a table or query will not have a code module. This considered to be a lightweight object.
Public functions can be added to event properties on lightweight forms but they don't seem to fire before the save prompt appears as the parent form is closing.
Binding a pivot query or temp table to a real form object that has 254 controls on it is the only way I found that doesn't prompt to save design changes when the parent form closes. Mapping the query/table columns to the 254 datasheet controls is super fast.
A class is a nice way to intercept events if you're not using a lightweight object. A class also lets you open multiple instances of the same object.
I ended up not implementing my solution without using a class because there were no events I needed to capture. At this point I don't need my frmDynDS to be used for other purposes and I'd like to keep the object count down (this is a large app with 1400+ objects).
I put the following code in a function that gets called when the subform is loaded or refreshed.
With subCrosstab
' Set the subform source object to the special Dynamic Datasheet form. This could be set at design time on the Subform control.
.SourceObject = "Form.frmDynDS"
' Run the code to assign the form controls to the Recordset columns.
.Form.LoadTable "qry_tmp_Pivot" ' A query object works as well as a table.
' Optional code
.Form.OnCurrent = "=SomePublicFunction()"
.Form.AllowAdditions = False
.Form.AllowEdits = False
.Form.AllowDeletions = False
.Form.DatasheetFontHeight = 9
End With
I can now resize the columns and never get a prompt to save layout changes no matter how I close the parent form.
Consider a simple .NET form with a couple of radio buttons and a checkbox.
Each of the radio buttons has a CheckedChanged handler setup that performs some action based on the state of the checkbox.
My problem is, when I initialize on the default radiobutton to be checked (from the designer properties window) the CheckedChanged event is fired for that radio button, but the Checkbox hasn't been initialized yet so I either get a null pointer exception or the wrong value is used in the handler. Either way, I don't want that handler code to be run unless the user picks a radio button after the form has been loaded.
I currently get around this by not initializing the radio button, but I need to set that default eventually and the best place is from the designer. I also can add a boolean field that's not set to true until the form is fully loaded and not process the events if that is false, but it's a dirty hack.
What can I do to prevent that handler from running its code?
To make it feel slightly less dirty, if you initialize the controls in the constructor of the form you might be able to use the forms IsHandleCreated property rather than your own bool to check if it should actually validate or not.
I would think that normally you wouldn't want to validate anything before it's been shown for the first time and handle isn't created until it is.
Code Example:
Private Sub myRadioButton_CheckedChanged(sender As Object, e As EventArgs) Handles myRadioButton.CheckedChanged
If myRadioButton.Checked AndAlso myRadioButton.IsHandleCreated Then
'Do Work
End If
End Sub
"I also can put a boolean field that's not set to true until the form is fully loaded and not process the events if that is false, but it's a dirty hack."
It's also the easist and best way to do it!
Lets say .NET provides a neat way to turn an and off all the event handlers until the form is loaded. Even just the ones YOU are handling. It would still not be sufficiently flexible to disable what you wanted to enable but disable what you didn't. Often form setups happen and you want the events to fire. Also the form won't build right if no events fire.
The easy solution is to declare an initializing variable:
Private Initializing as boolean = True
Private Sub rb_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles rbNuevos.CheckedChanged, RbDesaparecidos.CheckedChanged, RbModificados.CheckedChanged, RbNoDesap.CheckedChanged, RbDesHoy.CheckedChanged, RbChT.CheckedChanged
if Initializing then return
'Your Code
End Sub
Public Sub New()
' Llamada necesaria para el Diseñador de Windows Forms.
InitializeComponent()
' Agregue cualquier inicialización después de la llamada a InitializeComponent().
initializing = false
end sub
Most sophisticated: Remove the "handles" from the method, and use AddHandler on the new method.
Public Sub New()
' Llamada necesaria para el Diseñador de Windows Forms.
InitializeComponent()
' Agregue cualquier inicialización después de la llamada a InitializeComponent().
AddHandler RbChT.CheckedChanged, AddressOf rb_CheckedChanged
end sub
For radiobutton see Hans Olsson answer
For numeric up down, do it like this
Private Sub myNumeric_ValueChanged(sender As Object, e As EventArgs) Handles myNumeric.ValueChanged
If myNumeric.Value >= 0 AndAlso myNumeric.IsHandleCreated Then
'Do the work
End If
End Sub
The keyword is myNumeric.Value and IsHandleCreated
Yet another way:
Private Sub dgvGroups_CellValueChanged(sender As System.Object, e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgvGroups.CellValueChanged
If Me.Visible = False Then Exit Sub ' Sub gets called on form load which causes problems
wksGroups.Cells(e.RowIndex + 1, 1) = dgvGroups.Item(e.ColumnIndex, e.RowIndex).Value
wksGroups.Cells(1, 5) = dgvGroups.RowCount
One thing I've found that works is adding the events manually after you load the form.
To do this you can simply go into the generated form code found in the designer file of that form, and pull out the lines that add the event. It would look something like this:
this.controlName.CheckedChanged += new System.EventHandler(this.controlName_CheckedChanged);
Then put all of these calls into a method that you call after the InitializeComponent call in your form's constructor.
Just in case anyone is still searching for this the event is fired upon initializing the form BUT the form is not yet visible, Also Say that you have a foreign key relationship upon which you have a default value needed issue that gets fired every row update too. So the following code worked for me....
if (Visible && !(e.ColumnIndex == 0))
{
phoneEdited = true;
MessageBox.Show("A Phone entry has been entered");
}
Don't set checked on a control that really does much in designer.
The global flag and conditional exits where needed.
Try..Catch the sore spots to ignore a meaningless exception.
(Using VS 2017) It appears to me that it is an annoyance but not a bug. It is consistent with the model in use. The event is fired by normal operation of code, but code I did not write (but can access where fools fear to tread) and where there appears to be no (decent) place earlier in the normal flow to anticipate it.
The cleanest answer seems to be not to check radio button or checkbox controls in the designer at all if they trigger any significant code. Instead these controls should be changed by code (e.g. checked = true) in the Load event (for example) AFTER all the initialization is done.
There is no loss of flexibility here since both are fixed before the build, only in different places. The event handlers will handle it exactly as if a user had clicked the control in the natural flow of a well designed GUI application. (This reminds me of the ancient RPG proverb "Don't buck the cycle". (Anyone here remember RPG? I, not part of IBM-oriented team, never used it but had interesting discussions with some who did. ) Pre-checking controls hits the wrong part of the VS cycle.)
If for any reason that will not work, the next best thing is the kludge suggested elsewhere of a single status boolean initialized false and set true at the appropriate time with conditional exits in the necessary places to prevent them from crashing until then. It will get the job done, but it's ugly. Better than failure.
Another thing I tried before I decided that designer level pre-set checks were the problem and there was a very acceptable alternative was to put the danger spots in a Try..Catch to be able to ignore the exception. Also a kludge.
For the cleanest code, reverse the True/False approach used in some other examples. Focus on 'ready' rather than 'busy'. Here's an example for a Windows Form:
At the Class level, add Private app_ready As Boolean (it will be False by default).
At the end of the Form.Shown event handler, add app_ready = True.
In each control event handler where it's needed, add:
If app_ready Then
' code
End If
Starting a routine with something like If initialising Then Exit Sub just doesn't feel right!
Maybe for some functionality you can use the click event instead of the check changed event.
I put a public variable in the Module1 file
Dim Public bolForm_LoadingTF as Boolean = True
In each formLoad event I put
bolForm_LoadingTF = True
In each control with an OnSelectedIndexChanged
event I put if bolForm_LoadingTF = True then Exit Sub
At the end of the form load event I put
bolForm_LoadingTF = False
I am probably breaking a bunch of rules but this works
for me.
System Description: I have a userform that takes input on an item that is being returned. A user clicks the row of the item that needs to be returned and then clicks a "Check-In button"
My Attempt: I created a button checkin_cmdbutton on the spreadsheet that measures which item is selected by which cell is selected Application.ActiveCell.Row, writes the info into a userform Checkin_Form, the user finishes the rest of the check-in info, and clicks submit.
This code is the event for the button checkin_cmdbutton on the spreadsheet:
Private Sub checkin_cmdbutton_Click()
Set ItemID = Cells(Application.ActiveCell.Row, 1)
Set ItemDescription = Cells(Application.ActiveCell.Row, 2)
If ItemID Is Nothing Then
MsgBox ("ID is null, ending...")
Exit Sub
End If
Checkin_Form.UserForm_Initialize
Checkin_Form.itemid_dynamiclabel.Caption = ItemID.Value
Checkin_Form.description_dynamiclabel.Caption = ItemDescription.Value
Checkin_Form.checkin_datepicker.Value = Date
Checkin_Form.Show
End Sub
Problem: The code throws an error 91 "Object variable or with block variable not set" at Checkin_Form.itemid_dynamiclabel.caption and the following 2 lines. Why is an object on a form throwing this error? I can't declare these, can I?
You shouldn't be explicitly calling UserForm_Initialize - that's an event handler, and there's a reason handlers are Private by default: they're invoked by the event provider, when the event provider deems it necessary - in this case, when the object instance is getting initialized.
The best way to ensure the form gets initialized properly, is to treat it like the object it is, instead of storing global state on its default instance.
A UserForm class is little more than a class module with a designer and a VB_PredeclaredId module attribute. This attribute makes VBA create a global-scope object variable named after the class, and that is how this code is legal:
UserForm1.Show
Except, it shouldn't be.
You DON'T want to store global state in the default instance: that's the very last thing you want, especially if your form involves dynamic controls.
New it up instead.
With New UserForm1
.Show
'what follows only executes when the form is closed:
'...
End With
For this to work, you must handle the form's QueryClose event, to prevent the object instance from self-destructing itself when the user clicks the [X] button.
For this to work, you must also avoid destroying the form yourself, e.g. with Unload Me (or worse, Unload UserForm1) calls - say, when the user clicks the [Ok] button. Instead, you Hide (or Me.Hide) the form, so that the caller (the code that New'd it up) can still access the object's state.
From the look of your code - i.e. with the .Show call being the very last thing your macro does, I can tell that you're having the form run the show: this is an anti-pattern that will keep creating problems every time you do that.
Forms don't implement application logic: forms present and collect data. Nothing more, nothing less. It's not the form's job to write to any spreadsheet, or even to know anything about worksheets.
Read this recent article of mine if you want more information about doing forms right.
Now, the actual problem.
Checkin_Form.itemid_dynamiclabel.Caption = ItemID.Value
If that label is dynamic (i.e. created at run-time), then I'm surprised accessing it like this even compiles. First, remove the underscore in the form's name: underscores have a special meaning in VBA - I'm sure you've noticed the pattern by now, of how VBA generates event handlers for a given object:
Private Sub ObjectName_EventName()
End Sub
If ObjectName or EventName has an underscore, you're asking for compile errors at one point or another - one day you'll want to use an Implements statement and discover that your code can't be compiled anymore, if you kept that underscore habit: better lose it now.
If the control is dynamic, you can't do what you're trying to do the way you're doing it.
Dynamic controls need to be accessed through the form's Controls collection:
Dim myLabel As MSForms.Label
Set myLabel = Me.Controls("NameOfTheLabelControl")
Otherwise, you need to keep a reference to the dynamic contols at module-level, in the form's code-behind - you could expose it via a property:
Option Explicit
Dim myLabel As MSForms.Label
Private Sub UserForm_Initialize()
Set myLabel = Me.Controls.Add(...)
End Sub
Public Property Get ThatLabel() As MSForms.Label
Set ThatLabel = myLabel
End Property
Or better, use an actual model class, and let the calling code not be bothered with controls at all - see the previously linked article for details.
TL;DR:
You're getting that error because your label object instance isn't initialized, i.e. it's Nothing. Since you aren't showing your form's code-behind, we can't really point out why that is the case, but my money is on the form's default instance making you yet another victim of the "hey look how easy it is!" VBA tutorials that teach things wrong.
Implement the worksheet-handling code outside the form, make the form collect data, make the calling code read this data after the form is hidden, and then make the calling code create and destroy the form instance.
Now, with all that said, I've no idea why you think you need a dynamic control for this.
Just shooting in the dark, as far as I really do not know the names of your variables and what they are (a few screenshots will be helpful). Try like this, if your code is in a form (as far as you have _Click I assume it is):
Private Sub checkin_cmdbutton_Click()
Set ItemID = Cells(Application.ActiveCell.Row, 1)
Set ItemDescription = Cells(Application.ActiveCell.Row, 2)
Me.itemid_dynamiclabel.Caption = ItemID.Value
Me.description_dynamiclabel.Caption = ItemDescription.Value
Me.checkin_datepicker.Value = Date
Me.Show
End Sub
And try at least declaring the variables (e.g. ItemID etc) and using Option Explicit on top.
I have a VBA (Excel 2010) system which involves selecting an item from a listbox and then displaying it in another form. Here is a very simplified version of what happens.
' Part of frmForm1 code module
sub lstListbox_Click
dim MyEvent as string
dim i as integer
i=me.lstListbox.listindex
MyEvent=me.lstlistbox.list(i)
' Now show the item in the second form
Load frmForm2
me.hide
ThisWorkbook.LoadDataIntoForm2 (frmForm2, MyEvent)
frmForm2.show
unload frmForm2
me.show
end sub
The listbox accepts the click, and first the event (the event handler is giver above). Key parts of the event handler are:
Load the second form (to display the detail data)
Pass the second form as a UserForm parameter to a procedure (LoadDataIntoForm2)
Hide the host form (frmForm1) and show the second form (frmForm2)
When the second form processes an Exit click, the code looks like this:
' Part of frmForm2 code module
sub cmdExit_Click
me.hide
end sub
The first time round it works fine - but when I return to frmForm1 (in the tail end of the lstListBox_Click procedure), even though the rest of the form is operative, the listbox remains stubbornly unresponsive.
I've managed to abstract this down to a little demo system if that would help - the same behavior is seen there. (It's regular .xls file, but that seems not to be easily acceptable as an upload)
Has anyone seen this before? And does anyone have any ideas how I might get this to work the way I want it to?
Thanks,
Tony
The default for the .Show method is to make the form modal. Explicitly set it to modeless:
Sub lstListbox_Click
...
Me.Show vbModeless
End Sub
Is it better to show ProgressBar UserForms in VBA as modal or modeless? What are the best practices for developing progress indicators in VBA?
Modeless UserForms require the use of Application.Interactive = False, whereas Modal UserForms by their very nature block any interaction with the application until the core procedure has finished, or is cancelled.
If Application.Interactive = False is used, however, the Esc key interrupts code execution, so the use of Application.EnableCancelKey = xlErrorHandler and error handling (Err.Number = 18) is required in both the UserForm and the calling procedure.
Resource intensive calling procedures can also result in CommandButton_Click and UserForm_Activate events misfiring in modeless UserForms.
In general, progress indicators that use modal UserForms seem simpler, because the code that is being executed is fully contained in the UserForm module, and there is less need for passing of variables.
The problem, however, with using modal UserForms for progress indicators is that a separate UserForm module is required for every procedure that needs a progress indicator, because the calling procedure has to be inside the UserForm_Activate procedure.
So, while it is possible to have a single reusable progress indicator in a modeless UserForm, it will be less reliable than executing the code from within multiple modal UserForms.
Which way is better?
Thanks!
There's also a third way, using the Application.StatusBar.
You can even simulate a true progress bar by using a sequence of U+25A0 and U+25A1 characters.
I am going to close this one out and say Modal is the winner. I have tried both ways, but you end up trying to close too many loopholes with modeless userforms. Modal is more difficult because it is more strict, but it encourages you to break up your code into smaller chunks which is better in the long run anyway.
Definately Modal.
If you are going to consider Modeless, you ought to run it on a seperate out-of-process thread and not on the Excel.exe main thread.
I think that the initial topic is worth of replying since the question was formulated so nicely that google finds it first.
Section 1 - Theory
The first thing to say is that to transfer the variables between the modules is not difficult at all.
The only thing you need to do is to create a separate module and put there all the global variables. Then you will be able to read them everywhere in all forms, sheets, modules.
The second thing is the window should be a MODELESS. Why that?
The answer is to keep the mobility of the code, i.e.
the function where the most routine process is executed is not to be located in the UserForm module
you can call the window with progress bar from everywhere and
the only connection between the routine function/procedure are the global variables
This is a great advantage to be versatile here.
Section 2 - Practice
1) Create a module "Declaration" with the global variables:
Public StopForce As Integer
'this variable will be used as an indicator that the user pressed the cancel button
Public PCTDone As Single
' this is the % of the work that was done already
Public CurrentFile As String
' any other parameter that we want to transfer to the form.
2) Create the form with the button. In OnClick event of the button there should be a code where we refer to the global variable StopForce in Declaration module
Private Sub CommandButton1_Click()
Declaration.StopForce = 1
End Sub
3) Add one procedure where you update the progress bar
Sub UpdateProgressBar(PCTDone_in As Single)
With UserForm1
' Update the Caption property of the Frame control.
.FrameProgress.Caption = Format(PCTDone_in, "0%")
' Widen the Label control.
.LabelProgress.Width = PCTDone_in * _
(.FrameProgress.Width)
' Display the current file from global variable
.Label1.Caption = Declaration.CurrentFile
End With
End Sub
4) in any other module we must have the functions or the procedure/sub where the routine is done:
For i=1 to All_Files
Declaration.CurrentFile = myFiles (i)
FormFnc.UpdateProgressBar (i / .Range("C11").Value)
DoEvents
If Declaration.StopForce = 1 Then
GoTo 3
End If
Next i
Actually you have following properties, resulting in pros/cons depending on your need:
Type | Impact on UI | Impact on caller execution
----------|--------------|-----------------------------
Modal | Blocked | Blocked until Form is closed
Modeless | Not blocked | Continues
If you want to block the UI AND let the caller continue, then you need to open the Form in modal mode with Application.OnTime.