Using Object Linking and Embedding (OLE) from PowerBuilder - whether in OLE Automation or the use of ActiveX controls - has long been a source of frustration for many PowerBuilder developers.
There are several reasons for this:
- There is a lack of a full understanding of OLE.
- There is little documentation and practical examples of using OLE with PowerBuilder.
- There are some limitations inherent in the method that PowerBuilder implements OLE.
This article is intended to address some of these issues by providing a primer on OLE, providing some practical examples of its use, and demonstrating some methods for addressing the limitations of PowerBuilder's implementation of OLE.
The first thing we need to do is cover some background information, beginning with some definitions of terms and then some explanation of how OLE works.
COM (Component Object Model) was Microsoft's response to CORBA (Common Object Request Broker Architecture) and is an umbrella for a number of different implementing technologies such as:
- DCOM: Distributed COM
- COM+: Microsoft's response to Enterprise Java Beans (EJB)
- OLE: Object Linking and Embedding
We're primarily concerned here with the last of those, OLE. OLE is a framework that allows individual applications and controls to be combined into a "compound document" that can interact with the user and with each other. When one application is using OLE to interact with another application, the process is called OLE Automation. If the OLE interaction is between an application and a control, the control is referred to as an OLE Custom Control. Originally, OLE Custom Controls were referred to as OCX controls, but were later renamed to ActiveX controls.
OLE objects implement either an interface called IUnknown or an interface called IDispatch or they may implement both, in which case they are referred to as having a dual interface. OLE objects also provide a type library that contains a definition of the objects that can be accessed, their properties, events, and methods as well as the constants that are used when working with the object. This is somewhat similar to the textual include files used with C, except that a type library is a binary file and uses Visual Basic (VB) specific data types.
If a development tool is capable of utilizing the OLE object's type library, it can perform early binding in which the IUnknown interface is used and all references to objects, properties, methods, events, and constants for the OLE object are validated when the code is compiled. Doing so, however, requires support for the VB data types used by the type library. A number of development tools - including PowerBuilder - don't implement the VB data types. Instead, they utilize late binding through the IDispatch interface, in which all the references to objects, properties, and events for the OLE object are not validated until the code is actually run. This also means that these development tools are unable to directly reference the constants defined in the type library.
There are some advantages to the late binding approach. This is particularly true in the case of OLE Automation, where the end user of the application may have a different version of the target application that the developer designed for. Late binding allows for graceful failure of references to objects, methods, properties or events of the OLE object that don't actually exist at runtime. Such can be the case if the end user has a different version of the target OLE application than the developers used. This is somewhat similar to triggering an event in PowerBuilder using the "dynamic" keyword. The event does not have to exist at script compile time, and if the event does not actually exist when the code is run, a "silent" failure will occur (the application will continue operating unaffected).
This is, however, the greatest source of frustration for PowerBuilder developers trying to use OLE. The script appears to be correct, because PowerBuilder allows the application to compile. However, nothing works correctly at runtime. A late bound call to an object method can fail at runtime because:
- The object that it exists on has not been instantiated yet during runtime (e.g., referring to properties of an Outlook message object before the message has been created).
- The object referred to does exist, but the method called actually belongs to a different object (e.g., calling a method that belongs to the Word Document object, but using the Documents object).
- The name of the method is spelled incorrectly.
- The number and/or data types of the arguments passed to the method do not match any valid combination for that method.
When a runtime error does occur, the error message provided is generally not helpful in determining which of these issues (or others) caused the error. And unless they are using a third-party utility that can use the OLE object's type library, PowerBuilder developers can't simply browse through the OLE object to validate the call the way they can for PowerBuilder objects.
One other type of OLE object you may run into is called an ActiveX Designer (for example, Data Dynamic's Active Reports). ActiveX Designers may look like other OLE Custom Controls, but they are actually add-ins for Visual Basic that are custom designed to interact with the Visual Basic IDE. As a result, they won't operate correctly within PowerBuilder. (see Figure 1)
OLE Custom Controls
We will cover OLE Custom Controls first because they are a bit simpler to work with. One reason is that although OLE Custom Control methods and properties are late bound and as a result are not directly viewable within the IDE, their events are exposed within PowerBuilder. Therefore you can at least browse the events of the control using the Object Browser and code those events within the User Object painter. (see Figure 2)
In addition, OLE Custom Controls have the OLE Control Properties and OLE Control Help buttons in the Properties view within the User Object painter.
The example being shown here is for the TX Text Control object from The Imaging Source Europe GmbH. Because it's actually a set of four controls, I've created a Custom Visible user object that acts as a container for the set. (see Figure 3)
I've then added functions to the custom visible user control for the various operations that I want to use. Those functions, in turn, interact with the OLE Custom Controls. By doing this, the issue of dealing with late binding is contained within the custom visible user control.
An excellent source for obtaining evaluation versions of OLE Custom Controls is Component Source (www.componentsource.com).
For example, the TX Text Control object uses two variations of a single method to handle displaying a Find or a Find/Replace dialog.
- FindReplace ( 1 ): Displays a Find dialog
- FindReplace ( 2 ): Displays a Find/Replace dialog
The custom visual user object has two separate functions to implement those methods:
The "object" keyword is the mechanism through which late binding is accomplished. Similar to the "object" keyword used for DataWindow property and data dot notation, PowerBuilder does not attempt to validate anything beyond the keyword during development.
OLE Custom Control properties are handled similarly. In this same example, the initial color scheme for the control is set during the object's constructor event as follows:
ole_control.object.TextBkColor = RGB(255,255,255)
ole_control.object.ForeColor = RGB(0,0,0)
ole_control.object.BackColor = RGB(255,255,255)
In some cases, it may become necessary to trigger events on the OLE Custom Control (the events shown within the IDE are the events that the control fires). The PowerBuilder Send function is used to accomplish this. For example, when printing using the TX Text Control, it's necessary to acquire and then release the Device Context (DC) for the printer by triggering events on the control. That is accomplished, in the case of releasing the DC, as follows:
Send(ole_control.Object.hWnd, ll_TX_RELEASEPRINTERDC, ll_hDC, 0)
where hWnd is the Windows handle to the control, ll_TX_RELEASEPRINTERDC is an instance variable that holds the number of the event we wish to fire, and ll_hDC is the device context we previously acquired and now wish to release.
In the Component One Charting control sample, the grid is populated using the code that includes the following:
...array gets populated...
//Copy the array to the chart
( MyArray )
ChartGroups is actually a 'collection', essentially equivalent to an array. PowerScript uses parentheses to indicate an element of an OLE Custom Control collection, rather than brackets as might have been expected. PowerScript does not support referencing collection elements by name. Instead, collection elements generally have a name property, and it becomes necessary to iterate through the elements of the collection individually looking for the one with the right name.
In the accompanying example for ESRI's MapObjects, the zoomout function event of the control has the following code:
loo_rect = this.Object.Extent
this.Object.Extent = loo_rect
Notice that the loo_rect object is created from the class OLEObject. Many of the properties of OLE objects - either OLE Custom Controls or OLE Automation applications - are themselves OLE objects. In the case of a function that returns an OLEObject or an existing property, we don't use the CREATE command for the OLEObject, because it's simply a pointer to the object that already exists.
Whereas the TX Text Control example was actually several controls, the MapObjects control is a single control. What I have done in that example is declared a custom user object of type "standard visual" based on the OLEControl class, and then assigned the MapObjects control to it. Then, similar to what we did with the TX Text Control sample, we can add events and functions to that control that are more "PowerBuilder-friendly." For example, an "addlayer" function has been added to the control that takes a reference to a data file as a string. The script for that function is shown in Listing 1.
In Listing 1 we are also using local OLEObject variables, but in this case we are using the CREATE function to instantiate them. That is because the function that adds the layer takes an OLEObject as an argument, and we'll have to create one to pass to it. We use ConnectToNewObject calls to define the data type for the newly created OLEObjects. Without that call, the OLEObjects would have no specific methods or properties we could use to set them up correctly.
The same approach was taken with the Data Dynamics #Grid sample. The #Grid control is a hierarchical grid control, which means that groups within the grid control can be collapsed similarly to a Treeview - a formatting style not easily achieved within a DataWindow. A standard visual control was inherited from the OLEControl class and the #Grid control assigned to it. The OLEDB version was used, though the choice of the OLEDB (ADO) or ICursor (DAO) version is immaterial. Why use either OLEDB or ICursor methods to interact with the database when we have the DataWindow? Therefore, the control is used in the unbound mode in the sample and instead a DataWindow retrieves the data. One of the "PowerBuilder friendly" methods we added to the standard visual control is a "setdata" method that accepts a DataStore as an argument and populates the control based on it. The script for that method is shown in Listing 2.
The script queries the DataWindow to get display information about each of the columns, and then uses that information to dynamically create the grid columns. The data from the DataWindow is then extracted in tab-delimited format and imported into the grid control.
Note: The control does have an XML import method. However, the format of the XML it requires (rows as elements, columns as attributes) is rather unique, and it was easier to use the tab-delimited approach rather than try to force PB to generate XML in such an unusual format.
The script refers to constants of the controls (i.e., sgFormatCharSeparatedValue). This is possible because those constants were defined as instance variable constants within the standard visual user object:
//Data is saved/loaded in an XML format.
constant integer sgFormatXML = 1
//Data is saved/loaded as character separated values.
constant integer sgFormatCharSeparatedValue = 2
//Data is saved in an Excel format.
constant integer sgFormatExcel = 3
Notice also the use of an uninstantiated OLEObject variable as a pointer (i.e., loo_column). The variable points to the newly created column from the Columns.Add method of the control. Because it's just a pointer, we're able to use the same variable recursively for each of the columns. We use structure exception handling around the ImportDataString method to ensure that an error in the import will not result in the application crashing. The use of structured exception handling will be discussed in more detail in Part 2 on OLE Automation.
OLE provides a very powerful way to extend the capabilities of PowerBuilder either through the use of third-party controls or by allowing PowerBuilder to interact with other applications.
. . .
This article is based on PowerBuilder 9 Internet and Distributed Application Development by various authors (ISBN 0672324997), published by Sams Publishing.
--This article was originally published on PBDJ.