PowerBuilder Authors: Chris Pollach, Yeshim Deniz, Jayaram Krishnaswamy, Kevin Benedict, Avi Rosenthal

Related Topics: PowerBuilder, Microsoft Cloud

PowerBuilder: Article

Using the Tag Property - Part 2

Tips and techniques

This is the second part of a two-part article. In the last article we learned how to use the tag property to create our own microhelp and automate it. In this article we are going to go one step further and use the tag property for other things.

The list of items that I used the tag property for has shrunk over the years as Sybase has tried to give us more tools. Apart from the microhelp the most common use I had was to implement a tool tip. Now Sybase gives us a tool tip without our having to code a pop-up window.

Luckily I don't have to have a long list of items; one more is sufficient to show you the technique that I desire. I want to show you how to use the tag property for more than one thing. We have the microhelp, how about something else such as automatically bolding the static text that is associated with the control?

What I mean is that we can set it up so that a control could also register a text object inside the DataWindow control and have it be bolded when the control gains focus and unbolded when it loses focus. We could also set the border to Lowered when our control gains focus and No Border when it loses focus.

Before we begin, this code builds upon the code that was in last month's column. If you don't have it you should download it or write me at [email protected] and I'll send it to you.

Now, the magic that makes all this work is the ability to put more than one key/value on the DataWindow tag. That actually is a common requirement so let's do a reusable object for it. We can get a whole lot of functionality for this object using a Datastore to maintain the data.

Before we begin let's cover the concept of Key/Value. A Key/Value is a pair of objects, in this case strings, where the key identifies the value. A single line in an ini file is a very good example. You have a key, like "DBMS," and a value, like "Informix". That line might be:


Let's begin with a couple of requirements:

  1. We need an object that will hold a key and value so we can pass that back and forth to our collection.
  2. We need to expose the number of items in the collection.
  3. We need to be able to add key/value combinations to our collection.
  4. We must determine if we want to allow duplicate keys.
  5. We must provide a way to search for a key and get a value.

There are other things that we could do, especially if we are allowing duplicate keys. This article is about the tag object though, not a key/value object. Let's list the other things that might be added to the key/value and then we can cover them in the next article.

  • We will need to be able to get any particular row.
  • We will need a key/value object so we can return an array of key/values.
  • We will need a way to add that key/value object to our collection.
  • We will need the ability to return a subset of the values, rows 1-6 for example.
  • We will need a way to get all the key/value pairs for a particular key (given that duplicate keys are allowed).
  • We need a way to loop through the values with first/prev/next/last functionality.
  • We will need a way to loop through duplicate keys with the first/prev/next/last functionality
  • We could provide a custom delimiter.

We could provide a way to get a subset of the values. We could provide functionality to get all rows of a particular key or a way to do the first/prev/next/last functionality on a particular key within the object.

Getting Started
I'm not going to have to build upon my libraries here. I will be using them as we go so I'll remind you that they are available by downloading he code that comes with the article or writing me at [email protected].

We will start with the workhorse of our object, a Datastore. We need a Datastore with two columns, both strings. It needs an external datasource. I've given them the names as_key char(30) and as_value char(100). I saved mine as ds_key_value.

Consider that we are going to have to provide the programmer with a key/value pair. We used to handle this with structures but for many years now the object is a better choice. Let's create one that will be used just to hold the key value. It's a new (Ctrl-N) Pb Object/Custom Class as shown in Figure 1.

Why a custom class? Why not a structure? PowerBuilder still supports structures, why not use that?

The main reason is that we really don't know the future. Right now we might not be able to see that there is a need for functionality beyond the simplest holding of two strings, but that could change. We may find one day that we want to inherit from this and have a particular kind of key/value; maybe one that supports binary objects for the values. You never know what will happen. Let's go ahead and put it in an object. It costs us little to use the object as opposed to a structure and it gives us a lot of flexibility.

I call my object n_cst_key_value. It has two instance variables that look like this:

N_cst_key_value instance variables
private string is_key = ""
private string is_value = ""

You might notice that I declared both values to be private and I initialized both values. This is typically the kind of code that you will find written by older programmers who have reluctantly come to the conclusion that they can trust absolutely nothing.

Now you will need two functions for each of the values, four functions in all. You will need two for the key, one for setting and one for the getting. Here's the code, not a lot of need for explanation.

// FUNCTION n_cst_key_value.of_key
// RETURN VALUE string
// DESCRIPTION returns the value of is_key
return is_key

Here we set the key value:

// FUNCTION n_cst_key_value.of_key
// ARGUMENTS string as_key
// RETURN VALUE string
// DESCRIPTION Sets the key value and returns the value
//          of is_key before the call

string ls_retVal
ls_retVal = of_key() // Call the OTHER of_key
is_key = as_key
return ls_retVal

Now that you see how I did of_key, I assume that you can do the same for of_value, right? If not, then you might need to download the code and see how I did it. Space in this article is limited.

Now we need a non-visual (custom) object. It will work the Datastore. Create a new custom class.

I named mine n_cst_key_value_collection. Let's begin with point 1 from our requirements. We have to allow the programmer to allow or disallow duplicate keys. We need an instance variable and a couple of functions to set and receive it.

N_cst_key_value_collection Instance variables
Private Boolean ib_allow_duplicates
Private Datastore ids_key_values // Our collection

Now we have to instantiate the Datastore so it will work for us. We need to add some code to the constructor:

// FUNCTION n_cst_key_value_collection.constructor
// DESCRIPTION instantiates the datastore
ids_key_value = create datastore
ids_key_value.dataobject = "ds_key_value"

Next we need a function to get the value:

// RETURN VALUE boolean
// DESCRIPTION Returns whether or not duplicates are allowed
// in this function.
return ib_allow_duplicates

At the same time we need to allow the programmer to set whether or not we will allow duplicates. This is a little more complicated than it might seem. The problem is that if there are already duplicates, then we can't let the programmer deny duplicates:

// FUNCTION n_cst_key_value.of_allow_duplicates
// ARGUMENTS - ab_how
// RETURN VALUE boolean
// DESCRIPTION Allows or disallows duplicate key
// values in the datastore. If there are rows in the
// datastore and the programmer is trying to change
// to FALSE then we need to check for duplicates. If
// we find any we need to return FALSE which means
// error

boolean lb_retVal, lb_found_duplicate = FALSE
// The following line is polymorphic, not recursive.
// I'm not calling this same function. I'm calling the
// one that doesn't have an argument.
lb_retVal = of_allow_duplicates()

if ids_key_value.rowCount() > 0 then
// We have to see if there are any duplicates.
long ll_row, ll_max
ll_max = of_count( )
// I'm going to be a little tricky here.
// I'm going to sort the datastore then check
// for duplicates. It's a lot easier.
datastore lds
lds = ids_key_value
lds.setsort( "as_key")
for ll_row = 1 to ll_max - 1
if lds.getItemstring(ll_row, "as_key") = &
lds.getItemString(ll_row + 1, "as_key") then
lb_found_duplicate = TRUE
end if
if lb_found_duplicate and not ab_how then ab_how = TRUE
end if
ib_allow_duplicates = ab_how
return lb_retVal

Requirement 2: Expose the number of items in the collection
This one is simple. Your collection is the Datastore in your instance variables:

// FUNCTION n_cst_key_value_collection.of_count
// DESCRIPTION returns the number of items in the collection

return ids_key_value.rowCount()

