Make Outlook AppointmentItem Read-Only - vba

I am creating outlook appointment items programmatically using VBA in MS Access and the Outlook Object Model (though the language shouldn't matter).
Items are added to multiple calendars belonging to a single user that other users are given read/write permissions to. The users have no reason to create or edit appointments on the calendar using Outlook. Appointment data is then stored in backend tables. Essentially, Outlook is being used as my "calendar view."
I am having major issues, however, with users changing appointment items directly in Outlook, which in turn do not update in my backend.
I would love an updateable "ReadOnly" property that can be set per appointment item and that disallows changes unless set back to False...but don't think one exists. Any suggestions?
Things I've tried or dismissed as solutions:
Reminding users of the rules.
Script that finds all mismatched items - this works but is not practical.
Custom Outlook form that doesn't allow edits - doesn't prevent users from dragging appointment around.
UPDATE:
Using the suggestion by nemmy below, I have manged to get this far. This only works if the user selects the appointment before changing anything. It does not work if the appointment is selected and dragged in the same click.
Private WithEvents objExplorer As Outlook.Explorer
Private WithEvents appt As Outlook.AppointmentItem
Public Sub Application_Startup()
Set objExplorer = Application.ActiveExplorer
End Sub
Private Sub objExplorer_SelectionChange()
If objExplorer.CurrentFolder.DefaultItemType = olAppointmentItem Then
If objExplorer.Selection.Count > 0 Then
Set appt = objExplorer.Selection(1)
End If
End If
End Sub
Private Sub appt_Write(Cancel As Boolean)
If Not appt.Mileage = "" Then 'This appointment was added by my program
MsgBox ("Do not change appointments directly in Outlook!")
Cancel = True
appt.Close (olDiscard)
End If
End Sub

Can you hook into the Write Event of appointment items? You could prevent changes being made that way. Something like below might work (Disclaimer not tested):
Public WithEvents myItem As Outlook.AppointmentItem
Sub Initialize_handler()
Set myItem = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderCalendar).Items("Your Appointment")
End Sub
Private Sub myItem_Write(Cancel as boolean)
Cancel=true
End Sub

The problem you have is that the people have write access, can you or is it possible to only give them read access? If that is not acceptable, then my answer is you cannot or should not stop them from changing the items. You need to deal with it. This is what I do.
So when you create the calendar item give it a unique ID for example the ID from your backend table row. Add this to a property of the calendar item like the mileage property.
Now just create an update method which loops through all current calendar items in your table and get them from outlook with the ID, check it has not changed and if it has update your table.
Alternatively and given your comment below;
In my opinion you must control outlook. As such nemmy is on the right track you need to hook into the outlook object probably with the use of an outlook addin. Then you need to get each appointment item that the user opens and check if it has a mileage ID. If it does you either need to tell them to change this in your data base and not outlook, or you need to get the events relevant indicating changes to the appointment item and wait for it to be changed. Then send these changes from outlook to your database.

Related

Split Form creates a separate collections for each of its parts

