Outlook VSTO - Save AppointmentItem Parent - vsto

I'm trying to save a appointment series subject. This works as expected but after the save, When I performed move/edit the calendar item, I get this error.
Code to reproduce error.
public void OnMyButtonClickContext(Office.IRibbonControl control)
{
var sel = control.Context as Microsoft.Office.Interop.Outlook.Selection;
var i = sel[1] as Microsoft.Office.Interop.Outlook.AppointmentItem;
i.Parent.Subject = i.Parent.Subject + " [CONFIRMED]";
i.Parent.Save();
}
I've tried setting i to null, using Marhsal.ReleaseComObject(i). Neither of which seems to help.

I don't see any release COM objects statements in the code. What objects exactly did you try to release?
Why do you always use the Parent property of the AppointmentItem class?
Each time you call the Parent property you get the reference counter increased. And then you need to release such objects in the code.
Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object.
Read more about that in the Systematically Releasing Objects article.
Here is what MSDN states for that:
When you work with recurring appointment items, you should release any prior references, obtain new references to the recurring appointment item before you access or modify the item, and release these references as soon as you are finished and have saved the changes. This practice applies to the recurring AppointmentItem object, and any Exception or RecurrencePattern object. To release a reference in Visual Basic for Applications (VBA) or Visual Basic, set that existing object to Nothing. In C#, explicitly release the memory for that object.
Note that even after you release your reference and attempt to obtain a new reference, if there is still an active reference, held by another add-in or Outlook, to one of the above objects, your new reference will still point to an out-of-date copy of the object. Therefore, it is important that you release your references as soon as you are finished with the recurring appointment.
Looks like you didn't release all underlying COM objects in the code.

Related

VBA CreateObject versus Type Library References

I have build a COM object via ATL, to use compiled C++ in VBA. My type library is (say) "MyObjLib", and the object is "MyObj".
If I use the Object Browser in VBA, all looks good: it shows me the Library as MyObjLib, and within that I see a class MyObj as a member of the library.
If, in VBA, I include this library through the References menu, I can write:
Dim obj as MyObj
Set obj = new MyObj
and it all works fine. However if I try:
Dim obj as Object
Set obj = CreateObject("MyObjLib.MyObj")
it fails with "Runtime Error 429: ActiveX component can't create object."
This is unfortunate as I now want to use the COM object from Python. Any ideas what I am missing?
Thanks for the comments. I spent some time searching my C++ code for the ProgId. Then I stumbled across another SO answer, about someone who had left the ProgId field blank in the ATL Simple COM object Wizard ... which is exactly what I had done! Hence I had never registered the ProgId for the class (and hence no entry in HKCR in the Registy).
I created another project using the Wizard, this time entering a ProgID and copied the syntax from the .rgs file in the new project to my existing one.
Hey presto, CreateObject() works fine now.

Outlook vb.net how to handle the ItemAdd event for the SentItems folder

I am trying to handle the ItemAdd event fired when a new item gets added to the SentItems folder in a VB.net vsto add-in. When I try this:
Private WithEvents mySentItems As Outlook.Items
mySentItems = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail).Items
I get a Declaration Expected error on the second line, which I find super bizarre since I thought I JUST declared it.
If I do this:
Private WithEvents mySentItems As Outlook.Items = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail).Items
The add-in compiles but then outlook gets really angry and won't even load the add in after throwing this exception:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. --->
System.NullReferenceException: Object reference not set to an instance of an object.
I am doing all of this just after the ThisAddin class declaration before any of the class subs are declared.
Thanks for any help you may be able to provide.
You can use the following declarations in the code after all Outlook objects are initialized, looks like a property or method returns null (Nothing in VB.NET):
Private WithEvents mySentItems As Outlook.Items = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail).Items
Another aspect is that multiple dots are used in the single line of code. It is hard to understand what property or method exactly fires an exception until you break the chain and declare each property and method on a single line of code.
Moreover, you don't release underlying COM objects instantly. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object. Read more about that in the Systematically Releasing Objects article.
It seems you cannot make assignments outside of a sub or function. I moved the line
mySentItems = Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderSentMail).Items
to the ThisAddin_Startup sub and it worked as expected.
Thanks for looking into this if you did, and honestly I don't do this to just post my own answer, I hand't managed to solve this for a day before I finally asked the question.

