How to Run a Macro Properly in Outlook [closed] - vba

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I Believe this question is very common but I am getting a very unique situation.
I have a code that I am using for delay delivery.
But the problem is I am unable to run the macro.
Public Sub Applicaion_Reminder(ByVal Item As Object)
Dim objPeriodicalMail As MailItem
If Item.Class = olTask Then
If InStr(LCase(Item.Subject), "send an email periodically") Then
Set objPeriodicalMail = Outlook.Application.CreateItem(olMailItem)
'Change the following email information as per your actual needs
With objPeriodicalMail
.Subject = "Email to Gmail"
.To = "bfarhan8#gmail.com"
.HTMLBody = "<HTML><BODY>It's a Test</HTML></BODY>"
.Importance = olImportanceHigh
.ReadReceiptRequested = True
.Send
End With
End If
End If
End Sub
When I hit on the run it asks me for Macro Name when I define a name it creates a new Sub.
If I remove the parameters of the
Application_Reminder()
to match the name with a macro name It gives an error on line number 3.
My question is how to run this Macro Properly. I searched the web but didn't find any useful help.

What you have missed is that macros can either be subroutines or functions and for each there are two major types.
A subroutine does something. Your Application_Reminder is a subroutine because it does something: send a reminder. A function can do something, but its real purpose is to return a value.
Some subroutines and functions need parameters, but some do not.
If I write a function Sqrt, the immediate question is: square root of what? I want to be able to write:
Answer = Sqrt(5)
That is, today I want the square root of 5. Tomorrow, I might want the square root of 7.
I would write:
Function Sqrt(ByVal Number as Double) as Double
‘ Code to calculate square root of Number
Sqrt = ResultOfCalculation
End Function
Almost all functions have parameters, but it is not essential. I could have a function, GetCurrentTemperature that reads a thermometer and returns a temperature. It would not need a parameter.
You have written a subroutine that has a parameter: Applicaion_Reminder(ByVal Item As Object). When you try to run Applicaion_Reminder, the interpreter wants to know what Item. I do not think the interpreter’s response is very sensible. It should have just told you, “You cannot run a subroutine with a parameter.”
You need a subroutine without a parameter that decides which Item is to be processed. With some computer languages, that subroutine must have the name Main. With VBA it can have any name.
That is, you need a subroutine like this:
Sub PickAnItemThatNeedsAReminder()
Dim Item as Object
‘ Code to set Item to the required MailItem
Call Applicaion_Reminder(Item)
End Sub
There are four distinct methods of selecting a MailItem. I imagine you scrolling down your Sent Items folder looking for emails to which you have not received a reply. When you find such an email, you run PickAnItemThatNeedsAReminderwhich sends a reminder.
Sub PickAnItemThatNeedsAReminder ()
Dim Exp As Explorer
Dim Item As Object
Set Exp = Outlook.Application.ActiveExplorer
If Exp.Selection.Count = 0 Then
Call MsgBox("Please select one or more emails then try again", vbOKOnly)
Exit Sub
Else
For Each Item In Exp.Selection
Call Applicaion_Reminder(Item)
Next
End If
End Sub
Exp.Selection is a list of all the currently selected emails. You can select as many emails as you want and them run PickAnItemThatNeedsAReminder. It will call Applicaion_Reminder for every selected email.
Additional Background
My belief is you have found a routine that runs off an event and have tried to adapt it to your requirements. Events are an incredibly useful feature of Outlook. However, if you do not yet understand that you cannot run a macro without a parameter, you are not yet ready for events. We say: walk before you run.
BraX and Super Symmetry would be correct in telling you to use ThisOutlookSession if you are going to use events. I have suggested you use Explorer (which is technically an event) but which is much easier for a beginner to understand than an application level event which is what you seemed to have found. With my approach, all your code can be in an ordinary module.

Application.Reminder event Occurs immediately before a reminder is displayed
set up Task Item with reminder then call your vba function - Applicaion_Reminder
See example on this answer
https://stackoverflow.com/a/40144594/4539709
if you want to call it with selected email then see Tony's answer

Related

