Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Introduction to Thin Client Framework, Part 2: The TCF programming model

Explore in detail the most relevant TCF components

Peter Bahrs, PhD (tcfhelp@us.ibm.com), Distinguished Engineer, IBM 
Dr. Peter Bahrs is an IBM Distinguished Engineer and IT Architect in the IBM Software Services organization. Dr. Bahrs specializes in large e-business systems development in the financial services industries. He holds several patents and has spoken at industry conferences such as JavaOne. Dr. Bahrs is currently on assignment to IBM Zurich, Switzerland. He can be reached at tcfhelp@us.ibm.com.
Barry Feigenbaum, Ph.D., Senior Consulting IT Specialist, IBM 
author
Dr. Barry Feigenbaum is a member of the IBM Worldwide Accessibility Center, where he serves as a member of a team that helps IBM make its products accessible to people with disabilities. Dr. Feigenbaum has published several books and articles, holds several patents, and has spoken at industry conferences such as JavaOne. He serves as an Adjunct Assistant Professor of Computer Science at the University of Texas, Austin. You can contact Dr. Feigenbaum at feigenba@us.ibm.com.

Summary:  In this second part of a two-part introduction to the Thin Client Framework, you will learn about the TCF programming model. Follow along as two key members of the original TCF design team explain and illustrate the role, major functionality, and related data values of the most relevant TCF components.

Date:  07 Jan 2003
Level:  Introductory
Also available in:   Japanese

Activity:  4260 views
Comments:  

In Part 1 of this two-part series, we introduced the Thin Client Framework (TCF). We provided a conceptual overview of the framework's purpose and foundations, outlined its most relevant components, and used a working example to demonstrate how those components work together in a TCF application. In this second part of the series, we'll offer a more detailed discussion of TCF, focusing almost entirely on the framework's programming model. We'll describe each of the major TCF components, explaining its role, function, and related data values. Code examples will be used to demonstrate how each component is used. Because the discussion here builds on the information relayed in the first part of the series, you should read Part 1 before continuing on with Part 2.

TCF architecture: A review

Before we launch into a detailed discussion of TCF's components, we'll take a moment to review the TCF architecture. Figure 1 illustrates the TCF architectural pattern. Ovals represent major TCF interfaces or classes, while rectangles or diamonds represent TCF support classes. All TCF communication is event based.


Figure 1. The TCF architecture
A diagram of the TCF architecture

For a more in-depth explanation of the TCF architecture, see Part 1 of this series.


TCF programming model

TCF is implemented as a small set of Java interfaces and classes, as shown in Figure 2:


Figure 2. TCF interfaces and classes
List of TCF interfaces and classes

In the above list, the following key applies:

  • Circle-I indicates an interface
  • Circle-C indicates a class
  • Superscript-A indicates an abstract class
  • Superscript-F indicates a final class

As you can see, TCF consists of a relatively small number of components. The framework's power comes not from an extensive library of code but from its simplicity, combined with its generality and well-architected distribution of responsibilities. In the sections that follow, we'll describe most of the TCF component types illustrated in Figure 2, focusing on the details of how each one functions and is coded.


The JTC interface

Java Thin Client (JTC) was the original iteration of TCF. Much of the original JTC code remains in the current TCF implementation. For example, the JTC interface is implemented by all the major TCF componentry. The JTC interface yields the following benefits:

  • JTC allows reference to all objects with a consistent type and behavior.

  • A number of essential classes implement JTC. They are AbstractViewController, AbstractApplicationMediator, AbstractTransporter, and AbstractDestination, as well as any top-level application classes.

  • The getJTCs() method is particularly useful, in that it:
    • Ensures that each JTC implementation is expected to return all the JTC objects it creates
    • Enables a runtime reconfiguration of the application
    • Allows non-intrusive logging, tracing, and debugging

Factory support

The Factory class is used to load and create shared and singleton objects. For example, in a single JVM, you may want to create and share one instance of a Transporter across multiple locations. To achieve this, you would code each transporter-instance location as follows:

Transporter t = Factory.newInstance(
    "com.ibm.jtc.DefaultTransporter", true);

In this case, the class name is implicitly used as a key. The true value indicates that a singleton should be created. In addition to the class name, you can add a custom key, as shown below:

Customer c = Factory.newInstance( 
    "CUST", "com.abc.Customer", true);

The Factory class delegates its implementation to the FactoryImpl class. To change the behavior of the Factory class, you simply subclass FactoryImpl and override its non-static methods. From there, you need only create an instance of your subclass and call the static method Factory.setInstance(yourInstance). The static methods in the Factory class will then call the instance methods of your factory instance.


JTCEvent

JTCEvent is the superclass for all TCF event classes. It extends java.util.EventObject. In addition to the inherited source field, JTCEvent provides the following fields:

  • major: An int to describe the type of event; defaults to -1

  • minor: An int to describe a variant on the type of the event; defaults to -1

  • isConsumed: A boolean; true indicates that the dispatching to subsequent handlers (that is, listeners) should stop

  • data: An object; a generic reference that is carried along to listeners; often a collection class (such as a Vector or Hashtable) or a customized data object

Each JTCEvent subclass will define static final values for the major and minor fields that represent its purpose.


ViewEvent

ViewEvent, a subclass of JTCEvent, is the mechanism for communication between ViewControllers and ViewListeners. ViewEvent provides many predefined constants (too many to show here) for typical GUI-input sources such as buttons.

A ViewController typically converts AWT events such as button presses into ViewEvents. The ViewEvents are then processed, often by the ApplicationMediator.

Let's take a look at a typical event scenario. The code samples below show how ViewControllers and ApplicationMediators interact. The ViewController first listens for the GUI event:

nextButton.addActionListener(this);

Upon being called back by an AWT event (in this case from nextButton) in the ActionListener interface, the ViewController creates and fires a ViewEvent to process the next action:

public void actionPerformed(ActionEvent ae) {
    if (ae.getSource() == nextButton) {
        ViewEvent ve = new ViewEvent(this);
        ve.setMajor(ViewEvent.NEXT);
        fireViewEvent(ve);
    }
}

ViewEvents are received by the ViewListener. The following code samples demonstrate a typical usage of the ViewListener. Typically, the role of ViewListener is implemented in an ApplicationMediator. First, the ApplicationMediator adds itself to the ViewController as a ViewListener:

public void init(){
    //...
    detailsViewController.addViewListener(this);
}

Upon being called back in the viewEventPerformed method, the ApplicationMediator decodes and processes the ViewEvent:

public void viewEventPerformed(ViewEvent ve) {
    switch (ve.getMajor()) {
        case ViewEvent.NEXT:
            // perform next action
            break;
        case ViewEvent.OK:  
            // perform OK action
            break;
        :
     }
}


RequestEvent

RequestEvent, a subclass of JTCEvent, is used to represent a lightweight transaction or single-step service request. In addition to the rather general major and minor values inherited from JTCEvent, RequestEvents provide two additional java.lang.String values: family and command. These values provide the specific event identification RequestEvents typically require. The family value is used for routing to Destinations and the command value indicates the desired function. The major and minor values can still be used to modify a command code.

The code samples below illustrate a typical usage of a RequestEvent.

Create a RequestEvent:

RequestEvent re = new RequestEvent();  
re.setFamily("Loans");
re.setCommand("SubmitCustomerInfo");

Fire the event to a registered RequestListener:

try {
    fireRequestEvent(this, re); // asynchronous
      -- or --
    fireRequestEvent(re);       // synchronous
} 
catch (RequestException e) {
   // **** process any exception ****
}

Note that a synchronous request is processed on the calling class's thread. Synchronous processing should be used only for events that require minimal and rapid processing. An asynchronous request is processed on a different thread. Asynchronous processing allows more time for each event without tying up the requesting thread. Asynchronous processing leads to increased GUI responsiveness.

