Handling ActiveX events on an Access form - ms-access

I have an ActiveX control for opening and manipulating a proprietary image type. I am able to embed the control in an Access form, and by using the Object Browser am able to ascertain all the class members. The only problem I am having is handling some of the events raised by the control.
For example, from the Object Browser I can see the following event definition:
Event RightClicked()
And when I add the following code to the form:
Private Sub CtrlInstanceName_RightClicked()
'Anything here
End Sub
Everything works as expected. However, if the event declaration has parameters passed to it, such as:
Event MeasurementUpdated(id as Long)
Adding:
Private Sub CtrlInstanceName_MeasurementUpdated(id as Long)
'Anything here
End Sub
Produces the following error:
The expression ... you entered as the event property setting produced
the following error: Procedure declaration does not match description
of event or procedure having the same name.
Even more strange after adding this second handler, all event handlers on the form produce this error, including events raised by the form and other controls on the form.
I have tested the ActiveX control in a C# .NET application and am able to handle the events there (although they don't quite work as expected). In Visual Studio, the function prototypes end up being a little odd with auto-defined event handler class types.
Any ideas on how I need to change the event procedure declaration to avoid this error?

Well like most Friday questions, I later found the answer to this one was rather quite simple.
First, due to my unfamiliarity with the VBA code editor, I did not know that it was possible to create stubs for embedded controls by using the Object combobox above the code. After selecting my control name I selected the MeasurementUpdate event from the procedure combobox.
The correct declaration is simply:
Private Sub CtrlInstanceName_MeasurementUpdated(ByVal id as Long)
'Anything here
End Sub
The key difference being the ByVal modifier.

Related

MS Access: Property defined with 'Form' return type in a interface gives user-defined type not defined compilation error

Looking for help writing an interface that has a property (or function) that returns an object typed as a "Form" (e.g. Access.Form as the return type).
Problem description:
The following simple example code returns a "User-defined type not defined" error if I attempt to compile the project.
I_TestInterface:
Public Property Get MyForm() as Form
End Property
cls_TestClass:
Implements I_TestInterface
Public Property Get I_TestInterface_MyForm() as Form
End Property
This is the only code/objects in a otherwise blank Access-2016 database and asking VBA to 'compile' produces a "User-defined type not defined" error. No lines are highlighted, it simply won't compile. Same occurs if replace 'Form' by "Access.Form"
My usecase is writing classes that wrap/hold a reference to a form internally - sometimes it is easiest to provide a reference to the underlying form so that consuming code can get at form properties without coding them all into the wrapper class.
Steps taken:
Lots of searches on 'user-defined type not defined' errors, in almost all cases this is due to a missing reference. Does not seem applicable since can create a standard module and happy to compile if write a function with a Form return type there. (*)
There was one previous SO thread on similar 'ambiguous' 'user-defined type not defined' error but I can't find it again and it wasn't specific to the 'Form' type in MS-Access
Have a clumsy workaround of providing property that returns an Object type that consuming code just has to cast to a Form (e.g. public property get MyFormAsObjectThatCanBeCastToForm() as Object).
(*). Possible clue? - if I just change the return types to object, the code still won't compile. I need to exit and use the 'de-compile' start up switch when relaunch Access. Then the above code with Object used as a return type is happily compiled.
Question / request:
Before I start re-installing Office, can others reproduce this?
Has anyone experienced this before? Developed a solution?
Thanks
PAHTDC

Where to implement callback in ribbon xml with Access

I created a ribbon xml in Access 2010, one of the related element as follows:
<button id="button1" size="normal" label="Sample1" getImage="GetImage" onAction="OnMenuAction" />
I implemented OnAction in Main module as:
Public Sub GetImage(ByVal control As Office.IRibbonControl, ByRef image)
image = "HappyFace"
End Sub
Public Sub OnMenuAction(ByVal control As Office.IRibbonControl)
MsgBox "You've clicked the button " & control.ID & " on the Ribbon"
End Sub
The Tab did appear in ribbon, but without "HappyFace" image and shows error message:
"Access cannot run the macro or callback function 'GetImage'.
Make sure the macro or function exists and takes the correct parameters."
When I click the button, Access also can't find the callbacks and shows the Message:
"Access cannot run the macro or callback function 'OnMenuAction'.
Make sure the macro or function exists and takes the correct parameters."
My question is where should I put these callbacks besides Main module? Thank You.
Finally I solved the problem by including Microsoft Office 14.0 Object Library.

