Summary
OLE Documents, such as Word, Excel or PowerPoint, and ActiveX Controls such as Internet Explorer are COM objects that can be embedded into other applications running on a Microsoft® Windows® platform. This article provides an overview of integrating OLE Documents and ActiveX Controls into an application using SWT.By Veronika Irvine, OTI
March 22, 2001
OLE Objects can be contained in SWT widgets. From there, they can be activated and deactivated for user interaction, and manipulated by the application according to their specification. For example, edited data and state can be retrieved or saved. If the object is no longer required, it can be disposed.
The first step in embedding an OLE Object is to create the OleFrame:
Display display = new Display();Shell shell = new Shell(display);OleFrame frame = new OleFrame(shell, SWT.NONE);
When activated, an OLE Document displays its own menu bar over top of the application‘s menu bar. The application can contribute menus to the OLE Document‘s menu bar through the OleFrame as follows:
Menu bar = new Menu(shell, SWT.BAR);shell.setMenuBar(bar);MenuItem fileItem1 = new MenuItem(bar, SWT.CASCADE);fileItem1.setText("&File_Item_1");MenuItem fileItem2 = new MenuItem(bar, SWT.CASCADE);fileItem2.setText("&File_Item_2");MenuItem containerItem = new MenuItem(bar, SWT.CASCADE);containerItem.setText("&Container_Item");MenuItem windowItem1 = new MenuItem(bar, SWT.CASCADE);windowItem1.setText("&Window_Item_1");MenuItem windowItem2 = new MenuItem(bar, SWT.CASCADE);windowItem2.setText("&Window_Item_2");frame.setFileMenus(new MenuItem[] {fileItem1, fileItem2});frame.setContainerMenus(new MenuItem[] {containerItem});frame.setWindowMenus(new MenuItem[] {windowItem1, windowItem2});
The next step is to create an OleClientSite or an OleControlSite. Some COM objects can function as both an OLE Document and an ActiveX Control. Embedding an OLE Document is equivalent to embedding the entire application. The OLE Document provides its own toolbar and menu bar for accessing its behavior. An ActiveX Control only provides the content part and the parent application must manage the behavior of the content through the API of the ActiveX Control. To determine if a COM object supports the OLE Document behavior, look for the IOleDocument interface. To determine if a COM object supports the ActiveX Control behavior, look for the IOleControl interface. To see which interfaces the OLE Object implements, look at the type library.
To embed an OLE Document, create an OleClientSite object. To embed an ActiveX Control, create an OleControlSite object. The parent in either case is the OleFrame. When you create the OleClientSite or the OleControlSite, the associated OLE Document or ActiveX Control will automatically be created and associated with the container site.
There are two ways to create an OleClientSite:
OleClientSite clientSite = new OleClientSite(frame, SWT.NONE, “Word.Document”);
File file = new File(“C:\\OleDocumentation.doc”); OleClientSite clientSite = new OleClientSite(frame, SWT.NONE, file);
An OleControlSite is created from the ProgramID for the ActiveX Control. For example, the ProgramID for the Internet Explorer is “Shell.Explorer”. The web browser can be embedded in an application as follows:
OleControlSite controlSite = new OleControlSite(frame, SWT.NONE, "Shell.Explorer");
public int doVerb(int verb)where
Applications like Word save additional information in the Storage file such as paragraph formats, Author‘s name and Company. If you save a Word Document to an ordinary file, only the text will be saved.
Some applications edit resources which are not OLE specific and therefore can not save their contents into a Storage file because then other applications will not understand the contents. For example, a bitmap can be edited with Microsoft‘s Paint application which can be embedded as an OLE Document. A bitmap, however, has a format that does not include an OLE header. In this case, the contents must be saved to an ordinary file.
Before saving the file, check that a save is necessary using the method isDirty() on the OleClientSite. If a save is required, invoke the method save(File file, boolean includeOleInfo) on the OleClientSite. The boolean includeOleInfo should be set to true to save to a Storage file and false to save to an ordinary file.
File file = new File("C:\\OleDocumentation.doc");OleClientSite clientSite = new OleClientSite(frame, SWT.NONE, file);// ... edit the document ...if (clientSite.isDirty()) {File tempFile = new File(file.getAbsolutePath() + ".tmp");file.renameTo(tempFile);if (clientSite.save(file, true)){// save was successful so discard the backuptempFile.delete();} else {// save failed so restore the backuptempFile.renameTo(file);}}
NOTE: In the case of OLE Documents, the order of activating and deactivating is very important – first deactivate the old document and then activate the new document otherwise the menu bar will not appear correctly.
Deactivating an OLE Document or ActiveX Control is done by calling deactivateInPlaceClient on the OleClientSite or OleControlSite:
currentSite.deactivateInPlaceClient();
myClientSite.dispose();or
myControlSite.dispose();
Note: When an OLE Object is terminated, saving is not performed automatically, nor will there be any checking for a “dirty” state (no checking for unsaved changes before closing). The application must write this code.
int OleClientSite.exec(int cmdID, int options, Variant in, Variant out)where
In and out parameters are defined using a Variant, which is the OLE mechanism for generically passing around any type of data. A Variant may contain an integer, a boolean, a string or many other different types of objects.
The OLE Object may or may not recognize the command. Before sending a command, you can ask an OLE Object if it recognizes the command using OleClientSite.queryStatus(int cmdID). This will return some combination of the following values:
Here is an example of how the Exec command is used:
int result = controlSite.queryStatus(OLE.OLECMDID_PRINT);if ((result & OLE.OLECMDF_ENABLED) == OLE.OLECMDF_ENABLED) {controlSite.exec(OLE.OLECMDID_PRINT, OLE.OLECMDEXECOPT_PROMPTUSER, null, null);}
Further Reading on “exec”: MSDN Library: Platform SDK->Component Services ->COM ->OLE and Data Transfer->Reference ->Interfaces->IOleCommandTarget
A simple case is the Web Browser. It provides commands like navigate, back, forward, home which you can access like this:
OleControlSite controlSite = new OleControlSite(frame, SWT.NONE, "Shell.Explorer");OleAutomation automation = new OleAutomation(controlSite );
Variant getProperty(int dispIdMember)where
OleControlSite controlSite = new OleControlSite(frame, SWT.NONE, "Shell.Explorer");OleAutomation automation = new OleAutomation(controlSite);int[] rgdispid = automation.getIDsOfNames(new String[]{"LocationName"});int dispIdMember = rgdispid[0];Variant result = automation.getProperty(dispIdMember);System.out.println("The Web Browser is currently viewing the URL "+result.getString());Matching type library record:
interface IWebBrowser : IDispatch {…[id(0x000000d2), propget, helpstring("Gets the short (UI-friendly) name of the URL/file currently viewed.")]HRESULT LocationName([out, retval] BSTR* LocationName);…}
boolean OleAutomation.setProperty(int dispIdMember, Variant rgvarg).where
// Allow multiple selection in an embedded Calendar ControlOleControlSite controlSite = new OleControlSite(frame, SWT.NONE, "MSComCtl2.MonthView");OleAutomation automation = new OleAutomation(controlSite);int[] rgdispid = automation.getIDsOfNames(new String[]{"MultiSelect"});int dispIdMember = rgdispid[0];Variant multiSelect = new Variant((short)1); // set to true (0 = false)automation.setProperty(dispIdMember, multiSelect);Matching type library record:
interface IMonthView : IDispatch {…[id(0x000000013), propput, helpstring("Allow multiple selection."), helpcontext(0x00030da8)]HRESULT MultiSelect([in] BSTR pbMultiSelect);…}
The “GoForward” command on the Web Browser takes no parameters:
OleControlSite webSite = new OleControlSite(frame, SWT.NONE, "Shell.Explorer");OleAutomation automation = new OleAutomation(webSite);int[] rgdispid = automation.getIDsOfNames(new String[]{"GoForward"});int dispIdMember = rgdispid[0];Variant pVarResult = automation.invoke(dispIdMember);Note: Call getIDsOfNames to get the ID of the command
Matching type library record:
interface IWebBrowser : IDispatch {…[id(0x00000065), helpstring("Navigates to the next item in the history list.")]HRESULT GoForward();…}Example 2:
The “Navigate” command of the Web Browser takes several arguments but there is only one mandatory parameter and that is the “URL”. In this example we are not going to send any of the optional arguments:
OleControlSite controlSite = new OleControlSite(frame, SWT.NONE, "Shell.Explorer");OleAutomation automation = new OleAutomation(controlSite);int[] rgdispid = automation.getIDsOfNames(new String[]{"Navigate"});int dispIdMember = rgdispid[0];Variant[] rgvarg = new Variant[1]; // this is the URL parameterrgvarg[0] = new Variant("www.ibm.com");Variant pVarResult = automation.invoke(dispIdMember, rgvarg);Matching type library record:
interface IWebBrowser : IDispatch {…[id(0x00000068), helpstring("Navigates to a URL or file.")]HRESULT Navigate([in] BSTR URL, [in, optional] VARIANT* Flags,[in, optional] VARIANT* TargetFrameName, [in, optional] VARIANT* PostData, [in, optional] VARIANT* Headers);…}Example 3:
The “FormatFont” command in Word Basic takes several optional parameters and we are interested in just a few of them:
// This is a helper method for getting access to Word‘s WordBasic IDispatch interface// because it is rather complicatedprivate OleAutomation getWordBasic(OleClientSite clientSite) {// Get generic IDispatch interfaceOleAutomation dispInterface = new OleAutomation(clientSite);// Get Applicationint[] appId = dispInterface.getIDsOfNames(new String[]{"Application"});if (appId == null) OLE.error(OLE.ERROR_APPLICATION_NOT_FOUND);Variant pVarResult = dispInterface.getProperty(appId[0]);if (pVarResult == null) OLE.error(OLE.ERROR_APPLICATION_NOT_FOUND);OleAutomation application = pVarResult.getAutomation();// Get Word Basicint[] wbId = application.getIDsOfNames(new String[]{"WordBasic"});if (wbId == null) OLE.error(OLE.ERROR_APPLICATION_NOT_FOUND);Variant pVarResult2 = application.getProperty(wbId[0]);if (pVarResult2 == null) OLE.error(OLE.ERROR_APPLICATION_NOT_FOUND);return pVarResult2.getAutomation();}OleClientSite clientSite = new OleClientSite(frame, SWT.NONE, "Word.Document");OleAutomation automation = getWordBasic(clientSite);// set the font to 12 point, Italic, Bold - ignore Color and Font nameint[] rgdispid = automation.getIDsOfNames(new String[]{"FormatFont", "Points", "Color", "Font", "Bold", "Italic"});int dispIdMember = rgdispid[0];Variant[] rgvarg = new Variant[3];int[] rgdispidNamedArgs = new int[3];rgvarg[0] = new Variant(12); // this is the Points parameterrgdispidNamedArgs[0] = rgdispid[1];rgvarg[1] = new Variant(1); // this is the Bold parameterrgdispidNamedArgs[1] = rgdispid[4];rgvarg[2] = new Variant(1); // this is the ItalicrgdispidNamedArgs[2] = rgdispid[5];automation.invokeNoReply(dispIdMember, rgvarg, rgdispidNamedArgs);
Note: Here we have used invokeNoReply instead of invoke. Word was the first OLE Document ever written and as such it is written slightly differently than other OLE Documents. If a Word command does not return a value, you should use the “InvokeNoReply” variations of invoke. For most other OLE Objects, you can always use “invoke” even if there is no value returned.
Further Reading: MSDN Library: Books->Inside OLE->Chapter 14->The Mechanics of OLE Automation->The IDispatch Interface
Example:
// Respond to ProgressChange events in the Web Browser by updating the applications Progress barOleControlSite controlSite = new OleControlSite(frame, SWT.NONE, "Shell.Explorer");OleAutomation automation = new OleAutomation(controlSite);int ProgressChange = 108; //0x6C - obtained from the type libraryProgressBar progressBar = new ProgressBar(shell, SWT.BORDER);controlSite.addEventListener(ProgressChange, new OleListener() {public void handleEvent(OleEvent event) {if (event.type != ProgressChange) return;Variant progress = event.arguments[0];Variant maxProgress = event.arguments[1];if (progress == null || maxProgress == null) return;progressBar.setMaximum(maxProgress.getInt());progressBar.setSelection(progress.getInt());}});Note: The application must know the identifier for the event (ProgressChange) and what kind of data it is being given in the event.argument (In our example event.arguments[0] is the current progress value and event.arguments[1] is the maximum progress value)– You can find this out from the type library:
dispinterface DWebBrowserEvents {…[id(0x0000006c), helpstring("Fired when download progress is updated.")]void ProgressChange([in] long Progress, [in] long ProgressMax);…}addPropertyListener – allows the user to be notified before property changes occur – with the option to veto the change – and after property change has occurred.
Example:
OleControlSite controlSite = new OleControlSite(frame, SWT.NONE, "Shell.Explorer");OleAutomation automation = new OleAutomation(controlSite);int[] rgdispid = automation.getIDsOfNames(new String[]{"ReadyState"});int READYSTATE = rgdispid[0];// Listen for changes to the ready state and print out the current statecontrolSite.addPropertyListener(READYSTATE, new OleListener() {public void handleEvent(OleEvent event) {if (event.detail == OLE.PROPERTY_CHANGING) {// Print out the old stateVariant state = automation.getProperty(READYSTATE);System.out.println("Web State changing from " + state.getInt());event.doit = true; // allow property change to happen}if (event.detail == OLE.PROPERTY_CHANGED) {Variant state = automation.getProperty(READYSTATE);System.out.println("Web State changed to " + state.getInt());}}});
You can listen for a change to any property defined in the type library.
The type library also contains enumerations that can be used to interpret the property values. For example the “ReadyState” values in the previous example are integer values belonging to the following enumeration:
typedef enum {READYSTATE_UNINITIALIZED = 0,READYSTATE_LOADING = 1,READYSTATE_LOADED = 2,READYSTATE_INTERACTIVE = 3,READYSTATE_COMPLETE = 4} tagREADYSTATE;
On the Windows platform, adding OLE Objects to your application expands the user experience. SWT makes this easy to do by allowing OLE Objects to be embedded in SWT Container widgets, making the Object‘s interface available to your application, and therefore to your users.
Microsoft, Windows, Windows NT, and the Windows logo are trademarks of Microsoft Corporation in the United States, other countries, or both.