Using OLE in an Application

About this chapter

This chapter describes several ways of implementing OLE in your PowerBuilder applications.

OLE support in PowerBuilder

OLE, originally an acronym for Object Linking and Embedding, is a facility that allows Windows programs to share data and program functionality. PowerBuilder OLE controls are containers that can call upon OLE server applications to display and manipulate OLE objects.

OLE control

The OLE control in the Window painter allows you to link or embed components from several applications in a window. For most servers, you can also control the server application using functions and properties defined by that server.

In PowerBuilder, the OLE control is a container for an OLE object. The user can activate the control and edit the object using functionality supplied by the server application. You can also automate OLE interactions by programmatically activating the object and sending commands to the server. OLE servers might be either DLLs or separate EXE files. They could be running on a different computer.

You can use PowerScript automation on an OLE control that is visible in a window, or use it invisibly on an object whose reference is stored in an OLEObject variable. The OLEObject datatype lets you create an OLE object without having an OLE container visible in a window.

OLECustomControl

A second control, OLECustomControl, is a container for an ActiveX control (also called an OLE custom control or OCX control). ActiveX controls are DLLs (sometimes with the extension OCX) that always run in the same process as the application that contains them.

Managing OLE objects

You can manage OLE objects by storing them in variables and saving them in files. There are two object types for this purpose: OLEStorage and OLEStream. Most applications will not require these objects, but if you need to do something complicated (such as combining several OLE objects into a single data structure), you can use these objects and their associated functions.

Other areas of OLE support

For information about OLE objects in a DataWindow object, see the section called “Using OLE in a DataWindow Object” in Users Guide.

OLE controls in a window

You can add OLE objects and ActiveX controls to a window or user object. To do so, you use one of the PowerBuilder OLE controls, which acts as an OLE container. This section explains how you select the control you want by choosing whether it holds an OLE object (also called an insertable object) or an ActiveX control:

  • An insertable OLE object is a document associated with a server application. The object can be activated and the server provides commands and toolbars for modifying the object.

  • An ActiveX control or OLE custom control is itself a server that processes user actions according to scripts you program in PowerBuilder. You can write scripts for ActiveX control events and for events of the PowerBuilder container. Those scripts call functions and set properties that belong to the ActiveX control. When appropriate, the ActiveX control can present its own visual interface for user interaction.

ActiveX controls range from simple visual displays (such as a meter or a gauge) to single activities that are customizable (spellchecking words or phrases) to working environments (image acquisition with annotation and editing).

OLE control container features

All OLE control containers support a set of required interfaces. PowerBuilder provides some additional support:

  • Extended control

    An OLE control can determine and modify its location at runtime using its extended control properties. PowerBuilder supports the X (Left), Y (Top), Width, and Height properties, all of which are measured in PowerBuilder units. The control writer can access these properties using the IDispatch-based interface returned from the GetExtendedControl method on the IOleControlSite interface.

  • Window as OLE container

    PowerBuilder implements the IOleContainer class at the window level, so that all OLE controls on a window are siblings and can obtain information about each other. The control writer can access this information using the OLE EnumObjects method. Information about siblings is useful when the controls are part of a suite of controls. Unlike other controls, the OLE controls on a window are stored in a flat hierarchy.

    OLE objects and controls only

    Only OLE objects and controls are visible to this object enumerator. You cannot use this technique to manipulate other controls on the window.

  • Message reflection

    If a control container does not support message reflection, a reflector window is created when an OLE control sends a message to its parent. The reflector window reflects the message back to the control so that the control can process the message itself. If the container supports message reflection, the need for a reflector window, and the associated runtime overhead, is eliminated. PowerBuilder OLE control containers perform message reflection for a specific set of messages.

Defining the control

This procedure describes how to create an OLE control and select its contents.

To place an OLE control in a window or user object:

  1. Open the window or user object that will contain the OLE control.

  2. Select Insert>Control>OLE from the menu bar.

    PowerBuilder displays the Insert Object dialog box. There are three tabs to choose from.

  3. Choose a server application or a specific object for the control (which embeds or links an object in the control), select a custom control, or leave the control empty for now:

    • To create and embed a new object, click the Create New tab. After you have chosen a server application, click OK.

    • To choose an existing object for the control, click the Create From File tab. After you have specified the file, click OK.

    • To insert a custom control (ActiveX control), click the Insert Control tab. After you have chosen an ActiveX control, click OK.

    • To leave the control empty, click Cancel.

      If you click Cancel, the control becomes an OLE control rather than an OLE custom control, and you can choose to link or embed an OLE object in it at any time; you cannot insert an ActiveX control later.

  4. Click where you want the control.

    If you inserted an object, PowerBuilder opens the server application so you can view and edit the object. ActiveX controls cannot be opened.

    If you want to insert an object that belongs to an OLE server application that is not in the list, see the server documentation to find out how to install it.

For more information about using the Insert Object dialog box, see the section called “Adding an OLE object to a DataWindow object” in Users Guide.

OLE controls and insertable objects

The OLE control contains an insertable OLE object. You can change the object in the control in the painter or in a script. You specify what is allowed in the control by setting PowerBuilder properties.

Setting up the OLE control

