How To Dismiss a Dialog Box Displayed by an Office Application by Using Visual C++ .NET and MFC

Article translations Article translations
Article ID: 310744 - View products that this article applies to.
This article was previously published under Q310744
Expand all | Collapse all

On This Page

Note Microsoft Visual C++ .NET (2002) supports both the managed code model that is provided by the Microsoft .NET Framework and the unmanaged native Microsoft Windows code model. The information in this article applies only to unmanaged Visual C++ code.

SUMMARY

This step-by-step article discusses how to use the object models for Office applications to avoid dialog boxes during Automation. It also provides an example of how to simulate user input to programmatically dismiss a dialog box that cannot be avoided by using the usual properties and methods that are exposed in the object models.

Prevent the Display of Dialog Boxes During Automation

When you are automating an Office application from Visual C++ .NET, the Office application may display a dialog box. The dialog box causes the Visual C++ .NET application to appear to stop responding (hang) because the Visual C++ .NET application is waiting for the dialog box to be dismissed. The dialog box must be dismissed before the Visual C++ .NET application can continue.

At times, you may want to automate an Office application but not require any user interaction with the Office application. In this case, if the Office application displays a dialog box, your application appears to stop responding until a user can dismiss the dialog box. However, a user who can dismiss the dialog box may not be near the computer.

Office applications are not designed for unattended execution. Therefore, an application that automates Office may sometimes encounter a dialog box that is displayed by the Office application. From normal testing of the application, you can usually determine which dialog boxes occur, and you can write your code to avoid those particular dialog boxes.

The following are some recommended strategies for avoiding dialog boxes while you automate an Office application:
  • Determine if the property or method that you are using (the one that is causing the dialog box) has optional arguments that you can pass to it. Sometimes, by passing all arguments to the property or method, you can avoid a dialog box. For example, if you are using the Open method to open an Excel workbook and that workbook is password-protected, and you do not provide the Password argument when you call the Open method, Excel displays a dialog box that asks the user to enter the password. To avoid the dialog box, provide a value for the Password argument when you call the Open method. Similarly, when you use the Close method to close a document, you can specify the SaveChanges argument to avoid a dialog box that asks the user to save changes.For additional information on how to determine the arguments that are available for the property or method that you are calling, click the article number below to view the article in the Microsoft Knowledge Base:
    222101 How To Find and Use Office Object Model Documentation
  • Study the object model of the Office application to see if a property exists that prevents certain dialog boxes. For example, the Excel Application object has AskToUpdateLinks and AlertBeforeOverwriting properties.
  • Set the Application.DisplayAlerts property (in Excel, Project, or Word) or use Application.DoCmd.SetWarnings False (in Access only) to turn off the display of alert messages. Many, but not all, dialog boxes can be avoided by using this setting.
  • Set the Application.FeatureInstall property (in Office 2000 and later) to handle possible This feature is not installed dialog boxes when you access a component that may not be installed on the system.
  • Use Exception handling to avoid run-time error messages that may occur, such as an error message that appears when you try to set Application.ActivePrinter when no printer driver is installed on the system.
  • Test your application thoroughly to help anticipate when dialog boxes may occur. For example, you may call the SaveAs method of an Office application to save to a file. If that file already exists, a dialog box may appear and request confirmation to replace the existing file. If you modify your code to check for the file before you call the SaveAs method, you can prevent the dialog box from appearing. For example, if the file already exists, you can delete it by using the DeleteFile function before you call the SaveAs method.
NOTE: Even if you use these techniques and carefully design your application to avoid dialog boxes, you may still be faced with a situation in which a dialog box cannot be avoided with the methods and properties that are exposed in the object model of the Office application. In such situations, it may be necessary to programmatically dismiss a dialog box by simulating user input. The following demonstration illustrates how to do this with a Visual C++ .NET application and Microsoft Foundation Classes (MFC).

Sample Automation Client

