The Ultra-Thin VB Client

David Birmingham

In this article, David shows how to combine the lowly FRM files with notions of thin clients to deploy easy-to-maintain applications.

In a consulting role, I talk to a lot of people about architecture and problem solving. One particular theme keeps arising -- the configuration of the client tier and its server, or service tier(s). It seems that the developers have a good grip on the "n" tiers below the user interface, but many senior developers consider their "dues paid" when they never again have to code a VB form.

The form object's event-driven environment brings up so many pesky little bugs that, well . . . programmers would rather spend more time in the virtual world of objects and plain-vanilla threading issues than jump into the multi-threaded, preemptive world of the UI. After all, the UI problem resolutions are often very nit-picky and repetitive -- "Didn't I just solve this problem?" Then there's the all-too-common scenario where the client requests a single (though simple) feature. This typically requires an additional control object and some coding and debug -- the same coding and debug for every single form. It's a frustrating environment for seasoned object purists, because all of us believe that the primary purpose of the object is to solve a problem once.

There are two basic ways to look at a form object or, more importantly, the program called the "client" running on the "client" machine: as the driving force of the application or as just another source of data to a more central, core set of application code that isn't "the client." And each viewpoint drives a solution set -- the traditional (also called "classic," "fat client," or "client/server") application, or the "browser" application. A traditional application is loaded with all sorts of custom code in the client, while a browser application has absolutely no custom code on the client -- it uses a generic browser to gain user access to the core business logic.

It's also no secret that a lot of end-users want a highly responsive, customized look and feel for their desktop, and the off-the-shelf browser doesn't provide either. What you really need is somewhere in between -- the robust, crisp power of VB and its hard-won custom look and feel, plus the ability to treat the client like a browser. 

Building a thin client -- and I mean an ultra-thin client -- like this takes some thought. But all you really need is a means to manufacture a form at runtime with a customized look and feel, which you can pump user and display data to and from and then throw away -- all in the context of the application driving the form object as a data and event resource. The issues are:

What a strange configuration: The EXE file resides on the ActiveX server, and the client is an ActiveX DLL! Most apps I've seen implement just the reverse -- the client holds the EXE, and the server contains the DLL (the client runs the show, and the server software is the slave). I've deliberately inverted this equation because I want the server to run the show and the client to possess no more functional intelligence about my implementation than a common browser.

The application (on the server) provides the client with metadata references concerning a given form's display characteristics (number, type and position of controls, size and type -- modal, non-modal -- of the form itself, and so forth). It will drive the form object and its controls and use them as resources to capture data and high-level, repackaged events. I now have the best of all worlds -- my client is now a 100 percent reusable DLL. Since it will stabilize early and rarely change, I distribute it to the clients less often. My more volatile code, at the server level, is under more central control.

Additionally, many form-driven applications allow the forms to request database information on demand. Forms within the same business group, therefore, have a tendency to hit the database multiple times in multiple ways to retrieve their display information. This scenario allows the business server to control the data accesses for multiple forms and pump data to the display as required.

The appeal of 100 percent runtime configurability

In fact, I can roll out multiple applications on the server, all of them using the same client, and all of them 100 percent runtime configurable. The client's form-manufacturing metadata is the key. I often hear the refrain, "If I only had a boilerplate" -- a single form object for use in manufacturing the look and feel of a display. I could then manage one and only one form object (as far as coding is concerned), because at runtime I could manufacture as many of these as I wanted and paint them with controls and labels to affect business functionality. In other words, rather than compiling a customized client with hundreds of specific forms and their code, I only compile one generic form in the app and use it as a boilerplate to manufacture "n" custom forms at runtime. Now we're talking really thin, because this dramatically reduces the actual runtime size of the application. And the space savings is additive -- 200 forms compiled into the application take up 200 times more space than the simple boilerplate and its supporting code. If users don't require certain forms, fine. At least they're not compiled into the application as vestigial overhead, either.

In the accompanying Download file, I show how to manufacture controls on the VB form using the LOAD method, using metadata for type, position, sizing, and so on. I've heard programmers suggest I put this metadata in the application itself on the server. Another idea was to put the metadata in the database. Still another was to place it in text files on the server and give the client DLL the full path to them. The database idea was tossed for performance considerations. Having the metadata embedded in the applications seemed to defeat the purpose of metadata. Simple text files would mean one thing -- we'd need a separate application to manage the text files so the form/screen could be manufactured and managed visually. I couldn't imagine attempting to build forms without such an application.

