Welcome!

Richard (Rik) Brooks

Subscribe to Richard (Rik) Brooks: eMailAlertsEmail Alerts
Get Richard (Rik) Brooks via: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


PowerBuilder: Article

Dynamic Data Windows 1

Dynamic Data Windows 1

"Welcome back, my friends, to the show that never ends, we hope you will attend, step inside, step inside." Those words - from an old song by Emerson, Lake and Palmer - have been floating through my head all day, ever since I decided on the subject of my next series of articles on the DataWindow.

It was a hard decision. We've gone through five months of graphs and, while we haven't really explored the entire subject, five months of it is enough for anybody. It's time to move on.

The subject of the next series of articles is dynamic DataWindows. Here we're moving well beyond the commonplace. With these articles you'll enter the dark domain of the DataWindow master. They'll guide you on an exploration of the inner workings of this remarkable object and allow you to do things that only gurus can do.

Having said that, let's just dive right in. Gather 'round, dear readers, as I open my box of tricks....

Defining Our Terms
Let's begin with some basic definitions, starting with the word DataWindow itself.

DataWindow
A DataWindow is a generic term. It may mean a DataWindow control or a DataWindow object. In fact, a lot of people sometimes call DataStores DataWindows.

DataWindow Control
A DataWindow control is the item you place on the surface of a window that will "hold" a DataWindow object. Actually, you associate a DataWindow control with a DataWindow object by setting the DataObject property of that control. The fact that this DataObject property is in fact nothing more than a string has some interesting implications.

First of all, it means that the DataWindow the user sees isn't hard-coded into the window. In other words, the setting of this property (by right-clicking on the control and going to Properties) doesn't actually bring that specific DataWindow object into the window. It simply tells PowerBuilder which object to load at runtime.

Think about that. The DataWindow control doesn't really hold a DataWindow object. It just holds a string that identifies which one to load at runtime. Doesn't this mean that if, at runtime, we change that property, another DataWindow object will be loaded? You bet your sweet bippy it does!

Besides that, it means there's some sort of definition somewhere that tells PB how to draw and manipulate this DataWindow object - otherwise it wouldn't be able to put it in the control at runtime. Doesn't this mean we don't need a DataWindow at all - that if we understand the way that PB does it we can in fact reproduce it ourselves and make up complete DataWindow objects on the fly? Again, you get a cigar.

If you think a little more about this, you'll come to realize that all those functions that work on the DataWindow control (dw_1.update()) are working not on the DataWindow object but on the DataWindow control. They may seem to affect the object, but they really don't. All those functions really have nothing to do with the object, only the particular instance of that object held by the control.

The main thing I'm trying to tell you is that, self-evident though it may sound, a DataWindow control is a holder for a DataWindow object. Nothing more, nothing less.

DataWindow Object
The DataWindow object is what you're creating when you're in the DataWindow painter and is what most people are actually referring to when they say DataWindow. When you "create a new DataWindow," you're usually creating a new DataWindow object.

Unlike the DataWindow control, this object has no events associated with it. After all, the user never interacts with the DataWindow object but with a DataWindow control. The result of those things you do inside a DataWindow painter is not, in fact, something you can "see." It's a few lines of codelike descriptions. The DataWindow painter, then, is a whole lot of graphics that help you create those few lines of codelike descriptions.

"Gee," you must be thinking, "those must be some very complex lines if we need all that front-end work to generate them." Well, yes and no. It's not just the lines - although they're bad enough in themselves; the position of the lines is also relevant. In other words, their order.

I remember version 1 of PowerBuilder. We had it at work. The desktop version wasn't even under consideration back then and PB was bought by the "seat." As a mid-level corporate programmer, I couldn't afford my own copy. So I'd export my files from the Library Painter and work on them at home in Notepad. Then I'd bring them back in to work and import them.

In this way I became very familiar with the DataWindow object. You can do the same. Create a DataWindow object and then go to your Library Painter and export it. You'll see those lines I've been talking about. If you look at them closely, you can see they make sense. It's not like they're written in hex-code or anything. It's just that none of us (I'm sure) would want to have to generate DataWindows "in the raw" this way.