The steps in this section demonstrate how to create an application by using Visual C++ .NET and MFC to automate Word and print a document by using the PrintOut method of the Word Document object. If the default printer is configured to print to the FILE port, a call to the PrintOut method produces a dialog box that prompts the user to enter a file name. To determine if the PrintOut method causes this dialog box to appear, the application initializes a flag variable to False before it calls the PrintOut method, then sets the flag to True after the PrintOut method. Also prior to calling PrintOut, a separate procedure is run on a new thread. That procedure waits 5 seconds and checks the value of the flag variable. If the flag is True, the procedure ends without taking further action. The document is printed and the code execution continues beyond the PrintOut method. However, if the procedure determines the flag variable is still False, it is assumed that the PrintOut method has not completed and that the delay is caused by a dialog box that is waiting for user input. The procedure then gives focus to Word and uses keybd_event to dismiss the dialog box.

NOTE: For demonstration purposes, this sample uses the PrintOut method in such a way that it displays a dialog box intentionally when it prints to a printer that is set to a FILE port. Note that the PrintOut method has two arguments, OutputFileName and PrintToFile, that you can provide to avoid this dialog box.

Additionally, this sample assumes that a printer driver is installed and is working properly. If your system displays dialog boxes as a result of a malfunctioning printer, this sample will not handle those types of dialog boxes.