does VB6 allow referencing Form instance as a singleton just by naming its data type? or what is happening?

I am seeing code like "Unload frmMain" where from what I can tell frmMain is the type/module name, and I don't think it could also be simultaneously a variable name of the "ObjFrmMain" sort. Nevertheless, this command does successfully induce the form in question to unload.
So is the data type being used as an alias for its single existing instance? Or maybe for all of its instances?
Does VB6 do similar things to data types other than those derived from Form?
Yes, VB6 has odd object behavior. It gives you some shortcuts for dealing with form objects.
Load frmMain
...will load a single instance of that form under that variable name. In fact:
frmMain.lblSomeLabel.Caption = "some caption"
... will load that instance. However:
frmMain.SomeStringMember = "some value"
... will not load the form object (meaning the window itself) but you can access these variables, so in essence, the name of the form is a global variable.
You can, however, create new instances:
Dim newForm As MyForm
Set newForm = New MyForm
newForm.Show vbModal
That will actually create a new instance of MyForm, load it and show it, so you can have multiple instances of one form.
Also beware of the oddness in the New keyword:
Dim newObject As New MyClass
Set newObject = Nothing
newObject.SomeStringProperty = "some value"
This works without an "Object Reference Not Set ..." error. When you declare a reference variable using the As New syntax, you can destroy the object by setting it to Nothing and then reference that variable again and it will create a new instance.
In fact that's what's really going on with the forms. There is an implicit:
Dim frmMain As New frmMain
Personally I prefer not to use the As New syntax because it's confusing and dangerous. It also has a performance penalty, vs. this:
Dim newObject As MyClass
Set newObject = New MyClass
... but you're stuck with it for the forms.
What's happening when you call Unload frmMain is that it unloads the window (and all the controls) so all the data in those are gone, but the object frmMain is still hanging around. Therefore even after you unload it, you can still access any member variables and properties. However, if anything references any control on the form, it will trigger an implicit Load frmMain. This is the source of a lot of subtle programming errors in VB6, especially when you're trying to shut down.
Yes, it's a special functionality in VB6 and earlier. I normally tried to avoid doing it, since I saw it more as a source of confusion rather than a help.
The following comment In Visual Basic 6.0 and earlier versions, a special default instance of each form is automatically created for you, and allows you to use the form's name to access this instance. is taken from this MSDN page: Working with Multiple Forms in Visual Basic .NET: Upgrading to .NET

COM Interop & Outlook - Make Outlook Visible?