Outlook get warned before deleting messages from Outlook's explorer pane

This post first part is an answer to a question I couldn't find an answer to, while the second part is a remaining question on the topic.
Basically I tend to accidentally delete Outlook mails, sometimes even without noticing and therefore wanted a confirmation.
There is a solution for this when deleting an email opened in a separate window:
https://www.datanumen.com/blogs/get-warned-moving-deleting-items-outlook/ (Many thanks to that author btw)
but this solution does not cover the case of deleting an email from within Outlook's explorer pane.
This post gave a hint but not an answer
https://www.datanumen.com/blogs/get-warned-moving-non-empty-folder-outlook/
So the solution is (much of the code taken from the posts referenced above):
Private WithEvents objExplorer As Outlook.Explorer
Private WithEvents objCurrentFolder As Outlook.Folder
Private Sub Application_Startup()
Set objExplorer = Outlook.Application.ActiveExplorer
End Sub
Private Sub objExplorer_Activate()
'Get the currently selected folder
Set objCurrentFolder = objExplorer.CurrentFolder
End Sub
Private Sub objCurrentFolder_BeforeItemMove(ByVal Item As Object, ByVal MoveTo As MAPIFolder, Cancel As Boolean)
Dim xPrompt As String
Dim xYesOrNo As Integer
xPrompt = "Delete?"
xYesOrNo = MsgBox(xPrompt, vbYesNo + vbDefaultButton2, "Confirm Deleting Mail")
If xYesOrNo = vbYes Then
Cancel = False
Else
Cancel = True
End If
End Sub
Remaining question
The annoying thing with the above code is that when deleting a selection of multiple emails, the confirmation request is issued for each email, which is quite a drag when deleting 10+ emails.
So far I've failed to come up with a proper, i.e. robustly coded solution for this (tried the selection approach – see the update below).
→ Any ideas?
Update 2020-June-08 – Quasi solution
By now I've got what I'd call a quasi solution covering all tricky cases encountered so far including events caused by undo, moving emails, opening an email in a separate window and then deleting, …
The trouble is that I now have got one flag (true/false) like variable and a counter which have to be updated each time one of the above events is fired. Result: Difficult to overview spaghetti like code 🙁
→ I can post the code in case someone's interested, but be aware that it is not nice and short.
So your question is how to avoid multiple prompts? Off the top of my head:
1. On the first prompt, check the number of selected items (Application.ActiveExplorer.Selection.Count), store in a separate variable and skip that many future prompts by decrementing the counter each time the event handler fires and prompting only when it reaches 0).
2. Check the time when the prompt was displayed, and avoid showing future prompts for another, say, 3 seconds.

MS Access 2019: How do I check for duplicate records before update, then run a specific procedure if duplicates are found?