Build the Sample

  1. Follow the steps in the "Create an Automation Client" section of the following Microsoft Knowledge Base article to create a basic Automation client:
    307473 How To Use a Type Library for Office Automation from Visual C++ .NET
    In step 4d of the article, select the following interfaces:

    • _Application
    • _Document
    • Documents
    • Selection
    In step 6 of the article, when you add #include statements for each of the classes for which you generated wrappers, make sure that you add the following #include statements to AutoProjectDlg.cpp:
    #include "CApplication.h"
    #include "CDocument0.h"
    #include "CDocuments.h"
    #include "CSelection.h"
    					
  2. In the IDD_AUTOPROJECT_DIALOG dialog box, right-click Run and select Add event handler from the drop-down menu. In the Event Handler Wizard, select the BN_CLICKED message type and then click Add and Edit. Add the following code to automate Word in the handler:
    void CAutoProjectDlg::OnBnClickedRun()
    {		
    	// Convenient constants.
    	COleVariant covTrue((short)TRUE),
    		covFalse((short)FALSE),
    		covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
    
    	CApplication oWord;
    
    	CDocuments oDocs;
    	CDocument0 oDoc;
    	CSelection oSel;
    	CWinThread* pRunningThread = NULL;
    
    	try
    	{
    		// Get the IDispatch pointer and attach it to the oWord object.
    		if (!oWord.CreateDispatch("Word.Application"))
    		{
    			AfxMessageBox("Cannot automate Word.");
    			return;
    		}
    
    		// Get Hwnd for Word instance.
    		oWord.put_Caption("MyInstanceOfWord");
    		g_HwndWordApp = ::FindWindow("OPUSAPP", oWord.get_Caption());
    		oWord.put_Caption(""); //reset caption to Microsoft Word.
    
    		// Make Word visible.
    		oWord.put_Visible(TRUE);
    
    		// Create a new document.
    		oDocs = oWord.get_Documents();
    		oDoc = oDocs.Add(covOptional, covOptional, covOptional, covOptional);
    
    		// Put text in the document.
    		oSel = oWord.get_Selection();
    		oSel.TypeText("Hello World!");
    
    		// Set a flag to indicate that the PrintOut method is not completed.
    		g_bReady = FALSE;
    
    		// Start a new worker thread to check the g_bReady flag.
    		pRunningThread = AfxBeginThread(MyThreadProc, NULL);
    
    		// Call the PrintOut method, which may prompt the user to
    		// select an output file if the default printer is set to FILE.
    		oDoc.PrintOut(covFalse, covOptional, covOptional, covOptional,
    			covOptional, covOptional, covOptional, covOptional, covOptional,
    			covOptional, covOptional, covOptional, covOptional, covOptional,
    			covOptional, covOptional, covOptional, covOptional);
    		// NOTE: The PrintOut method in Word 2002 and newer accepts 18 arguments,
    	 // and the PrintOut method in Word 2000 accepts 19 arguments.
      // The above is for Word 2002 and newer.
    
    		// Set flag to indicate that the PrintOut method has completed.
    		g_bReady = TRUE;
    
    		// Wait for controlling function to end, with
    		//  a timeout of 30 seconds.
    		::WaitForSingleObject(pRunningThread->m_hThread, 30000);
    
    		// Additional Automation code goes here.
    
    		return;
    	} // End of processing.
    
    	catch(COleException *e)
    	{
    		// Set a flag to prevent the thread from taking action.
    		g_bReady = TRUE;
    
    		char buf[1024];
    		sprintf(buf, "COleException. SCODE: %08lx.", (long)e->m_sc);
    		::MessageBox(NULL, buf, "COleException", MB_SETFOREGROUND | MB_OK);
    	}
    
    	catch(COleDispatchException *e)
    	{
    		// Set a flag to prevent the thread from taking action.
    		g_bReady = TRUE;
    
    		char buf[1024];
    		sprintf(buf,
    			"COleDispatchException. SCODE: %08lx, Description: \"%s\".",
    			(long)e->m_wCode,(LPSTR)e->m_strDescription.GetBuffer(512));
    		::MessageBox(NULL, buf, "COleDispatchException",
    			MB_SETFOREGROUND | MB_OK);
    	}
    
    	catch(...)
    	{
    		// Set a flag to prevent the thread from taking action.
    		g_bReady = TRUE;
    
    		::MessageBox(NULL, "General Exception caught.", "Catch-All",
    			MB_SETFOREGROUND | MB_OK);
    	}
    
    	// Because of the exception, quit Word.
    	try
    	{
    		oWord.Quit(covFalse, covOptional, covOptional);
    	}
    	catch(...){}
    
    	// Wait for controlling function to end, with
    	//  a timeout of 30 seconds.
    	::WaitForSingleObject(pRunningThread->m_hThread, 30000);
    }
    					
  3. Add the following code immediately above the OnBnClickedRun method:
    BOOL CALLBACK EnumThreadWndProc(HWND hwnd, LPARAM lParam);
    BOOL MyActivateApp(HWND hwnd);
    void PutKey(BYTE vkey, BOOL bUseShift = FALSE);
    UINT MyThreadProc(LPVOID pParam);
    BOOL g_bReady = FALSE;
    HWND g_HwndWordApp = 0;
    HWND g_HwndWordDlg = 0;
    
    BOOL CALLBACK EnumThreadWndProc(HWND hwnd, LPARAM lParam)
    {
    	// Find the first visible, enabled window that has
    	// the WS_DLGFRAME style.
    	if((::GetWindowLong(hwnd, GWL_STYLE) & WS_DLGFRAME) && 
    		::IsWindowEnabled(hwnd) &&
    		::IsWindowVisible(hwnd))
    	{
    		g_HwndWordDlg = hwnd;
    		return FALSE; // stop enum
    	}
    	else
    		return TRUE; // continue enum
    }
    
    BOOL MyActivateApp(HWND hwnd)
    {
    	// hwnd is the window handle of the Office
    	// application to activate (g_HwndWordApp)
    	if(hwnd == 0)
    		return FALSE;
    	else
    	{
    		DWORD dwThreadID = ::GetWindowThreadProcessId(hwnd, NULL);
    		g_HwndWordDlg = 0;
    		::EnumThreadWindows(dwThreadID, EnumThreadWndProc, 0);
    		if (g_HwndWordDlg != 0)
    			return ::SetForegroundWindow(g_HwndWordDlg);
    		else
    			return FALSE;
    	}
    }
    
    void PutKey(BYTE vkey, BOOL bUseShift)
    {
    	if(bUseShift)
    		keybd_event(VK_SHIFT, 0, 0, 0);
    	keybd_event(vkey, 0, 0, 0);
    	keybd_event(vkey, 0, KEYEVENTF_KEYUP, 0);
    	if(bUseShift)
    		keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
    }
    
    UINT MyThreadProc(LPVOID pParam)
    {
    	// Pause for 5 seconds.
    	::Sleep(5000);
    
    	// Determine if PrintOut method is done.
    	if(!g_bReady)
    	{
    		// Make sure Word has focus before
    		// simulating keyboard events.
    		MyActivateApp(g_HwndWordApp);		
    
    		// Delete the output file if it already exists.
    		::DeleteFile("c:\\MyOutput.prn");
    
    		// Now simulate the user typing
    		// c:\MyOutput.prn for the print file.
    		PutKey(0x43); // c
    		PutKey(0xBA, TRUE); // : (shift-semicolon)
    		PutKey(0xDC); // (back-slash)
    		PutKey(0x4D, TRUE); // M
    		PutKey(0x59); // y
    		PutKey(0x4F, TRUE); // O
    		PutKey(0x55); // u
    		PutKey(0x54); // t
    		PutKey(0x50); // p
    		PutKey(0x55); // u
    		PutKey(0x54); // t
    		PutKey(VK_DECIMAL); // .
    		PutKey(0x50); // p
    		PutKey(0x52); // r
    		PutKey(0x4E); // n
    		PutKey(VK_RETURN); // Enter key
    	}
    	return 0;
    }
    					
    NOTE: The MyThreadProc procedure creates a file at C:\Myoutput.prn. You can change this in the code to a different file if desired. Also, you can customize the wait time to be greater or less than five seconds when you use the Sleep function.