But wait -- we already had all that! In fact, both VB and VC++ have a published, textual interface that's used to manufacture the form object in the guise of the FRM file. Open a FRM file in text mode sometime, and you'll see all the controls and their types, positions, sizes, and names -- everything you need -- and the FRM file is a stable, published interface specification. All you need to manipulate it is VB. At runtime, you'll need what I've called a Form Interpreter, one that can read the FRM file as text and use it to manufacture another form object (this is also provided in ActiveClient).

This opens lots of doors. With the client itself already stable and manufacturing form objects at runtime from metadata, you could easily change the look and feel of a custom screen at any time from VB. Just get the FRM file, make the changes, then drop the finished FRM file into the application's metadata repository, and you're done -- no re-compiling or re-publishing of the application. Rather than shipping loads of metadata to the client machine, the application simply gives the client the network path to the given FRM file.

Another benefit isn't so obvious. Within this configuration, the client is a DLL, controlled and launched by the ActiveX server. How, then, does the user, from the client machine, launch the client or gain access to the application on the server? All you need is a login/registration application on the client machine, along with the registered client ActiveX DLL. The login application will notify the ActiveX server that the client needs attention. It will identify the client on the network, and the server will validate and take over from there. The login then shuts down. Examine carefully what this implies -- the ActiveX server now has the option to launch and control the client or to hand off the launch/control to another ActiveX server. Let's say your primary server is currently handling five clients, and you've set this as the upper limit for any of your servers. When the sixth client logs in, the ActiveX server hands off to another ActiveX server. This provides you with dynamic, programmable, and robust scalability. 

Yet another benefit is that at runtime, the client form objects and controls contain only the data they're displaying, and no more. Got a grid with 500 rows? 1,000 rows? The ActiveBusiness layer (on the server) contains the full 1,000 rows and only ships the currently displayable rows to the client -- on demand. This dramatically changes your network loading (for the better). Client machines will only capture events and data, relaying them to their servers in small, high-frequency packets -- no more mass deployment of new updates to myriad individual PCs. Granted, this is shifting the burden to ActiveX servers, but it's almost always easier to configure and optimize their few network paths to the database server than it is to optimize the clients over an entire enterprise. Networks are ideally suited for high-frequency, low-footprint transactions as opposed to mass data download.

As long as I'm generating control objects on the form, it's easy enough to implement standardized behavior using common properties and then wrap the objects with adapters. This allows me to access all control objects with a set of common, top-level properties. It also allows me to enhance their functionality and standardize their interfaces. For example, some controls are modified through a Value property, others through a Text property, or perhaps Item. I chose to standardize everything with the Item property, allowing data values to be shared between controls simply by assigning Items. Most importantly, I can standardize behavior, which is a paramount key to User Interface stability.

I also wrapped the form object with an adapter (to further control its load, activation, and unload), and its Item property accepts an additional parameter -- the Tag name of a given control. Now you can get/put any value in any control object at a distance, from the ActiveX server, with a simple notation: moFA.Item("Tagname") = Value.

The standardization on the Item property provides me with another benefit -- the implementation of Observer patterns. I can now transfer data between controls using the standard Item property and make it automatic with Observers. For example, if on-screen functionality requires update of a text box with the column value in a grid each time the grid scrolls, I can configure the text box's adapter to automatically observe the grid's column and automatically relay any changes. Let's say a check box will be True based upon a given value in a combo box, otherwise False. You can set up the check box's adapter to behave based upon data assigned to its Item property, and with an Observer object between them, the behavior is automatic. I configured the Form Adapter to observe, by default, every change in every control. This allows me to implement a Command pattern -- Undo and Redo for every control on the form -- automatically with Observers. What a framework!

Conclusion

Download and execute the projects in the accompanying Download file and play with them a bit. I used MSDBGrid in unbound mode for grid loading and testing. Add your own creativity and you'll find that this framework is robust and fertile ground for a multitude of uses. It will stabilize your production environment and the behavior of the client, provide the ability to centrally upgrade the functionality of the application with minimal distribution headaches, and generally increase reuse of your coding efforts.

Download sample projects here.

David Birmingham is a manager with KPMG Peat Marwick and has been providing reusable and scalable architectures for more than 12 years. Dbirmingham@kpmg.com.