I am very new to both StackOverflow and to doing any sort of advanced programming in MS Access. I created a database to catalog my trading card collection (Excel just wasn't cutting it since we're talking about over 2000 unique cards). At first it was just a simple table of records, but now it's turned into a full-fledged database that I have search forms for and queries and everything.
What I'm trying to do right now is streamline my process a bit, and there is something very specific that I want to make Access do. I'm almost certain that whatever I want to do will have to be done in VBA, and I'm just not familiar enough with it to do what I want.
What I want it to do is this: Any time a new record is entered, I want it to check the record before it saves the record into the DB (I'm fairly confident that I need to use the "Before Update" event for this) and make sure that the "Sort ID" field (an auto-calculated field I've created) contains no duplicates (I know I'll most likely have to use queries for this since auto-calculated fields can't be indexed). If the program detects a duplicate, I want it to produce a message box saying that I'm trying to enter a duplicate record and ask me if I want to update the "number owned" field of the existing record instead of creating a new one, and then take me to the record in question on an affirmative response.
What I currently have is a validation rule that uses an index (comprised of the fields that generate the Sort ID), which generates a custom error message by using the following VBA code in the "On Error" event:
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Const conErrRequiredData = 3022
If DataErr = conErrRequiredData Then
MsgBox ("Duplicate Sort ID. Please update the 'number owned' field on the existing record instead.")
Response = acDataErrContinue
Else
Response = acDataErrDisplay
End If
End Sub
This code works exactly as it should, but I want more than a pop-up error that I can't do anything with. I have a query entitled "CheckDuplicateSortID" that I created using the Query Wizard, and it checks the "Sort ID" field for duplicates, but that's as far as I've managed to get. The example on This site is about the closest I've managed to find to what I'm looking for, but the code sample given is very difficult for me to understand because there's very little explanation with it; I'm not familiar enough with Access VBA to know which parts are important code and which parts are his specific field names and other variables; I haven't gotten any error messages because I'm stumped on even trying to figure out what needs to be changed from that sample code and what it needs to be changed to.
Edit: Just for the sake of clarification, the solution doesn't have to involve the Sort ID field. I created that so I'd have one field I can point the program to. But if it would be simpler to just use the index that I use for my current validation rules (with the error message generated by the above code), I'm open to that too.
After some considerable finagling, I actually managed to find a solution to this on my own, though it is probably needlessly complicated (I'm definitely open to simpler solutions, if anyone has any).
Rather than using the actual SortID field, I modified the code in the OP to this:
Private Sub Form_Error(DataErr As Integer, Response As Integer)
Dim strMsg As String
Dim iResponse As Integer
'The text to be displayed in the message prompt.
strMsg = "Unable to save record. The values you have entered would generate a duplicate." & Chr(10)
strMsg = strMsg & "Would you like to clear this form and edit the existing record instead?"
'Calls for the yes/no message prompt specifically when the no-duplicate
'validation rule is violated (error 3022).
Const conErrRequiredData = 3022
If DataErr = conErrRequiredData Then
iResponse = MsgBox(strMsg, vbQuestion + vbYesNo, "Invalid Sort ID")
Response = iResponse
If iResponse = vbYes Then
'Calls a custom function that opens the record in question for editing.
UpdateOnError
Else
'Cancels the operation on a negative response and does not clear the form.
Cancel = True
End If
Else
Response = acDataErrDisplay
End If
End Sub
As you can see from the above code, the event calls a custom function, which is coded as follows:
Function UpdateOnError()
On Error GoTo UpdateOnError_Err
Dim UpdateGoToID As Variant
'Selects the Sort ID in question for the purpose of opening the existing record.
UpdateGoToID = Forms![Card List Entry Form]!txtSortID
'Clears the invalid form.
DoCmd.RunCommand acCmdUndo
'Opens the existing record the user attempted to duplicate.
DoCmd.OpenForm "Card List Entry Form", acNormal, "", "[Sort ID]=" & "'" & UpdateGoToID & "'", , acNormal
UpdateOnError_Exit:
Exit Function
UpdateOnError_Err:
MsgBox Error$
Resume UpdateOnError_Exit
End Function
Like I said, this is probably a lot more steps than actually needed, but it does work as I want it to: when the user attempts to enter a duplicate record, an error message pops up asking if they would like to update the existing record. If yes, it takes them to the existing record. If no, it closes the error message without clearing the form or saving the record.

Input box getting a compile error in VBA

I am learning how to create input boxes and I keep getting the same error. I have tried two different computers and have received the same error. The error I get is a "Compile Error: Wrong number of arguments or invalid property assignment"
Here is my code:
Option Explicit
Sub InputBox()
Dim ss As Worksheet
Dim Link As String
Set ss = Worksheets("ss")
Link = InputBox("give me some input")
ss.Range("A1").Value = Link
With ss
If Link <> "" Then
MsgBox Link
End If
End With
End Sub
When I run the code, it highlights the word "inputbox"
And help would be greatly appreciated.
Thanks,
G
Three things
1) Call your sub something other than the reserved word InputBox as this may confuse things. *Edit... and this alone will resolve your error. See quote from #Mat's Mug.
2) A̶p̶p̶l̶i̶c̶a̶t̶i̶o̶n̶.̶I̶n̶p̶u̶t̶B̶o̶x̶(̶"̶g̶i̶v̶e̶ ̶m̶e̶ ̶s̶o̶m̶e̶ ̶i̶n̶p̶u̶t̶"̶)̶ Use VBA.Interaction.InputBox("give me some input"). You can do this in addition to the first point. Documentation here.
3) Compare with vbNullString rather than "" . See here. Essentially, you will generally want to do this as vbNullString is, as described in that link, faster to assign and process and it takes less memory.
Sub GetInput()
Dim ss As Worksheet
Dim Link As String
Set ss = Worksheets("ss")
Link = VBA.Interaction.InputBox("give me some input")
ss.Range("A1").Value = Link
' With ss ''commented out as not sure how this was being used. It currently serves no purpose.
If Link <> vbNullString Then
MsgBox Link
End If
' End With
End Sub
EDIT: To quote #Mat's Mug:
[In the OP's code, what is actually being called is] VBA.Interaction.InputBox, but the call is shadowed by the procedure's identifier "InputBox", which is causing the error. Changing it to Application.InputBox "fixes" the problem, but doesn't invoke the same function at all. The solution is to either fully-qualify the call (i.e. VBA.Interaction.InputBox), or to rename the procedure (e.g. Sub DoSomething(), or both.
Sub InputBox()
That procedure is implicitly Public. Presumably being written in a standard module, that makes it globally scoped.
Link = InputBox("give me some input")
This means to invoke the VBA.Interaction.InputBox function, and would normally succeed. Except by naming your procedure InputBox, you've changed how VBA resolves this identifier: it no longer resolves to the global-scope VBA.Interaction.InputBox function; it resolves to your InputBox procedure, because VBAProject1.Module1.InputBox (assuming your VBA project and module name are respectively VBAProject1 and Module1) are always going to have priority over any other function defined in any other referenced type library - including the VBA standard library.
When VBA resolves member calls, it only looks at the identifier. If the parameters mismatch, it's not going to say "hmm ok then, not that one" and continue searching the global scope for more matches with a different signature - instead it blows up and says "I've found the procedure you're looking for, but I don't know what to do with these parameters".
If you change your signature to accept a String parameter, you get a recursive call:
Sub InputBox(ByVal msg As String)
That would compile and run... and soon blow up the call stack, because there's a hard limit on how deep the runtime call stack can go.
So one solution could be to properly qualify the InputBox call, so that the compiler knows exactly where to look for that member:
Link = VBA.Interaction.InputBox("give me some input")
Another solution could be to properly name your procedure so that its name starts with a verb, roughly describes what's going on, and doesn't collide with anything else in global scope:
Sub TestInputBox()
Another solution/work-around could be to use a similar function that happens to be available in the Excel object model, as QHarr suggested:
Link = Application.InputBox("give me some input")
This isn't the function you were calling before though, and that will only work in a VBA host that has an InputBox member on its Application class, whereas the VBA.Interaction.InputBox global function is defined in the VBA standard library and works in any VBA host.
A note about this:
If Link <> "" Then
This condition will be False, regardless of whether the user clicked OK or cancelled the dialog by "X-ing out". The InputBox function returns a null string pointer when it's cancelled, and an actual empty string when it's okayed with, well, an empty string input.
So if an empty string needs to be considered a valid input and you need to be able to tell it apart from a cancelled inputbox, you need to compare the string pointers:
If StrPtr(Link) <> 0 Then
This condition will only be False when the user explicitly cancelled, and will still evaluate to True if the user provided a legit empty string.

Programming VBA in an Outlook form

I created my own Outlook form to use it as standard surface to enter certain orders instead of the normal message form. The creation, editing and sending works perfectly fine and in the next step I want to insert some code via VBA.
My problem is that I can´t access the objects of my form in the VBA editor. E.g. I want to show a message box when a certain checkbox is checked. According code would be:
Sub example()
If CheckBox1.Value = True Then
MsgBox("Checkbox 1 is checked.")
End If
End Sub
When I run the code I get the error that the object could not be found. The same goes for every other object, like textboxes or labels etc.
I guess the solution is pretty simple, like putting Item. or sth. like that in front of each object. But so far I wasn't able to find the solution.
I´m using Outlook 2010.
I know this is a year too late but you'll want to do something like this example below. It's kinda a work around but you can get whatever value was selected.
Sub ComboBox1_Click()
Set objPage = Item.GetInspector.ModifiedFormPages("Message")
Set Control = objPage.Controls("ComboBox1")
MsgBox "The value in the " & Control.Name & _
"control has changed to " & Control.Value & "."
End Sub
You should be able to get the value, just get a handle on the object you want using the Inspector
The following is an excerpt from here
When you use a custom form, Outlook only supports the Click event for
controls. This is a natural choice for buttons but not optimal for
controls like the combo box. You write the code by inserting it into a
form’s VBScript editor. You need to have the Outlook form open in the
Form Designer and click the View Code button found in the Form group
of the Developer tab.
Sub CheckBox1_Click()
msgbox "Hello World"
End Sub
The code page is fairly minimal with no syntax highlighting. I just tried this now and it does work. Dont forget to Publish your form to pick up the new changes.
I know this is almost 6 years late but, in VB and VBA, simply start with the form name. (And if that doesn't work, just keep going up a parent object and you'll get there.) So, your code becomes:
Sub example()
If MYFORMNAME.CheckBox1.Value = True Then
MsgBox("Checkbox 1 is checked.")
End If
End Sub
Of course, after typing "MYFORMNAME." you'll know if it will work because typomatic will kick in when the system recognizes "MYFORMNAME" after you hit the period.

Openform action was canceled in MS-Access VBA code

I am supporting an application which was running for the past 3 years. It was developed completely in MS Access and written in VBA.
Suddenly the application is facing the mentioned error at the following lines:
DoCmd.OpenForm FormName:="frmNewPeerGroup", View:=acNormal, windowmode:=acWindowNormal, OpenArgs:=5
FrmNewPeerGroup code
Private Sub Form_Open(Cancel As Integer)
Dim lDept As Long, lDiv As Long
lType = OpenArgs 'Supplied by caller
lAssmtVer = 1 'Current
sName = ""
sDescription = ""
dtCreatedDate = Format(Now(), "dd/mm/yyyy")
sCreatedBy = UCase(userPerms.NTLoginName)
lSupervisorID = userPerms.userID
lTeam = 0
With cmbBxType
.RowSourceType = "Value List"
.RowSource = GetValueListDict(pgType)
.Value = lType
.Enabled = (OpenArgs = 1)
End With
With cmbBxVersion
.RowSourceType = "Value List"
.RowSource = GetValueListDict(pgAssmtType)
.Value = lAssmtVer
End With
mgLogoDesc.Visible = False
txtBxCreatedDate.Value = dtCreatedDate
txtBxCreatedBy.Value = sCreatedBy
If OpenArgs = 5 Then
lTeam = oActiveAssmt.TeamID
lDept = GetParentID(aTeams(), CInt(lTeam))
lDiv = GetParentID(aDepts(), CInt(lDept))
With cmbBxDivision
.RowSourceType = "Value List"
.RowSource = GetValueListArray(aDivs())
.Value = lDiv
.Enabled = False
End With
With cmbBxDepartment
.RowSourceType = "Value List"
.RowSource = GetValueListArray(aDepts())
.Value = lDept
.Enabled = False
End With
With cmbBxTeam
.RowSourceType = "Value List"
.RowSource = GetValueListArray(aTeams())
.Value = lTeam
.Enabled = False
End With
Else
With cmbBxDivision
.RowSourceType = "Value List"
.RowSource = GetValueListArray(aDivs())
.Enabled = False
End With
cmbBxDepartment.Enabled = False
cmbBxTeam.Enabled = False
End If
End Sub
Many instances of the DoCmd.OpenForm command are giving the error in a message box saying:
The expression On Click you entered as the event property setting
produced the following error: The OpenForm action was canceled.
- The expression may not result in the name of macro, the name of
a user-defined function, or [Event procedure].
- There may have been an error evaluating the function, event, or macro.
This is the error message I am receiving.
My problem is, the same code was running around 3 years, but suddenly some updates to Microsoft or Office might be giving trouble to this code.
Did anyone come across this error in the past weeks? Please let me know what else we can do to make this work again.
This thread is very old but I came across the same error and spent a few hours looking for an answer. I was able to find the cause after some time and thought of posting my answer as it may help someone in a similar situation. Creating a application using Access Forms is new to me, so the error message was not directly intuitive.
My forms were Master table data entry forms and configured to be Pop-up and Modal with Me.Form.Name sent as parameter in the DoCmd.OpenForm command using a button (OnClick event) placed next to the Combo controls on a transaction form to allow user to quickly add new records. This parameter value was picked up in the Form_Open(Cancel As Integer) event and used later to refresh the combo box (Forms!<formname>.Controls!<controlname>.Requery) once data was submitted to the master table using the pop-up form.
It appears that the Open event doesn't occur when you activate a form that's already open (ref: https://msdn.microsoft.com/en-us/library/office/aa211439(v=office.11).aspx). Each time I received the error, my data entry form was open in Design view in Access. So I closed the form in design mode, and repeated the steps. And Voila! no error!
Since I will have more than one forms open, I now need to test and try to use Form_Activate() as recommended in the above MSDN reference link.
I don't know if this qualifies as an answer, but the code in that OnOpen event is dependent on a lot of outside functions. Specifically, the code is assigning value lists for the RowSources of a bunch of combo boxes. The immediate red flag that occurs to me is that non-SQL Rowsources have a finite length, and in Access 97, that limit was 2048 characters (in Access 2003, it's 32,750 -- don't ask me why it's that number!).
So, the immediate thing I see is that perhaps what ever data drives the functions that create those value lists has begun to exceed 2048 characters in length.
If that's the actual answer, then you can write a callback function that will return the values in the arrays, and it won't have the limitation on the returned length. You'd set the RowsourceType to the name of your callback function and leave the Rowsource property blank.
An example of the callback function is found in the A97 help (though I can't find the same example in the A2K3 help). In A97 help, you get there by searching for RowsourceType, and then in the help window, click on the link in the sentence reading "You can also set the RowSourceType property with a ____user-defined function____."
To check this out, you just need to find out the length of the string returned from GetValueListArray() by each of the arrays referenced in the OnOpen event.
It also might be helpful to add an error handler to the OnOpen event, particularly given that there are so many outside dependencies in the code in that particular sub.
And last of all, let me say that it looks like horrible programming. Most of this ought to be settable with default properties, seems to me. I also question that kind of dependency on OpenArgs with such an undocumented input value. What does "5" mean? And what does "1" mean? Is that documented somewhere? It's just terrible, terrible code, in my opinion.
I'd likely do this with a standalone class module instead, because that will be self-documenting in terms of what does what. You'd set a particular named property to 5 and that would control what the form gets from the class module methods for populating the combo boxes. It would all be in one place, and you could use a meaningful property name to make it clear what the values 5 and 1 represent. It's particularly helpful to do this if you have the same kind of code in the OnOpen event of multiple forms. In that case, it's a no-brainer to move it out of the form modules, and the only question is whether you put it in a regular module or in a standalone class module (as I'm suggesting).
Anyway, perhaps none of this is on point, but it might give you some ideas.
Could it be the security settings is Access? All recent versions of Access has a security settings dialog where you can enable (or disable) macros in the application. I think you will get this error if macros are disabled.
Are you sure one of the required references (VBA IDE > Option > References) isn't missing?
If you're referencing Excel/Word or external objects, are you sure that the references to the type libraries are the right ones (if you're using specific versions instead of doing late binding)
Are you building the MDE on a 64 bit machine by any chance?
What is the code on the form frmNewPeerGroup? What version of Access are you using? If it is 2003, sp3 causes problems for which there is a hotfix. Have you tried decompile and / or compact and repair?
If you have an original mdb, check the references to make sure that none of them are marked MISSING. This is quite a likely reason for problem in that it has suddenly occurred.
To check the references, look at Tools->References on the menu for a code window.
If no references are missing, you could try stepping through the form code to get a more exact idea of where the error is occurring.