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 rikbrooks[at]aol.com 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:
DBMS=Informix
Let's begin with a couple of requirements:
- We need an object that will hold a key and value so we can pass that back and forth to our collection.
- We need to expose the number of items in the collection.
- We need to be able to add key/value combinations to our collection.
- We must determine if we want to allow duplicate keys.
- 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 rikbrooks[at]aol.com.
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.
N_cst_key_value.of_key
return is_key
Here we set the key value:
N_cst_key_value.of_key(as_your_key)
string ls_retVal
ls_retVal = 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
Now we have to instantiate the Datastore so it will work for us. We need to add some code to the constructor:
N_cst_key_value_collection.constructor
ids_key_value = create datastore
ids_key_value.dataobject = "ds_key_value"
Next we need a function to get the value:
N_cst_key_value_collection.of_allow_duplicates
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:
N_cst_key_value_collection.of_allow_duplicates
boolean lb_retVal, lb_found_duplicate = FALSE
lb_retVal = of_allow_duplicates()
if ids_key_value.rowCount() > 0 then
long ll_row, ll_max
ll_max = of_count( )
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
next
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:
N_cst_key_value_collection.of_count
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).
N_cst_key_value_collection.of_find_key
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:
N_cst_key_value_collection.of_add
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)
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:
W_main.cb_add
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:
N_cst_key_value_collection.of_value
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.