Build user interfaces for object-oriented systems, Part 2: The visual-proxy architecture

A scalable architecture for building object-oriented user interfaces

1 2 3 4 5 6 7 Page 5
Page 5 of 7

/ ****************************************

Add to the current form a field that represents an attribute of some class.

/

055   public final void add_field(String class_name, String attribute, Rectangle location)
056     {   fields.add( new Element(class_name, attribute, location ) );
057         update_preferred_size( location );
058         location.translate( INSET, INSET );
059     }
060 

/ ****************************************

Add an invariant field to the current form.

/

061    public final void add_field( JComponent item, Rectangle location)
061      {   fields.add( new Element( item, location ) );
061          update_preferred_size( location );
061          location.translate( INSET, INSET );
061      }
066  

/ ****************************************

Populate the form with elements described in UNICODE. Not yet implemented.

/

067    public void load( InputStream source )
068      {   throw new UnsupportedOperationException(
069                              "Form.load() not yet supported" );
070      }
071  

/ ****************************************

Flush a description of the form to the Writer in the format discussed in the description of the load method. Not yet implemented.

/

072    public void store( OutputStream destination )
073      {   throw new UnsupportedOperationException(
074                              "Form.store() not yet supported" );
075      }
076  

/ ****************************************

Attach some model-level object to the current form. When made visible, the form will display the attributes of that object that are specified by some Element of the form. The form will maintain references to all the proxies provided by the "thing" as long as the form is displayed, but these proxies are released automatically when the form is shut down. You'll have to reattach objects if you want to display the form again. Note that you can attach objects while the form is visible, but you should send it an invalidate() message after doing so for the proxies of the newly attached objects to become visible.

/

077    public final void attach( User_interface thing )
078      {   Assert.is_true( thing != null, "thing==null" );
079          D.ebug( "Attaching " + thing.getClass().getName() + "to Form" );
080  
081          for(Iterator i = fields.iterator(); i.hasNext() ;)
082              ((Element) i.next() ).attach(thing);
083      }
084  

/ ****************************************

Attach some set of model-level objects to the current form. When made visible, the form will display the attributes of that object.

@param things.

An array of things to display. Objects in the array7 that don't implement

User_interface

are silently ignored.

If more than one object in the array is an instance of a given class, only the last such object (the one at the higher index) is displayed. Proxies will be requested from all objects of a given class, however.

/

085    public final void attach( Object[] things )
086      {   Assert.is_true( things != null, "things==null" );
087  
088          for( int i = 0; i < things.length; ++i )
089          {   if( things[i]!=null && (things[i] instanceof User_interface))
090                  this.attach((User_interface) things[i]);
091          }
092      }
093  

/ ****************************************

Attach some set of model-level objects to the current form. When made visible, the form will display the attributes of that object.

@param things

an iterator, initialized to traverse some

Collection

or

Map

that contains objects that implement

User_interface

.

If more than one object in the collection is an instance of a given class, only the last such object is displayed. Proxies will be requested from all objects of a given class, however.

/

094    public final void attach( Iterator things )
095      {   Assert.is_true( things != null, "things==null" );   
096  
097          while( things.hasNext() )
098          {   Object  abstraction = things.next();
099              if( abstraction instanceof User_interface )
100                this.attach((User_interface)abstraction);
101        }
102    }
103
/ ****************************************
Called by system when the form is invalidated. Actually lays out the elements and makes them visible
/
104    public final void doLayout()
105      {   
106          D.ebug("Laying out container");
107          for( Iterator i = fields.iterator(); i.hasNext(); )
108              ((Element) i.next() ).activate();
109      }
110  

