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 Figure 19-3.
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.)
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")<br />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()<br />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<br />stg_data = CREATE OLEStorage<br />stg_subdata = CREATE OLEStorage<br />ole_1.Open("FILE_A.OLE")<br />stg_data.Open("FILE_B.OLE")<br />stg_subdata.Open("subdata", stgReadWrite!, &<br /> stgExclusive!, stg_data)<br />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.
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:
1 |
OLEStorage stg_data, stg_subdata<br />stg_data = CREATE OLEStorage<br />stg_subdata = CREATE OLEStorage<br />ole_1.Open("FILE_A.OLE")<br />stg_data.Open("FILE_B.OLE")<br />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 |
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<br />result = stg_data.MemberExists("subdata", lb_exists)<br />IF result = 0 AND lb_exists THEN<br /> result = stg_subdata.Open(stg_data, "subdata")<br />END IF |
To use MemberExists with the storage member
IOle10Native, use the following construction:
1 |
ole_storage.memberexists(char(1) + 'Ole10Native', &<br /> 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:
1 |
ole_stream.open(ole_storage, char(1) + 'Ole10Native', &<br /> StgReadWrite!, StgExclusive!) |
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 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.
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:
1 |
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:
1 |
integer result<br />stg_prod_pic = CREATE OLEStorage<br />result = stg_prod_pic.Open("PICTURES.OLE")<br />dw_prod.SetTransObject(SQLCA)<br />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:
1 |
integer result<br />string prodid<br />//Get the product identifier from the DataWindow.<br />prodid = this.Object.prod_id[currentrow]<br /><br />// Use the id to build the file name. Insert the <br />// file's object in the control.<br />result = ole_product.InsertFile( &<br /> GetCurrentDirectory() + "" + prodid + ".gif")<br /><br />// Save the OLE object to the storage. Use the <br />// same identifier to name the storage.<br />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<br />result = stg_prod_pic.Save()<br />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):
1 |
OLEStorage stg_prod_pic<br />//Instantiate the storage variable and open the file<br />stg_prod_pic = CREATE OLEStorage<br />result = stg_prod_pic.Open("PICTURES.OLE")<br /><br />// Get the storage name from the DataWindow<br />// This assumes it has been added to the DataWindow's<br />// rowfocuschanging event<br />prodid = this.Object.prod_id[newrow]<br />//Open the picture into the control<br />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<br />OLEStorage stg_pic<br />OLEStream stm_pic_label<br />/***************************************************<br />Allocate memory for the storage and stream variables<br />***************************************************/<br />stg_pic = CREATE OLEStorage<br />stm_pic_label = CREATE OLEStream<br />/***************************************************<br />Open the storage and check the return value<br />***************************************************/<br />result = stg_prod_pic.Open("picfile.ole")<br />IF result <> 0 THEN RETURN<br />/***************************************************<br />Open the stream and check the return value<br />***************************************************/<br />result = stm_pic_label.Open(stg_prod_pic, &<br /> "pic_label", stgReadWrite!)<br />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 |
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.
Figure 19-6 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<br />stg_prod_pic = CREATE OLEStorage<br />result = stg_prod_pic.Open( is_ole_file) |
The script for the RowFocusChanged event of dw_prod is:
1 |
integer result<br />string prodid, labelid, ls_data<br />long ll_stmlength<br />OLEStream stm_pic_label<br />/***************************************************<br />Create the OLEStream variable.<br />***************************************************/<br />stm_pic_label = CREATE OLEStream<br />/***************************************************<br />Get the product id from the DataWindow.<br />***************************************************/<br />this.Object.prod_id[currentrow]<br />/***************************************************<br />Open the picture in the storage file into the<br />control. The name of the storage is the product id.<br />***************************************************/<br />result = ole_prod.Open(stg_prod_pic, prodid)<br />IF result <> 0 THEN RETURN<br />/***************************************************<br />Construct the name of the product label stream and<br />open the stream.<br />***************************************************/<br /><br />labelid = prodid + "_lbl"<br />result = stm_pic_label.Open( stg_prod_pic, &<br /> labelid, stgReadWrite! )<br />IF result <> 0 THEN RETURN<br />/***************************************************<br />Get the length of the stream. If there is data<br />(length > 0), read it. If not, write a label.<br />***************************************************/<br />result = stm_pic_label.Length(ll_stmlength)<br />IF ll_stmlength > 0 THEN<br /> result = stm_pic_label.Read(ls_data)<br /> IF result <> 0 THEN RETURN<br /> // Display the stream data in st_label<br /> st_label.Text = ls_data<br />ELSE<br /> result = stm_pic_label.Write( labelid )<br /> IF result < 0 THEN RETURN<br /> // Display the written data in st_label<br /> st_label.Text = labelid<br />END IF<br />/****************************************************<br />Close the stream and release the variable's memory.<br />***************************************************/<br />result = stm_pic_label.Close()<br />DESTROY stm_pic_label |
The script for the window’s Close event is:
1 |
integer result<br />result = stg_prod_pic.Save()<br />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.