When you create an OLE control and insert an object, PowerBuilder activates the server application to allow you to modify the object. After you deactivate it (by clicking outside the object's borders in the Layout view), you can use the control's property sheets to set up the control.

To specify the control's appearance and behavior:

  1. Double-click the control, or select Properties from the control's pop-up menu.

  2. In the Properties view, give the control a name that is relevant to your application.

    You will use this name in scripts. The default name is ole_ followed by a number.

  3. Specify a value for Display Name for use by the OLE server. The OLE server can use this name in window title bars.

  4. Specify the control's appearance and behavior by choosing appropriate settings in the Properties view.

    In addition to the standard Visible, Enabled, Focus Rectangle, and Border properties, which are available for most controls, there are several options that control the object's interaction with the server:

    Option

    Meaning

    Activation

    How the user activates the control.

    Options are:

    • Double Click -- When the user double-clicks the control, the server application is activated.

    • Get Focus -- When the user clicks or tabs to the control, the server is activated. If you also write a script for the GetFocus event, do not call MessageBox or any function that results in a change in focus.

    • Manual -- The control can be activated only programmatically with the Activate function.

    The control can always be activated programmatically, regardless of the Activation setting.

    Display Type

    What the control displays.

    Options are:

    • Contents -- Display a representation of the object, reduced to fit within the control.

    • Icon -- Display the icon associated with the data. This is usually an icon provided by the server application.

    • ActiveX document -- Display as an ActiveX document. ActiveX documents fill the space of the container and have access to all the features of the server application.

    Contents

    What the user can insert in the control at runtime.

    Options are:

    • Any -- The user can insert either a linked or embedded object.

    • Embedded -- The user can insert an embedded object.

    • Linked -- The user can insert a linked object.

    Setting Contents changes the value of the ContentsAllowed property.

    Link Update

    When the object in the control is linked, the method for updating link information.

    Options are:

    • Automatic -- If the link is broken and PowerBuilder cannot find the linked file, it displays a dialog box in which the user can specify the file.

    • Manual -- If the link is broken, the object cannot be activated. You can re-establish the link in a script using the LinkTo or UpdateLinksDialog function.

    Setting Link Update changes the value of the LinkUpdateOptions property.

    Size Mode

    How the object is displayed in the container.

    Options are:

    • Clip -- The object's image displays full size. If it is larger than the OLE control, it is clipped by the control's borders.

    • Stretch -- The object's image is resized to fit into and fill the OLE control (default).


Activating the object in the painter

The object in the OLE control needs to be activated so that the server application can manipulate it. For the user, double-clicking is the default method for activating the object. You can choose other methods by setting the control's Activation property, as described in the preceding table. During development, you activate the object in the Window painter.

To activate an OLE object in the Window painter:

  1. Select Open from the control's pop-up menu.

    If the control is empty, Open is unavailable. You must select Insert to assign an object to the control first.

    PowerBuilder invokes the server application and activates the object offsite.

  2. Use the server application to modify the object.

  3. When you have finished, deactivate the object by clicking outside its hatched border.

    You can also choose Exit or Return on the server's File menu, if available.

Changing the object in the control

In the painter, you can change or remove the object in the control.

To delete the object in the control:

  • Select Delete from the control's pop-up menu.

    The control is now empty and cannot be activated. Do not select Clear -- it deletes the control from the window.

To insert a different object in the control:

  1. Select Insert from the control's pop-up menu.

    PowerBuilder displays the Insert Object dialog box.

  2. Select Create New and select a server application, or select Create from File and specify a file, as you did when you defined the control.

  3. Click OK.

During execution

You can insert a different object in the control by calling the InsertObject, InsertFile, InsertClass, or LinkTo function. You can delete the object in the control by calling Cut or Clear.

How the user interacts with the control

When the window containing the OLE control opens, the data is displayed using the information stored with the control in the PBL (or PBD or EXE file if the application has been built).

When the object is activated, either because the user double-clicks or tabs to it or because a script calls Activate, PowerBuilder starts the server application and enables in-place editing if possible. If not, it enables offsite editing.

As the user changes the object, the data in the control is kept up to date with the changes. This is obvious when the object is being edited in place, but it is also true for offsite editing. Because some server applications update the object periodically, rather than continually, the user might see only periodic changes to the contents of the control. Most servers also do a final update automatically when you end the offsite editing session. However, to be safe, the user should select the server's Update command before ending the offsite editing session.

Linking versus embedding

An OLE object can be linked or embedded in your application. The method you choose depends on how you want to maintain the data.

Embedding data

The data for an embedded object is stored in your application. During development, it is stored in your application's PBL. When you build your application, it is stored in the EXE or PBD file. This data is a template or a starting point for the user. Although the user can edit the data during a session, the changes cannot be saved because the embedded object is stored as part of your application.

Embedding is suitable for data that will not change (such as the body of a form letter) or as a starting point for data that will be changed and stored elsewhere.

To save the data at runtime, you can use the SaveAs and Open functions to save the user's data to a file or OLE storage.

Linking data

When you link an object, your application contains a reference to the data, not the data itself. The application also stores an image of the data for display purposes. The server application handles the actual data, which is usually saved in a file. Other applications can maintain links to the same data. If any application changes the data, the changes appear in all the documents that have links to it.

Linking is useful for two reasons:

  • More than one application can access the data.

  • The server manages the saving of the data, which is useful even if your PowerBuilder application is the only one using the data.

Maintaining link information

The server, not PowerBuilder, maintains the link information. Information in the OLE object tells PowerBuilder what server to start and what data file and item within the file to use. From then on, the server services the data: updating it, saving it back to the data file, updating information about the item (for example, remembering that you inserted a row in the middle of the range of linked rows).

Fixing a broken link

Because the server maintains the link, you can move and manipulate an OLE object within your application without worrying about whether it is embedded or linked.

If the link is broken because the file has been moved, renamed, or deleted, the Update setting of the control determines how the problem is handled. When Update is set to Automatic, PowerBuilder displays a dialog box that prompts the user to find the file. You can call the UpdateLinksDialog function in a script to display the same dialog box. You can establish a link in a script without involving the user by calling the LinkTo function.

PowerBuilder displays a control with a linked object with the same shading that is used for an open object.

Offsite or in-place activation

During execution, when a user activates the object in the OLE control, PowerBuilder tries to activate an embedded object in place, meaning that the user interacts with the object inside the PowerBuilder window. The menus provided by the server application are merged with the PowerBuilder application's menus. You can control how the menus are merged in the Menu painter (see Menus for in-place activation).

When the control is active in place, it has a wide hatched border:

Offsite activation means that the server application opens and the object becomes an open document in the server's window. All the server's menus are available. The control itself is displayed with shading, indicating that the object is open in the server application.

Limits to in-place activation

The server's capabilities determine whether PowerBuilder can activate the object in place. OLE 1.0 objects cannot be activated in place. In addition, the OLE 2.0 standards specify that linked objects are activated offsite, not in place.

From the Window painter, the object is always activated offsite.

Changing the default behavior

You can change the default behavior in a script by calling the Activate function and choosing whether to activate an object in place or offsite. If you set the control's Activation setting to Manual, you can write a script that calls the Activate function for the DoubleClicked event (or some other event):

ole_1.Activate(Offsite!)

When the control will not activate

You cannot activate an empty control (a control that does not have an OLE object assigned to it). If you want the user to choose the OLE object, you can write a script that calls the InsertObject function.

If the object in the control is linked and the linked file is missing, the user cannot activate the control. If the Update property is set to Automatic, PowerBuilder displays a dialog box so that the user can find the file.

If the Update property is set to Manual, a script can call the UpdateLinksDialog function to display the dialog box, or call LinkTo to replace the contents with another file.

Menus for in-place activation

When an object is activated in place, menus for its server application are merged with the menus in your PowerBuilder application. The Menu Merge Option settings in the Menu painter let you control how the menus of the two applications are merged. The values are standard menu names, as well as the choices Merge and Exclude.

To control what happens to a menu in your application when an OLE object is activated:

  1. Open the menu in the Menu painter.

  2. Select a menu item that appears on the menu bar. Menu Merge Option settings apply only to items on the menu bar, not items on drop-down menus.

  3. On the Style property page, choose the appropriate Menu Merge Option setting. The following table lists these settings.

    You can choose

    Meaning

    Source of menu in resulting menu bar

    File

    The menu from the container application (your PowerBuilder application) that will be leftmost on the menu bar. The server's File menu never displays.

    Container

    Edit

    The menu identified as Edit never displays. The server's Edit menu displays.

    Server

    Window

    The menu from the container application that has the list of open sheets. The server's Window menu never displays.

    Container

    Help

    The menu identified as Help never displays. The server's Help menu displays.

    Server

    Merge

    The menu will be displayed after the first menu of the server application.

    Container

    Exclude

    The menu will be removed while the object is active.

     


  4. Repeat steps 2 and 3 for each item on the menu bar.

Standard assignments for standard menus

In general, you should assign the File, Edit, Window, and Help Menu Merge options to the File, Edit, Window, and Help menus. Because the actual menu names might be different in an international application, you use the Menu Merge Option settings to make the correct associations.

Resulting menu bar for activated object

The effect of the Menu Merge Option settings is that the menu bar displays the container's File and Window menus and the server's Edit and Help menus. Any menus that you label as Merge are included in the menu bar at the appropriate place. The menu bar also includes other menus that the server has decided are appropriate.

Modifying an object in an OLE control

When an OLE object is displayed in the OLE control, the user can interact with that object and the application that created it (the server). You can also program scripts that do the same things the user might do. This section describes how to:

  • Activate the OLE object and send general commands to the server

  • Change and save the object in the control

  • Find out when data or properties have changed by means of events

For information about automation for the control, see OLE objects in scripts.

Activating the OLE object

Generally, the OLE control is set so that the user can activate the object by double-clicking. You can also call the Activate function to activate the object in a script. If the control's Activation property is set to Manual, you have to call Activate to start a server editing session:

ole_1.Activate(InPlace!)

You can initiate general OLE actions by calling the DoVerb function. A verb is an integer value that specifies an action to be performed. The server determines what each integer value means. The default action, specified as 0, is usually Edit, which also activates the object.

For example, if ole_1 contains a Microsoft Excel spreadsheet, the following statement activates the object for editing:

ole_1.DoVerb(0)

Check the server's documentation to see what verbs it supports. OLE verbs are a relatively limited means of working with objects; automation provides a more flexible interface. OLE 1.0 servers support verbs but not automation.

Changing the object in an OLE control

PowerBuilder provides several functions for changing the object in an OLE control. The function you choose depends on whether you want the user to choose an object and whether the object should be linked or embedded, as shown in the following table.

When you want to

Choose this function

Let the user choose an object and, if the control's Contents property is set to Any, whether to link or embed it.

InsertObject

Create a new object for a specified server and embed it in the control.

InsertClass

Embed a copy of an existing object in the control.

InsertFile

Link to an existing object in the control.

LinkTo

Open an existing object from a file or storage. Information in the file determines whether the object is linked or embedded.

Open


The following figure illustrates the behavior of the three functions that do not allow a choice of linking or embedding.

Figure: Functions that do not allow a choice of linking or embedding

You can also assign OLE object data stored in a blob to the ObjectData property of the OLE control:

blob myblob
... // Code to assign OLE data to the blob
ole_1.ObjectData = myblob

The Contents property of the control specifies whether the control accepts embedded and/or linked objects. It determines whether the user can choose to link or embed in the InsertObject dialog box. It also controls what the functions can do. If you call a function that chooses a method that the Contents property does not allow, the function will fail.

OLE information in the Browser

Use the Browser to find out the registered names of the OLE server applications installed on your system. You can use any of the names listed in the Browser as the argument for the InsertClass function, as well as the ConnectToObject and ConnectToNewObject functions (see Programmable OLE Objects).

For more information about OLE and the Browser, see OLE information in the Browser.

Using the clipboard

Using the Cut, Copy, and Paste functions in menu scripts lets you provide clipboard functionality for your user. Calling Cut or Copy for the OLE control puts the OLE object it contains on the clipboard. The user can also choose Cut or Copy in the server application to place data on the clipboard. (Of course, you can use these functions in any script, not just those associated with menus.)

There are several Paste functions that can insert an object in the OLE control. The difference is whether the pasted object is linked or embedded.

When you want to

Choose this function

Embed the object on the clipboard in the control

Paste

Paste and link the object on the clipboard

PasteLink

Allow the user to choose whether to embed or link the pasted object

PasteSpecial


If you have a general Paste function, you can use code like the following to invoke PasteSpecial (or PasteLink) when the target of the paste operation is the OLE control:

graphicobject lg_obj
datawindow ldw_dw
olecontrol lole_ctl

// Get the object with the focus
lg_obj = GetFocus()

// Insert clipboard data based on object type
CHOOSE CASE TypeOf(lg_obj)
   CASE DataWindow!
      ldw_dw = lg_obj
   ldw_dw.Paste()
   ...
   CASE OLEControl!
   lole_ctl = lg_obj
   lole_ctl.PasteSpecial()
END CHOOSE

Saving an embedded object

If you embed an OLE object when you are designing a window, PowerBuilder saves the object in the library with the OLE control. However, when you embed an object during execution, that object cannot be saved with the control because the application's executable and libraries are read-only. If you need to save the object, you save the data in a file or in the database.

For example, the following script uses SaveAs to save the object in a file. It prompts the user for a file name and saves the object in the control as an OLE data file, not as native data of the server application. You can also write a script to open the file in the control in another session:

string myf
ilename, mypathname
integer result
GetFileSaveName("Select File", mypathname, &
   myfilename, "OLE", &
   "OLE Files (*.OLE),*.OLE")
result = ole_1.SaveAs(myfilename)

When you save OLE data in a file, you will generally not be able to open that data directly in the server application. However, you can open the object in PowerBuilder and activate the server application.

When you embed an object in a control, the actual data is stored as a blob in the control's ObjectData property. If you want to save an embedded object in a database for later retrieval, you can save it as a blob. To transfer data between a blob variable and the control, assign the blob to the control's ObjectData property or vice versa:

blob myblob
myblob = ole_1.ObjectData

You can use the embedded SQL statement UPDATEBLOB to put the blob data in the database (see the section called “UPDATEBLOB” in PowerScript Reference).

You can also use SaveAs and Save to store OLE objects in PowerBuilder's OLEStorage variables (see Opening and saving storages).

When the user saves a linked object in the server, the link information is not affected and you do not need to save the open object. However, if the user renames the object or affects the range of a linked item, you need to call the Save function to save the link information.

Events for the OLE control

There are several events that let PowerBuilder know when actions take place in the server application that affect the OLE object.

Events for data

Events that have to do with data are:

  • DataChange

    The data has been changed

  • Rename

    The object has been renamed

  • Save, SaveObject

    The data has been saved

  • ViewChange

    The user has changed the view of the data

When these events occur, the changes are reflected automatically in the control. If you need to perform additional processing when the object is renamed, saved, or changed, you can write the appropriate scripts.

Because of the architecture of OLE, you often cannot interact with the OLE object within these events. Trying to do so can generate a runtime error. A common workaround is to use the PostEvent function to post the event to an asynchronous event handler. You do not need to post the SaveObject event, which is useful if you want to save the data in the object to a file whenever the server application saves the object.

Events for properties

If the server supports property notifications, then when values for properties of the server change, the PropertyRequestEdit and PropertyChanged events will occur. You can write scripts that cancel changes, save old values, or read new values.

For more information about property notification, see Creating hot links.

OLE custom controls

The OLE control button in the Controls menu gives you the option of inserting an object or a custom control in an OLE container. When you select an OLE custom control (ActiveX control), you fix the container's type and contents. You cannot choose later to insert an object and you cannot select a different custom control.

Each ActiveX control has its own properties, events, and functions. Preventing the ActiveX control from being changed helps avoid errors later in scripts that address the properties and methods of a particular ActiveX control.

Setting up the custom control

The PowerBuilder custom control container has properties that apply to any ActiveX control. The ActiveX control itself has its own properties. This section describes the purpose of each type of property and how to set them.

PowerBuilder properties

For OLE custom controls, PowerBuilder properties have two purposes:

  • To specify appearance and behavior of the container, as you do for any control

    You can specify position, pointer, and drag-and-drop settings, as well as the standard settings on the General property page (Visible, Enabled, and so on).

  • To provide default information that the ActiveX control can use

    Font information and the display name are called ambient properties in OLE terminology. PowerBuilder does not display text for the ActiveX control, so it does not use these properties directly. If the ActiveX control is programmed to recognize ambient properties, it can use the values PowerBuilder provides when it displays text or needs a name to display in a title bar.

To modify the PowerBuilder properties for the custom control:

  1. Double-click the control, or select Properties from the control's pop-up menu.

    The OLE Custom Control property sheet displays.

  2. Give the control a name that is relevant to your application. You will use this name in scripts. The default name is ole_ followed by a number.

  3. Specify values for other properties on the General property page and other pages as appropriate.

  4. Click OK when you are done.

Documenting the control

Put information about the ActiveX control you are using in a comment for the window or in the control's Tag property. Later, if another developer works with your window and does not have the ActiveX control installed, that developer can easily find out what ActiveX control the window was designed to use.

ActiveX control properties

An ActiveX control usually has its own properties and its own property sheet for setting property values. These properties control the appearance and behavior of the ActiveX control, not the PowerBuilder container.

To set property values for the ActiveX control in the control:

  1. Select OLE Control Properties from the control's pop-up menu or from the General property page.

  2. Specify values for the properties and click OK when done.

The OLE control property sheet might present only a subset of the properties of the ActiveX control. You can set other properties in a script.

For more information about the ActiveX control's properties, see the documentation for the ActiveX control.

Programming the ActiveX control

You make an ActiveX control do its job by programming it in scripts, setting its properties, and calling its functions. Depending on the interface provided by the ActiveX control developer, a single function call might trigger a whole series of activities or individual property settings, and function calls may let you control every aspect of its actions.

An ActiveX control is always active -- it does not contain an object that needs to be opened or activated. The user does not double-click and start an OLE server. However, you can program the DoubleClicked or any other event to call a function that starts ActiveX control processing.

Setting properties in scripts

Programming an ActiveX control is the same as programming automation for insertable objects. You use the container's Object property to address the properties and functions of the ActiveX control.

This syntax accesses a property value. You can use it wherever you use an expression. Its datatype is Any. When the expression is evaluated, its value has the datatype of the control property:

olecontrol.Object.ocxproperty

This syntax calls a function. You can capture its return value in a variable of the appropriate datatype:

{ value } = olecontrol.Object.ocxfunction ( { argumentlist } )

Errors when accessing properties

The PowerBuilder compiler does not know the correct syntax for accessing properties and functions of an ActiveX control, so it does not check any syntax after the Object property. This provides the flexibility you need to program any ActiveX control. But it also leaves an application open to runtime errors if the properties and functions are misnamed or missing.

PowerBuilder provides two events (ExternalException and Error) for handling OLE errors. If the ActiveX control defines a stock error event, the PowerBuilder OLE control container has an additional event, ocx_event. These events allow you to intercept and handle errors without invoking the SystemError event and terminating the application. You can also use a TRY-CATCH exception handler.

For more information, see Handling errors.

Using events of the ActiveX control

An ActiveX control has its own set of events, which PowerBuilder merges with the events for the custom control container. The ActiveX control events appear in the Event List view with the PowerBuilder events. You write scripts for ActiveX control events in PowerScript and use the Object property to refer to ActiveX control properties and methods, just as you do for PowerBuilder event scripts.

The only difference between ActiveX control events and PowerBuilder events is where to find documentation about when the events get triggered. The ActiveX control provider supplies the documentation for its events, properties, and functions.

The PowerBuilder Browser provides lists of the properties and methods of the ActiveX control. For more information, see OLE information in the Browser.

New versions of the ActiveX control

If you install an updated version of an ActiveX control and it has new events, the event list in the Window painter does not add the new events. To use the new events, you have to delete and recreate the control, along with the scripts for existing events. If you do not want to use the new events, you can leave the control as is -- it will use the updated ActiveX control with the pre-existing events.

Programmable OLE Objects

You do not need to place an OLE control on a window to manipulate an OLE object in a script. If the object does not need to be visible in your PowerBuilder application, you can create an OLE object independent of a control, connect to the server application, and call functions and set properties for that object. The server application executes the functions and changes the object's properties, which changes the OLE object.

For some applications, you can specify whether the application is visible. If it is visible, the user can activate the application and manipulate the object using the commands and tools of the server application.

OLEObject object type

PowerBuilder's OLEObject object type is designed for automation. OLEObject is a dynamic object type, which means that the compiler will accept any property names, function names, and parameter lists for the object. PowerBuilder does not have to know whether the properties and functions are valid. This allows you to call methods and set properties for the object that are known to the server application that created the object. If the functions or properties do not exist during execution, you will get runtime errors.

Using an OLEObject variable involves these steps:

  1. Declare the variable and instantiate it.

  2. Connect to the OLE object.

  3. Manipulate the object as appropriate using the OLE server's properties and functions.

  4. Disconnect from the OLE object and destroy the variable.

These steps are described next.

Declaring an OLEObject variable

You need to declare an OLEObject variable and allocate memory for it:

OLEObject myoleobject
myoleobject = CREATE OLEObject

The Object property of the OLE container controls (OLEControl or OLECustomControl) has a datatype of OLEObject.

Connecting to the server

You establish a connection between the OLEObject object and an OLE server with one of the ConnectToObject functions. Connecting to an object starts the appropriate server:

When you want to

Choose this function

Create a new object for an OLE server that you specify. Its purpose is similar to InsertClass for a control.

ConnectToNewObject

Create a new OLE object in the specified remote server application if security on the server allows it and associate the new object with a PowerBuilder OLEObject variable.

ConnectToNewRemoteObject

Open an existing OLE object from a file. If you do not specify an OLE class, PowerBuilder uses the file's extension to determine what server to start.

ConnectToObject

Associate an OLE object with a PowerBuilder OLEObject variable and start the remote server application.

ConnectToRemoteObject


After you establish a connection, you can use the server's command set for automation to manipulate the object (see OLE objects in scripts).

You do not need to include application qualifiers for the commands. You already specified those qualifiers as the application's class when you connected to the server. For example, the following commands create an OLEObject variable, connect to Microsoft Word 's OLE interface (word.application), open a document and display information about it, insert some text, save the edited document, and shut down the server:

OLEObject o1
string s1
o1 = CREATE oleobject

o1.ConnectToNewObject("word.application")
o1.documents.open("c:\temp\temp.doc")

// Make the object visible and display the 
// MS Word user name and file name
o1.Application.Visible = True
s1 = o1.UserName
MessageBox("MS Word User Name", s1)
s1 = o1.ActiveDocument.Name
MessageBox("MS Word Document Name", s1)

//Insert some text in a new paragraph
o1.Selection.TypeParagraph()
o1.Selection.typetext("Insert this text")
o1.Selection.TypeParagraph() 

// Insert text at the first bookmark
o1.ActiveDocument.Bookmarks[1].Select
o1.Selection.typetext("Hail!")

// Insert text at the bookmark named End
o1.ActiveDocument.Bookmarks.item("End").Select
o1.Selection.typetext("Farewell!")

// Save the document and shut down the server
o1.ActiveDocument.Save()
o1.quit()
RETURN

For earlier versions of Microsoft Word, use word.basic instead of word.application. The following commands connect to the Microsoft Word 7.0 OLE interface (word.basic), open a document, go to a bookmark location, and insert the specified text:

myoleobject.ConnectToNewObject("word.basic")
myoleobject.fileopen("c:\temp\letter1.doc")
myoleobject.editgoto("NameAddress")
myoleobject.Insert("Text to insert")

Do not include word.application or word.basic (the class in ConnectToNewObject) as a qualifier:

// Incorrect command qualifier
myoleobject.word.basic.editgoto("NameAddress")

Microsoft Word 7.0 implementation

For an OLEObject variable, word.basic is the class name of Word 7.0 as a server application. For an object in a control, you must use the qualifier application.wordbasic to tell Word how to traverse its object hierarchy and access its wordbasic object.

Shutting down and disconnecting from the server

After your application has finished with the automation, you might need to tell the server explicitly to shut down. You can also disconnect from the server and release the memory for the object:

myoleobject.Quit() 
rtncode = myoleobject.DisconnectObject()
DESTROY myoleobject

You can rely on garbage collection to destroy the OLEObject variable. Destroying the variable automatically disconnects from the server.

It is preferable to use garbage collection to destroy objects, but if you want to release the memory used by the variable immediately and you know that it is not being used by another part of the application, you can explicitly disconnect and destroy the OLEObject variable, as shown in the code above.

For more information, see Garbage collection and memory management.

Assignments among OLEControl, OLECustomControl, and OLEObject datatypes

You cannot assign an OLE control (object type OLEControl) or ActiveX control (object type OLECustomControl) to an OLEObject.

If the vendor of the control exposes a programmatic identifier (in the form vendor.application), you can specify this identifier in the ConnectToNewObject function to connect to the programmable interface without the visual control. For an ActiveX control with events, this technique makes the events unavailable. ActiveX controls are not meant to be used this way and would not be useful in most cases.

You can assign the Object property of an OLE control to an OLEObject variable or use it as an OLEObject in a function.

For example, if you have an OLEControl ole_1 and an OLECustomControl ole_2 in a window and you have declared this variable:

OLEObject oleobj_automate

then you can make these assignments:

oleobj_automate = ole_1.Object
oleobj_automate = ole_2.Object

You cannot assign an OLEObject to the Object property of an OLE control because it is read-only. You cannot make this assignment:

ole_1.Object = oleobj_automate //Error!

Events for OLEObjects

You can implement events for an OLEObject by creating a user object that is a descendant of OLEObject. The SetAutomationPointer PowerScript function assigns an OLE automation pointer to the descendant so that it can use OLE automation.

Suppose oleobjectchild is a descendant of OLEObject that implements events such as the ExternalException and Error events. The following code creates an OLEObject and an instance of oleobjectchild, which is a user object that is a descendant of OLEObject, connects to Excel, then assigns the automation pointer to the oleobjectchild:

OLEObject ole1
oleobjectchild  oleChild

ole1 = CREATE OLEObject
ole1.ConnectToNewObject( "Excel.Application")

oleChild = CREATE oleobjectchild
oleChild.SetAutomationPointer( ole1 )

You can now use olechild for automation.

Automation scenario

The steps involved in automation can be included in a single script or be the actions of several controls in a window. If you want the user to participate in the automation, you might:

  • Declare an OLE object as an instance variable of a window

  • Instantiate the variable and connect to the server in the window's Open event

  • Send commands to the server in response to the user's choices and specifications in lists or edit boxes

  • Disconnect and destroy the object in the window's Close event

If the automation does not involve the user, all the work can be done in a single script.

Example: generating form letters using OLE

This example takes names and addresses from a DataWindow object and letter body from a MultiLineEdit and creates and prints letters in Microsoft Word using VBA scripting.

To set up the form letter example:

  1. Create a Word document called CONTACT.DOC with four bookmarks and save the file in your PowerBuilder directory.

    These are the bookmarks:

    • name1 -- for the name in the return address

    • name2 -- for the name in the salutation

    • address1 -- for the street, city, state, and zip in the return address

    • body -- for the body of the letter

    The letter should have the following content:

    Multimedia Promotions, Inc.
    1234 Technology Drive
    Westboro, Massachusetts
    January 12, 2003
    
    [bookmark name1]
    [bookmark address1]
    
    Dear [bookmark name2]:
    [bookmark body]
    
    Sincerely,
    Harry Mogul
    President

    You could enhance the letter with a company and a signature logo. The important items are the names and placement of the bookmarks.

  2. In PowerBuilder, define a DataWindow object called d_maillist that has the following columns:

    id

    first_name

    last_name

    street

    city

    state

    zip

    You can turn on Prompt for Criteria in the DataWindow object so the user can specify the customers who will receive the letters.

  3. Define a window that includes a DataWindow control called dw_mail, a MultiLineEdit called mle_body, and a CommandButton or PictureButton:

  4. Assign the DataWindow object d_maillist to the DataWindow control dw_mail.

  5. Write a script for the window's Open event that connects to the database and retrieves data for the DataWindow object. The following code connects to a SQL Anywhere database. (When the window is part of a larger application, the connection is typically done by the application Open script.)

    /**************************************************
    Set up the Transaction object from the INI file
    **************************************************/
    SQLCA.DBMS=ProfileString("myapp.ini", &
       "Database", "DBMS", " ")
    
    SQLCA.DbParm=ProfileString("myapp.ini", &
       "Database", "DbParm", " ")
    /**************************************************
    Connect to the database and test whether the 
    connect succeeded
    **************************************************/
    CONNECT USING SQLCA;
    IF SQLCA.SQLCode <> 0 THEN
       MessageBox("Connect Failed", "Cannot connect" &
          + "to database. " + SQLCA.SQLErrText)
       RETURN
    END IF
    /**************************************************
    Set the Transaction object for the DataWindow control and retrieve data
    **************************************************/
    dw_mail.SetTransObject(SQLCA)
    dw_mail.Retrieve()
  6. Write the script for the Generate Letters button (the script is shown below).

    The script does all the work, performing the following tasks:

    • Creates the OLEObject variable

    • Connects to the server (word.application)

    • For each row in the DataWindow object, generates a letter

      To do so, it uses VBA statements to perform the tasks in the following table.

      VBA statements

      Task

      open

      Opens the document with the bookmarks

      goto and typetext

      Extracts the name and address information from a row in the DataWindow object and inserts it into the appropriate places in the letter

      goto and typetext

      Inserts the text the user types in mle_body into the letter

      printout

      Prints the letter

      close

      Closes the letter document without saving it


    • Disconnects from the server

    • Destroys the OLEObject variable

  7. Write a script for the Close button. All it needs is one command:

    Close(Parent)

Script for generating form letters

The following script generates and prints the form letters:

OLEObject contact_ltr
integer result, n
string ls_name, ls_addr
/***************************************************
Allocate memory for the OLEObject variable
***************************************************/
contact_ltr = CREATE oleObject
/***************************************************
Connect to the server and check for errors
***************************************************/
result = &
   contact_ltr.ConnectToNewObject("word.application")
IF result <> 0 THEN
   DESTROY contact_ltr
   MessageBox("OLE Error", &
      "Unable to connect to Microsoft Word. " &
      + "Code: " &
      + String(result))
      RETURN
END IF
/***************************************************
For each row in the DataWindow, send customer
data to Word and print a letter
***************************************************/
FOR n = 1 to dw_mail.RowCount()
/************************************************
   Open the document that has been prepared with
   bookmarks
************************************************/
   contact_ltr.documents.open("c:\pbdocs\contact.doc")
/************************************************
   Build a string of the first and last name and
   insert it into Word at the name1 and name2
   bookmarks
************************************************/
   ls_name = dw_mail.GetItemString(n, "first_name")&
   + " " + dw_mail.GetItemString(n, "last_name")
   contact_ltr.Selection.goto("name1")
   contact_ltr.Selection.typetext(ls_name)
   contact_ltr.Selection.goto("name2")
   contact_ltr.Selection.typetext(ls_name)
/************************************************
   Build a string of the address and insert it into
   Word at the address1 bookmark
************************************************/
   ls_addr = dw_mail.GetItemString(n, "street") &
      + "~r~n" &
      + dw_mail.GetItemString(n, "city") &
      + ", " &
      + dw_mail.GetItemString(n, "state") &
      + " " &
      + dw_mail.GetItemString(n, "zip")
   contact_ltr.Selection.goto("address1")
   contact_ltr.Selection.typetext(ls_addr)
/************************************************
   Insert the letter text at the body bookmark
***********************************************/
   contact_ltr.Selection.goto("body")
   contact_ltr.Selection.typetext(mle_body.Text)
/************************************************
   Print the letter
************************************************/
   contact_ltr.Application.printout()
/************************************************
   Close the document without saving
************************************************/
   contact_ltr.Documents.close
   contact_ltr.quit()
NEXT
/***************************************************
Disconnect from the server and release the memory for the OLEObject variable
***************************************************/
contact_ltr.DisconnectObject()
DESTROY contact_ltr

Running the example

To run the example, write a script for the Application object that opens the window or use the Run/Preview button on the PowerBar.

When the application opens the window, the user can specify retrieval criteria to select the customers who will receive letters. After entering text in the MultiLineEdit for the letter body, the user can click on the Generate Letters button to print letters for the listed customers.

OLE objects in scripts

This chapter has described the three ways to use OLE in a window or user object. You have learned about:

  • Inserting an object in an OLE control

  • Placing an ActiveX control in an OLE custom control

  • Declaring an OLEObject variable and connecting to an OLE object

In scripts, you can manipulate these objects by means of OLE automation, getting and setting properties, and calling functions that are defined by the OLE server. There are examples of automation commands in the preceding sections. This section provides more information about the automation interface in PowerBuilder.

The automation interface

In PowerBuilder, an OLEObject is your interface to an OLE server or ActiveX control. When you declare an OLEObject variable and connect to a server, you can use dot notation for that variable and send instructions to the server. The instruction might be a property whose value you want to get or set, or a function you want to call.

The general automation syntax for an OLEObject is:

oleobjectvar.serverinstruction

For OLE controls in a window, your interface to the server or ActiveX control is the control's Object property, which has a datatype of OLEObject.

The general automation syntax for an OLE control is:

olecontrol.Object.serverinstruction

Compiling scripts that include commands to the OLE server

When you compile scripts that apply methods to an OLEObject (including a control's Object property), PowerBuilder does not check the syntax of the rest of the command, because it does not know the server's command set. You must ensure that the syntax is correct to avoid errors during execution.

Make sure you give your applications a test run to ensure that your commands to the server application are correct.

What does the server support?

A server's command set includes properties and methods (functions and events).

OLE server applications publish the command set they support for automation. Check your server application's documentation for information.

For custom controls and programmable OLE objects, you can see a list of properties and methods in the PowerBuilder Browser. For more information about OLE information in the Browser, see OLE information in the Browser.

Setting properties

You access server properties for an OLE control through its Object property using the following syntax:

olecontrolname.Object.{ serverqualifiers.}propertyname

If the OLE object is complex, there could be nested objects or properties within the object that serve as qualifiers for the property name.

For example, the following commands for an Excel spreadsheet object activate the object and set the value property of several cells:

double value
ole_1.Activate(InPlace!)
ole_1.Object.cells[1,1].value = 55
ole_1.Object.cells[2,2].value = 66
ole_1.Object.cells[3,3].value = 77
ole_1.Object.cells[4,4].value = 88

For an Excel 95 spreadsheet, enclose the cells' row and column arguments in parentheses instead of square brackets. For example:

ole_1.Object.cells(1,1).value = 55

For properties of an OLEObject variable, the server qualifiers and property name follow the variable name:

oleobjectvar.{ serverqualifiers.}propertyname

The qualifiers you need to specify depend on how you connect to the object. For more information, see Qualifying server commands.

Calling functions

You can call server functions for an OLE control through its Object property using the following syntax:

olecontrolname.Object.{ serverqualifiers.}functionname ( { arguments } )

If the OLE object is complex, there could be nested properties or objects within the object that serve as qualifiers for the function name.

Required parentheses

PowerScript considers all commands to the server either property settings or functions. For statements and functions to be distinguished from property settings, they must be followed by parentheses surrounding the parameters. If there are no parameters, specify empty parentheses.

Arguments and return values and their datatypes

PowerBuilder converts OLE data to and from compatible PowerBuilder datatypes. The datatypes of values you specify for arguments must be compatible with the datatypes expected by the server, but they do not need to be an exact match.

When the function returns a value, you can assign the value to a PowerBuilder variable of a compatible datatype.

Passing arguments by reference

If an OLE server expects an argument to be passed by reference so that it can pass a value back to your script, include the keyword REF just before the argument. This is similar to the use of REF in an external function declaration:

olecontrol.Object.functionname ( REF argname )

In these generic examples, the server can change the values of ls_string and li_return because they are passed by reference:

string ls_string
integer li_return
ole_1.Object.testfunc(REF ls_string, REF li_return)

This example illustrates the same function call using an OLEObject variable.

OLEObject ole_obj
ole_obj = CREATE OLEObject
ole_obj.ConnectToNewObject("servername")
ole_obj.testfunc(REF ls_string, REF li_return)

Setting the timeout period

Calls from a PowerBuilder client to a server time out after five minutes. You can use the SetAutomationTimeout PowerScript function to change the default timeout period if you expect a specific OLE request to take longer.

Word and automation

Microsoft Word 6.0 and 7.0 support automation with a command set similar to the WordBasic macro language. The command set includes both statements and functions and uses named parameters. Later versions of Microsoft Word use Visual Basic for Applications (VBA), which consists of a hierarchy of objects that expose a specific set of methods and properties.

WordBasic statements

WordBasic has both statements and functions. Some of them have the same name. WordBasic syntax differentiates between statements and functions calls, but PowerBuilder does not.

To specify that you want to call a statement, you can include AsStatement! (a value of the OLEFunctionCallType enumerated datatype) as an argument. Using AsStatement! is the only way to call WordBasic statements that have the same name as a function. Even when the statement name does not conflict with a function name, specifying AsStatement! is more efficient:

olecontrol.Object.application.wordbasic.statementname
    ( argumentlist, AsStatement! )

For example, the following code calls the AppMinimize statement:

ole_1.Object.application.wordbasic. &
   AppMinimize("",1,AsStatement!)

Named parameters

PowerBuilder does not support named parameters that both WordBasic and Visual Basic use. In the parentheses, specify the parameter values without the parameter names.

For example, the following statements insert text at a bookmark in a Word 6.0 or 7.0 document:

ole_1.Activate(InPlace!)
Clipboard(mle_nameandaddress.Text)
ole_1.Object.application.wordbasic.&
   fileopen("c:\msoffice\winword\doc1.doc")
ole_1.Object.application.wordbasic.&
   editgoto("NameandAddress", AsStatement!)
ole_1.Object.application.wordbasic.&
   editpaste(1, AsStatement!)

The last two commands in a WordBasic macro would look like this, where Destination is the named parameter:

EditGoto.Destination = "NameandAddress"
EditPaste

In a PowerBuilder script, you would use this syntax to insert text in a Word 97 or later document:

ole_1.Object.Selection.TypeText("insert this text")

In the corresponding Visual Basic statement, the named parameter Text contains the string to be inserted:

Selection.TypeText Text:="insert this text"

Automation is not macro programming

You cannot send commands to the server application that declare variables or control the flow of execution (for example, IF THEN). Automation executes one command at a time independently of any other commands. Use PowerScript's conditional and looping statements to control program flow.

Example of Word automation

To illustrate how to combine PowerScript with server commands, the following script counts the number of bookmarks in a Microsoft Word OLE object and displays their names:

integer i, count
string bookmarklist, curr_bookmark
ole_1.Activate(InPlace!)

count = ole_1.Object.Bookmarks.Count
bookmarklist = "Bookmarks = " + String(count) + "~n"
   
FOR i = 1 to count
   curr_bookmark = ole_1.Object.Bookmarks[i].Name
   bookmarklist = bookmarklist + curr_bookmark + "~n"
END FOR

MessageBox("BookMarks", bookmarklist)

Word automation tip

You can check that you are using the correct syntax for Word automation with the Word macro editor. Turn on macro recording in Word, perform the steps you want to automate manually, then turn off macro recording. You can then type Alt+F11 to open the macro editor and see the syntax that was built. Remember that PowerBuilder uses square brackets for array indexes.

Example of Word 6.0 and 7.0 automation

The following script counts the number of bookmarks in a Microsoft Word 6.0 or 7.0 OLE object and displays their names:

integer i, count
string bookmarklist, curr_bookmark
ole_1.Activate(InPlace!)

// Get the number of bookmarks
count = ole_1.Object. &
    application.wordbasic.countbookmarks
bookmarklist = "Bookmarks = " + String(count) + "~n"

// Get the name of each bookmark
FOR i = 1 to count
   curr_bookmark = ole_1.Object. &
   application.wordbasic.bookmarkname(i)
   bookmarklist = bookmarklist    + curr_bookmark + "~n"
END FOR

MessageBox("BookMarks", bookmarklist)
Qualifying server commands

Whether to qualify the server command with the name of the application depends on the server and how the object is connected. Each server implements its own version of an object hierarchy, which needs to be reflected in the command syntax. For example, the Microsoft Excel object hierarchy is shown in the following figure.

Figure: Microsoft Excel object hierarchy

When the server is Excel, the following commands appear to mean the same thing but can have different effects (for an Excel 95 spreadsheet, the cells' row and column arguments are in parentheses instead of square brackets):

ole_1.Object.application.cells[1,2].value = 55
ole_1.Object.cells[1,2].value = 55

The first statement changes a cell in the active document. It moves up Excel's object hierarchy to the Application object and back down to an open sheet. It does not matter whether it is the same one in the PowerBuilder control. If the user switches to Excel and activates a different sheet, the script changes that one instead. You should avoid this syntax.

The second statement affects only the document in the PowerBuilder control. However, it will cause a runtime error if the document has not been activated. It is the safer syntax to use, because there is no danger of affecting the wrong data.

Microsoft Word 6.0 and 7.0 implement the application hierarchy differently and require the qualifier application.wordbasic when you are manipulating an object in a control. (You must activate the object.) For example:

ole_1.Object.application.wordbasic.bookmarkname(i)

Later versions of Microsoft Word do not require a qualifier, but it is valid to specify one. You can use any of the following syntaxes:

ole_1.Object.Bookmarks.[i].Name
ole_1.Object.Bookmarks.item(i).Name
ole_1.Object.application.ActiveDocument. &
       Bookmarks.[i].Name

When you are working with PowerBuilder's OLEObject, rather than an object in a control, you omit the application qualifiers in the commands because you have already specified them when you connected to the object. (For more about the OLEObject object type, see Programmable OLE Objects.)

Automation and the Any datatype

Because PowerBuilder knows nothing about the commands and functions of the server application, it also knows nothing about the datatypes of returned information when it compiles a program. Expressions that access properties and call functions have a datatype of Any. You can assign the expression to an Any variable, which avoids datatype conversion errors.

During execution, when data is assigned to the variable, it temporarily takes the datatype of the value. You can use the ClassName function to determine the datatype of the Any variable and make appropriate assignments. If you make an incompatible assignment with mismatched datatypes, you will get a runtime error.

Do not use the Any datatype unnecessarily

If you know the datatype of data returned by a server automation function, do not use the Any datatype. You can assign returned data directly to a variable of the correct type.

The following sample code retrieves a value from Excel and assigns it to the appropriate PowerBuilder variable, depending on the value's datatype. (For an Excel 95 spreadsheet, the row and column arguments for cells are in parentheses instead of square brackets.)

string stringval
double dblval
date dateval
any anyval

anyval = myoleobject.application.cells[1,1].value
CHOOSE CASE ClassName(anyval)
   CASE "string"
      stringval = anyval
   CASE "double"
      dblval = anyval
   CASE "datetime"
      dateval = Date(anyval)
END CHOOSE

OLEObjects for efficiency

When your automation command refers to a deeply nested object with multiple server qualifiers, it takes time to negotiate the object's hierarchy and resolve the object reference. If you refer to the same part of the object hierarchy repeatedly, then for efficiency you can assign that part of the object reference to an OLEObject variable. The reference is resolved once and reused.

Instead of coding repeatedly for different properties:

ole_1.Object.application.wordbasic.propertyname

you can define an OLEObject variable to handle all the qualifiers:

OLEObject ole_wordbasic
ole_wordbasic = ole_1.Object.application.wordbasic
ole_wordbasic.propertyname1 = value
ole_wordbasic.propertyname2 = value

Example: resolving an object reference

This example uses an OLEObject variable to refer to a Microsoft Word object. Because it is referred to repeatedly in a FOR loop, the resolved OLEObject makes the code more efficient. The example destroys the OLEObject variable when it is done with it:

integer li_i, li_count
string ls_curr_bookmark
OLEObject ole_wb

ole_1.Activate(InPlace!)
ole_wb = ole_1.Object.application.wordbasic

// Get the number of bookmarks
li_count = ole_wb.countbookmarks
// Get the name of each bookmark
FOR li_i = 1 to count
   ls_curr_bookmark = ole_wb.bookmarkname(i)
   ... // code to save the bookmark name in a list
END FOR

Handling errors

Statements in scripts that refer to the OLE server's properties are not checked in the compiler because PowerBuilder does not know what syntax the server expects. Because the compiler cannot catch errors, runtime errors can occur when you specify property or function names and arguments the OLE server does not recognize.

Chain of error events

When an error occurs that is generated by a call to an OLE server, PowerBuilder follows this sequence of events:

  1. If the error was generated by an ActiveX control that has defined a stock error event, the ocx_error event for the PowerBuilder OLE control is triggered.

  2. Otherwise, the ExternalException event for the OLE object occurs.

  3. If the ExternalException event has no script or its action argument is set to ExceptionFail! (the default), the Error event for the OLE object occurs.

  4. If the Error event has no script or its action argument is set to ExceptionFail! (the default), any active exception handler for a RuntimeError or its descendants is invoked.

  5. If no exception handler exists, or if the existing exception handlers do not handle the exception, the SystemError event for the Application object occurs.

  6. If the SystemError has no script, an application runtime error occurs and the application is terminated.

You can handle the error in any of these events or in a script using a TRY-CATCH block. However, it is not a good idea to continue processing after the SystemError event occurs.

For more information about exception handling, see Handling exceptions.

Events for OLE errors

PowerBuilder OLE objects and controls all have two events for error handling:

  • ExternalException

    Triggered when the OLE server or control throws an exception or fires an error event (if there is no ocx_error event). Information provided by the server can help diagnose the error.

  • Error

    Triggered when the exception or error event is not handled. PowerBuilder error information is available in the script.

If the OLE control defines a stock error event, the PowerBuilder OLE control container has an additional event:

  • ocx_error

    Triggered when the OLE server fires an error event. Information provided by the server can help diagnose the error.

The creator of an OLE control can generate the stock error event for the control using the Microsoft Foundation Classes (MFC) Class Wizard. The arguments for the ocx_error event in PowerBuilder map to the arguments defined for the stock error event.

Responding to the error

If the PowerBuilder OLE control has an ocx_error event script, you can get information about the error from the event's arguments and take appropriate action. One of the arguments of ocx_error is the boolean CancelDisplay. You can set CancelDisplay to TRUE to cancel the display of any MFC error message. You can also supply a different description for the error.

In either the ExternalException or Error event script, you set the Action argument to an ExceptionAction enumerated value. What you choose depends on what you know about the error and how well the application will handle missing information.

ExceptionAction value

Effect

ExceptionFail!

Fail as if the event had no script. Failing triggers the next error event in the order of event handling.

ExceptionIgnore!

Ignore the error and return as if no error occurred.

Caution

If you are getting a property value or expecting a return value from a function, a second error can occur during the assignment because of mismatched datatypes.

ExceptionRetry!

Send the command to the OLE server again (useful if the OLE server was not ready).

Caution

If you keep retrying and the failure is caused by an incorrect name or argument, you will set your program into an endless loop. You can set up a counter to limit the number of retries.

ExceptionSubstituteReturnValue!

Use the value specified in the ReturnValue argument instead of the value returned by the OLE server (if any) and ignore the error condition.

You can set up an acceptable return value in an instance variable before you address the OLE server and assign it to the ReturnValue argument in the event script. The datatype of ReturnValue is Any, which accommodates all possible datatypes.

With a valid substitute value, this choice is a safe one if you want to continue the application after the error occurs.


Example: ExternalException event

The ExternalException event, like the ocx_error event, provides error information from the OLE server that can be useful when you are debugging your application.

Suppose your window has two instance variables: one for specifying the exception action and another of type Any for storing a potential substitute value. Before accessing the OLE property, a script sets the instance variables to appropriate values:

ie_action = ExceptionSubstituteReturnValue!
ia_substitute = 0
li_currentsetting = ole_1.Object.Value

If the command fails, a script for the ExternalException event displays the Help topic named by the OLE server, if any. It substitutes the return value you prepared and returns. The assignment of the substitute value to li_currentsetting works correctly because their datatypes are compatible:

string ls_context

// Command line switch for WinHelp numeric context ID
ls_context = "-n " + String(helpcontext)
IF Len(HelpFile) > 0 THEN
   Run("winhelp.exe " + ls_context + " " + HelpFile)
END IF

Action = ExceptionSubstituteReturnValue!
ReturnValue = ia_substitute

Because the event script must serve for every automation command for the control, you would need to set the instance variables to appropriate values before each automation command.

Error event

The Error event provides information about the PowerBuilder context for the error. You can find out the PowerBuilder error number and message, as well as the object, script, and line number of the error. This information is useful when debugging your application.

The same principles discussed in the ExceptionAction value table for setting the Action and ReturnValue arguments apply to the Error event, as well as ExternalException.

For more information about the events for error handling, see PowerScript Reference.

Creating hot links

Some OLE servers support property change notifications. This means that when a property is about to be changed and again after it has been changed, the server notifies the client, passing information about the change. These messages trigger the events PropertyRequestEdit and PropertyChanged.

PropertyRequestEdit event

When a property is about to change, PowerBuilder triggers the PropertyRequestEdit event. In that event's script you can:

  • Find out the name of the property being changed by looking at the PropertyName argument.

  • Obtain the old property value and save it

    The property still has its old value, so you can use the standard syntax to access the value.

  • Cancel the change by changing the value of the CancelChange argument to TRUE

PropertyChanged event

When a property has changed, PowerBuilder triggers the PropertyChanged event. In that event's script, you can:

  • Find out the name of the property being changed by looking at the PropertyName argument

  • Obtain the new property value

    The value has already changed, so you cannot cancel the change.

Using the PropertyName argument

Because the PropertyName argument is a string, you cannot use it in dot notation to get the value of the property:

value = This.Object.PropertyName // Will not work

Instead, use CHOOSE CASE or IF statements for the property names that need special handling.

For example, in the PropertyChanged event, this code checks for three specific properties and gets their new value when they are the property that changed. The value is assigned to a variable of the appropriate datatype:

integer li_index, li_minvalue
long ll_color

CHOOSE CASE Lower(PropertyName)
   CASE "value"
   li_index = ole_1.Object.Value
   CASE "minvalue"
   li_minvalue = ole_1.Object.MinValue
   CASE "backgroundcolor"
   ll_color = ole_1.Object.BackgroundColor
   CASE ELSE
   ... // Other processing
END CHOOSE

If a larger change occurred

In some cases the value of the PropertyName argument is an empty string (""). This means a more general change has occurred -- for example, a change that affects several properties.

If notification is not supported

If the OLE server does not support property change notification, then the PropertyRequestEdit and PropertyChanged events are never triggered, and scripts you write for them will not run. Check your OLE server documentation to see if notification is supported.

If notifications are not supported and your application needs to know about a new property value, you might write your own function that checks the property periodically.

For more information about the PropertyRequestEdit and PropertyChanged events, see the section called “PropertyRequestEdit” in PowerScript Reference and the section called “PropertyChanged” in PowerScript Reference.

Setting the language for OLE objects and controls

When you write automation commands, you generally use commands that match the locale for your computer. If your locale and your users -- locale will differ, you can specify the language you have used for automation with the SetAutomationLocale function.

You can call SetAutomationLocale for OLE controls, custom controls, and OLEObjects, and you can specify a different locale for each automation object in your application.

For example, if you are developing your application in Germany and will deploy it all over Europe, you can specify the automation language is German. Use this syntax for an OLE control called ole_1:

ole_1.Object.SetAutomationLocale(LanguageGerman!)

Use this syntax for an OLEObject called oleobj_report:

oleobj_report.SetAutomationlocale(LanguageGerman!)

The users of your application must have the German automation interface for the OLE server application.

What languages do your users' computers support?

When your users install an OLE server application (particularly an OLE application from Microsoft), they get an automation interface in their native language and in English. It might not be appropriate for you to write automation commands in your native language if your users have a different language.

For more information, see the section called “SetAutomationLocale” in PowerScript Reference.

Low-level access to the OLE object

If you need low-level access to OLE through a C or C++ DLL that you call from PowerBuilder, you can use these functions:

  • GetNativePointer (for OLEControl and OLECustomControl)

  • GetAutomationNativePointer (for OLEObject)

When you have finished, you must use these functions to free the pointer:

  • ReleaseNativePointer (for OLEControl and OLECustomControl)

  • ReleaseAutomationNativePointer (for OLEObject)

For more information, see the section called “GetNativePointer” in PowerScript Reference, the section called “GetAutomationNativePointer” in PowerScript Reference, the section called “ReleaseNativePointer” in PowerScript Reference, and the section called “ReleaseAutomationNativePointer” in PowerScript Reference.

OLE objects in DataWindow objects

The preceding sections discuss the automation interface to OLE controls and OLE objects. You can also use scripts to change settings for an OLE object embedded in a DataWindow object, and you can address properties of the external OLE object.

This section describes how to use the Object property in dot notation to set DataWindow properties and issue automation commands for OLE objects in DataWindow objects.

Naming the OLE object

To use dot notation for the OLE object, give the object a name. You specify the name on the General page in the object's property sheet.

Setting properties

You set properties of the OLE container object just as you do for any object in the DataWindow object. The Object property of the control is an interface to the objects within the DataWindow object.

For example, this statement sets the Pointer property of the object ole_word:

dw_1.Object.ole_word.Pointer = "Cross!"

It is important to remember that the compiler does not check syntax after the Object property. Incorrect property references cause runtime errors.

For more information about setting properties, handling errors, and the list of properties for the OLE DWObject, see the section called “Properties for OLE Object controls in DataWindow objects” in DataWindow Reference.

OLE objects and the Modify function

You cannot create an OLE object in a DataWindow object dynamically using the CREATE keyword of the Modify function. The binary data for the OLE object is not compatible with Modify syntax.

Functions and properties

There are four functions you can call for the OLE DWObject. They have the same effect as for the OLE control. They are:

  • Activate

  • Copy

  • DoVerb

  • UpdateLinksDialog

To call the functions, you use the Object property of the DataWindow control, just as you do for DataWindow object properties:

dw_1.Object.ole_word.Activate(InPlace!)

Four properties that apply to OLE controls in a window also apply to the OLE DWObject.

Property

datatype

Description

ClassLongName

String

(Read-only) The long name for the server application associated with the OLE DWObject.

ClassShortName

String

(Read-only) The short name for the server application associated with the OLE DWObject.

LinkItem

String

(Read-only) The entire link name of the item to which the object is linked.

For example, if the object is linked to C:\FILENAME.XLS!A1:B2, then LinkItem would contain C:\FILENAME.XLS!A1:B2.

ObjectData

Blob

If the object is embedded, the object itself is stored as a blob in the ObjectData property.

If the object is linked, this property contains the link information and the cached image (for display).


Automation

You can send commands to the OLE server using dot notation. The syntax involves two Object properties:

  • The Object property of the DataWindow control

    Gives you access to DataWindow objects, including the OLE container DWObject

  • The Object property of the OLE DWObject

    Gives you access to the automation object

The syntax is:

dwcontrol.Object.oledwobject.Object.{ serverqualifiers. }serverinstruction

For example, this statement uses the WordBasic Insert function to add a report title to the beginning of the table of data in the Word document:

dw_1.Object.ole_word.Object.application.wordbasic.&
   Insert("Report Title " + String(Today()))
OLE columns in an application

OLE columns in a DataWindow object enable you to store, retrieve, and modify blob data in a database. To use an OLE column in an application, place a DataWindow control in a window and associate it with the DataWindow object.

For users of SQL Server

If you are using a SQL Server database, you must turn off transaction processing to use OLE. In the Transaction object used by the DataWindow control, set AutoCommit to TRUE.

For how to create an OLE column in a DataWindow object, see the section called “Using OLE columns in a DataWindow object” in Users Guide.

Activating an OLE server application

Users can interact with the blob exactly as you did in preview in the DataWindow painter: they can double-click a blob to invoke the server application, then view and edit the blob. You can also use the OLEActivate function in a script to invoke the server application. Calling OLEActivate simulates double-clicking a specified blob.

The OLEActivate function has this syntax:

dwcontrol.OLEActivate (row, columnnameornumber, verb )

Specifying the verb

When using OLEActivate, you need to know the action to pass to the OLE server application. (Windows calls these actions verbs.) Typically, you want to edit the document, which for most servers means you specify 0 as the verb.

To obtain the verbs supported by a particular OLE server application, use the advanced interface of the Windows Registry Editor utility (run REGEDT32 /V).

For information about Registry Editor, see the Windows online Help file REGEDT32.HLP.

Example

For example, you might want to use OLEActivate in a Clicked script for a button to allow users to use OLE without their having to know they can double-click the blob's icon.

The following statement invokes the OLE server application for the OLE column in the current row of the DataWindow control dw_1 (assuming that the second column in the DataWindow object is an OLE column):

dw_1.OLEActivate(dw_1.GetRow(), 2, 0)

For more information

For more information about using OLE in a DataWindow object, see the section called “Using OLE columns in a DataWindow object” in Users Guide.

OLE information in the Browser

The system stores information about the OLE server applications and OLE custom controls installed on your computer in the registry.

PowerBuilder reads the registry and displays the registration information for all registered OLE servers and custom controls.

To view the OLE information:

  1. Click the Browser button on the PowerBar.

  2. Click the OLE tab in the Browser.

There are three categories of OLE object, as shown in the following table.

OLE object category

Description

Insertable objects

OLE servers that can link or embed objects in OLE containers. OLE servers that support insertable objects must have a visual component.

Custom controls

ActiveX controls that can be included in an OLE container. ActiveX controls can also be insertable objects. If so, they will appear on both lists.

Programmable objects

OLE servers to which you can send automation instructions. A programmable object might not have a visual aspect, which means it supports only automation and cannot support insertable objects.


When you expand each of these categories, you see the individual OLE servers that are installed. Each OLE server can also be expanded. The information provided depends on the category.

Class information

All the categories provide class information about the OLE server. You see a list of registry keys. Some of the keys are meaningful in their own right and some have values. The values, or simply the presence or absence of keys, tell you how to find the OLE server and what it supports.

The following table lists some typical keys and what they mean.

Registry key

Value

GUID

The global unique identifier for the OLE server.

TypeLib - GUID

The global unique identifier for the type library for an ActiveX control.

ProgID

A string that identifies the OLE server or ActiveX control. It usually includes a version number.

VersionIndependentProgID

A string that identifies the OLE server or ActiveX control, but does not include a version number.

InprocServer32

The name of the file for the 32-bit version of an ActiveX control.

ToolboxBitmap32

The name of a bitmap file for the 32-bit ActiveX control that can be used to represent the ActiveX control in toolbars or toolboxes of a development environment.

DefaultIcon

The name of an icon file or executable containing an icon to be used for an insertable icon that is being displayed as an icon.

Version

The version number of the OLE server or ActiveX control.

Insertable

No value -- specifies that the entry is an OLE server that supports insertable object.

Control

No value -- specifies that the entry is an ActiveX control.

Verb

No value -- specifies that the entry accepts verbs as commands.


In addition to registry information, the Browser displays the properties and methods of ActiveX controls and programmable objects. To provide the information, PowerBuilder uses the registry information to query the ActiveX control for its properties and methods. The information includes arguments and datatypes.

Browser as script-writing tool

Take advantage of the Browser when writing scripts. You can find property and function names and paste them into your scripts. The Browser provides the full syntax for accessing that property.

To paste OLE information into a script:

  1. Open the Browser.

  2. Click the OLE tab.

  3. Expand the list to find what you want. For example, find the ActiveX control you want and expand the list further to find a property.

  4. Highlight the property and select Copy from the pop-up menu.

  5. Position the insertion point in the Script view and select Paste from the pop-up menu.

    The Browser inserts syntax like this into your script:

    OLECustomControl.Object.NeedlePosition

    After you change OLECustomControl to the actual name of your control, your script correctly accesses the NeedlePosition property.

What the Browser pastes into your script depends on what you have selected. If you select an object (a level above its properties in the hierarchy), PowerBuilder pastes the object's ProgID. You can use the ProgID in the ConnectToNewObject function.

Advanced ways to manipulate OLE objects

In addition to OLE objects in controls and objects for automation, PowerBuilder provides an interface to the underpinnings of OLE data storage.

OLE data is stored in objects called streams, which live in objects called storages. Streams and storages are analogous to the files and directories of a file system. By opening, reading, writing, saving, and deleting streams and storages, you can create, combine, and delete your OLE objects. PowerBuilder provides access to storages and streams with the OLEStorage and OLEStream object types.

When you define OLE controls and OLEObject variables, you have full access to the functionality of server applications and automation, which already provide you with much of OLE's power. You might never need to use PowerBuilder's storage and stream objects unless you want to construct complex combinations of stored data.

Storage files from other applications

This section discusses OLE storage files that a PowerBuilder application has built. Other PowerBuilder applications will be able to open the objects in a storage file built by PowerBuilder. Although Excel, Word, and other server applications store their native data in OLE storages, these files have their own special formats, and it is not advisable to open them directly as storage files. Instead, you should always insert them in a control (InsertFile) or connect to them for automation (ConnectToObject).

Structure of an OLE storage

An OLE storage is a repository of OLE data. A storage is like the directory structure on a disk. It can be an OLE object and can contain other OLE objects, each contained within the storage, or within a substorage within the storage. The substorages can be separate OLE objects -- unrelated pieces like the files in a directory -- or they can form a larger OLE object, such as a document that includes pictures as shown in the following figure.

Figure: OLE storage structure

A storage or substorage that contains an OLE object has identifying information that tags it as belonging to a particular server application. Below that level, the individual parts should be manipulated only by that server application. You can open a storage that is a server's object to extract an object within the storage, but you should not change the storage.

A storage that is an OLE object has presentation information for the object. OLE does not need to start the server in order to display the object, because a rendering is part of the storage.

A storage might not contain an OLE object -- it might exist simply to contain other storages. In this case, you cannot open the storage in a control (because there would be no object to insert).

Object types for storages and streams

PowerBuilder has two object types that are the equivalent of the storages and streams stored in OLE files. They are:

  • OLEStorage

  • OLEStream

These objects are class user objects, like a Transaction or Message object. You declare a variable, instantiate it, and open the storage. When you are through with the storage, you close it and destroy the variable, releasing the OLE server and the memory allocated for the variable.

Opening a storage associates an OLEStorage variable with a file on disk, which can be a temporary file for the current session or an existing file that already contains an OLE object. If the file does not exist, PowerBuilder creates it.

You can put OLE objects in a storage with the SaveAs function. You can establish a connection between an OLE control in a window and a storage by calling the Open function for the OLE control.

A stream is not an OLE object and cannot be opened in a control. However, streams allow you to put your own information in a storage file. You can open a stream within a storage or substorage and read and write data to the stream, just as you might to a file.

Performance tip

Storages provide an efficient means of displaying OLE data. When you insert a file created by a server application into a control, OLE has to start the server application to display the object. When you open an object in an OLE storage, there is no overhead for starting the server -- OLE uses the stored presentation information to display the object. There is no need to start the server if the user never activates the object.

Opening and saving storages

PowerBuilder provides several functions for managing storages. The most important are Open, Save, and SaveAs.

Using the Open function

When you want to access OLE data in a file, call the Open function. Depending on the structure of the storage file, you might need to call Open more than once.

This code opens the root storage in the file into the control. For this syntax of Open, the root storage must be an OLE object, rather than a container that only holds other storages. (Always check the return code to see if an OLE function succeeded.)

result = ole_1.Open("MYFILE.OLE")

If you want to open a substorage in the file into the control, you have to call Open twice: once to open the file into an OLEStorage variable, and a second time to open the substorage into the control. stg_data is an OLEStorage variable that has been declared and instantiated using CREATE:

result = stg_data.Open("MYFILE.OLE")
result = ole_1.Open(stg_data, "mysubstorage")

Using the Save function

If the user activates the object in the control and edits it, then the server saves changes to the data in memory and sends a DataChange event to your PowerBuilder application. Then your application needs to call Save to make the changes in the storage file:

result = ole_1.Save()
IF result = 0 THEN result = stg_data.Save()

Using the SaveAs function

You can save an object in a control to another storage variable or file with the SaveAs function. The following code opens a storage file into a control, then opens another storage file, opens a substorage within that file, and saves the original object in the control as a substorage nested at a third level:

OLEStorage stg_data, stg_subdata
stg_data = CREATE OLEStorage
stg_subdata = CREATE OLEStorage
ole_1.Open("FILE_A.OLE")
stg_data.Open("FILE_B.OLE")
stg_subdata.Open("subdata", stgReadWrite!, &
   stgExclusive!, stg_data)
ole_1.SaveAs(stg_subdata, "subsubdata")

The diagram illustrates how to open the nested storages so that you can perform the SaveAs. If any of the files or storages do not exist, Open and SaveAs create them. Note that if you call Save for the control before you call SaveAs, the control's object is saved in FILE_A. After calling SaveAs, subsequent calls to Save save the object in subsubdata in FILE_B.

Figure: Nested OLE storages

The following example shows a simpler way to create a sublevel without creating a storage at the third level. You do not need to nest storages at the third level, nor do you need to open the substorage to save to it:

OLEStorage stg_data, stg_subdata
stg_data = CREATE OLEStorage
stg_subdata = CREATE OLEStorage
ole_1.Open("FILE_A.OLE")
stg_data.Open("FILE_B.OLE")
ole_1.SaveAs(stg_data, "subdata")
Getting information about storage members

When a storage is open, you can use one of the Member functions to get information about the substorages and streams in that storage and change them.

Function

Result

MemberExists

Checks to see if the specified member exists in a storage.

Members can be either storages or streams. Names of members must be unique -- you cannot have a storage and a stream with the same name. A member can exist but be empty.

MemberDelete

Deletes a member from a storage.

MemberRename

Renames a member in a storage.


This code checks whether the storage subdata exists in stg_data before it opens it. (The code assumes that stg_data and stg_subdata have been declared and instantiated.)

boolean lb_exists
result = stg_data.MemberExists("subdata", lb_exists)
IF result = 0 AND lb_exists THEN
   result = stg_subdata.Open(stg_data, "subdata")
END IF

To use MemberExists with the storage member IOle10Native, use the following construction:

ole_storage.memberexists(char(1) + 'Ole10Native', &
   lb_boolean)

The char(1) is required because the "I" in IOle10Native is not an I, as you see if you look at the storage with a utility such as Microsoft's DocFile Viewer.

You need to use a similar construction to open the stream. For example:

ole_stream.open(ole_storage, char(1) + 'Ole10Native', &
   StgReadWrite!, StgExclusive!)
Example: building a storage

Suppose you have several drawings of products and you want to display the appropriate image for each product record in a DataWindow object. The database record has an identifier for its drawing. In an application, you could call InsertFile using the identifier as the file name. However, calling the server application to display the picture is relatively slow.

Instead you could create a storage file that holds all the drawings, as shown in the diagram. Your application could open the appropriate substorage when you want to display an image.

Figure: OLE storage file

The advantage of using a storage file like this one (as opposed to inserting files from the server application into the control) is both speed and the convenience of having all the pictures in a single file. Opening the pictures from a storage file is fast, because a single file is open and the server application does not need to start up to display each picture.

OLE objects in the storage

Although this example illustrates a storage file that holds drawings only, the storages in a file do not have to belong to the same server application. Your storage file can include objects from any OLE server application, according to your application's needs.

This example is a utility application for building the storage file. The utility application is a single window that includes a DataWindow object and an OLE control.

The DataWindow object, called dw_prodid, has a single column of product identifiers. You should set up the database table so that the identifiers correspond to the file names of the product drawings. The OLE control, called ole_product, displays the drawings.

List of scripts for the example

The example has three main scripts:

  • The window's Open event script instantiates the storage variable, opens the storage file, and retrieves data for the DataWindow object. (Note that the application's Open event connects to the database.)

  • The RowFocusChanged event of the DataWindow object opens the drawing and saves it in the storage file.

  • The window's Close event script saves the storage file and destroys the variable.

Add controls to the window

First, add the dw_prodid and ole_product controls to the window.

Application Open event script

In the application's Open event, connect to the database and open the window.

Instance variable

Declare an OLEStorage variable as an instance variable of the window:

OLEStorage stg_prod_pic

Window Open event script

The following code in the window's Open event instantiates an OLEStorage variable and opens the file PICTURES.OLE in that variable:

integer result
stg_prod_pic = CREATE OLEStorage
result = stg_prod_pic.Open("PICTURES.OLE")
dw_prod.SetTransObject(SQLCA)
dw_prod.Retrieve()

Retrieve triggers the RowFocusChanged event

It is important that the code for creating the storage variable and opening the storage file comes before Retrieve. Retrieve triggers the RowFocusChanged event, and the RowFocusChanged event refers to the OLEStorage variable, so the storage must be open before you call Retrieve.

RowFocusChanged event script

The InsertFile function displays the drawing in the OLE control. This code in the RowFocusChanged event gets an identifier from the prod_id column in a DataWindow object and uses that to build the drawing's file name before calling InsertFile. The code then saves the displayed drawing in the storage:

integer result
string prodid
//Get the product identifier from the DataWindow.
prodid = this.Object.prod_id[currentrow]

// Use the id to build the file name. Insert the 
// file's object in the control.
result = ole_product.InsertFile( &
   GetCurrentDirectory() + "\" + prodid + ".gif")

// Save the OLE object to the storage. Use the 
// same identifier to name the storage.
result = ole_product.SaveAs( stg_prod_pic, prodid)

Close event script

This code in the window's Close event saves the storage, releases the OLE storage from the server, and releases the memory used by the OLEStorage variable:

integer result
result = stg_prod_pic.Save()
DESTROY stg_prod_pic

Check the return values

Be sure to check the return values when calling OLE functions. Otherwise, your application will not know if the operation succeeded. The sample code returns if a function fails, but you can display a diagnostic message instead.

Running the utility application

After you have set up the database table with the identifiers of the product pictures and created a drawing for each product identifier, run the application. As you scroll through the DataWindow object, the application opens each file and saves the OLE object in the storage.

Using the storage file

To use the images in an application, you can include the prod_id column in a DataWindow object and use the identifier to open the storage within the PICTURES.OLE file. The following code displays the drawing for the current row in the OLE control ole_product (typically, this code would be divided between several events, as it was in the sample utility application above):

OLEStorage stg_prod_pic
//Instantiate the storage variable and open the file
stg_prod_pic = CREATE OLEStorage
result = stg_prod_pic.Open("PICTURES.OLE")

// Get the storage name from the DataWindow
// This assumes it has been added to the DataWindow's
// rowfocuschanging event
prodid = this.Object.prod_id[newrow]


//Open the picture into the control
result = ole_product.Open( stg_prod_pic, prodid )

The application would also include code to close the open storages and destroy the storage variable.

Opening streams

Streams contain the raw data of an OLE object. You would not want to alter a stream created by a server application. However, you can add your own streams to storage files. These streams can store information about the storages. You can write streams that provide labels for each storage or write a stream that lists the members of the storage.

To access a stream in an OLE storage file, you define a stream variable and instantiate it. Then you open a stream from a storage that has already been opened. Opening a stream establishes a connection between the stream variable and the stream data within a storage.

The following code declares and creates OLEStorage and OLEStream variables, opens the storage, and then opens the stream:

integer result
OLEStorage stg_pic
OLEStream stm_pic_label
/***************************************************
Allocate memory for the storage and stream variables
***************************************************/
stg_pic = CREATE OLEStorage
stm_pic_label = CREATE OLEStream
/***************************************************
Open the storage and check the return value
***************************************************/
result = stg_prod_pic.Open("picfile.ole")
IF result <> 0 THEN RETURN
/***************************************************
Open the stream and check the return value
***************************************************/
result = stm_pic_label.Open(stg_prod_pic, &
   "pic_label", stgReadWrite!)
IF result <> 0 THEN RETURN

PowerBuilder has several stream functions for opening and closing a stream and for reading and writing information to and from the stream.

Function

Result

Open

Opens a stream into the specified OLEStream variable. You must have already opened the storage that contains the stream.

Length

Obtains the length of the stream in bytes.

Seek

Positions the read/write pointer within the stream. The next read or write operation takes place at the pointer.

Read

Reads data from the stream beginning at the read/write pointer.

Write

Writes data to the stream beginning at the read/write pointer.

If the pointer is not at the end, Write overwrites existing data. If the data being written is longer than the current length of the stream, the stream's length is extended.

Close

Closes the stream, breaking the connection between it and the OLEStream variable.


Example: writing and reading streams

This example displays a picture of a product in the OLE control ole_product when the DataWindow object dw_product displays that product's inventory data. It uses the file constructed with the utility application described in the earlier example (see Example: building a storage). The pictures are stored in an OLE storage file, and the name of each picture's storage is also the product identifier in a database table. This example adds label information for each picture, stored in streams whose names are the product ID plus the suffix _lbl.

The following figure shows the structure of the file.

Figure: OLE storage file structure

The example has three scripts:

  • The window's Open event script opens the storage file and retrieves data for the DataWindow object. (Note that the application's Open event connects to the database.)

  • The RowFocusChanged event of the DataWindow object displays the picture. It also opens a stream with a label for the picture and displays that label in a StaticText. The name of the stream is the product identifier plus the suffix _lbl.

    If the label is empty (its length is zero), the script writes a label. To keep things simple, the data being written is the same as the stream name. (Of course, you would probably write the labels when you build the file and read them when you display it. For the sake of illustration, reading and writing the stream are both shown here.)

  • The window's Close event script saves the storage file and destroys the variable.

The OLEStorage variable stg_prod_pic is an instance variable of the window:

OLEStorage stg_prod_pic

The script for the window's Open event is:

integer result
stg_prod_pic = CREATE OLEStorage
result = stg_prod_pic.Open( is_ole_file)

The script for the RowFocusChanged event of dw_prod is:

integer result
string prodid, labelid, ls_data
long ll_stmlength
OLEStream stm_pic_label
/***************************************************
Create the OLEStream variable.
***************************************************/
stm_pic_label = CREATE OLEStream
/***************************************************
Get the product id from the DataWindow.
***************************************************/
this.Object.prod_id[currentrow]
/***************************************************
Open the picture in the storage file into the
control. The name of the storage is the product id.
***************************************************/
result = ole_prod.Open(stg_prod_pic, prodid)
IF result <> 0 THEN RETURN
/***************************************************
Construct the name of the product label stream and
open the stream.
***************************************************/

labelid = prodid + "_lbl"
result = stm_pic_label.Open( stg_prod_pic, &
   labelid, stgReadWrite! )
IF result <> 0 THEN RETURN
/***************************************************
Get the length of the stream. If there is data
(length > 0), read it. If not, write a label.
***************************************************/
result = stm_pic_label.Length(ll_stmlength)
IF ll_stmlength > 0 THEN
   result = stm_pic_label.Read(ls_data)
   IF result <> 0 THEN RETURN
   // Display the stream data in st_label
   st_label.Text = ls_data
ELSE
   result = stm_pic_label.Write( labelid )
   IF result < 0 THEN RETURN
   // Display the written data in st_label
   st_label.Text = labelid
END IF
/****************************************************
Close the stream and release the variable's memory.
***************************************************/
result = stm_pic_label.Close()
DESTROY stm_pic_label

The script for the window's Close event is:

integer result
result = stg_prod_pic.Save()
DESTROY stg_prod_pic

Strategies for using storages

Storing data in a storage is not like storing data in a database. A storage file does not enforce any particular data organization; you can organize each storage any way you want. You can design a hierarchical system with nested storages, or you can simply put several substorages at the root level of a storage file to keep them together for easy deployment and backup. The storages in a single file can be from the different OLE server applications.

If your DBMS does not support a blob datatype or if your database administrator does not want large blob objects in a database log, you can use storages as an alternative way of storing OLE data.

It is up to you to keep track of the structure of a storage. You can write a stream at the root level that lists the member names of the storages and streams in a storage file. You can also write streams that contain labels or database keys as a way of documenting the storage.