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

Related Topics: PowerBuilder

PowerBuilder: Article

A Thorough Subscription Model

Inverting the roles of responsibility in the event model in PowerBuilder

In PowerBuilder, an event is used as a place to put code in reaction to something. The .NET world uses events differently and, in my opinion, more appropriately. In .NET, an event is just notification that something happened. This lets other objects react to changes while PowerBuilder's events only let an object know about its own changes.

Where is the benefit to the .NET approach? One of the most common scenarios is the master/detail window. The master view has a list of items and allows you to open a detail view for a specific item. How much work is done to update the master list once a detail item has been saved? What if you have a different window that uses that master list in a drop-down? How much work does it take to update that drop-down list when the detail window changes? The subscription model for .NET events makes it much easier. I'd like to share with you how I implemented that subscription model in PowerBuilder, applicable in versions six and later.

What Is That?
There's a plethora of articles on subscription models, and almost all of them use the analogy of a magazine. It works, so why should I break the mold? Here's a list of terms that I'll be using throughout this article:

  • Subscription: The desire to receive notification when an event happens.
  • Subscriber: The object that actually receives the notification.
  • Publisher: The object on which the event happens.
  • Article: The name of the event on the publisher to which the subscriber wishes to subscribe.
  • Mailbox: The mailbox basically is how the publisher will deliver the notification to the subscriber.
  • Publish: The act of providing notification to the subscriber.

Probably the one thing that I miss most in PowerBuilder is a hashtable. PB.NET gives you access to that, but many of us are on previous versions. Here's a quick-and-dirty implementation that allows you to store any object and reference it using a string value.

Create a new custom nonvisual object and save it as "_hashtable". (I always start framework-level class names with an underscore.) Mark this object as autoinstantiated. Create a public instance string variable called is_Key as an unbound array. Create another of  type any called ia_Value that is also an unbound array. The only other instance variable we need is a public long called Count.

PUBLIC String is_Key[]
PUBLIC Any ia_Value[]
PUBLIC Long Count

We'll start with the ability to add things to the hashtable. We need a Put() method. Our Put() method needs two arguments: the key, which is a string, and the value which is of type Any. When we Put() something in the hashtable, we need to either update the value (if the key already exists) or add it anew.

public subroutine put (String key, Any value)
Long    i

i = IndexOf (Key)
IF i = 0 THEN
i = UpperBound (is_Key) + 1
Count ++

is_Key[i] = Key
ia_Value[i] = Value

end subroutine

What is this IndexOf() method? That's how we find things in the hashtable when given a key.

public function Long indexof (String key)
Long    i, ll_Return = 0

FOR i = UpperBound (is_Key) TO 1 STEP -1
IF is_Key[i] = Key THEN
ll_Return = i
EXIT // No point in continuing to look...

RETURN ll_Return

end function

Now we can find things and we can add things. Let's see about getting a value.

public function any get (string key):

Long    i
Any     la_Return
SetNull (la_Return)

i = IndexOf (Key)
IF i > 0 THEN la_Return = ia_Value[i]

RETURN la_Return

end function

Now let's remove an item.

public function any remove (string key)
Any     la_Return
Long i

SetNull (la_Return)
i = IndexOf (Key)

IF i > 0 THEN
la_Return = Get (Key)
SetNull (is_Key[i])
SetNull (ia_Value[i])
Count --

RETURN la_Return
end function

We can add; we can remove; we can get a value. A hashtable has two other handy methods: KeyExists(), to see if something is there, and Clear(), to quickly clean it out.

public function boolean keyexists (string key):

RETURN (IndexOf (Key) > 0)
end function

public subroutine clear ()
String  ls_Empty[]
Any     la_Empty[]

is_Key = ls_Empty
ia_Value = la_Empty
Count = 0
end subroutine

Don't forget to save and bask in the glory of having a simple hashtable in PowerBuilder!

The Architecture
Our publisher will be keeping track of all of our subscription information. My personal line of thought is:

  • What articles have been subscribed to?
  • For each article, what are the mailboxes that I'll be delivering notification to?
  • What objects are using each mailbox for delivery?

The last two may seem backwards to most people, but thinking of it in this way makes the code so much easier. Since articles and mailboxes are just string (the names of events), it makes our hashtable extremely useful. Our base object will basically have a hashtable containing mailboxes that are referenced by the article name. Each of these "mailbox" entries will itself be a hashtable of subscribers.

When we publish an article, we'll grab a reference to each of the subscribing objects and use TriggerEvent() to pass notification. TriggerEvent() is the closest thing to a callback function that I can think of that's available in PowerBuilder.

Let's Write Some Code!
Open up your base nonvisual object. (You do have one, right? If not, create one. Mine is called "_nonvisualobject".) Add a private instance variable of type "_hashtable" and call it "Articles". This is where we'll store all of our subscription data.

We'll start with adding a subscription. Since the publisher is storing all of the data, it makes sense to me to think of the publisher as being responsible for all of the actions. instead of a "SubscribeTo" name, I have an AddSubscription() method.

Our AddSubscription() method takes three arguments: a reference to the object that is subscribing, the article to which it is subscribing, and the mailbox to which notification is sent. I'd like to make a note about that first argument, though.

We'll be passing in a reference to the calling object, so our argument type is "PowerObject", the base ancestor for all PowerBuilder classes. Any time you pass an object around that is not autoinstantiated, you're actually passing around a pointer to the object. That means that the scope of the argument applies to the pointer and not to the object it references. This is why you can pass in an object as "read only" and still modify the object's properties. In most instances when I want to pass an object reference into a function, I want to use the THIS keyword. The THIS keyword is a constant and constants must always be passed in as "read only."