I'm automating Outlook from a VB.NET program, transferring items into the calendar and contacts at the user's request. It's all working, that isn't the problem; the problem is that automating Outlook like this when it wasn't already open creates a hidden instance. I can perhaps understand how this could be useful, to stop the user closing it down while you're still working on it, but since Outlook appears to force one instance only, if the user tries to inspect the changes made while my program is still hooked into Outlook, nothing happens - the one instance is that hidden instance and the user can't see anything.
In the old days of COM automation I used to be able to make Word or Excel visible, but I seem unable to do that with Outlook. I've tried:
OutlookApp.Visible = True
OutlookApp.Application.Visible = True
OutlookApp.ActiveWindow.Visible = True
OutlookApp.ActiveExplorer.Display()
but none of them work.
It's not critical, but does anyone know if I can get Outlook to show its main window? Bonus points if I can get it to disallow the user to close down the instance, but I'll settle for just showing the window :)
You can show your create mail like this :
mailItem.Display();
It is c# code, but I think, this but be close to your vb.
I normaly test to see if the "Outlook" process is running first, if not then shell up Outlook.exe then attach. This way you should never get a hidden process.
There really is no way to cancel the shut down outlook, you can hook the application quit event to disconnect and dispose in your app though.
I came across this question just now because I had the same challenge. I wasn't entirely happy with the accepted answer since this meant I would have to determine the full path of Outlook.exe. "Shelling up Outlook.exe" does not work. Therefore, I looked for and found another solution. But before I present that, let's look at how you can determine the full path of Outlook.exe if you want to do that.
To determine the full path of Outlook.exe, you need to fetch the Path value from the
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\16.0\Outlook\InstallRoot
registry key (assuming you have Office 16, a.k.a. Office 2016 or 2019) and build the full path of Outlook.exe. On my machine, the Path value is
C:\Program Files\Microsoft Office\root\Office16
so the full path is
C:\Program Files\Microsoft Office\root\Office16\Outlook.exe
However, you need to take into account that the user might have an older (e.g., Office 15, a.k.a. Office 2013) or newer Office version installed and pick the appropriate registry key. You can also get the Office version by retrieving the default value of the
HKEY_CLASSES_ROOT\Outlook.Application\CurVer
key (e.g., Outlook.Application.16). From this, you can infer the version number (e.g., 16) and build the segment of your registry key (e.g., 16.0). Or you can try to find the key with a subkey Outlook\InstallRoot having a Path value. Anyhow, you might see why I wanted to avoid this.
So let's look at an easier solution, noting that I am working with multiple Office applications and, therefore, have the following using directive:
using Outlook = Microsoft.Office.Interop.Outlook;
To make Outlook show its main window if no window is currently visible, I wrote the following utility method:
private static void EnsureOutlookIsVisible(Outlook.Application outlook)
{
object window = null;
NameSpace ns = null;
MAPIFolder folder = null;
try
{
// Check whether Outlook has an active window. If so, Outlook is visible
// and we don't have to do anything.
window = outlook.ActiveWindow();
if (window != null) return;
// No active window is shown, so Outlook is not visible and we need to
// have Outlook display a window.
ns = outlook.GetNamespace("MAPI");
folder = ns.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
folder.Display();
}
finally
{
folder.ReleaseComObject();
ns.ReleaseComObject();
window.ReleaseComObject();
}
}
The above method uses the following extension method to release COM objects:
public static void ReleaseComObject<T>(this T resource) where T : class
{
if (resource != null && Marshal.IsComObject(resource))
{
Marshal.ReleaseComObject(resource);
}
}
With the above methods, to attach to a new or existing Outlook process and make sure Outlook shows its main window, all you need are the following two lines of code:
var outlook = new Outlook.Application();
EnsureOutlookIsVisible(outlook);

AddHandler/RemoveHandler Not Disposing Correctly

Using the AddHandler method, if I never use RemoveHandler, will that lead to memory leaks in some conditions and situations? I'm not so sure about the truth of this.
And are there other causes to memory leaks that are solely available in VB as opposed to C#?
Well usually it doesn't.. but the possibility exists.
When you subscribe to an event, you basically give a delegate (a func pointer if you will) to your method to the event publisher, who holds on to it as long as you do not unsubscribe with the -= operator.
So take for example, the case where you spawn a child form and the form subscribes to the Click button event on the form.
button1.Click += new EventHandler(Form_Click_Handler);
Now the button object will hold on to the form reference.. When the form is closed/disposed/set to null both form and button are not needed anymore; memory is reclaimed.
The trouble happens when you have a global structure or object which has a bigger lifetime. Lets say the Application object maintains a list of open child windows. So whenever a child form is created, the application object subscribes to a Form event so that it can keep tabs on it. In this case, even when the form is closed/disposed the application object keeps it alive (a non-garbage object holds a ref to the form) and doesn't allow its memory to be reclaimed. As you keep creating and closing windows, you have a leak with your app hogging more and more memory. Hence you need to explicitly unsubscribe to remove the form reference from the application.
childForm.Event -= new EventHandler(Form_Handler)
So its recommended that you have a unsubscribe block (-=) complementing your subscribe routine (+=)... however you could manage without it for the stock scenarios.
If object a is suscribed to the object b event then object b will not be collected until object a is collected.
An event suscription counts as a reference to the publisher object.
And yes, this happens on C# too, i has nothing to do with the language.