Test the Sample

  1. Press F5 to build and run the application.
  2. When the dialog box appears, click Run. The program automates Word, adds a new document with some text, and then sends the file to the printer by using the PrintOut method. You do not see a dialog box if your printer is configured to print to a printer.
  3. Close the document (do not save the file) and quit Word.
  4. In Control Panel, change your default printer so that it is configured to print to the FILE port.
  5. Click Run again and note that a dialog box appears in Word. Do not dismiss the dialog box. Wait five seconds, and note that the dialog box is programmatically dismissed and the C:\Myoutput.prn file is created.

REFERENCES

For additional information, click the article numbers below to view the articles in the Microsoft Knowledge Base:
257757 INFO: Considerations for Server-Side Automation of Office
253235 FILE: OFFAUTMN.EXE Discusses Office 97 and 2000 Automation and Provides Sample Code
311452 INFO: Develop Microsoft Office Solutions with Visual Studio .NET
For more information, see the following Microsoft Developer Network (MSDN) Web site:
Microsoft Office Development with Visual Studio
http://msdn2.microsoft.com/en-us/library/aa188489(office.10).aspx

Properties

Article ID: 310744 - Last Review: January 17, 2007 - Revision: 5.4
APPLIES TO
  • Microsoft Visual C++ .NET 2003 Standard Edition
  • Microsoft Visual C++ .NET 2002 Standard Edition
  • Microsoft Excel 2000 Standard Edition
  • Microsoft Excel 2002 Standard Edition
  • Microsoft Office Excel 2003
  • Microsoft PowerPoint 2000 Standard Edition
  • Microsoft PowerPoint 2002 Standard Edition
  • Microsoft Office PowerPoint 2003
  • Microsoft Word 2000
  • Microsoft Word 2002
  • Microsoft Office Word 2003
  • Microsoft Access 2000 Standard Edition
  • Microsoft Access 2002 Standard Edition
  • Microsoft Office Access 2003
Keywords: 
kbautomation kbhowtomaster KB310744

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