In fact, I strongly suggest you do precisely that. Export a DataWindow, then look at it in a file editor. Later in this series we'll explore that exported file in great depth. We'll use it, just as I did all those years ago, to learn things about the DataWindow that Sybase only hints at in the documentation.

Are we still all together?

Question & Answer
Q: We said that you create the DataWindow object in the DataWindow painter. But don't you also create the DataStore in the DataWindow painter? I mean, there's no DataWindow control to associate the DataWindow object with. So isn't a DataStore also done in the DataWindow painter?

A: No. Like the DataWindow control, the DataStore is a holder for a DataWindow object - it's just that the visual attributes for the DataStore are ignored. Remember, to create a DataStore you have to set the DataObject property. Thus the DataStore is more akin to the DataWindow control than to the DataWindow object. We'll cover the DataStore in detail in another article. There are other differences between the DataStore and the DataWindow control (like the lack of events).

Let's Get Started: Introducing Sherlock
I'm a firm believer that we can learn a lot better by doing than by reading. So let's create an application that puts this article to work. We'll begin an application and expand it in this series until we have something quite remarkable. I'll name the application "Sherlock."

I once wrote a report generator application for a client that I also named Sherlock, but it was for that one client and was geared specifically to their particular database. The application we're going to build here will be completely different. Still, the idea is so similar - querying the database to create custom DataWindows - that I'm going to use the same name.

To begin, I've decided to use a modern look and feel for the application. No sense in following a style that's on its way out just for the sake of tradition. We'll have one main window and it'll be full-screen, appearing to take over your computer entirely. It'll have no title bar, no menu, no border; it needs to look as though it's taken over your machine.

Let's create a folder for your application. I called mine "Sherlock." Under Sherlock I created a folder called "Graphics" where, obviously, I'll store the graphics I need for the application. I also created a folder called "0.1" where I'll store this month's version of the application.

The first thing to consider is what we're trying to accomplish. We can deal with this in a broad-stroke kind of way right now. We want the application to look modern - we'll implement this by having basically the one window. This window will have only one control: a DataWindow control. Everything will react to this control so we need to beef up the standard PB DataWindow control to handle just a bit more than it does now. Figure 1 shows what my application looks like after I finished it (version 0.1).

Now we can make a couple of decisions. In the upper right-hand corner we have the Exit Application. This is going to be a static text, which reminds me that I'm going to need some code to point out to the user that this is a "clickable" item. I'll show you how to do that a little later.

Notice that I put the checkboxes along the top. I like to separate input fields of different types and try to group them together. Checkboxes and Radio Buttons work particularly well along the top. I also gave them a tab order of 0. I'll default these, and if users want to change them, well, they'll just have to click on them.

In the upper-left corner of the window I have a static text that identifies it as the Login window. I'll maintain that convention throughout the application since I'm not using a title bar for the window.

As for the columns, I took those directly from my Database painter. I right-clicked on the Database I wanted, then went to Properties. There I found a tab page for Preview. When I looked at that I saw the following:

// Profile EAS Demo DB V3
SQLCA.DBMS = "ODBC"
SQLCA.Database = "EAS Demo DB V3"
SQLCA.AutoCommit = False
SQLCA.DBParm = "ConnectString='DSN=EAS Demo DB V3;UID=dba;PWD=sql'"
Okay, I used this to remind me what I need for logging in.

The only other thing I'd like to point out is that along the bottom I have something that resembles a microhelp. It's a text object that says "Awaiting User Input." We'll be interacting with that as well.

Create Your DataWindow Superclass (Ancestor)
This is where we start. It's pretty much where I always start. The DataWindow - the core of any well-designed PB application - receives my first attention.

