CMSC 498B: Developing User Interfaces - Spring 2002

Widget Toolkits

Swing

Java's GUI toolkit.  Learn about it from the Java Tutorial:
http://java.sun.com/docs/books/tutorial/uiswing/index.html

The Swing Components from the Java Tutorial:
http://java.sun.com/docs/books/tutorial/uiswing/components/components.html

Java Toolkits and Threads

What are threads?  Conceptual simultaneous execution paths through your code.  Might be run through alternation or actually simultaneously (if on a multi-processor machine).

/*
 * A Java program that demonstrates simple use of threads
 *
 * Ben Bederson, February 20, 2002
 */

public class ThreadDemo implements Runnable {
    int value;

    public ThreadDemo(int value) {
	this.value = value;
    }

    public void run() {
	do {
	    System.out.println("value = " + value);
	    try {
		Thread.sleep(200);
	    } catch (InterruptedException e) {}
	} while (true);
    }

    static public void main(String[] args) {
	new Thread(new ThreadDemo(1)).start();
	new Thread(new ThreadDemo(2)).start();
    }
}

Source: ThreadDemo.java

What does this have to do with user interfaces?

  • All interface events are generated within a single special thread created by Swing.
  • Swing is not "thread safe"  (what does this mean?)
  • The event dispatch thread is not the same as the main thread

=> You are not allowed to manipulate Swing objects outside the Swing thread

=> Your own code needs to be thread safe if you want to modify your model outside of the Swing thread, but access it within the Swing thread.

=> From another thread, you can access Swing's event dispatch thread with SwingUtilities.invokeLater() and SwingUtiltiies.invokeAndWait()

public static void main(String[] args) {
    JFrame f = new JFrame(...);
    //Add components to the frame here...
    f.pack();
    f.setVisible(true);
    //Don't do any more GUI work here.
}

How to have slow event-driven operations?  What if they need to update the display?

public void mousePressed(MouseEvent e) {
    new Thread(new SlowProcess()).start();
}

...