I have a split Form in MS Access where users can select records in the datasheet and add it to a listbox in the Form thereby creating custom selection of records.
The procedure is based on a collection. A selected record gets transformed into a custom Object which gets added to the collection which in turn is used to populate the listbox. I have buttons to Add a Record, Remove a Record or Clear All which work fine.
However I thought, that pressing all these buttons is a bit tedious if you create a Selection of more then a dozen records, so i reckoned it should be simple to bind the actions of the Add and Remove buttons to the doubleclick event of a datasheet field and the listbox respectively.
Unfortunately i was mistaken as this broke the form. While a doubleclick on a field in the datasheet part of the form added the record to the listbox it was now unable to remove an item from the underlying collection giving me Run-time error 5: "Invalid procedure call or argument". Furthermore the clear all Button doesn't properly reset the collection anymore. When trying to Clear and adding a record of the previous selection the code returns my custom error that the record is already part of the selection and the listbox gets populated with the whole previous selection, which should be deleted. This leaves me to believe, that for some Reason the collection gets duplicated or something along these lines. Any hints into the underlying problem is apprecciated. Code as Follows:
Option Compare Database
Dim PersColl As New Collection
Private Sub AddPerson_Click()
AddPersToColl Me!ID
FillListbox
End Sub
Private Sub btnClear_Click()
Set PersColl = Nothing
lBoxSelection.RowSource = vbaNullString
End Sub
Private Sub btnRemovePers_Click()
PersColl.Remove CStr(lBoxSelection.Value)
FillListbox
End Sub
Private Sub FillListbox()
Dim Pers As Person
lBoxSelection.RowSource = vbaNullString
For Each Pers In PersColl
lBoxSelection.AddItem Pers.ID & ";" & Pers.FullName
Next Pers
lBoxSelection.Requery
End Sub
Private Function HasKey(coll As Collection, strKey As String) As Boolean
Dim var As Variant
On Error Resume Next
var = IsObject(coll(strKey))
HasKey = Not IsEmpty(var)
Err.Clear
End Function
Private Sub AddPersToColl(PersonId As Long)
Dim Pers As Person
Set Pers = New Person
Pers.ID = PersonId
If HasKey(PersColl, CStr(PersonId)) = False Then
PersColl.Add Item:=Pers, Key:=CStr(PersonId)
Else: MsgBox "Person Bereits ausgewählt"
End If
End Sub
This works alone, but Simply Adding this breaks it as described above.
Private Sub Nachname_DblClick(Cancel As Integer)
AddPersToColl Me!ID
FillListbox
End Sub
Further testing showed that its not working if i simply remove the Private Sub AddPerson_Click()
Edit1:
Clarification: I suspected that having 2 different events calling the same subs would somehow duplicate the collection in memory, therefore removing one event should work. This is however not the case. Having the subs called by a button_Click event works fine but having the same subs called by a double_click event prompts the behaviour described above. The issue seems therefore not in having the subs bound to more than one event, but rather by having them bound to the Double_Click event.
Edit2: I located the issue but I Haven't found a solution yet. Looks like Split-Forms are not really connected when it comes to the underlying vba code. DoubleClicking on the record in the datasheet view creates a Collection while using the buttons on the form part creates another one. When trying to remove a collection item by clicking a button on the form, it prompts an error because this collection is empty. However clicking the clear all button on the form part doesn't clear the collection associated with the datasheet part.
Putting the collection outside into a separate module might be a workaround but i would appreciate any suggestions which would let me keep the Code in the form module.
The behavior is caused by the split form which creates two separate collections for each one of its parts. Depending from where the event which manipulates the collection gets fired one or the other is affected. I suspect that the split form is in essence not a single form, but rather 2 instances of the same form-class.
A Solution is to Declare a collection in a separate module "Coll":
Option Compare Database
Dim mColl as new Collection
Public Function GetColl() as Collection
Set GetColl= mColl
End Function
And then remove the Declaration of the Collection in the SplitFormclass and Declare the Collection in every Function or Sub by referencing the collection in the separate Module like in the following example:
Option Compare Database
Private Sub AddPersToColl(PersonId As Long)
Dim Pers As Person
Dim PersColl as Collection
Set PersColl = Coll.GetColl
Set Pers = New Person
Pers.ID = PersonId
If HasKey(PersColl, CStr(PersonId)) = False Then
PersColl.Add Item:=Pers, Key:=CStr(PersonId)
Else: MsgBox "Person Bereits ausgewählt"
End If
End Sub
This forces the form to use the same Collection regardless if the event is fired from the form or datasheet part of the split-form. Any Further information is appreciated but the matter is solved for now.
Thanks everyone for their time

Setting Default Signature selection in Outlook

I can't find an answer to this on the forum, or maybe i'm not typing my query in accurately enough.
With my Outlook 2010 at my workplace the default Signature block keeps getting changed to a default option when I load up Outlook. I don't have access to the source file to make any changes as it is all Server side.
What I want to do is change the default selection of my Signature from the old one to a new one.
Under File -> Options -> Mail -> Signatures I want to change my default Signature to something else upon start up of Outlook 2010 using a VBA code of some form. Is there any way that this can be done?
I have already created the new Signature but I need to reselect it as the default option every time I log onto my terminal, which is frustrating.
Looking for any help please.
After some cursory searching, it looks like Outlook signatures are managed through the Windows registry (for example, see here and here). Specific registry paths seem to depend on your version of Outlook.
Of course, if it's your work email, it's likely you can't make any changes to your registry.
However, if all you want is to automatically insert some specific text to any new email, that can be done via VBA. Basically, you want to use the Open event of a new email to insert specific text. To do that, you need to add the Open hook during the ItemLoad event. Something like this:
' declare the mail item that will have the Open event available
Public WithEvents myItem As Outlook.mailItem
' defines how the Open event will be handled
' note that you only want to do this with unsent items
' (hence the .Sent check)
Private Sub myItem_Open(cancel As Boolean)
If Not myItem.Sent Then
'insert your signature text here
End If
End Sub
' hooks the Open event to a mail item using the ItemLoad event
Private Sub Application_ItemLoad(ByVal Item As Object)
Dim mailItem As Outlook.mailItem
If Item.Class = OlObjectClass.olMail Then
Set myItem = Item
End If
End Sub
For more information, see the relevant Microsoft articles on the Application.ItemLoad event and MailItem.Open event.

Outlook Macro to delete old messages when a new message with same subject arrives

Am trying to write a outlook VB macro, to achieve following:
Triggers when a new email arrives.
Go to a folder inside Inbox - temp1.
Check if there is an existing email which matches the subject of
this new email.
Delete the old email.
Code:
Option Explicit
Private objNS As Outlook.NameSpace
Private WithEvents objItems As Outlook.Items
Private Sub Application_Start()
Dim olApp As Outlook.Application
Dim objWatchFolder As Outlook.Folder
Set olApp = Outlook.Application
Set objNS = Application.GetNamespace("MAPI")
'Set the folder and items to watch:
Set objWatchFolder = objNS.GetDefaultFolder(olFolderInbox).Folders("temp1")
Set objItems = objWatchFolder.Items
Set objWatchFolder = Nothing
End Sub
Private Sub objItems_ItemAdd(ByVal Item As Object)
Dim intCount As Integer
Dim objVariant As Variant
For intCount = objItems.Count To 1 Step -1
Set objVariant = objItems.Item(intCount)
If objVariant.Subject = Item.Subject And objVariant.SentOn < Item.SentOn
Then
objVariant.Delete
Else
End If
Next
End Sub
Issue:
Macro not getting triggered.
Notes:
Have Trust center settings updated to enable macros.
Have added this code in Class Module
When I do F5, it doesn't show any MACRO that it can run.
Seeking some expert help pls!
(1) You need to handle the NewMailEx event of the Application class which is fired when a new item is received in the Inbox. The NewMailEx event fires when a new message arrives in the Inbox and before client rule processing occurs. You can use the Entry ID returned in the EntryIDCollection array to call the NameSpace.GetItemFromID method and process the item. Use this method with caution to minimize the impact on Outlook performance. However, depending on the setup on the client computer, after a new message arrives in the Inbox, processes like spam filtering and client rules that move the new message from the Inbox to another folder can occur asynchronously. You should not assume that after these events fire, you will always get a one-item increase in the number of items in the Inbox.
(2) It looks like you already know how to get the a subfolder of Inbox:
Set objWatchFolder = objNS.GetDefaultFolder(olFolderInbox).Folders("temp1")
(3) To find items with the same subject you need to use the Find/FindNext or Restrict methods of the Items class instead of iterating over all items in the folder. Read more about these methods in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
Also if you need to find items in multiple folders you may find the AdvancedSearch method of the Application class helpful. The key benefits of using the AdvancedSearch method in Outlook are:
The search is performed in another thread. You don’t need to run another thread manually since the AdvancedSearch method runs it automatically in the background.
Possibility to search for any item types: mail, appointment, calendar, notes etc. in any location, i.e. beyond the scope of a certain folder. The Restrict and Find/FindNext methods can be applied to a particular Items collection (see the Items property of the Folder class in Outlook).
Full support for DASL queries (custom properties can be used for searching too). You can read more about this in the Filtering article in MSDN. To improve the search performance, Instant Search keywords can be used if Instant Search is enabled for the store (see the IsInstantSearchEnabled property of the Store class).
You can stop the search process at any moment using the Stop method of the Search class.
See Advanced search in Outlook programmatically: C#, VB.NET for more information.
(4) Use the Delete method for removing items.
Items.ItemAdd Event (Outlook) describes how to apply the ItemAdd event in a Class module. You would then use Application_Startup in ThisOutlookSession to initialize objItems.
What you have is code for the ThisOutlookSession module.
You are watching the ItemAdd event on the temp1 folder, not the Inbox.

How can I implement the PropertyChange event?

I wish to get the details of what has been changed in a MailItem.
I have been able to get ItemChange to trigger based upon Application.Session.Folders("TargetFolder") to a variable as such:
Private Sub olInvMbxItems_ItemChange(ByVal X As Object).
I would like to know the property changed in X. If I pass X to an global variable for Private Sub X_PropertyChange(ByVal x As Object), it will miss the first iteration as X was not initialized on the first pass of ItemChange.
How can I monitor a folder of MailItems to detect Category changes. Whilst ItemChange does this, it gives duplication of action, if I look for specific categories, as many changes trigger ItemChange as mentioned here:
Handle ItemChange event when outlook appointments are dismissed
and here:
Outlook Macro that will copy an email I flag and put it in a folder
The binary flag for the UserProperties in the second item will not function as it is not a one-off event.
Outlook Events VBA says to use PropertyChange but doesn't provide a technique for implementation.
There is no way to do that - even on the MAPI level, the store provider does not keep track on what changed. Your only solution is to compare the old (saved elsewhere by you) and new values to see what changed.
Here is a method that works for a single selection. Refinement yet needed, but this is a starting point:
Private WithEvents olExplorer As Outlook.Explorer
Private olCurSel As Selection
Private WithEvents olCurSelItem As Outlook.MailItem
Private Sub olExplorer_SelectionChange()
Set olCurSel = olExplorer.Selection
Set olCurSelItem = Nothing
Dim i As Long
For i = 1 To olCurSel.Count
If TypeName(olCurSel.Item(i)) = "MailItem" Then
Set olCurSelItem = olCurSel.Item(i)
End If
Next i
End Sub
Private Sub olCurSelItem_PropertyChange(ByVal Name As String)
Debug.Print Name; " is what changed!"
End Sub
Using Outlook.Explorer.Selection we can know that an item has been selected. We can then assign that item to and Outlook.MailItem conditionally and use the PropertyChange event of that Outlook.MailItem trigger the action we wish to take.
Problems:
Does not handle multiple selections
SelectionChange is triggered by the Reading Pane on each selection as well. Thus it is fired twice if the reading pane is open. As written above, olCurSelItem is set twice each SelectionChange.
This is only triggered by local changes. Other systems with access to the mailbox may change the item and it will not trigger the action.

Outlook automatically change reminder based on category

I need to automate Outlook so that when a user sets a certain category on an appointment, it automatically sets the reminder time based on the category.
For example, the user has an "On site meeting" category and an "Off site meeting" category. He wants the reminder time to automatically change to 15 minutes for an on site meeting and 30 minutes for the off site meeting. He understands that if he sets the category wrong or applies both categories the time wont change correctly.
Is it possible to do this, and if so how do I go about it? I imagine there is an event I can catch and handle when an appointment category is changed.
Thank you
EDIT: The appointment requests are received in email, he sets the categories when he accepts the meeting request. The reminder time should be set whenever the category changes.
How to hook up to the event is the part I can't seem to find.
When exactly is the change of reminder time supposed to happen? When initially composing the meeting? Whenever categories are modified?
(e.g. if the user already set a value for the reminder, and then changed the category, would the reminder change?)
Anyway, I guess the solution is to hook up to some events that happen in Outlook and set these values according to your logic. But until the above questions are answered it's not clear which events you'll need to hook up to.
You will want ItemAdd and ItemChange
http://msdn.microsoft.com/en-us/library/office/ff869609(v=office.14).aspx
http://msdn.microsoft.com/en-us/library/office/ff865866(v=office.14).aspx
Something like this:
Public Sub Application_Startup()
Set objCalendar = Outlook.Session.GetDefaultFolder(olFolderCalendar).Items
End Sub
Private Sub objCalendar_ItemAdd(ByVal Item As Object)
setReminder Item
End Sub
Private Sub objCalendar_ItemChange(ByVal Item As Object)
setReminder Item
End Sub
Sub setReminder(ByVal Item As Object)
If InStr(Item.Categories, "A")
' Set the reminder time A
GoTo exitRoutine ' A the longer takes priority over B the shorter
End If
If InStr(Item.Categories, "B")
' Set the reminder time B
End If
exitRoutine:
End Sub