Access 2007 crashes when canceling On No Data and calling function from On Close

MS Access 2007 (and perhaps later versions) appears to have a bug that causes a hard crash when a report open is canceled in the On No Data event and the On Close event contains a call to a public function. This does not appear to be an issue with MS Access 2003 or earlier.
Steps to reproduce:
Create a new report in MS Access 2007 (this is not an issue in MS Access 2003 and prior)
Set the recordsource (it can be anything, but it must be a bound report)
Set event On No Data to [Event Procedure]
In the code module for the report, enter:
Private Sub Report_NoData(Cancel As Integer)`
Cancel = True
End Sub
Set event On Close to =Foo()
In a standard code module, add the following code:
Public Function Foo()
End Function
Open the report using a filter that excludes all data (in order to raise the No Data event)
Results:
Access immediately suffers a hard crash with the "Access has stopped working..." error message
Two questions:
Is there a workaround?
Is this a known issue? (known to Microsoft or the greater MS Access community)
Workaround
There are a couple of workarounds:
Replace the function call in the report's On Close property with "[Event Procedure]" and then call the function from within the report's Private Sub Report_Close() in VBA.
- OR -
Move the function call from the report's On Close property to its On Unload property. (Note: the On Unload report property was introduced with MS Access 2007)

MS Access raise form events programmatically

Is it possible to raise built-in MS Access form events programmatically? I have a
feeling it isn't but thought I would check. (I am using Access 2003).
For instance, I want to do something like this within a private sub on the
form:
RaiseEvent Delete(Cancel)
and have it trigger the Access.Form delete event -- i.e. without actually
deleting a bound record.
Note my delete event is not handled by the form itself but by an external
class, so I can't simply call Form_Delete(Cancel).
I can understand your confusion -- I didn't explain any of the bigger context. Sorry.
Basically, the situation is I have an 'index' i.e. 'continuous-forms' form which is bound to a read-only query. The query has to be read only because it involves an outer join. But, I want to be able to delete underlying records from this form.
So my first thought was to do the deletion outside the form recordset, eg. using a delete query. And I was hoping to hook the standard Delete/BeforeDelConfirm/AfterDelConfirm events around this manual deletion routine by raising these events myself. But alas, this is not possible.
If the form itself handled these events, I could simply call the handlers (Form_Delete, etc), but my project has custom classes that handle form delete and update events (validation, confirmation, logging, etc.) for all the forms. (#Smandoli, it's not well-documented, I just discovered it a few months ago and use it extensively now -- maybe you know about it already -- you can set up external classes to handle your form events. See for example here)
Long story short, I found a workaround I'm satisfied with. It involves making the 'index' form a subform of another form that is bound to a recordset that can be deleted from. So the deletion can be done in the outer form using the standard Access form events, based on the selection in the inner form.
#Knox, I disagree in principle that being able to raise 'built-in' events yourself is difficult to document and maintain. Plenty of other frameworks depend on it. In practice, I agree with you, since we all have to work within the limitations of our tools and 'best practices' that evolve around those limitations. The blessing and curse of Access is its tight binding between recordsets and forms...
In common, there is simply no need to fire standard form events on your own, normally this shows a wrong understanding of form events in general (if not even events in general).
The form events exists to react on user interaction with the form or to notify the code behind of something that generally happens (like the Form_Load event). The event subs are there to react on these event - nothing more.
It is an often seen thing that people wants to execute event subs directly, but that's also a wrong way. There is a reason why event subs are in general declared as "Private" and not "Public", it should prevent calling them directly from outside the code module, but in fact you should also not execute any of them inside the same code module. Event subs always has to be called exclusively by their events, although it's possible to call them directly.
If an event sub has any code which should be executed also elsewhere then create a private or public sub inside the same module (depending on if you want to execute them from outside or not) and then call this sub from the event sub. If you think you must execute the same sub from elsewhere you can now also call the same sub. This is not a question of "it is possible to call the event sub directly", it is mainly a design question. You should always be sure that an event sub was called only by the event itself and never by any code. The problem when calling an event sub by code is that you can get in trouble very fast if you execute a code and also a real event executes it. In the end you get a big chaos of code which is very hard to debug.
It is, by the way, of course possible to call the event subs from a class module which has a reference to the form (which is needed if you use the class module to handle general events). You only would need to declare the event subs as Public and then you can call them with the form reference, but as stated above: Don't do that.
If a class module is used to handle the events then you can do anything here, you don't need the form code.
If a query is read only and you want to delete a record of a base table no event sub could help you. They are fired when the user wants to delete something which he can't do because it's read only so DoCmd does also not help you.
Like David said in the comment above, simply create a Delete button anywhere you want which can then read out the ID of the current row in the continous form and start a "DELETE" SQL command, then simply requery the continous form and you're done. You can also handle this in your standard class module because you can not only forward form events you can also forward control events on the same way. Create an Init procedure in your class module which takes all the controls from your form you want to handle with it any maybe additional the name of the base table in each continous form, then the class module can assign it to a standard "WithEvents" defined control variable of for example type CommandButton and save the base table name to a string variable. (Don't forget to set the OnClick event to "[Event Procedure]" in the Init procedure.)
In the Load event of your continous form where you probably initialize your class module you can then forward the base table name and the delete button control to the Init procedure of the class module which then can handle the deletion on a very generic way by starting a DELETE query on the base table and requery the form because it already has the form reference also. No need to call any event procedure.
Last but not least: Maybe there are frameworks which allow you to raise events directly but in common I would say that the creators of such frameworks also didn't understand the purpose of event procedures. If you have ever created an own event in a class module of your own you will see that they also cannot be raised outside the class module. Of course, you CAN create a "RaiseEvent" sub on your own to call them externally - and in fact, in case of own events it can make sense in some scenarios. In case of form (control...) events they should inform the code about something happened and there should be a reaction now. If you use events in own class modules you would normally also create a "WithEvents" variable in the outside module to get informed when an event happened in the other class module. An event should make it possible to make the module objects independent of each other. The module with the event will only raise the event and it doesn't know if anyone is listening to it or react on this event. It informs "the world" that there was something which happened in the class module, nothing else. Like a radio station which sends the daily news "to the world" but it doesn't know about if anyone listens to it. Normally, no listener of the radio station would go to the radio station and reads his own news for other listeners. Only the people at the radio station decides what to send and when. Same story.

ms-access vba Eval function behavior

I have a public function in an Access form
Public Function PopupProcess() as long
MsgBox Me.ActiveControl
PopupProcess = 1
End Function
When I call
eval("forms('MyForm').popupprocess")
it shows a message box 2 times. Does anybody know why it does that?
I Have Access 2003 with SP3.
EDIT : Main idea is to call function from that form for Custom Commandbar control OnAction.
Maybe you have better way to call function from a form for commandbar control.
This is a very long standing bug that’s been around since the days of access 97, (about 4-5 versions of access).
The solution here is to NEVER use the forms qualifier, simply place the following in your on action event, and you’ll be just fine
=PopUpProcess()
Note that you must precede it with=, and the suffix must have the brackets ()
Keep in mind that you can actually use behavior to your advantage. The function that runs is going to be from the form that currently has the focus on the screen. That means you can have different forms with the same name of the function, and whichever form has the focus, that function with that name will run from that forms code module.
Even better, if one of the forms does not have that function as public in the forms code module, then the function in a standard code module is used. So you might have nine forms, that all use the standard one function in the main standard code module. However, the 10th form might need to run special code, so you simply place that function code in the form’s code module as public and it will run in place of the public on in the standard code module.
This approach allows you to build a single custom menu bar that applies to many different forms, but those many forms will run different code on from that custom menu bar. This also encourages you to place the menu code in the form it belongs.
So to solve your problem, simply don’t use a form’s qualifier, and use the above format.
Note that you can pass Parameters from those functions also, for example
=PopUpProcess(‘hello’)
And then declare the function as:
Public Function PopUpProcess(strParm as string)
Keep in mind that the function and syntax and all of what I stated above also applies to when you use the on action in a ribbon for access 2007.
No idea. What happens if you call it like this?
Call Forms("MyForm").PopupProcess
Try using the CallByName function, instead of eval, to call your function. It should only fire your function once, and it will still allow you to parameterize the form name and the function or sub name:
CallByName Forms("MyForm"), "PopupProcess", VbMethod