We have a read-only argument of type PowerObject that we'll call "Subscriber". We have a string argument called "Article" (which event on the publisher does the subscriber want to know about) and another string argument called "Mailbox" (the name of an event on the subscriber that we'll trigger when we publish the given article).

Based on our architecture, our "Articles" is a hashtable that associates the name of an event with a collection of mailboxes. Each mailbox is a hashtable that associates the name of an event with the objects that use that event to receive notification. When we add a subscription, we look to see if anyone else has subscribed to that article. If so, we look to see if anyone else is using that mailbox. If so, we add the object reference to that collection (see Listing 1).

Tell Me About It
Now that we can add a subscription, we need to be able to publish our articles. In .NET, events have arguments so that you not only know that something happened but you also know what happened. We need a generic "properties" object that we can descend from later and use for passing data. In a generic situation, there's only one piece of information that we have available: a reference to the publishing object. Create a custom nonvisual object and add a public instance variable of type "PowerObject" to it. Call this instance variable PublishingObject for clarity. When I saved this object, I called it "_eventproperties". This object should not be autoinstantiated.

In all of this subscription service code, there are only two things that I am unsatisfied with. Because we lack callback functions, we have to pass data using the global Message object and the Message object can be rather volatile. The second thing that I am unsatisfied with is that we can't strongly type our event arguments. Despite this, we have an extremely usable subscription model and now that we have data, we're ready to publish!

Our base object needs a Publish() method with two arguments: the name of the article being published and an object of type _eventproperties containing the data for the event. In this method, we'll set the reference to the publishing object, then look for any subscribers that have subscribed to the given article and send notification to them (see Listing 2).

As you can see, we first check to see if anything has subscribed to the given article. If so, we loop through the assigned mailboxes. For each mailbox, we loop through the subscribers. We set the event data to the message object, then call the appropriate event on the subscriber. (The "mailbox" is the name of the event on the subscriber to call.)

There's only one more method we need to add and we'll be done. For ease of use, let's overload the Publish() method so we only have to specify the article name. I find this useful when we don't need to supply any data for the event.

public subroutine publish (string article)
_EventProperties EmptyProperties
Publish (Article, EmptyProperties)
end subroutine

Put It to Use
That's not really a lot of work. We added two new objects: a hashtable and a generic "event properties" object. And we added one instance variable and three methods to our base object. Now let's see how easy it is to use this. For our example, we're going to have an object that "changes." When it "changes," it will publish a "Changed" event, providing the old and the new values. We'll also have an object that subscribes to this event.

Start by inheriting from _EventProperties to create an object called ChangedEventData. On this object, add two string instance variables called OldValue and NewValue.

Next, inherit from your base object to create an object called "Publisher_Example". Add a string instance variable called MyValue. Add an event to this object called "Changed" that has two arguments: OldValue of type string and NewValue of type string. Add a function to this object called "ChangeValue" that takes a single string argument. Here's the script for the ChangeValue() method:

Public subroutine changevalue (string newvalue)
String OldValue
OldValue = MyValue
IF IsNull (OldValue) THEN OldValue = "[NULL]"

MyValue = NewValue
THIS.EVENT Changed (OldValue, NewValue)
End subroutine

And in the Changed event, put this script:

event changed (string OldValue, string NewValue)
ChangedEventData MyData
MyData = CREATE ChangedEventData

MyData.OldValue = OldValue
MyData.NewValue = NewValue

Publish ("Changed", MyData)
End event

Inherit from your base object, this time creating an object called "Subscriber_Example". Add an event to this object called "On_PublisherChanged". In this event, put the following code:

Event on_publisherchanged ()
ChangedEventData RealEventData
RealEventData = Message.PowerObjectParm

MessageBox ("Changed", "The value was changed from " + RealEventData.OldValue + " to " + RealEventData.NewValue)
End event

To make it all work, our subscriber needs to subscribe to the publisher. On the Subscriber_Example object, add a method called SeeTheMagic(). That script should look like this:

Public subroutine seethemagic()
Publisher_Example ExamplePublisher
ExamplePublisher = CREATE Publisher_Example
ExamplePublisher.AddSubscription (THIS, "Changed", "On_PublisherChanged")

ExamplePublisher.ChangeValue ("Our first change!")
ExamplePublisher.ChangeValue ("Oops, I did it again.")
End subroutine

What did we do here? Basically, we've inverted the roles of responsibility in the event model in PowerBuilder. Now, when one object is dependent on a second object, the first object can say, "What do I need to do when that other object's state changes?" We've realigned our thinking to closer match .NET, thus easing our migration in the future. And we've better encapsulated our scope of responsibility because the publishing object doesn't need to know anything about any other object that might be using it. What can we do with this? If we have a list of things in memory (the "C" in an "MVC" [1] design pattern), then any window can subscribe to a change event and update itself automatically. If we have a nonstandard user input device (like an RS232 barcode scanner or credit card scanner), we can add a subscription for when data is received. Or... I'll leave it up to the reader to come up with new ways to use this.


  1. The Model-View-Controller pattern hinges on a clean separation of objects into one of three categories - models for maintaining data, views for displaying all or a portion of the data, and controllers for handling events that affect the model or view(s). See also http://en.wikipedia.org/wiki/Model-View-Controller.

More Stories By Jason Fenter

Jason Fenter is an independent consultant working with PowerBuilder since 1996. With experience ranging from large companies with teams of ten or more to private companies with a single person for the IT department – and with projects ranging from inventory management to customer tracking to finance – Jason has an extensive background and knowledge base to draw upon.

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.