Our First Test
I don't like to do too many things without a test. So let's start our test now. I inherited a window from w_root in my tools but you can just create a standard window and open it from your application open event. My window looks like Figure 2. It contains a couple of single line edits for my key/value, a button to add them (even though we haven't written that part yet), one for counting how many there are, and one for closing our window. Finally I have a checkbox for allowing duplicate keys or denying them.

Let's start by instantiating an instance variable for our key/value collection:

W_main instance variables
private n_cst_key_value_collection io_key_value

Now we can add a little code. We have to create it in the open event:

W_main open or post_open events
io_key_value = create n_cst_key_value_collection

It's not too early to set our Allow Duplicates:

W_main.cbx_allow_duplicates.clicked event
io_key_value.of_allow_duplicates( this.checked)

We can code the count button too:

W_main.cb_count.clicked event
messagebox("Count", string(io_key_value.of_count()) + " values are in the collection")

There isn't much we can do right now, but we have a start. We can run the application and click on the count button and see that we have no values yet.

Requirement 3: We need to be able to add key/value combinations to our collection
The functionality for adding a key/value into the collection is done in n_cst_key_value_collection. The first thing that needs to be done is to search for an item. That way we can satisfy the allowing or denying of duplicates. Since we are using a Datastore the function is actually quite easy (the comments are bigger than the function).

// FUNCTION n_cst_key_value_collection.of_find_key
// ARGUMENTS string as_key
// Looks for as_key. If it finds it then the index is returned.
// If it doesn't find it then 0 is returned

return ids_key_value.find( "as_key='" + as_key + "'", 0, ids_key_value.rowCount())

Now that we have the search we can do the add function:

// FUNCTION n_cst_key_value_collection.of_add
// string as_key
// string as_value
// Checks to make sure that there are
// no duplicate keys if the duplicates are not allowed.
// If it passes then it adds the values to the datastore
// and returns the number of rows in the datastore

if not of_allow_duplicates( ) and of_find_key( as_value) > 0 then
return -1
end if
long ll_row
ll_row = ids_key_value.insertRow(0) // add a row at the end.
ids_key_value.setitem( ll_row, "as_key", as_key)
ids_key_value.setItem(ll_row, "as_value", as_value)
return of_count( )

Having finished this we can finally create a test. We need to add the call to of_add in the Add button of the window. It looks like this:

long ll_rows
string ls_key, ls_value

ls_key = sle_key.text
ls_value = sle_value.text

ll_rows = io_key_value.of_add(ls_key , ls_value)

messagebox("You now have", string(ll_rows) + " rows")

Let's leave the allow duplicates off. In other words, not allow duplicates, then add two non-duplicate values and a third one that is duplicate. Here's the test:

  • Add key "First" and value "one." You should see a messagebox that says "1 rows."
  • Add key "Second" and value "two". You should see a messagebox that says "2 rows."
  • Click Add button without changing key or value. You should see a messagebox that says "-1 rows."
  • Click the Count button. You should see a messagebox that says "2 values are in the collection."

That would be a first test. We can also test the allow duplicate functionality:

  • Click Allow Duplicates to allow them.
  • Add "key1," "value1." You should see "1 rows."
  • Click Add again without changing. You should see "2 rows."
  • Click Allow Duplicates again, turning it off.
  • Click Add again. You should see "3 rows" because you already have duplicates and are not allowed to deny duplicates when you already have them.

Now close the application and come back in. We have one more test to perform and we can pass this. We need to immediately disallow duplicates and try to insert them.

  • Add "key2" and "value1". You should see "1 rows."
  • Click Add without changing anything. You should see "-1 rows."
  • Click Count. You should see "1 values are in the collection.
  • Add "key2" and "value2". You should see "2 rows."

Now we have a fully functional object. Let's add the last few functions to it and tests and wrap this up until Part 3. All that we have left is the ability to get a value. That's a fairly simple function:

// FUNCTION n_cst_key_value_collection.of_value
// ARGUMENTS string as_key
// RETURN VALUE string
// Returns the value at the first as_key. If as_key is not in the
// collection this returns an empty string

long ll_row
ll_row = of_find_key( as_key)
if ll_row = 0 then return ""
return ids_key_value.getitemstring(ll_row, "as_value")

This object is functional just as it is. Next month we are going to use this to store the values that will go into our Tag property of the DataWindow.

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.