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
- 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"
- 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);
}
- 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
- Press F5 to build and run the application.
- 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.
- Close the document (do not save the file) and quit
Word.
- In Control Panel, change your default printer so that it is
configured to print to the FILE port.
- 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.