Our DataWindow doesn't need any extra functionality right now, but rest assured - it will. So let's just create one. If you already have your own ancestor (if you're using the PFC, for example), skip this step; you'll be adding a little to your ancestor later.

When you're working with your ancestor (or superclass) DataWindow, bear in mind that this is the DataWindow control we're working with. Don't get them confused. You have to firmly keep in mind that when working with this object you don't know anything at all about the specifics of the DataWindow object that will be controlled by your ancestor. You don't know the columns or their types. You don't know the presentation and you don't know the data source. You know nothing specific about it. If you need to refer to anything at all in the DataWindow object that'll be housed by your DataWindow control, you'll have to write some code that'll uncover that information at runtime. I can't emphasize this enough. Don't ever think you can directly reference any column in your ancestor control. If you do, it'll cause you unending heartburn later.

So, with all these things in mind, create your DataWindow ancestor. To do this, simply click on New in your toolbar, then Standard Visual. This will present you with a dialog from which you may select DataWindow. Once you've done this, you'll be in a painter that'll allow you to modify it. I'm going to add an instance variable as follows:

boolean ib_activeTags = FALSE
Oops, Can't Get There from Here
Now let's go to the clicked event. The first thing I like to implement is an exit from the application - that way, while I'm testing, I can get out of the application if I need to. So I look at the clicked event. I have that "Exit Application" text up in the upper right-hand corner of the DataWindow. How do I get that to exit from the application? It's just a text object - it doesn't respond to the clicked event.

We need to add functionality to the DataWindow control. We have to insist that it start recognizing practically anything as operable or important. We've scratched the surface on how to do this in previous articles. We've discussed tool tips and automatic microhelp. We'll do something similar here. I won't go into why we're taking the approach we are. I've been over the reasons in previous articles. I'll just tell you what we're going to do.

We'll use the Tag attribute of objects in our DataWindow object to tell us what we can do and how to do it. We can go about this in several ways, but they all consist of tokens and targets. After a lot of thought I decided that the particular methodology for separating these Tag attributes is largely a matter of choice. So I chose a method made familiar in the use of ini files. This makes use of square brackets. Take a look at Figure 2.

In this figure you see a line that I pulled out of one of my functioning programs. This is the Tag attribute for a column that holds an age. I've added underlines to make the tokens obvious. Each underlined item in that string is a target, an item I search by. The value after that token, and continuing until the start of the next token or the end of the string, is the Value. So the Value for microhelp is "Enter Age" and so on.

Clearly, the first thing we need is something to parse out this string. If you're using the PFC, you already have the string object, and that has just the function you need. If you don't, you'll have to write your own (or just take the one I'm giving you). I suggest you create your own object for it. That way we can enhance it as time goes on.

Creating the String Object
For your purposes you need a new Custom Class. You may already have an ancestor for all new Custom Classes, as I do. If so, simply inherit from that.

The function I'm going to write is called of_getTokenizedValue. As is my wont, I actually write two of them, using polymorphism. One takes only two arguments, the token and the target. The other takes those two plus a boolean that tells me if this is a case-sensitive search. Going back to Figure 2, if I wrote the following lines:

string ls_value, ls_token
dojo_n_cst_string lo_string

ls_token = "[microhelp] Enter Age [post] ue_checkAge"
ls_value = lo_string.of_getTokenizedValue(ls_token, "microhelp")

then the value of ls_value would be "Enter Age". It found Enter Age to be associated with Microhelp.

The code for the two of_getTokenizedValue functions is shown in Listings 1 and 2.

Setting Up Your Application
I'm now going to give you a series of quick steps to follow exactly (or download the code):

  1. Create the new application
  2. Create a new window. Immediately save it as w_main.
  3. Go back to the application and open w_main.
This gives us a start. For our purposes we won't make our connection in the application script. I usually do connect to the database there, but in our case I want w_main to be the nexus of this application. Everything centers on that window, so my connection to the database will be in w_main.

Now that you're in w_main, let's set some properties. You don't want a menu. You don't want a title. You want the window to be full screen - and to accomplish this your window has to be a pop-up.

Add your custom user object to this window (the DataWindow control we just spoke of).

For this article we're just going to provide a way to get out of the application. In the clicked event of the DataWindow control add a line as follows to close the parent:

close(parent)
This should let you run the application and close it.

I wish that we could go further this month, but I've run out of space. Next month we'll handle the login process.

More Stories By Richard (Rik) Brooks

Rik Brooks has been programming in PowerBuilder since the final beta release before version 1. He has authored or co-authored five books on PowerBuilder including “The Definitive DataWindow”. Currently he lives in Mississippi and works in Memphis, Tennessee.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.