CMSC 498B: Developing User Interfaces - Spring 2002Widget Toolkits | |||||||||
SwingJava's GUI toolkit. Learn about it from the Java Tutorial: The Swing Components from the Java Tutorial: Java Toolkits and ThreadsWhat 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(); } } What does this have to do with user interfaces?
=> 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: 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.
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 ManagementHow 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: 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); ... 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. 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} ); ... Look at Visual Studio.NET to see the GUI designer, and how these work in action |