BUG: Cancel parameter for Office events is ignored in Visual Studio .NET 2003

Article translations Article translations
Article ID: 830519 - View products that this article applies to.
This article has been archived. It is offered "as is" and will no longer be updated.
Expand all | Collapse all

On This Page

SUMMARY

When you work with Microsoft Visual Studio .NET 2003, you may experience problems when you use the Cancel parameter in a COM event that is raised by Microsoft Word, by Microsoft Excel, or by Microsoft Outlook. Although the code receives the COM event and the code can set the parameter accordingly, the COM event is not canceled. This problem occurs when you use COM Interop from a managed client (such as Microsoft Visual C# or Microsoft Visual Basic .NET), and you also use version 1.1 of the Microsoft .NET Framework.

CAUSE

The .NET Framework 1.1 has introduced security changes to enhance type safety when interacting with a COM component. The .NET Framework 1.1 enhances type safety by using the marshaling information in the type library of the component. When you automate Word, Excel, or Outlook from a managed client, the COM Interop Assembly (IA) or the Primary Interop Assembly (PIA) for Microsoft Office XP uses the type information that is stored in the Microsoft Office type libraries to handle events. Existing versions of the Word type libraries, the Excel type libraries, and the Outlook type libraries mark the Cancel parameter as an [in] parameter. The Cancel parameter is marked as an [in] parameter so that the common language runtime enforces this restriction and prevents any changes that are made by the managed code from being returned to the caller. Therefore, the event cannot be canceled from a managed code IA (or PIA).

RESOLUTION

This bug was corrected in Microsoft Office 2003.

WORKAROUND

If you use an earlier version of Word, an earlier version of Excel or an earlier version of Outlook, you must establish your own connection point sink in your managed code. A custom handler can avoid the type-library dependency and the enforced-type protection that typically apply. Therefore, the argument can be passed back to the caller.

Microsoft provides programming examples for illustration only, without warranty either expressed or implied. This includes, but is not limited to, the implied warranties of merchantability or fitness for a particular purpose. This article assumes that you are familiar with the programming language that is being demonstrated and with the tools that are used to create and to debug procedures. Microsoft support engineers can help explain the functionality of a particular procedure, but they will not modify these examples to provide added functionality or construct procedures to meet your specific requirements.

Generating a Helper Class

To directly sink up events from Visual C#, you must create your own dispinterface definition and you must also create a helper class to implement your event code. The dispinterface definition must match the globally unique identifier (GUID) and the Dispatch identifiers (DISPIDs) for the event sink that is expected by the Office object that you want to sink events for. To find out what these values are, you can use either of the following methods:
  • You can use the OLE/COM Object Viewer that is included with Visual Studio .NET 2003 Professional Edition
  • You can use the Platform SDK. For additional information about the Platform SDK, visit the following Microsoft Web site:

    http://www.microsoft.com/msdownload/platformsdk/sdkupdate
After you know the GUID and the DISPIDs, you can build your own definition of the dispinterface definition in Visual Basic .NET or in Visual C#.

This article provides you two examples that show you how to use custom connection point to work around this problem. The first example demonstrates how to sink the Application object events for Word 2000 and then cancel the DocumentBeforeClose event so that the user cannot close the document. The second example sinks the ItemSend event for Outlook 2002, so that you can cancel the Send operation in your application.

Example 1: Cancel DocumentBeforeClose event

  1. Start Visual Studio .NET 2003. Start a new project. Select Visual C# as the project type. Select Windows Application as the template. Name the project MyWordEventTest, and then click OK.

    By default, Form1 is created.
  2. On the Project menu, click Add Reference. In the dialog box, select Microsoft Word 9.0 Object Library on the COM tab. Click Select to add a reference, and then click OK to close the dialog box.

    A custom IA is generated for you automatically.
  3. On the Project menu, click Add a Class. Select Code File, and then name the class Word9EventHelper.cs. Click OK to generate the file.
  4. Add the following code to the code window for Word9EventHelper.cs:
    using System;
    using System.Runtime.InteropServices;
    using Word; // The default name for a custom Word IA.
    // If you use Office XP and you have the PIAs
    // referenced in your project, you must change the previous line to read:
    // using Word = Microsoft.Office.Interop.Word;
    
    namespace WordAppEvents9
    {
    	[InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
    	GuidAttribute("000209FE-0000-0000-C000-000000000046")]
    	public interface DWordApplicationEvents9
    	{
    		[DispId(0x00000001)] void Startup();
    		[DispId(0x00000002)] void Quit();
    		[DispId(0x00000003)] void DocumentChange();
    		[DispId(0x00000004)] void DocumentOpen(Word.Document doc);
    		[DispId(0x00000006)] void DocumentBeforeClose(Word.Document doc, ref bool Cancel);	
    		[DispId(0x00000007)] void DocumentBeforePrint(Word.Document doc, ref bool Cancel);
    		[DispId(0x00000008)] void DocumentBeforeSave(Word.Document doc, ref bool SaveAsUI, ref bool Cancel);
    		[DispId(0x00000009)] void NewDocument(Word.Document doc);
    		[DispId(0x0000000a)] void WindowActivate(Word.Document doc, Word.Window wn);
    		[DispId(0x0000000b)] void WindowDeactivate(Word.Document doc, Word.Window wn);
    		[DispId(0x0000000c)] void WindowSelectionChange(Word.Selection sel);
    		[DispId(0x0000000d)] void WindowBeforeRightClick(Word.Selection sel, ref bool Cancel);
    		[DispId(0x0000000e)] void WindowBeforeDoubleClick(Word.Selection sel, ref bool Cancel);
    	}
    
    
    	public class WordAppEventHelper : DWordApplicationEvents9, IDisposable
    	{
    		public WordAppEventHelper()
    		{
    			m_oConnectionPoint = null;
    			m_Cookie = 0;
    		}
    
    		public void Startup()
    		{System.Diagnostics.Debug.WriteLine("Startup");}
    
    		public void Quit()
    		{System.Diagnostics.Debug.WriteLine("Quit");}
    
    		public void DocumentChange()
    		{System.Diagnostics.Debug.WriteLine("DocumentChange");}
    
    		public void DocumentOpen(Word.Document doc)
    		{System.Diagnostics.Debug.WriteLine("DocumentOpen");}
    
    		public void DocumentBeforeClose(Word.Document doc, ref bool Cancel)
    		{
    			System.Diagnostics.Debug.WriteLine("DocumentBeforeClose");
    			Cancel = true; // Cancel the close!
    		}
    
    		public void DocumentBeforePrint(Word.Document doc, ref bool Cancel)
    		{System.Diagnostics.Debug.WriteLine("DocumentBeforePrint");}
    
    		public void DocumentBeforeSave(Word.Document doc, ref bool SaveAsUI, ref bool Cancel)
    		{System.Diagnostics.Debug.WriteLine("DocumentBeforeSave");}
    
    		public void NewDocument(Word.Document doc)
    		{System.Diagnostics.Debug.WriteLine("NewDocument");}
    
    		public void WindowActivate(Word.Document doc, Word.Window wn)
    		{System.Diagnostics.Debug.WriteLine("WindowActivate");}
    
    		public void WindowDeactivate(Word.Document doc, Word.Window wn)
    		{System.Diagnostics.Debug.WriteLine("WindowDeactivate");}
    
    		public void WindowSelectionChange(Word.Selection sel)
    		{System.Diagnostics.Debug.WriteLine("WindowSelectionChange");}
    
    		public void WindowBeforeRightClick(Word.Selection sel, ref bool Cancel)
    		{System.Diagnostics.Debug.WriteLine("WindowBeforeRightClick");}
    
    		public void WindowBeforeDoubleClick(Word.Selection sel, ref bool Cancel)
    		{System.Diagnostics.Debug.WriteLine("WindowBeforeDoubleClick");}
    
    		private UCOMIConnectionPoint m_oConnectionPoint;
    		private int m_Cookie;
    
    		public void SetupConnection(Word.Application app)
    		{
    			if (m_Cookie != 0) return;
    
    			// GUID of the DIID_ApplicationEvents dispinterface.
    			Guid guid = new Guid("{000209FE-0000-0000-C000-000000000046}");
    
    			// QI for IConnectionPointContainer.
    			UCOMIConnectionPointContainer oConnPointContainer = (UCOMIConnectionPointContainer)app;
    
    			// Find the connection point and then advise.
    			oConnPointContainer.FindConnectionPoint(ref guid, out m_oConnectionPoint);
    			m_oConnectionPoint.Advise(this, out m_Cookie);
    		}
    
    		public void RemoveConnection()
    		{
    			if (m_Cookie != 0)
    			{
    				m_oConnectionPoint.Unadvise(m_Cookie);
    				m_oConnectionPoint = null;
    				m_Cookie = 0;
    			}
    		}
    
    		public void Dispose(){RemoveConnection();}
    	}
    }
  5. Switch back to Form1, and then add a command button. Double-click the command button to make the code window for Form1.cs appear. Or, on the View menu, click Code to make the code window for Form1.cs appear. Add the following code to the button handler for the Click event:
    Word.Document doc;
    Object missing = System.Reflection.Missing.Value;
    
    // Create a new instance of Word and then set up the event handler.
    m_oApp = new Word.ApplicationClass();
    m_oAppEvents = new WordAppEvents9.WordAppEventHelper();
    m_oAppEvents.SetupConnection(m_oApp);
    
    // Make Word visible and then display a new document to test close.
    m_oApp.Visible = true;
    doc = m_oApp.Documents.Add(ref missing, ref missing, ref missing, ref missing);
    doc.UserControl = true;
    doc.Content.Text = "Try to close the document";
    
    // You only have to do this one time in this sample.
    button1.Enabled = false;
    
  6. Add the following code to the Form1 class before the button handler:
    Word.Application m_oApp;
    WordAppEvents9.WordAppEventHelper m_oAppEvents;
    
  7. On the Build menu, click Build Solution to make the project. Click Debug, and then click Start to run the application.

    When the application starts, you can click the command button and then Word appears. You cannot close the Word window until you close your application.

    You can use similar code to handle events for Excel.

Example 2: Cancel ItemSend event

  1. Start Visual Studio .NET 2003. Start a new project. Select Visual C# as the project type. Select Windows Application as the template. Name the project MyOutlookEventTest, and then click OK.

    By default, Form1 is created.
  2. On the Project menu, click Add Reference. In the dialog box, select Microsoft Outlook 10.0 Object Library on the COM tab. Click Select to add a reference, and then click OK to close the dialog box.

    A custom IA is generated for you automatically.
  3. On the Project menu, click Add a Class. Select Code File, and then name the class Outlook10EventHelper.cs. Click OK to generate the file.
  4. Add the following code to the code window for Outlook10EventHelper.cs:
    using System;
    using System.Runtime.InteropServices;
    //using Outlook; // The default name for a custom Word IA.
    // If you use Office XP and you have the PIAs
    // referenced in your project, you must change the previous line to read:
    using Outlook = Microsoft.Office.Interop.Outlook;
    using System.Windows.Forms;
    
    namespace OutlookAppEvents10
    {
    	[InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
    	GuidAttribute("0006300E-0000-0000-C000-000000000046")]
    	public interface DOutlookApplicationEvents_10
    	{
    		void ItemSend(object Item, ref bool Cancel);
    		void NewMail();
    		void Reminder(object Item);
    		void OptionsPagesAdd(Outlook.PropertyPages Pages);
    		void Startup();
    		void Quit();
    		void AdvancedSearchComplete(Outlook.Search SearchObject);
    		void AdvancedSearchStopped(Outlook.Search SearchObject);
    		void MAPILogonComplete();
    	}
    
    	public class OutlookAppEventHelper : IDisposable
    	{
    		public OutlookAppEventHelper()
    		{
    			m_oConnectionPoint = null;
    			m_Cookie = 0;
    		}
    
    		private UCOMIConnectionPoint m_oConnectionPoint;
    		private int m_Cookie;
    
    		public void SetupConnection(Outlook.Application app)
    		{
    			if (m_Cookie != 0) return;
    			// GUID of the DIID_ApplicationEvents dispinterface.
    			Guid guid = new Guid("{0006300E-0000-0000-C000-000000000046}");
    
    			// QI for IConnectionPointContainer.
    			UCOMIConnectionPointContainer oConnectionPointContainer = (UCOMIConnectionPointContainer)app;
    
    			// Find the connection point and then advise.
    			oConnectionPointContainer.FindConnectionPoint(ref guid, out m_oConnectionPoint);
    			m_oConnectionPoint.Advise(this, out m_Cookie);
    		}
    
    		public void RemoveConnection()
    		{
    			if (m_Cookie != 0)
    			{
    				m_oConnectionPoint.Unadvise(m_Cookie);
    				m_oConnectionPoint = null;
    				m_Cookie = 0;
    			}
    		}
    
    		#region IDisposable Members
    
    		public void Dispose()
    		{
    			RemoveConnection();
    		}
    
    		#endregion
    
    		//#region DOutlookApplicationEvents_10 Members
    
    		[DispId(0x0000F002)]
    		public void ItemSend(object Item, ref bool Cancel)
    		{
    			DialogResult result;
    			result = MessageBox.Show("Do you want to cancel the ItemSend event?","",MessageBoxButtons.YesNo);
    			if(result == DialogResult.Yes)
    			{
    				System.Diagnostics.Debug.WriteLine("Cancelling Message");
    				Cancel = true;	
    			}
    			else
    			{
    				System.Diagnostics.Debug.WriteLine("Passing Message");
    				Cancel = false;
    			}
    		}
    
    		[DispId(0x0000F003)]
    		public void NewMail()
    		{
    			MessageBox.Show("NewMail");
    		}
    
    		[DispId(0x0000F004)]
    		public void Reminder(object Item)
    		{
    			MessageBox.Show("Reminder");
    		}
    
    		[DispId(0x0000F005)]
    		public void OptionsPagesAdd(Microsoft.Office.Interop.Outlook.PropertyPages Pages)
    		{
    			MessageBox.Show("OptionsPagesAdd");
    		}
    
    		[DispId(0x0000F006)]
    		public void Startup()
    		{
    			MessageBox.Show("Startup");
    		}
    
    		[DispId(0x0000F007)]
    		public void Quit()
    		{
    			MessageBox.Show("Quit");
    		}
    
    		[DispId(0x0000FA6A)]
    		public void AdvancedSearchComplete(Microsoft.Office.Interop.Outlook.Search SearchObject)
    		{
    			MessageBox.Show("AdvancedSearchComplete");
    		}
    
    		[DispId(0x0000FA6B)]
    		public void AdvancedSearchStopped(Microsoft.Office.Interop.Outlook.Search SearchObject)
    		{
    			MessageBox.Show("AdvancedSearchStopped");
    		}
    
    		[DispId(0x0000FA90)]
    		public void MAPILogonComplete()
    		{
    			MessageBox.Show("MAPILogonComplete");
    		}
    		//#endregion
    	}
    }
    Note You need to place the [DispId(#)] attribute on the methods. Otherwise, the event cannot be cancelled.
  5. Switch back to Form1, and then add a command button. Double-click the command button to make the code window for Form1.cs appear. Or, on the View menu, click Code to make the code window for Form1.cs appear. Add the following code to the button handler for the Click event:
    Outlook.MailItem item;
    
    if (m_oApp == null)
    {
    	// Create a new instance of Outlook and then set up the event handler.
    	m_oApp = new Outlook.ApplicationClass();
    	m_oAppEvents = new OutlookAppEvents10.OutlookAppEventHelper();
    	m_oAppEvents.SetupConnection(m_oApp);
    } 
    
    // Make Outlook visible and then display a new message to test send.
    item = (Outlook.MailItem)m_oApp.CreateItem(Outlook.OlItemType.olMailItem);
    item.Subject = "The Subject!";
    item.Body = "Try sending the message.";
    item.Display(m_oApp);
  6. Add the following code to the Form1 class before the button handler:
    Outlook.Application m_oApp;
    OutlookAppEvents10.OutlookAppEventHelper m_oAppEvents;
    
  7. On the Build menu, click Build Solution to make the project. Click Debug, and then click Start to run the application.

    When the application starts, you can click the command button and then Outlook appears. A new email draft is automatically created. Input a recipient in the To line, and then click Send. You will get a pop up dialog asking you if you want to cancel the ItemSend event. Click Yes to cancel it.

    You can use similar code to handle events for Excel.

STATUS

Microsoft has confirmed that this is a problem in the Microsoft products that are listed in the "Applies to" section of this article.

REFERENCES

For additional information about automating Office from Visual Studio .NET, click the following article number to view the article in the Microsoft Knowledge Base:
311452 Develop Microsoft Office solutions with Visual Studio .NET
317109 Office application does not quit after automation from Visual Studio .NET client

Properties

Article ID: 830519 - Last Review: February 27, 2014 - Revision: 3.5
APPLIES TO
  • Microsoft Excel 2002 Standard Edition
  • Microsoft Word 2002
  • Microsoft Outlook 2002 Standard Edition
  • Microsoft Excel 2000 Standard Edition
  • Microsoft Word 2000
  • Microsoft Outlook 2000 Standard Edition
  • Microsoft .NET Framework 1.1
  • Microsoft Visual Studio .NET 2003 Professional Edition
  • Microsoft Visual Studio .NET 2003 Enterprise Architect
  • Microsoft Visual Studio .NET 2003 Academic Edition
Keywords: 
kbnosurvey kbarchive kbnofix kbbug KB830519

Give Feedback

 

Contact us for more help

Contact us for more help
Connect with Answer Desk for expert help.
Get more support from smallbusiness.support.microsoft.com