/****************************************

Because a

Form

object does its own layout, it's inappropriate for you to add a layout manager. (Though you could argue reasonably that the

Form

should

be

a layout manager, similar to a

GridBag

, that is passed

Element

objects (similar to

GridBagConstraints

) that's bound to a displayable object.)

This method does nothing but throw an UnsupportedOperationException

/

111    public final void setLayout(LayoutManager manager)
112      {   throw new UnsupportedOperationException(
113                                  "May not set a layout manager in a Form");
114      }
115  

/****************************************

A convenience method that wraps a wait request. The equivalentcode is:
    Form form;
    //...
    try{ synchronized(form){form.wait();} }
    catch(InterruptedException e){}

/

116    synchronized public final void wait_for_close()
117      {   try{ wait(); }catch(InterruptedException e){/*ignore*/}
118      }
119  
/****************************************
Release any waiting threads.
/
120    synchronized public final void release()
121      {   notifyAll();
122      }
123  
/****************************************
A single element (field) in a form.
/
124    private final class Element
125      {
126        private final Rectangle     location;
127        private final String        class_name;
128        private final String        attribute;
129        private final boolean       is_invariant;
130        private       JComponent    proxy;
131  
/****************************************

Create a single element that represents some attribute of some class in the logical model.

@param location the location and size of the UI exposed by the proxy for this element.

@param the class that contains the attribute to be displayed

@param the

attribute to display.

/
132        public Element(String class_name, String attribute, Rectangle location)
133          {   this.location     = location;
134              this.class_name   = class_name;
135              this.attribute    = attribute;
136              this.is_invariant = false;
137              this.proxy        = null;
138  
139              Assert.is_true( class_name != null, "class_name==null" );
140              Assert.is_true( attribute  != null, "attribute==null" );
141              Assert.is_true( location   != null, "location==null" );
142          }
143  
/****************************************

Create an "invariant" element, one that represents an attribute of the form itself. Typically these elements will be icons, labels, and other graphical things that appear on every form, but which don't change from form to form.

@param location the location and size of the UI exposed by the proxy for this element.

@param invariant

the component to display at that location.

/
144        public Element(JComponent proxy, Rectangle location)
145          {   this.location     = location;
146              this.class_name   = "(Form)";
147              this.attribute    = "invariant";
148              this.is_invariant = true;
149              this.proxy        = proxy;
150  
151              Assert.is_true( proxy    != null, "proxy==null"     );
152              Assert.is_true( location != null, "location==null"  );
153  
154              Form.this.add(proxy);
155          }
156  
/****************************************

Attach a specific object to this form. The attached object will replace any object previously attached to the same form. Generally, all objects that the form represents must be attached before the form itself is displayed. This message is passed only from a form to one of its elements, thus is private.

@param thing

The thing to display. If the thing's class is not the class specified in the original constructor, then this method silently ignores the request.

/
157        private void attach( User_interface thing )
158          {   Assert.is_true( thing != null, "thing==null" );
159              D.ebug(   "Attaching "          + thing.getClass().getName()
160                      + " to element for "    + class_name +":"+ attribute 
161                      + ( !is_invariant ? ""
162                                        : (" (invariant " 
163                                              + proxy.getClass().getName()
164                                              +")"
165                                          )
166                        )
167                    );
168  
169              if( !is_invariant 
170                      && thing.getClass().getName().equals(class_name) )
171              {
172                  if( proxy != null ) 
173                      Form.this.remove(proxy);
174  
175                  proxy = thing.visual_proxy( attribute );
176  
177                  if( proxy != null )
178                      Form.this.add(proxy);
179              }
180          }
181  
/****************************************

Passed from the form to an element when the form wants to activate the element. Generally causes a UI to appear. This message is passed only from a form to one of its elements, thus is private.

@param here

The Form on which the element is to be displayed

/
182        private void activate()
183          {   
184              if( proxy==null || location==null )
185                  throw new RuntimeException( "No proxy for " +
186                                              class_name +":"+ attribute );
187              proxy.setBounds(location);
188              proxy.setVisible(true);
189          }
190      }
191  
192  }

Asserts and debug diagnostics

To fill in the remaining loose ends, I've left the assert statements and debugging diagnostics in the code, so you can see how I do such things. Asserts are implemented with two classes, both called Assert.java. (See Listings 5 and 6.) One is in the com.holub.tools package and does nothing. The other is in the com.holub.tools.debug package and throws an exception if the assertion fails. You can choose which one you want to use by putting one of the following lines at the top of your source file:

 import
com.holub.tools.debug.Assert;    // debugging version // import
com.holub.tools.debug.Assert; // release version 

Note that the HotSpot VM will optimize the methods of the non-debugging versions of these classes entirely out of existence. That is, HotSpot will notice that the methods are empty and that the arguments have no side effects, so it will remove the calls from the final code.


Listing 5: Assert.java
000  package com.holub.tools.debug;
001  
002  public class Assert
003  {
004    public final static void is_true( boolean expression )
005      {   if( !expression )
006              throw( new Assert.Failed() );   
007      }
008  
009    public final static void is_false( boolean expression )
010      {   if( expression )
011              throw( new Assert.Failed() );   
012      }
013  
014    public final static void is_true( boolean expression, String message)
015      {   if( !expression )
016              throw( new Assert.Failed(message) );    
017      }
018  
019    public final static void is_false( boolean expression, String message)
020      {   if( expression )
021              throw( new Assert.Failed(message) );    
022      }
023  
024    static public class Failed extends RuntimeException
025    {   public Failed(            ){ super("Assert Failed"); }
026        public Failed( String msg ){ super(msg);             }
027      }
028  }   

Listing 6: Assert.java
001  package com.holub.tools;
002  
003  public class Assert
004  {
005    public final static void is_true( boolean expression )
006      {}
007  
008    public final static void is_false( boolean expression )
009      {}
010  
011    public final static void is_true( boolean expression, String message)
012      {}
013  
014    public final static void is_false( boolean expression, String message)
015      {}
016  
017    static public class Failed extends RuntimeException
018    {   public Failed(            ){ super("Assert Failed"); }
019        public Failed( String msg ){ super(msg);             }
020      }
021  }

Debug diagnostics are handled in much the same way. (See Listings 7 and 8.) The line

 
D.ebug("Hello"); 

prints the string only if the debugging version (in com.holub.tools.debug) has been imported.

Listing 7: D.java
001  package com.holub.tools.debug;
002  
003  import com.holub.tools.Std;
004  
005  public class D
006  {   static private boolean enabled = true;
007  
008    public static final void ebug_enable (){ enabled = true;  }
009    public static final void ebug_disable(){ enabled = false; }
010  
011    public static final void ebug( String text )
012      {   if( enabled )
013              Std.err().println( text );
014      }
015  }   

Listing 8: D.java
001  package com.holub.tools;
002  
003  public class D
004  {
005    public static final void ebug_enable () {}
006    public static final void ebug_disable() {}
007    public static final void ebug    ( String text ) {}
008  }   

Related:
1 2 3 4 5 6 7 Page 5
Page 5 of 7