An asynchronous request calls back the RequestEvent originator in the RequestResponseListener interface, as shown here:

public void requestResponse(RequestEvent re) {
   // **** some success action ****
}
public void requestException(RequestException e) {
   // **** some failure action ****
}

(A synchronous request would immediately either return or throw a RequestException.)


ViewController

ViewController is an interface that defines a reusable user interface component (often a subclass of java.awt.Component such as a javax.swing.JPanel) as a part of an overall client-application GUI. Application authors typically subclass AbstractViewController to implement a part of the user interface, such as an input screen or dialog. It is best practice to keep ViewControllers simple (that is, just a few fields and buttons) so that they are reusable and can easily be composed into more complex GUIs. Figure 3 shows the ViewController runtime:


Figure 3. The ViewController runtime
ViewController runtime

ViewController provides the following methods:

  • add/setViewListener manages a list of ViewListeners.

  • getComponent returns the java.awt.Component that the ViewController maintains through either an is-a or a has-a relationship.

  • set/isEnabled accesses the ViewController's enabled state. If enabled, the ViewController will respond to input events.

  • isValid accesses the ViewController's valid state. The valid state is generally related to the application of validation rules to fields of the ViewController.

  • set/isVisible accesses the ViewController's visible state. Only visible ViewControllers can be seen.

  • refresh updates the ViewController with new data.

  • setValidationLevel indicates when the ViewController should apply ValidationRules.

  • setResources updates the ViewController with java.util.ResourceBundles.

ViewControllers can be implemented in two ways: through inheritance or through delegation. Both are shown here:

Inheritance (Java extends 'is-a'):

public class ViewControllerBase extends JPanel implements ViewController {
    public Component getComponent() { return this; }
}

Delegation (Java variable 'has-a'):

public class ViewControllerBase implements ViewController {
    //this can be a Panel, JPanel, Component, etc.
    Component comp = new ...();

    public java.awt.Component getComponent() { 
        return comp; 
    }
    public void setEnabled(boolean e) { 
        comp.setEnabled(e); 
    }
    public void setVisible(boolean v) { 
        comp.setVisible(v); 
    }
    :
}

For TCF users who do not have access to the TCF source, but who wish to change the default ViewControllerBase implementation (which extends javax.swing.JPanel), the TCF runtime package includes the following AbstractViewController implementations:

  • AbstractViewController extends ViewControllerBase. (AVC is maintained for legacy TCF use and for those who have access to the source.)

  • AbstractObjectViewController extends java.lang.Object and uses delegation.

  • AbstractPanelViewController extends javax.swing.JPanel and uses inheritance.

Every ViewController implementation extends either AbstractPanelViewController or AbstractObjectViewController. As a result, ViewController can be used as visual components (typical) or as other classes, as needed.


Validation rules

ValidationRule is an abstract class for a rule engine used to validate and format ViewController field content. Validation is usually used for user entry on a single text field or groups of text fields. For example, you might set up validation for a range of numbers such as a date or social security number. Other uses for validation include addresses, lengths, and other business-specific formats. ValidationRule throws a ValidationRuleException upon failure. TCF provides hints for the ViewController to use via the setValidationLevel method on the AbstractViewController class.

Using ValidationRule keeps validation processing separate from the data model and ViewControllers. Rather than having each object maintain its own validation, you merely encode the correct validation rule for each object. As a result, each component is more generic, and far more reusable. A ValidationRule can use inheritance or delegation chaining. As a result, you can code each ValidationRule for simplicity and increased function.

ValidationRule has two primary methods: edit() and normalize(); each one is shown in the code samples below.

edit() maps an input string to a human-friendly formatted form:

//validate and redisplay
String value = textfield.getText(), result = null;
try { 
    result = socialSecurityVR.edit(value); 
}
catch (ValidationRuleException e) { 
    result = **** fix here ****;
}
textField.setText(result);