Class SlowProcess implements Runnable {
    public run() {
        do {
            // do stuff
            updateProgressBar();
        }
    }

    public updateProgressBar() {
        SwingUtilities.invokeLater(new Runnable() {
            public run() {
                progressBar.setValue(...);
            }
        }
    }
}

Or, what if the GUI needs to be updated as a result of a non-GUI generated event (like network traffic)?

How is invokeLater implemented?

Learn more about Java and Threads from the Java Tutorial:
http://java.sun.com/docs/books/tutorial/uiswing/overview/threads.html

C# Toolkits and Threads (from the QuickStart guide)

Windows Forms controls can only execute on the thread on which they were created, that is, they are not thread-safe. If you want to get or set properties, or call methods, on a control from a background thread, the call must be marshaled to the thread that created the control.

There are five functions on a control that are safe to call from any thread: InvokeRequired, Invoke, BeginInvoke, EndInvoke and CreateGraphics. For all other method calls, you should use one of the invoke methods.

By default, Windows marshals the calls for you. However, if you are making multiple calls to a control, it is much more efficient to create a method that executes those calls and make the one cross-thread call yourself. You make the cross-thread call by calling one of the Control.Invoke methods. The Invoke methods take a reference to a delegate. Typically, this delegate is an instance of the MethodInvoker delegate.

InvokeRequired public bool InvokeRequired { get ; }

Returns true if the caller must call Invoke when making method calls to this control.

BeginInvoke public IAsyncResult BeginInvoke(Delegate method)
public IAsyncResult BeginInvoke(Delegate method, Object[] args)

Executes the given delegate on the thread that owns this Control's underlying window handle. The delegate is called asynchronously and this method returns immediately. You can call this from any thread, even the thread that owns the control's handle. If the control's handle does not exist yet, this will follow up the control's parent chain until it finds a control or form that does have a window handle. If no appropriate handle can be found, BeginInvoke will throw an exception. Exceptions within the delegate method are considered untrapped and will be sent to the application's untrapped exception handler.

EndInvoke public Object EndInvoke(IAsyncResult asyncResult)
 

Retrieves the return value of the asynchronous operation represented by the IAsyncResult interface passed. If the async operation has not been completed, this function will block until the result is available.

 

Invoke public Object Invoke(Delegate method)
public Object Invoke(Delegate method, Object[] args)
 

Executes the given delegate on the thread that owns this control's underlying window handle. The delegate is called synchronously and this method returns once the invoked method has returned. The return value is the result of the invoked method. It is an error to call this on the same thread that the control belongs to.

The following example demonstrates how to create a background thread that uses a MethodInvoker to update a ProgressBar control at regular intervals:

 ....

 // Start the background thread
 timerThread = new Thread(new ThreadStart(ThreadProc));
 timerThread.IsBackground = true;
 timerThread.Start();

 ....

 //This function is executed on a background thread - it marshalls calls to
 //update the UI back to the foreground thread
 public void ThreadProc() {

     try {
         MethodInvoker mi = new MethodInvoker(this.UpdateProgress);
         while (true) {

             //Call BeginInvoke on the Form
             this.BeginInvoke(mi);
             Thread.Sleep(500) ;
         }
     }
     //Thrown when the thread is interupted by the main thread - exiting the loop
     catch (ThreadInterruptedException e) {
         //Simply exit....
     }
     catch (Exception we) {
     }
 }

 ....

 //This function is called from the background thread
 private void UpdateProgress() {

     //Reset to start if required
     if (progressBar1.Value == progressBar1.Maximum) {
         progressBar1.Value = progressBar1.Minimum ;
     }


     //Reset to start if required
     progressBar1.PerformStep() ;
 }

 ....

 //Make sure we clean up the background thread in Dispose
 public override void Dispose() {
    if (timerThread != null) {
        timerThread.Interrupt();
        timerThread = null;
    }

    base.Dispose();
}

Layout Management

How do you specify where widgets go, and what size they are?

One option: Manually specify size and position:

jComponent.setBounds();
jComponent.setSize();
jComponent.setLocation();

What is wrong with this approach?

Java offers a general architecture that offers different layout approaches:

jComponent.setLayout(LayoutManager);

The layout manager manages the size and position of the component's children. 

Whenever a child is added, removed, modified, or if the bounds of the parent is modified, the layout manager lazily relays out the children according to the algorithm encapsulated in the LayoutManger (see the layoutContainer() method).

Learn more from the Java Tutorial:
http://java.sun.com/docs/books/tutorial/uiswing/overview/layout.html

Example - a simple web browser layout with a URL box, a go button, and a content area.

JPanel panel = new JPanel();
...
textField = new JTextField();
goButton = new JButton("Go");
editorPane = new JEditorPane();

JPanel northPane = new JPanel();
northPane.setLayout(new BorderLayout());
northPane.add(textField, BorderLayout.CENTER);
northPane.add(goButton, BorderLayout.EAST);

panel.setLayout(new BorderLayout());
panel.add(northPane, BorderLayout.NORTH);
panel.add(new JScrollPane(editorPane), BorderLayout.CENTER);
...

SimpleLayout.java

Approaches to layout:

Layout Managers tend to be difficult to program and to predict.  How can we get most of what we want without all the complexity?

Most interface have very simple resize and modification requirements.

.NET introduces a very simple approach which seems to handle most needs very simply.
See these two properties:

Control.Anchor;
Control.Dock;

Anchoring specifies that an edge of a widget stays in the same location (in pixels) relative to its parent.  Anchoring opposite edges (left-right or bottom-top) results in stretching the widget.

Docking makes an element stick to an edge of a parent - filling up that entire edge.

The same example - a simple web browser layout with a URL box, a go button, and a content area.

...
 	goButton = new Button();
	goButton.Text = "Go";
	goButton.Location = new Point(ClientRectangle.Width - goButton.Width, 0);
	goButton.Anchor = AnchorStyles.Top | AnchorStyles.Right;

	urlBox = new TextBox();
	urlBox.Bounds = new Rectangle(0, 0, 
				      ClientRectangle.Width - goButton.Width, 
				      goButton.Height);
	urlBox.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right;

	html = new TextBox();
	html.Multiline = true;
	html.ScrollBars = ScrollBars.Both;
	html.WordWrap = false;
	html.Bounds = new Rectangle(0, 
				    goButton.Height, 
				    ClientRectangle.Width, 
				    ClientRectangle.Height - goButton.Height);
	html.Anchor = AnchorStyles.Top | AnchorStyles.Bottom 
	    | AnchorStyles.Left | AnchorStyles.Right;

	Controls.AddRange(new Control[] {goButton, urlBox, html} );
...

simple-layout.cs

Look at Visual Studio.NET to see the GUI designer, and how these work in action