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

Related Topics: PowerBuilder

PowerBuilder: Article

Model-View-Controller In PowerBuilder

Model-View-Controller In PowerBuilder

Since the advent of three-tier computing, more and more people are telling us to "separate the business logic from the presentation." And well they should. By separating the business logic from the presentation we can achieve a high level of reusability. Furthermore, this is a necessity when they're located on separate tiers. If you're thinking about migrating your PowerBuilder applications to EAServer in the future, you should start separating your logic now. But how? How do we go about separating our business logic from our presentation? Traditionally this is done through the use of the MVC architecture, a paradigm that separates a GUI into three distinct parts: the model, the view and the controller. If you haven't previously considered using MVC in PowerBuilder, this article is a step-by-step guide to help you implement MVC in PowerBuilder.

What Is MVC?
In the MVC paradigm, the model is the business object, which is responsible for the data and behavior associated with the business domain; the view is responsible for the graphical representation of that data, i.e., drawing the component on the screen; and the controller is responsible for interpreting user input from the mouse or keyboard. The communication between the three components is based on the Observer pattern. The controller notifies the model when a user interaction triggers a change to the data. The model in turn notifies the view when the data has changed. Subsequently, the view updates the display accordingly. These three components, along with their strategy for communication, make up the MVC architecture (see Figures 1 and 2).

Should We Use MVC?
Developing MVC components isn't easy. It adds a new level of complexity to your GUI. The goal behind MVC is to separate your GUI into three distinct and reusable components. Any one of these components could then be used without the need for the other two. In practice, it's very difficult to create a controller that isn't coupled to its view. The controller needs to know too much about the view and, in practice, they end up being tightly coupled. Therefore the controller/view combinations are rarely used independently of each other. So component and framework designers have found at least one way to remove a little of the complexity from MVC. Another architecture based on MVC has arisen. In this architecture, the view and the controller are combined into one. Sun has implemented this architecture in Java's JFC or Swing and has named it the "Separable Model Architecture." This architecture is very attractive to PowerBuilder developers because of the inherent problems of creating a view and/or controller in PB. PowerBuilder standard controls have collapsed the MVC triad into one component. It's our goal then to work backwards: to take a standard PowerBuilder control and pull a model out of it, then alter that control to work with an MVC interface rather than the PowerBuilder default. The rest of this discussion will focus on creating a component based on the modified MVC architecture.

ListView Based on the Separable Model Architecture - The Model
Requirements for a Model
We must first pick a subject, a control to reengineer. Let's take the standard PowerBuilder listview. Our first step is to logically separate the model from the listview. So what data does a listview display? We know it shows a list of items. What then are the properties of those items? Looking at the ListViewItem datatype we see the following properties: CutHighlighted, Data, DropHighlighted, HasFocus, ItemX, ItemY, Label, OverlayPictureIndex, PictureIndex, Selected and StatePictureIndex. We can eliminate most of these properties because they relate only to how the item is displayed within a specific listview. We can also eliminate the Data property. This is a placeholder for user-defined information. This architecture is flexible enough that we won't need these kinds of placeholders. So our ending list is Label, OverlayPictureIndex, PictureIndex and StatePictureIndex. But wait. Notice that the pictures are represented by indexes. Those indexes are specific to the listview's picture list. We don't want our model to be tied to a specific listview. We must track the pictures as files rather than indexes. Our final list of properties is Label, Picture, OverlayPicture and StatePicture. Now that we know what we need to maintain, let's create an interface to it. See Figure 3 for the completed interface.

For now we're concerned only with the model's interface, not its implementation. We'll get to the implementation later. Besides maintaining the data, the model is also responsible for notifying the view when the data has been changed. In order to send this notification, the model must maintain a reference of the view. Therefore, we need to add a function to pass that reference into the model, of_addlistener (see Figure 3). Notice that the argument to this function is not of type listview but rather of type powerobject. Remember we don't want our model to know anything about its view (a.k.a., listeners), not even its type. We aren't going to implement this method now but let's assume that this method will store the passed reference in an instance variable in the model. This instance variable should be an array so that the model may have more than one attached view. When the setter methods are implemented, the programmer must remember to use this reference (or references in the case of multiple views) to notify the view that the data has changed. That completes the requirements for the model.

The actual implementation of these requirements will vary based on the situation you're faced with. If you're starting from scratch, you can build a new object to serve as the model. To support this, we'll create an abstract model. Or maybe you're converting an existing application and therefore you want an existing object to serve as the model.

Creating the Abstract Model
An abstract class is one that's never meant to be instantiated. It's designed only as an ancestor to other objects. An abstract class is a starting point for other objects. Let's create an abstract list model object that will fulfill the requirements we've just specified. We'll call it n_cst_abstractlistmodel. It will declare all the functions we listed above. We'll also implement the communication mechanism for the model. First, we can implement the of_addlistener function, then we need to add the notification functionality. To do that we'll add three more functions: of_fireitemchanged(long index), of_fireiteminserted(long index) and of_fireitemdeleted(long index). These functions will notify the attached views (or listeners) when an item is added, changed or deleted. They'll notify the view by triggering an event on the view. We'll have to add new events to our listview as well: mvc_itemchanged, mvc_iteminserted and mvc_itemdeleted. For example, the of_fireitemchanged function will trigger the mvc_itemchanged event on the view. These functions will need to be called in the setter methods as well as of_insertitem and of_deleteitem, but we aren't implementing those functions in this class. Those functions are to be implemented by the descendant class, therefore the developer coding the descendant class must code the setter methods to call these of_firexxxxx functions when appropriate. This communication mechanism is an implementation of the Observer pattern.