normalize() maps an input string to a minimal, computer-convenient form. The result of normalize is often used as a data-model value, or as a RequestEvent parameter sent on to a Destination:

//validate and update the data objects
String value = textfield.getText(), result = null;
try { 
    result = socialSecurityVR.normalize(value); 
}
catch (ValidationRuleException e) { 
    result = **** fix here ****;
}
dataObject.setText(result);

Here's an example of a rule execution:

edit("123456")          -> $1,234.56
normalize("$1,234.56")  -> +00123456 (as Decimal(8,2))
edit("12345x")          -> ValidationRuleException


ApplicationMediator

ApplicationMediator is an interface that defines the control logic of an application. ApplicationMediators can be nested (that is, implemented as hierarchical controllers). ApplicationMediators accept new data with the refresh() method, passing the data on to one or more contained ViewControllers or ApplicationMediators.

ApplicationMediator provides the following methods:

  • add/remove/fireEvent sends events to associated PlacementListeners, ViewListeners, RequestListeners, and TopListeners.

  • set/isEnabled accesses the ApplicationMediator's enabled state. Enabling an ApplicationMediator also enables any contained ViewControllers and ApplicationMediators.

  • setResources passes ResourceBundles to contained ViewControllers and ApplicationMediators.

Figure 4 shows the ApplicationMediator runtime:


Figure 4. The ApplicationMediator runtime
ApplicationMediator runtime

PlacementListener

Because users will often change their minds about how the desktop application should look, it is important to ensure that components on the GUI can be easily changed and moved. PlacementListener is an interface that manages the placement of ViewControllers on the screen.

In TCF, the following functions are separate, in that each one is handled by a different TCF component:

  • View creation (ViewController)
  • View ordering (ApplicationMediator)
  • View placement (PlacementListener)

Note that ApplicationMediators control the ordering of ViewControllers, but not their screen placement. The separation of functions increases the reusability of ViewControllers and ApplicationMediators, since they have no placement-related function of their own.

The PlacementListener runtime is shown in Figure 5:


Figure 5. The PlacementListener runtime
PlacementListener runtime

Here is an example usage of a PlacementListener, as typically contained (coded) in an ApplicationMediator:

PlacementEvent pe = 
  new PlacementEvent(this, customerVC, PlacementEvent.ADD);
firePlacementEvent(pe);

The following usage, typically contained in the main class of your application, shows an example placementEventPerformed method implementation from the PlacementListener interface.

public class MyApplication implements PlacementListener {
    public void placementEventPerformed(PlacementEvent pe) {
         switch (pe.getMajor()) {
             case PlacementEvent.ADD:
                 if (pe.getSource() instanceof AM1)
                    panel1.add( pe.getComponent(), BorderLayout.CENTER);
                 else
                    panel2.add(pe.getComponent()); 
                 break;
               :
         }
     }


TopListener

TopListener is an interface used to separate business- or environment-specific responsibilities from other TCF componentry. Again, the separation of responsibilities increases the reusability of TCF components. You use TopListeners when other services providers, such as RequestListeners and Destinations, aren't appropriate. TopListener functions could include:

  • Launching other applications
  • For Java applets, accessing the browser
  • Displaying system-level messages
  • Setting the GUI's title
  • Issuing security checks
  • Exiting the JVM

Figure 6 shows the TopListener runtime:


Figure 6. The TopListener runtime
TopListener runtime

And here's an example use of a TopListener (typically from the main class of your application):

ApplicationMediator am = new MyApplicationMediator();
am.addTopListener(this);

Or, in the ApplicationMediator:

TopEvent te = new TopEvent(this, TopEvent.STATUS, 0, "Loading files...");
fireTopEvent(te);

Later in the TopListener callback implementation of the TopEventListener interface:

public void topEventPerformed(TopEvent te) {
    switch(te.getMajor()) {
        case STATUS:
            browser.setMessage((String)te.getData());
            break;
        :
    }
}


Data model

In TCF, all data is passed as a parameter to some event type (that is, ViewEvent, RequestEvent, TopEvent, and PlacementEvent) as the data value (a Java object) or as the parameter of a callback method, refresh(). In general, data flows up, in events, toward the data processor, or down, via refresh, toward the event generator. During this flow the data object may be changed (typically modified or added to) by the components it flows through.

Figure 7 shows the data model runtime:


Figure 7. The data model runtime
Data Model runtime

Here's how a ViewController might respond to a refresh call using a java.util.Map data model:

public void refresh(Object data) {
    if (data != null) {
        refresh((Map)data);
    }
}

void refresh (Map data) {
    nameField.setText(data.get("CustomerName"));
    idField.setText(data.get("CustomerId"));
    repaint(); 
}

Alternate data models are easily supported, as shown here:

void refresh(Object data) {
    if (data instanceof Vector) {
        refresh((Vector)data);
    } else if (data instanceof Map) {
        refresh((Map)data);
    } else if (data instanceof DOM){
        refresh((DOM)data);
    } ... 
}

Typically, a ViewController would update any fields with the new data values and re-enable itself to accept new input (which would, in turn, cause it to generate ViewEvents).


Event processing

ApplicationMediators generate requests for service by creating and firing RequestEvents. RequestEvents are processed by implementers of the RequestListener interface. If a request cannot be processed, a RequestException is thrown.

Figure 8 illustrates a typical event-processing scenario:


Figure 8. An event-processing scenario
Diagram of an event-processing scenario

TCF provides built-in threading support. This can greatly simplify the implementation of ViewControllers, ApplicationMediators, and Destinations. Event dispatching is supported in the implementations of AbstractApplicationMediator, AbstractTransporter, and AbstractDestination. In general, asynchronous dispatching is required in only one of these implementations. AbstractTransporter is often the most convenient choice. Please note that synchronous processing is the default; to use asynchronous processing, you must enable it in the RequestEvent.

Figure 9 shows how event dispatching is supported in AbstractApplicationMediator; the other implementations are very similar.


Figure 9. The ApplicationMediator event dispatcher
The ApplicationMediator event dispatcher

Transporters and Destinations

TCF provides two implementations of the RequestListener interface: a Transporter and a Destination. The role of a Transporter is to select the appropriate Destination(s) to process a given request. The DefaultTransporter implementation provides this support. Any particular RequestEvent can be forwarded to one or several Destinations; multicasting is done in order of Destination registration and continues until all Destinations have processed the request or a RequestException is thrown.

Figure 10 shows the Transporter runtime:


Figure 10. Transporter runtime
Transporter runtime

Typically, Transporters map RequestEvents to RequestListeners, particularly Destinations. Here's an example Transporter setup:

Transporter t = new DefaultTransporter();

ApplicationMediator am = new MyApplicationMediator();
am.addRequestListener(t);

Here, the ApplicationMediator would fire RequestEvents to all RequestListeners (for example, the Transporter); for example:

String family = "Service", command = "execute";
RequestEvent re = new RequestEvent(this, family, command);
try { 
    fireRequestEvent(re); 
}
catch (RequestException e) {
  // **** process as needed ****
}

The Transporter would then forward the RequestEvent to any Destinations registered for that family of RequestEvent. Destinations are added to the Transporter as RequestListeners. They are added with a family value that defines the RequestEvents they are interested in receiving. For example:

EJBDestination ejbDestination = new EJBDestination();
Transporter t = new DefaultTransporter();
ApplicationMedaitor am = new MyApplicationMedaitor();
am.addRequestListener(t);
t.addRequestListener("Product", ejbDestination);

This approach allows you to multicast Destinations for a single RequestEvent. For example, in the code below, the RequestEvent will be sent to each listed Destination in the order indicated by its priority value.

Transporter t = new DefaultTransporter();
Destination d;

d = new WildDestination();      // low priority
t.addDestinationListener(Transporter.WILDCARD, d);

d = new EJBDestination();       // normal priority
t.addDestinationListener("Loans", d);

d = new PriorityDestination();  // high priority
t.addDestinationListener(Transporter.PRIORITY, d);

d = LoggerDestination();        // by position (often last)
t.addDestinationListener(Transporter.DONT_CARE, d);

Destinations

A Destination is representative of any service the TCF client needs, including services outside the client (such as network actions) or services local to the client (such as file-system access). A Destination can be simple or complex, and can be appropriate to a thin or a thick client.

Figure 11 shows the Destination runtime:


Figure 11. Destination runtime
Destination runtime

Destinations handle the following TCF functions:

  • Locate the server or data resources

  • Select network transport method, choosing among low-level protocols such as sockets, RMI, URLs, files, and so on

  • Select server type, choosing among APIs such as EJBs, servlets, Web servers, legacy, and so on

  • Select message format (that is, translate RequestEvents and data into server formats)

  • Process time-outs

  • Handle retries

  • Perform caching

  • Perform polling for sessions or printing

  • Perform logging (that is, save pre- and post-RequestEvents)

Extending TCF

Although it is intended to support the development of Java GUI-based applications, TCF is not limited to only this use. It has also been applied in Web-based applications. For example, it is quite practical to use the TCF back-end classes (from the Transporter onward) in server-side code. A servlet or JSP file can be coded to generate RequestEvents and fire them to the Transporter just as easily as an ApplicationMediator does. In fact, we know of an Apache Struts-based application where the ActionServlet is set up to fire RequestEvents (see Resources).

Another approach is to replace the Java-based GUI with an HTML-based one. To do this you create servlets or JSP files that fire ViewEvents in their doGet/doPost methods. These events are processed by ApplicationMediators as usual. The resulting information, provided by the refresh() method, controls the returned HTML generated to the servlet's or JSP file's out() stream.

If the ViewController servlets are designed to only create a small part of the full page, they can be composed into a larger page by use of a PlacementListener. For HTML this would be a servlet that builds a TABLE that includes, as table data cells, the results from the other ViewController servlets that compose the GUI. By careful design, it should be possible to build a TCF application that has both a Java- and an HTML-based user interface. In this case, you could completely reuse the code from the ApplicationMediator onward, so you would only have to custom code the ViewControllers and PlacementListeners.

Don't miss the rest of this series

Part 1, "The basic elements" (January 2003)

Conclusion

In this two-part series, we have provided a beginner's introduction to the Thin Client Framework architecture, design, and implementation. We have discussed the conceptual underpinnings of the framework, showed you how the TCF architecture is put together, and discussed the most relevant components of the framework in detail, from both a high-level and a programming perspective. We have also introduced a TCF implementation, Example04, to let you see for yourself the function and coding of the various components. In closing, we have suggested a number of ways TCF can be extended beyond its original intended use.


Resources

About the authors

Dr. Peter Bahrs is an IBM Distinguished Engineer and IT Architect in the IBM Software Services organization. Dr. Bahrs specializes in large e-business systems development in the financial services industries. He holds several patents and has spoken at industry conferences such as JavaOne. Dr. Bahrs is currently on assignment to IBM Zurich, Switzerland. He can be reached at tcfhelp@us.ibm.com.

author

Dr. Barry Feigenbaum is a member of the IBM Worldwide Accessibility Center, where he serves as a member of a team that helps IBM make its products accessible to people with disabilities. Dr. Feigenbaum has published several books and articles, holds several patents, and has spoken at industry conferences such as JavaOne. He serves as an Adjunct Assistant Professor of Computer Science at the University of Texas, Austin. You can contact Dr. Feigenbaum at feigenba@us.ibm.com.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10740
ArticleTitle=Introduction to Thin Client Framework, Part 2: The TCF programming model
publish-date=01072003
author1-email=tcfhelp@us.ibm.com
author1-email-cc=
author2-email=feigenba@us.ibm.com
author2-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers