Trigger VBA code when changing to a different mail in reading pane - vba

Once I read new mails from a specific sender I would like to move them from the Inbox to a subfolder.
I set Outlook to mark mails as read as soon as I open them. To make sure I can read through the mail I want to move the mail only when I close it/change to a different mail. (Note that I'm using the reading pane.)
I tried the Explorer.SelectionChange event but it triggers multiple times when I change to a new mail.
Private WithEvents expl As Outlook.Explorer
Private Sub Application_Startup()
Set expl = Application.ActiveExplorer()
End Sub
Private Sub expl_SelectionChange()
MsgBox "Selection changed"
End Sub
Why does this trigger multiple times?
How do I get a reference to the mail item I'm "closing"?

Why does this trigger multiple times?
I guess (I could be wrong), it is because of the View that you have. The above said behaviour was not noticed for RPO or Preview mode. When you have, say, a Compact view the selection change happens two times? First when the item is selected and the 2nd time when the item contents are displayed. But like I said, I could be wrong...
Alternative
It would have been easier if Outlook had Application.EnableEvents = False like MS Excel. Hope this alternative helps?
Option Explicit
Private WithEvents expl As Outlook.Explorer
Dim DisableEvents As Boolean
Private Sub Application_Startup()
Set expl = Application.ActiveExplorer()
End Sub
Private Sub expl_SelectionChange()
If DisableEvents = True Then
DisableEvents = False
Exit Sub
End If
MsgBox "Selection changed"
DisableEvents = True
End Sub

Related

Opening Outlook conversations without marking them as read using the VBA MarkAsUnread function

Assume you grouped your Outlook messages by conversation. How to prevent any mail from a conversation to get marked read when double clicking on a conversation's main header?
Code I came up with so far:
I use Application_ItemLoad to get the object of any selected mail
Then, myItem_Read to store the selected mail's UnRead property because it's not accessible yet at the time of ItemLoad
Finally, I listen on the PropertyChange event to change back any read mail to unread. However, in the else branch below the expression mySelection.Item(1).GetConversation.MarkAsUnread fails unexpectedly. From my understanding, mySelection.Item(1) selects the Conversation Header object. Then I try to obtain its Conversation object using GetConversationand call the MarkAsUnread method which should in theory mark all the conversation's messages as unread again. Just theory. And I don't know why.
1.
Public WithEvents myItem As Outlook.mailItem
Private Sub Application_ItemLoad(ByVal Item As Object)
If EventsDisable = True Then Exit Sub
If Item.Class = olMail Then
Set myItem = Item
End If
End Sub
2.
Private Sub myItem_Read()
If EventsDisable = True Then Exit Sub
unReadWhenSelected = myItem.UnRead
End Sub
3.
Private Sub myItem_PropertyChange(ByVal Name As String)
Dim mySelection As Selection
Dim oConvHeader As Outlook.ConversationHeader
Dim oConv As Outlook.Conversation
If EventsDisable = True Then Exit Sub
If Name = "UnRead" Then
If unReadWhenSelected = True And myItem.UnRead = False Then
Set mySelection = Outlook.ActiveExplorer.Selection.GetSelection(Outlook.OlSelectionContents.olConversationHeaders)
If mySelection.Count = 0 Then
myItem.UnRead = True
myItem.Save
Else
mySelection.Item(1).GetConversation.MarkAsUnread
End If
End If
End If
End Sub
Whole story for interested readers:
Let's say you wanted to open a conversation by double clicking its header without the conversation's elements getting marked as read.
I want to use Outlook E-Mails as tasks and change the meaning of a reading a mail to finishing a task. Thus, I use search folders with the option "only show unread messages". As soon as I finish a task, I just mark it read with a macro.
For all the other cases where I just want to read the mails etc. they need to remain unread.
I wrote a macro to accomplish this for single E-Mail messages which are not part of conversations. When it comes to conversations, this macro works for all its elements - but not for the first one also known as the main conversation's header entry.
Edit:
Proof-of-concept code for marking all emails in a conversation as read.
Why is this not working in my example code above?
Sub Testorino()
Dim mySelection As Selection
Set mySelection = Outlook.ActiveExplorer.Selection.GetSelection(Outlook.OlSelectionContents.olConversationHeaders)
mySelection.Item(1).GetConversation.MarkAsUnread
End Sub
Edit: MS says this shouldn't be done at all. Application.ItemLoad's items are not intended to be used again. Note the warning here: https://learn.microsoft.com/en-us/office/vba/api/outlook.application.itemload
It says:
The Item object passed in this event should not be cached for any use outside the scope of this event.
You should be able to use the Items.ItemChange and Items.Add events instead (after initially setting it to the Inbox's items) and ignore every unread change unless your macro started it.
Old Answer --> This seems to work in Outlook 2016 in the conversation view. When the item is closed, mark the conversation as unread. This way, it doesn't skip the first item as you mentioned in your comment. If this interferes with your macro, I can see if I can get it to work after opening the item, but I wasn't getting far with that.
Public WithEvents myItem As Outlook.MailItem
Dim EventsDisable As Boolean
Dim UnreadWhenSelected As Boolean
Private Sub Application_ItemLoad(ByVal Item As Object)
If EventsDisable = True Then Exit Sub
If Item.Class = olMail Then
Debug.Print ("Item_Load")
Set myItem = Item
End If
End Sub
Private Sub myItem_Close(Cancel As Boolean)
If EventsDisable = True Then Exit Sub
Debug.Print ("Item_Close")
If UnreadWhenSelected Then
' Ignore all events fired while marking the conversation as unread
EventsDisable = True
myItem.GetConversation.MarkAsUnread
EventsDisable = False
Else
' Ignore all events fired while marking the conversation as read
EventsDisable = True
myItem.GetConversation.MarkAsRead
EventsDisable = False
End If
End Sub
Private Sub myItem_Read()
If EventsDisable = True Then Exit Sub
UnreadWhenSelected = myItem.UnRead
Debug.Print ("Item_Read. Unread: " & UnreadWhenSelected)
End Sub

How to close or dismiss an Outlook Reminder via Outlook VBA

I want to run a Macro in Outlook at a certain time and so I'm using the Outlook Reminders to do it. I have written the below code, which successfully runs the Macro but after it has finished the If statement, it then pops up the reminder which I don't need to see and so therefore need to close/dismiss it.
Public Sub Application_Reminder(ByVal Item As Object)
If Item.Subject = "Refresh Data Test" Then
Call RunExcelMacros.TestRun
End If
End Sub
Please can someone help suggest how I can dismiss the reminder?
Okay, I think I've got it - the below seems to work, all code is setup in the "ThisOutlookSession" Module:
Private WithEvents OutlookReminders As Outlook.Reminders
Public Sub Application_Reminder(ByVal Item As Object)
Set OutlookReminders = Outlook.Reminders
If Item.Subject = "Refresh Data Test" Then
Call RunExcelMacros.TestRun
End If
End Sub
Private Sub OutlookReminders_BeforeReminderShow(Cancel As Boolean)
Dim OutlookReminder As Reminder
'After the "Application_Reminder" has run it will then run this code straight after which stops the reminder from actually popping up
For Each OutlookReminder In OutlookReminders
If OutlookReminder.Caption = "Refresh Data Test" Then
If OutlookReminder.IsVisible Then
OutlookReminder.Dismiss
Cancel = True
End If
Exit For
End If
Next OutlookReminder
End Sub

Outlook VBA - Detect when inbox is empty? Run Macro when inbox is empty?

I have some outlook VBA code for work that will automatically allocate my team members one email at a time to respond to from our customers, and as it does this it also scans for and gives them any emails that have come in later from the same email address, so the customer can be dealt with in one go.
I want this to run when their own folder becomes empty (i.e. they've dealt with one client, and it automatically runs the above to allocate them another when they move the current mail to an archive, leaving their main inbox folder empty).
Is there any way to do this? I know I can set the macro to check for it every 5 mins, but this will slow Outlook down massively. Any way to trigger the macro only when the user's folder is emptied?
Cheers
Chris
Events are perfect for this.
Events are triggered by the application, when key changes are made. This allows you to avoid using a timed loop.
You can use the WithEvents statement to create a variable that can handle event calls.
In this example the variable f points to the inbox. Whenever an item is deleted from this folder the f_BeforeItemMove procedure is called. It displays the number of items left, minus one. We subtract one because the event is fired before the deletion (this gives you a chance to cancel it, should you wish).
Because we are using an object variable we need to create and destroy it. This occurs when the app is started & exited.
Private WithEvents f As Folder ' Inbox folder, used to monitor events.
Private Sub Application_Startup()
' Register for events.
Set f = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
End Sub
Private Sub Application_Quit()
' Unregister.
Set f = Nothing
End Sub
Private Sub f_BeforeItemMove(ByVal Item As Object, ByVal MoveTo As MAPIFolder, Cancel As Boolean)
' Called when an item is moved out of the inbox.
' Display the number of items left, after delete.
MsgBox (f.Items.Count - 1)
End Sub
This code must be added to the ThisOutlookSession class. The startup and quit events will not fire if pasted into another module.
EDIT
The original solution, above, was trigger just before an item was deleted from the inbox. The OP wanted code that fired just after. This new solution does that.
Private WithEvents f As Folder ' Inbox folder, used to monitor events.
Private WithEvents i As Items ' Items within folder above
Private Sub Application_Startup()
' Register for events.
Set f = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
Set i = f.Items
End Sub
Private Sub Application_Quit()
' Unregister.
Set i = Nothing
Set f = Nothing
End Sub
Private Sub i_ItemRemove()
' Called each time an item is moved out of the inbox.
' This can be triggered by moving an item to another folder
' or deleting it.
' Display the new inbox item count.
MsgBox i.Count
End Sub
As before; this code should be placed inside ThisOutlookSession. You will need to restart Outlook, or manually execute Application_Startup.
You can trap the Items.ItemRemove event to monitor for when the Items.Count property evaluates to 0. Call the Initialize_handler() method during the Application_Startup() event:
Public WithEvents myOlItems As Outlook.Items
Public Sub Initialize_handler()
Set myOlItems = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Items
End Sub
Private Sub myOlItems_ItemRemove()
If myOlItems.Count = 0 Then
'Inbox is empty!
End If
End Sub

MailItem.Reply Event not working as expected

I want to write a script that changes the format of the mail, when I am replying to a text- or rtf-mail, using Outlook 2013. To have something to begin with. I used the reply event described in the MS dev centre. Unfortunately the example does not work as I expect it to. For testing, I put in a simple message box that should pop up after clicking the reply button. I never see that message box. What did I do wrong?
Public WithEvents myItem As MailItem
Sub Initialize_Handler()
Set myItem = Application.ActiveInspector.CurrentItem
End Sub
Private Sub myItem_Reply(ByVal Response As Object, Cancel As Boolean)
'Set Response.SaveSentMessageFolder = myItem.Parent
MsgBox "I never see this message box :("
End Sub
Do you click Reply in the Explorer or Inspector? Your code will only run if you click Reply button in an Inspector.
To use the method promoted by Microsoft you need this code in ThisOutlookSession. It would be needed if the event code is not in this special class module.
Private Sub Application_Startup()
Initialize_handler
End Sub
The method described in the answer from Max, where code is in Application_Startup rather than Initialize_handler, can be used if all code is in ThisOutookSession.
you have to put this into "ThisOutlookSession" - only there it will work!
Option Explicit
Private WithEvents objInspectors As Outlook.Inspectors
Private Sub Application_Startup()
Set objInspectors = Outlook.Inspectors
end Sub
Private Sub objInspectors_NewInspector(ByVal Inspector As Inspector)
If Inspector.CurrentItem.Class = olMail Then
Set newItem = Inspector.CurrentItem
End If
Set Inspector = Nothing
End Sub
Public Sub newItem_Open(Cancel As Boolean)
newItem.BodyFormat = olFormatHTML
If newItem.Sent = True Then Exit Sub
End Sub
This will work on any new mail-item, I do not know how to make this work only for replys. You could check the subject, if there is an subject already it will be an reply.

How can I trigger an event when multiple items added at once to Outlook Folder?

I use event handlers in VBA and Outlook frequently. One of them is one which marks any item which is deleted to be marked as read.
Private Sub deletedItems_ItemAdd(ByVal Item As Object)
Application_Startup
On Error Resume Next
Item.UnRead = False
End Sub
Declared via:
Private WithEvents deletedItems As Outlook.Items
and initialized in Application_Startup as:
Dim olNameSpace As Outlook.NameSpace
Set olNameSpace = olApp.GetNamespace("MAPI")
Set deletedItems = olNameSpace.GetDefaultFolder(olFolderDeletedItems).Items
Unfortunately, this does not affect all the items if I delete multiple items at once.
Is there a way I can do something to hijack this process somehow? I looked into using the _beforeDelete event but you have to set the item correctly each time, which if I could do this problem wouldn't exist anyways.
Apparently I wasn't clear - the use case I have is when I delete messages via the delete key from my inbox, drafts, whatever.
You don't have to.
I was curious about your question so I opened up Outlook and wrote this code in ThisOutlookSession:
Private WithEvents items As Outlook.items
Public Sub SetItems()
Set items = Application.GetNamespace("MAPI") _
.GetDefaultFolder(olFolderDeletedItems) _
.items
End Sub
Private Sub items_ItemAdd(ByVal Item As Object)
Dim mail As MailItem
On Error Resume Next
Set mail = Item
Err.Clear
On Error GoTo 0
If Not mail Is Nothing Then
MsgBox mail.Subject
mail.UnRead = False
End If
End Sub
Then I ran SetItems from the immediate pane, went to my inbox and deleted a SMS message - as expected mail was Nothing. Then I deleted a single email, and got the message with the mail's subject.
When I selected two emails and hit Delete, the event was fired once for each selected email, so I saw two message boxes - it just works! :)
The Outlook API doesn't seem to offer an event which would handle all deletions at once.
i have (almost) exactly the same code and it works also for multiple items - only after sleep-mode Outlook seems to forget how to handle deleted items...
Option Explicit
Public WithEvents itDeleted As items
Private Sub Application_Startup()
Set itDeleted = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderDeletedItems).items
End Sub
Private Sub itDeleted_ItemAdd(ByVal Item As Object)
'MsgBox "deleted-sub fired" 'this only for test-purposes
If TypeOf Item Is MailItem Then
Item.UnRead = False
End If
End Sub
I think the difference in the definition of "deletedItems" is the problem; that you are not checking the mailitem-property is also not optimal.
Hope this helps,
Max