Creating the Default Model
Our view must have an associated model. The view assumes a model exists, so we must create a default model for it to use when we have yet to pass our own model to it. Let's create a new class inherited from n_cst_abstractlistmodel and call it n_cst_defaultlistmodel. Unlike our abstract class, we'll implement the getter/setter methods. We'll use a DataStore to hold the data and our class will act as a wrapper to it. We must also remember to call the of_firexxxxx functions in our setter methods. This is a very simple model with no way to update or save the data. It's unlikely that you'll want to use this model for anything except prototyping.

Creating Your Models or Using An Existing NVO
In most situations you'll want to create a model of your own or use an existing NVO. If you're creating a new object, then inherit from our abstract model. You may use an existing NVO as the model as long as it fulfills the requirements we set down, that is, it must implement the 12 functions listed above. You may do this in one of two ways. You may actually create these 12 functions within your existing NVO or you can create an adapter class. An adapter class is like a wrapper for your existing NVO. The adapter will substitute as the model. It will instantiate your existing NVO and delegate the calls it gets from the view to the appropriate calls on your existing NVO. For example, let's say you have an existing object that represents a list of customers and their names. You'd like your listview to show a list of these names. You could create an adapter, inheriting a new object from the abstract model. That adapter will maintain an instance of your customer object. When the view calls of_getlabel on the model, the model (which is the adapter) will return the value of the customer object's of_getcustomername function. This object "adapts" the interface of your existing NVO to the interface that the view expects. This technique is also useful when you need to display data from multiple objects within one view.

Using Remote Models
This adapter technique has yet another use. You may want to use an existing object as your model, but that object may be a component located in the middle tier. In fact, that component might not even be written in PowerBuilder. Perhaps you've written a component using PowerJ and deployed it to EAServer. You can use this component as your model - just wrap it using the adapter technique. In fact, if you are creating a model that will be hosted in EAServer, you must use an adapter. EAServer components can't be passed references to visual objects. This prevents us from passing the view's reference into the component. So the adapter object, which will stay located on the client, will maintain the reference to the view instead of the component.

The View
Now let's design the view. Our first task is to add methods to associate a model to the listview. First, add the method of_setmodel, which will take the model object as an argument. This method will store the model reference in the listview and automatically pass a reference to itself into the model by calling the model's of_addviewlistener. This method will also reset the display to show the data associated with the new model. We also need to add a method to get the model's reference from the listview, of_getmodel. Our second task is to alter the listview's interface so that all methods that alter the data are delegated out to the model. Unfortunately, the listview's methods that alter the data can also alter the display. For example, because you may pass a listviewitem object into the setitem function and the listviewitem object includes properties relating to how the item is displayed, the setitem function can alter the display. Therefore we must inform the programmers using our new listview not to use these built-in listview functions. The list of functions that are now "off-limits" are setitem, getitem, insertitem and deleteitem. We will override these functions to return an error under any condition so if a developer does call these functions, they'll never succeed. We must now duplicate part of our model's interface within the listview to replace the functions we've outlawed. We should add the functions that allow us to get/set the properties of the listview items. These functions will just delegate the function call to the model. When you call the listview's of_setlabel, it will, in turn, call the model's of_setlabel. We do this so the developer using our listview doesn't have to worry about dealing with the model once it's assigned if he or she doesn't want to. He may just interface directly with the listview. To complete the communication between the view and the model, we must create the events we spoke about earlier: mvc_iteminserted, mvc_itemchanged and mvc_itemdeleted. Within these events, our logic should update the display appropriately. There's one last task before the view is complete. When the view is first created, we want it to automatically create and associate a default model. We'll do this in the constructor event. Our view is now complete (see Figure 4).

Using the New MVC ListView
Using our new component is relatively easy. We can simply place the new listview on a window as we normally would. We don't even have to associate a model with it if we don't want to; it'll use the default model. The only caveat is you must remember not to call the standard listview's interface to alter data, i.e., no setitem, getitem, insertitem or deleteitem. You must use the new interface of_setlabel, of_getlabel, of_insertitem, of_deleteitem, of_getpicture, of_setpicture, etc. If we have created our own model, then we want to instantiate it and attach it to the listview by calling of_setmodel. With that, you should be off and running with your new MVC component.

Despite the fact that development with MVC components adds complexity to your GUI, once you've successfully implemented the MVC architecture, you'll realize the benefits. First and foremost is reusability. Instead of writing business logic behind controls and on windows, you write your business logic in nonvisual objects, then attach those objects as models. One business object may serve as a model for multiple views on multiple windows. Our applications also become more maintainable. If a change is required to the business logic, we just implement that change in the model. If a change is required in how the data is displayed, we just implement the change in the view. Each domain is separate, therefore we can easily change just one without affecting the other. We can even plug the model into a different view if we so desire. Additionally, we can seamlessly integrate remote objects hosted in the middle tier into our GUI regardless of the language they were developed in. These benefits of reusability and maintainability outweigh the added time and effort on the part of the developer.

The completed component is available from my Web site ( www.geocities.com/~cgross1). I encourage you to download it. If you have any questions or comments just drop me an e-mail at [email protected]

More Stories By Chris Gross

Chris Gross is a software development manager for Chesapeake System Solutions in Owings Mills, Maryland.

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.