But over the years I've found things that make me want to slam my fist into the monitor, because they don't work consistently (or give different results in the debugger). Do a facepalm, because once you understand the (often inscrutable) docs it seems so obvious. Cock your head, like a dog hearing a strange sound, as you mumble to yourself "That CAN'T be how it's supposed to work". Or scream at the (probably long gone) Microsoft devs "Just how stupid are you?!?".
I do most of my coding in 2010. But based on Stack Overflow postings, most of these are probably still there. I'm sure some are here only because I'm the stupid one and would appreciate anyone who can help me understand what I missed.
Please feel free to add anything you've found in the comments. Might save someone else from rage-quitting a dev session when they stumble on it.
oView.LockUserChanges
Testing has shown that just setting oView.LockUserChanges is not reliable for Views other than ones that are an oExplorer.CurrentView. You have to update the XML <viewreadonly> element to be sure it's Locked.
oView.XML <viewreadonly>
Stupid doesn't look at the actual value of the <viewreadonly> element in the oView.XML, only if it exists. If it is <viewreadonly>1</viewreadonly> then LockUserChanges is True. But <viewreadonly>0</viewreadonly> will also cause LockUserChanges to be True. The only way to make LockUserChanges False, is to remove the <viewreadonly> element completely.
oExplorer.CurrentView
Undocumented? - It appears that the oExplorer.CurrentView is a separate cached copy of the View in every Explorer.
When you have multiple Explorers open to the same Folder and View, if you get oView from oExplorer.CurrentFolder.CurrentView, or by walking a oFolder.Views collection, the Object Reference, i.e. "oView Is oExplorer.CurrentView", will only return True for one of the Explorers. All the others will match on oFolder.FolderPath and oView.Name, but have a different Object reference.
The docs kind of hint at this, but don't come right out and say it. Leaves you scratching your head when you first stumble on this situation.
To obtain a View object for the view of the current Explorer, use Explorer.CurrentView instead of the CurrentView property of the current Folder object returned by Explorer.CurrentFolder.
You must save [Set] a reference to the View object returned by CurrentView before you proceed to use it for any purpose.
oMailItem.HTMLBody, string = oItem.HTML
If you make any changes to the oItem.HTMLBody string, it is reformatted back to Outlook standard (plus some weird changes) when you put the string back in the Item (oItem.HTMLBody = String).
Even In/Out with no changes will hoark it up:
Dim sHTMLBody As String
sHTMLBody = oItem.HTMLBody
oItem.HTMLBody = sHTMLBody
If oItem.HTMLBody = sHTMLBody -> FALSE
(If you keep up the In/Out loop, after the first time he only changes the end of an href in the first line of the HTML. <link rel=File-List href=...>)
This one is in the docs, but still frustrating when you need to work on hidden HTML elements (e.g. ones that the Word Editor object doesn't expose completely like HTML Divisions) or if you want an email to go out with exactly the HTML you specified (Stupid "fixes" it on a Send).
oMailItem.BodyFormat, oMailItem.Class = {Anything in OlMeetingStatus enumeration}
You can't change the Body Format of a Meeting response to HTML (oMailItem.BodyFormat = olFormatHTML). Even though Stupid does exactly this when he Sends it. He can do it, you can't.
oMailItem.Body, Response
(Using Word Find Syntax to describe the issue) If Stupid sees "{empty}^lxxxxx^l{empty}^p" in a Response he "fixes" it to "{empty}^lxxxxx^p" and puts a Paragraph SpaceAfter 12 on the ^p.
oMailItem.Body, Word Table, End Of Cell Marker
The end of a Word Table Cell is marked with Chr(13) & Chr(7). It shows as a little circle with four antenna sticking out when you have show formatting on.
This causes no end of fun if you naively use any string functions (e.g. Replace) on a Body, thinking that any Chr(13) is a Word paragraph marker. There are a few post about this on the web, but you'd never think of looking for help until after your code makes a mess of anything with a Word table.
IMAP oMailItem.Move
Rarely (but probability non zero) when you do an oItem.Move (which is really a Copy and Delete) from an IMAP Inbox, Stupid will do the Copy OK, but then sends the IMAP server TWO Deletes in quick succession. The IMAP server (rightfully so) rejects the second Delete. But Stupid only sees the results of the second Delete so he thinks the item is still on the IMAP server. He gives you an "Item was copied not moved" error and leaves a "zombie" item in the Inbox.
IMAP oMailItem.EntryId
Sometimes the oMailItem.EntryId you get from oInspector.CurrentItem is not the same as the EntryId of the actual Item in the IMAP folder. This only seems to happen on mail from certain senders (I've never figured out what makes them different). But it will happen around 25% of the time for those senders.
Stupid seems to know what's going on and will always get the right Item from the folder (e.g. doing a Delete from the Inspector). But if I'm in there mucking around and depending on (as the docs say) "[a] store provider assigns a unique ID string when an item is created in its store", it gets pretty interesting.
IMAP oMailItem.DownloadState ("Header Status"), "IMAP Staus"
Both fields are unreliable. On occasion, neither updates until some time after a change in the actual marked/download state. OK. Understandable. But what makes this frustrating is that Stupid always gets it right. He appears to be using some other property (that I can't see) that is instantly updated when the marked/download state changes. (IMAP subsystem running asynchronously?)
Then there is the weird behavior some users see when they use the "Del" key (instead of a menu choice) to delete an IMAP email. Stupid will add a Header Status column to the current view. Even if you are not using IMAP "mark" functionality. e.g. Mark For Delete/Purge, Download Headers Only, etc.
IPM.Post EditMessage
The Edit Mode action (CommandBars.ExecuteMso "EditMessage") for a Post Item opening in an Inspector is not enabled until some time AFTER you've gotten the LAST Activate event from the Inspector.
It seems you have to give Stupid some free time to finish setting things up before he enables the action. Do Nothing For Loops, DoEvents and Sleep don't work. I ended up using a Windows API Timer (callback every 30ms up to a total of 120ms) checking for the "EditMessage" to become enabled. Seems to give him enough time. But that could change on different hardware or configurations.
oMailItem _BeforeDelete Event
Only fires if the Item being deleted is open in an Inspector. Won't catch a delete directly from an Explorer if the Item isn't being displayed. This is documented, but that doesn't make it any less stupid.
oReminders _ReminderRemove Event
oMailItem.BeforeDelete may be a pretty useless event, but not as useless as this one. It gives you absolutely no information of any kind about what just happened. Not which Reminder was removed, not what Item it was attached to, or which of five different ways to delete a Reminder was used. All you can say is "A reminder was removed". Which is exactly what the example in the docs does.
oMailItem _Open Event, oMailItem.BodyFormat, Response
In the Open event handler for a Response, if you access the Body as a Word Doc, it is fully formed. But if you change the BodyFormat to Plain Text all that's there is the original email text not yet formatted as a response. And oItem.HTMLBody is empty. The Plain Text and HTML versions won't become fully populated until after the first Activate event (i.e. there was a oItem.Display executed).
oMailItem.Size
Is zero until the Item is saved or Sent. If you want to check the size of an Item before sending it (Duh), you have to either - Save a copy somewhere (Drafts), check the size, and then delete the saved copy. Or (what I ended up doing) get the size of the HTMLBody, plus the size of all the attachments, plus a fudge to account for all the garbage HTML that Stupid will put at the top of the email. FYI - GMail doesn't like anything over 20MB. Can't say as I blame them.
oFolder.Items _ItemAdd Event
Doesn't always fire. This one is pretty well documented, both by Microsoft and others. But most of what I found talks about it not firing for all the items in a bunch, when you throw them at Stupid really fast.
But I have found that (rarely) it fails even for a single item being added. No pattern that I can find. What the system is doing or what Outlook is doing doesn't seem to matter. Stupid just decides he's not going to tell you about that item.
oItem.UserProperties().Value
You can Get the value of a User Property by just saying oItem.UserProperties("PropName"). But to change it you have to say oItem.UserProperties("PropName").Value. Since Value is the default property for a UserProperties, it seems pretty priggish.
What will really drive you to drink is that if you don't put .Value on a change, then Stupid just ignores it and doesn't change the value and doesn't throw an error. Moral - Get into the habit of putting .Value on every UserProperties reference.
oFolder.Name, oFolder.FolderPath
Stupid URL encodes "\/%" in oFolder.FolderPath, but not in oFolder.Name. He has to encode in FolderPath, but he could at least be consistent. He also allows other junk {Tabs that I know of) in oFolder.Name.
I used the Microsoft example code Obtain a Folder Object from a Folder Path for years until I found out (the hard way) that it doesn't handle "\/%" in a FolderPath that has been URL encoded and had to add a decode part myself.
DAV Searching and Locating (DASL) syntax
Thank God for the Query Builder and SQL tabs in View Settings -> Filter. And OutlookSpy and MFCMAPI. Otherwise you don't stand a chance.
Public Sub with same name as the Module
This Sub in a Module named TEST
Public Sub TEST()
Stop
End Sub
Will show up on Developer -> Macros drop down. But will not execute.
Alt-F8 will show it and error on Run (Sub or Function not defined)
VBE Tools -> Macros will run it with no problems.
Word Find/Replace
I won't go into all the absurd machinations that you have to go through to make Word Find/Replace called from VBA do anything more complicated than: Replace "A" with "B". There are whole web sites devoted to that.
I got so frustrated with Find/Replace that I wrote my own functions that use VBA InStr/Mid/Replace syntax and that try (not always successfully) to hide the vulgarities of the native Word Methods.
But a few things about Word Find/Replace stand out (to me) as really stupid.
Unicode Notation
The ^u notation that you can use in a Word Find to locate a Unicode character will not work as Replacement Text. You have to use CharW(nnnn). This one seems pretty well known, and pretty stupid.
Indestructible ^p
If you do a Word Replace "^p" with "", on the naked "^p" just above a table, or the "End Of Doc ^p", Stupid will tell you that it was replaced, but it will still be there. I understand why it can't be removed, but he should at least tell you the Replace failed.
Find Style
The only way to test if a Style (Name or Object) exist in a Range is to walk the oDocument.Styles collection and see if one of them is in your Range. Too slow.
So you have to do a Set and trap instead. See https://roxtonlabs.blogspot.com/2015/09/vba-test-if-style-exists-in-word.html