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 may 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
the substorages can form a larger OLE object, such as a document
that includes pictures:
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 only be manipulated by that server application.
You might open a storage that is a server’s object to extract
an object within the storage, but you shouldn’t 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 doesn’t 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 savingstorages
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 may
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.)
1 |
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:
1 |
result = stg_data.Open("MYFILE.OLE") |
1 |
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:
1 |
result = ole_1.Save() |
1 |
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:
1 |
OLEStorage stg_data, stg_subdata |
1 |
stg_data = CREATE OLEStorage |
1 |
stg_subdata = CREATE OLEStorage |
1 |
ole_1.Open("FILE_A.OLE") |
1 |
stg_data.Open("FILE_B.OLE") |
1 |
stg_subdata.Open("subdata", stgRead!, & |
1 |
stgExclusive!, stg_data) |
1 |
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 creates 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:
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 can’t 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.)
1 |
boolean lb_exists |
1 |
result = stg_data.MemberExists("subdata", lb_exists) |
1 |
IF result = 0 AND lb_exists THEN |
1 |
result = stg_subdata.Open(stg_data, "subdata") |
1 |
END IF |
Example: buildinga 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 filename. 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:
The advantage to 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 doesn’t
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 don’t 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 filenames of the product drawings. The OLE control displays the
drawings.
List of scripts for the example
The example has three 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.
Instance variable
First, declare an OLEStorage variable as a instance variable
of the window:
1 |
OLEStorage stg_prod_pic |
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:
1 |
integer result |
1 |
stg_prod_pic = CREATE OLEStorage |
1 |
result = stg_prod_pic.Open("PICTURES.OLE") |
1 |
dw_prod.SetTransObject(SQLCA) |
1 |
dw_prod.Retrieve() |
Retrieve triggers the RowFocusChanged event It is important that the code for creating the storage variable
and opening the storage file come 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 filename before calling InsertFile.
The code then saves the displayed drawing in the storage:
1 |
integer result |
1 |
string prodid |
1 |
/*************************************************** |
1 |
Get the product identifier from the DataWindow. |
1 |
***************************************************/ |
1 |
prodid = dw_prodid.GetItemString( & |
1 |
dw_prodid.GetRow(), "prod_id" ) |
1 |
/*************************************************** |
1 |
Use the id to build the filename. Insert the file's |
1 |
object in the control. |
1 |
***************************************************/ |
1 |
result = ole_product.InsertFile( & |
1 |
"c:productsdrawings" + prodid + ".gif") |
1 |
/*************************************************** |
1 |
Save the OLE object to the storage. Use the same |
1 |
identifier to name the storage. |
1 |
***************************************************/ |
1 |
prodid = dw_prodid.GetItemString( & |
1 |
dw_prodid.GetRow(), "prod_id" ) |
1 |
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:
1 |
integer result |
1 |
result = stg_prod_pic.Save() |
1 |
DESTROY stg_prod_pic |
In the application’s Open event, connect to the database
and open the window.
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):
1 |
OLEStorage stg_prod_pic |
1 |
//Instantiate the storage variable and open the file |
1 |
stg_prod_pic = CREATE OLEStorage |
1 |
result = stg_prod_pic.Open("PICTURES.OLE") |
1 |
//Get the storage name from the DataWindow |
1 |
prodid = dw_prodid.GetItemString( & |
1 |
dw_prodid.GetRow(), "prod_id" ) |
1 |
//Open the picture into the control |
1 |
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:
1 |
integer result |
1 |
OLEStorage stg_pic |
1 |
OLEStream stm_pic_label |
1 |
/*************************************************** |
1 |
Allocate memory for the storage and stream variables |
1 |
***************************************************/ |
1 |
stg_pic = CREATE OLEStorage |
1 |
stm_pic_label = CREATE OLEStream |
1 |
/*************************************************** |
1 |
Open the storage and check the return value |
1 |
***************************************************/ |
1 |
result = stg_prod_pic.Open("picfile.ole") |
1 |
IF result <> 0 THEN RETURN |
1 |
/*************************************************** |
1 |
Open the stream and check the return value |
1 |
***************************************************/ |
1 |
result = stm_pic_label.Open(stg_prod_pic, & |
1 |
"pic_label", stgReadWrite!) |
1 |
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 diagram shows the structure of the file:
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:
1 |
OLEStorage stg_prod_pic |
The script for the window’s Open event is:
1 |
integer result |
1 |
stg_prod_pic = CREATE OLEStorage |
1 |
result = stg_prod_pic.Open( is_ole_file) |
The script for the RowFocusChanged event of dw_prod
is:
1 |
integer result |
1 |
string prodid, labelid, ls_data |
1 |
long ll_stmlength |
1 |
OLEStream stm_pic_label |
1 |
/*************************************************** |
1 |
Create the OLEStream variable. |
1 |
***************************************************/ |
1 |
stm_pic_label = CREATE OLEStream |
1 |
/*************************************************** |
1 |
Get the product id from the DataWindow. |
1 |
***************************************************/ |
1 |
prodid = dw_prod.GetItemString( & |
1 |
dw_prod.GetRow(), "prod_id" ) |
1 |
/*************************************************** |
1 |
Open the picture in the storage file into the |
1 |
control. The name of the storage is the product id. |
1 |
***************************************************/ |
1 |
result = ole_prod.Open(stg_prod_pic, prodid) |
1 |
IF result <> 0 THEN RETURN |
1 |
/*************************************************** |
1 |
Construct the name of the product label stream and |
1 |
open the stream. |
1 |
***************************************************/ |
1 |
labelid = prodid + "_lbl" |
1 |
result = stm_pic_label.Open( stg_prod_pic, & |
1 |
labelid, stgReadWrite! ) |
1 |
IF result <> 0 THEN RETURN |
1 |
/*************************************************** |
1 |
Get the length of the stream. If there is data |
1 |
(length > 0), read it. If not, write a label. |
1 |
***************************************************/ |
1 |
result = stm_pic_label.Length(ll_stmlength) |
1 |
IF ll_stmlength > 0 THEN |
1 |
result = stm_pic_label.Read(ls_data) |
1 |
IF result <> 0 THEN RETURN |
1 |
// Display the stream data in st_label |
1 |
st_label.Text = ls_data |
1 |
ELSE |
1 |
result = stm_pic_label.Write( labelid ) |
1 |
IF result < 0 THEN RETURN |
1 |
// Display the written data in st_label |
1 |
st_label.Text = labelid |
1 |
END IF |
1 |
/**************************************************** |
1 |
Close the stream and release the variable's memory. |
1 |
***************************************************/ |
1 |
result = stm_pic_label.Close() |
1 |
DESTROY stm_pic_label |
The script for the window’s Close event is:
1 |
integer result |
1 |
result = stg_prod_pic.Save() |
1 |
DESTROY stg_prod_pic |
Strategies for using storages
Storing data in a storage is not like storing data in a database.
A storage file doesn’t 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 doesn’t support a blob data type or
if your database administrator doesn’t 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.