Getting Java Swing

by Steven J. Owens (unless otherwise attributed)

There's a lot to Java Swing programming; unfortunately, a lot of Swing tutorials kind of throw you into the deep end. This is just a quick brain dump of some general points to help you "get" swing.

Another thing that a lot of swing tutorials do is drown you in details without ever giving you a chance to get to practical application. So at the end you'll find a simple, minimalist example Swing program. It simply pops up a JFrame that contains a JPanel that contains a JScrollPane that contains a JTable (see my comment about deep nesting, below).

To make really nice-looking interfaces takes some work, but it's doable. There's a Sun Java swing guy who has a blog, Romain Guy:

http://weblogs.java.net/blog/gfx/

Romain has done some some really, really cool looking stuff. Functional comes first, cool comes later, but it's important to know that it's possible.

To "get" Swing you need to get three things:

IDEs for GUI Creation

Various Java IDEs have WYSIWYG GUI creation tools. Some people swear by them, other people swear at them. These tools generally suffer from the same problem that WYSIWYG web design tools suffer from - the generated source is unmaintainable. Also, they're generally one-way - once you start customizing the code, forget about using the tool anymore.

Basics

A Swing GUI basically consists of three interlocking subsets; the visual components, the layout managers, and the listeners you use to hook them to the java classes that implement your functionality.

Every Swing tutorial starts off talking about MVC (Model/View/Controller) so I'll just hit it with a paragraph and get it out of the way. MVC a pretty common and generally useful approach for carving things up. Typically the View is the visual elements -- they load data from the Model and display it. When you click on a display button in the View, it calls a method to do something. That metod lives over in the Controller area. The Controller does some logic and updates the Model. And then the View notices the Model ahs changed and refreshes its data from the Model.

In this tutorial I'm lumping the controller and the model together as the business logic, but of course you're going to use good design and keep those cleanly separated, right?

Okay, so you have various specific components that provide different visual features. For example, JTables for displaying things in a table, JScrollPanes for displaying things in a scrolling pane - and you can put a JTable inside a JScrollPane to provide a scrolling table.

[There's a mess lurking here due to the original Java GUI API, AWT, and the "new" API, Swing, and "lightweight" components vs. "heavyweight" components, etc, but I'm just going to sidestep that; for now just focus on using Swing components that start with a "J" and research this heavy/light thing later.]

Layout managers are analogous to a web browser rendering HTML. Unfortunately, you can't use XML to code up your GUI (I have come across a few experimental Java GUI tools that use XML markup to generate the GUI, but AFAIK these aren't in mainstream use). They're generally much more complex and much harder to work with than HTML.

[I generally feel like they shouldn't be that hard, but then again, I haven't produced the ultimate Swing layout manager, so I guess it's too hard for me to get around to doing it.]

So, now you have windows popping up that contain components arranged in some layout. But you need those buttons to do things when the user clicks on them. The general scheme that Swing uses to do this is called "listeners". This is generally harder to learn than it should be, because it's really pretty straightforward. The general listener idea is that two sets of objects - the visual gui component objects and the business logic objects need to be kept in two cleanly separated groups, but also need to be connected in some fashion.

For example, in old-fashioned Visual Basic code they'd just cram the code into the individual buttons and such, and you'd end up with an unholy mess. So instead, you have the method on the button call a method on the logic code.

  • Now in Swing, you take it one step further; instead of simply
  • hard-coding the button's "I've-been-clickced" method to invoke the
  • logic code, Swing has the various Listener interfaces. These are java
  • interfaces, meaning they're predefined sets of methods that a class
  • implement. If life were simple, Swing would just have two interfaces
  • events). Unfortunately, life isn't simple.

    http://java.sun.com/docs/books/tutorial/uiswing/events/eventsandcomponents.html

    It's a bit more complex but in general you have several listener interfaces for different types of listeners. The listener interfaces follow a roughly similar pattern (methods that are called when a given event occurs, the parameter is an event object) and the components follow a similar pattern of having an "addFooListener(FooListener e)" method.

    What often makes this even more complicated is the Swing programming custom of using anonymous classes for the listeners. This is actually a very powerful and handy technique, but it tends to confuse people. In a nutshell, you often have several different listener events that can happen, that have to go to the same object. But the listener interfaces typically only allow for one method, for example actionPerformed(). So now what do you do?

    You could use a big if/else clause in the actionPerformed event to examine the event to figure out the source and call the right method. Kind of ugly. A cleaner approach is to insert an extra layer of adapters between the view and the controllers. This is where anonymous inner class listeners come in.

    An inner class is a class whose source code is defined inside the source code of another class; for example:

    public class Foo {
    	public class InnerFoo {
    	 	// some methods and instance variables
    	}
    }
    

    http://java.sun.com/docs/books/tutorial/uiswing/events/generalrules.html#innerClasses

    In general, inner classes seem to be for times when you really need more internal organization in a class than simply methods and instance variables. The chief reason for using inner classes in general is that the inner class has special access to the innards of its enclosing class. A classic example of such use is the various Iterator classes.

    An anonymous inner class is one that is defined on the fly, basically if you just need a very simple, minimally customized version of an existing class. For example, if you just need somewhere to stick an actionPerformed method. Here's a brute-force example excerpetd from a nice little tutorial over at JavaRanch:

    http://www.javaranch.com/campfire/StoryInner.jsp

    goButton.addActionListener
    (
        new ActionListener()
        {
            public void actionPerformed( ActionEvent e )
            {
                doImportantStuff();
            }
        }
    );
    

    Go check out the tutorial, I'll still be here when you get back. Probably.

    What's going on here is that the anonymous inner class can get at anything it needs to get at in the class, so it's just like you had a different actionPerformed method for each action event the class needs to handle. For example, to extend the above sample a bit, you could have a goButton and a stopButton:

    goButton.addActionListener
    (
        new ActionListener()
        {
            public void actionPerformed( ActionEvent e )
            {
                doImportantStuff();
            }
        }
    );
    stopButton.addActionListener
    (
        new ActionListener()
        {
            public void actionPerformed( ActionEvent e )
            {
                doOtherImportantStuff();
            }
        }
    );
    

    Incidentally, there's a parallel here with how MVC-based java web applications are properly written - only in the case of java web applications, the listener adapters are instead databeans that carry the form parameters into the business logic and out of the busines logic into the presentation layer.

    Layout Managers

    Re: swing threading, do as much as you can before setting the swing panel to be visible, and invokeAndWait and invokeLater are your friends.

    Get really comfortable with anonymous classes/threads, because you're going to use a lot of them.

    Here's a simple swing example program. All it does is display a jpanel that holds a jtable that lists all of the environment variables.

    package com.foo ;
    import java.util.Properties ;
    import java.util.Enumeration ;
    import java.util.Vector ;
    import java.awt.GridLayout ;
    import java.awt.Dimension ;
    import javax.swing.JPanel ;
    import javax.swing.JTable ;
    import javax.swing.table.TableModel ;
    import javax.swing.table.DefaultTableModel ;
    import javax.swing.SwingUtilities ;
    import javax.swing.JFrame ;
    import javax.swing.JScrollPane ;
    public class LaunchTest extends JPanel {
        public static void main(String[] args) {
            //Schedule a job for the event-dispatching thread:
            //creating and showing this application's GUI.
            javax.swing.SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    createAndShowGUI();
                }
            });
        }
        /**
         * Create the GUI and show it.  For thread safety,
         * this method should be invoked from the
         * event-dispatching thread.
         */
        private static void createAndShowGUI() {
            //Make sure we have nice window decorations.
            JFrame.setDefaultLookAndFeelDecorated(true);

    //Create and set up the window. JFrame frame = new JFrame("Launch Test"); frame.setDefaultCloseOperation(JFrame.EXITONCLOSE);

    //Create and set up the content pane. LaunchTest newContentPane = new LaunchTest(); newContentPane.setOpaque(true); //content panes must be opaque frame.setContentPane(newContentPane);

    //Display the window. frame.pack(); frame.setVisible(true); } int width = 800 ; int height = 600 ; JTable table; DefaultTableModel model ; public LaunchTest() { super(new GridLayout(1,0)); setSize(width, height) ;

    this.model = new DefaultTableModel() ; this.table = new JTable(model) ; table.setPreferredScrollableViewportSize(new Dimension(width, height)); // Disable auto resizing to make the table horizontal scrollable // table.setAutoResizeMode(JTable.AUTORESIZEOFF); populateModel() ; //Create the scroll pane and add the table to it. JScrollPane scrollPane = new JScrollPane(this.table); //Add the scroll pane to this panel. add(scrollPane); } public void populateModel() { this.model.addColumn("Property") ; this.model.addColumn("Value") ; Properties props = System.getProperties() ; Enumeration en = props.propertyNames() ; while (en.hasMoreElements()) { String name = (String)en.nextElement() ; String value = props.getProperty(name) ; addRow(name, value) ; // this.model.addRow(Object[]{name, value}) ; if (isPath(name)) { addPath(name, value) ; } } } public void addRow(String name, String value) { Vector v = new Vector(2) ; v.add(name) ; v.add(value) ; this.model.addRow(v) ; } public String separator = System.getProperty("path.separator") ; public boolean isPath(String name) { // return (-1 < name.indexOf(this.separator)) ; return name.endsWith(".path") ; } public void addPath(String name, String value) { String[] paths = value.split(this.separator) ; for (int i = 0; i < paths.length; i++) { addRow(name + "[" + i + "]", paths[i]) ; } } }

    Okay, so let's go through this and dissect it in detail. First off, this class is named LaunchTest, so it's in a file named LaunchTest.java. This is just a little swing demo that I cobbled together when I was experimenting with some installer and packaging tools for Java.

    package com.foo ;
    

    Okay, her we have the package statement. Just about every program in java has a package statement. In theory you don't absolutely need a package, but in practice you almost always need it, and even if you don't, you might as well get used to it.

    Just to save time, I'll address another issue related to packages. It sucks, but you basically must create a directory structure that mirrors the package structure, in this case I have a project directory; inside the project directory is a com directory, and inside that is a foo directory, in which we find LaunchTest.java.

    Now here's a gotcha: it's simplest and safest if you compile and run the class from the project directory. This is annoying as hell, and I guess most people just depend on their IDE to sort it out, but my IDE is emacs and the command-line tools. So to compile this and run it, I'd enter:

     
    /home/puff$ cd project
    /home/puff/project$ javac com/foo/LaunchTest.java
    /home/puff/project$ java com.foo.LaunchTest
    

    Okay, so moving on: next we have the imports:

    import java.util.Properties ;
    import java.util.Enumeration ;
    import java.util.Vector ;
    import java.awt.GridLayout ;
    import java.awt.Dimension ;
    import javax.swing.JPanel ;
    import javax.swing.JTable ;
    import javax.swing.table.TableModel ;
    import javax.swing.table.DefaultTableModel ;
    import javax.swing.SwingUtilities ;
    import javax.swing.JFrame ;
    import javax.swing.JScrollPane ;
    

    These imports list all of the classes we're going to be using, so the java compiler can sort out whether or not we're making any stupid mistakes. To do this, the java compiler needs to know what methods and arguments are available. To know that, it needs to know what class files to load.

    Next we have the main class declaration:

    public class LaunchTest extends JPanel {
    

    In this case, we're extending JPanel, because the LaunchTest is actually going to be a JPanel that we're going to insert into the JFrame that we display. Strictly speaking, we might want to create a distinct class to start this all, like LaunchTestStarter, to contain this next bit, the main() method that fires it all off. However, since this is a simple little example, we're going to put that main() method right in LaunchTest.

        public static void main(String[] args) {
            //Schedule a job for the event-dispatching thread:
            //creating and showing this application's GUI.
            javax.swing.SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    createAndShowGUI();
                }
            });
        }
    

    In java, everything happens with object instances. If you're not using object instances, you're not really doing java. However, something has to get the ball rolling.

    In a nutshell, in java, something sets up an interconnected web of objects - a bunch of objects that have references to each other. These objects exist inside a context of an even larger set of objects - the java environment, and specifically a framework of code, in this case Swing.

    The framework conveys input/output to the web of objects by invoking predefined methods (that's what makes it a framework - you define methods that fit the prescribed framework, and ba-da-bing, the framework can make things happen for you). Your program reacts to the input/output by invoking other objects, creating more instances, etc.

    But something needs to set up the dominos at the start, and that's where the main() method comes in. The main() method is static, which means that you can run the method directly, without needing a class instance.

    In this case, main() calls the SwingUtilities.invokeLater() method, passing in a runnable that in turn is encapsulating an anonymous class that has one job - to invoke LaunchTest's static createAndShowGUI() method.

    In this simple four lines we've opened up a whole ball of java wax, using both threads (runnables) and anonymous classes. You'll find that there's a lot of this sort of thing in Java.

        /**
         * Create the GUI and show it.  For thread safety,
         * this method should be invoked from the
         * event-dispatching thread.
         */
        private static void createAndShowGUI() {
            //Make sure we have nice window decorations.
            JFrame.setDefaultLookAndFeelDecorated(true);

    //Create and set up the window. JFrame frame = new JFrame("Launch Test"); frame.setDefaultCloseOperation(JFrame.EXITONCLOSE);

    //Create and set up the content pane. LaunchTest newContentPane = new LaunchTest(); newContentPane.setOpaque(true); //content panes must be opaque frame.setContentPane(newContentPane);

    //Display the window. frame.pack(); frame.setVisible(true); } int width = 800 ; int height = 600 ; JTable table; DefaultTableModel model ; public LaunchTest() { super(new GridLayout(1,0)); setSize(width, height) ;

    this.model = new DefaultTableModel() ; this.table = new JTable(model) ; table.setPreferredScrollableViewportSize(new Dimension(width, height)); // Disable auto resizing to make the table horizontal scrollable // table.setAutoResizeMode(JTable.AUTORESIZEOFF); populateModel() ; //Create the scroll pane and add the table to it. JScrollPane scrollPane = new JScrollPane(this.table); //Add the scroll pane to this panel. add(scrollPane); } public void populateModel() { this.model.addColumn("Property") ; this.model.addColumn("Value") ; Properties props = System.getProperties() ; Enumeration en = props.propertyNames() ; while (en.hasMoreElements()) { String name = (String)en.nextElement() ; String value = props.getProperty(name) ; addRow(name, value) ; // this.model.addRow(Object[]{name, value}) ; if (isPath(name)) { addPath(name, value) ; } } } public void addRow(String name, String value) { Vector v = new Vector(2) ; v.add(name) ; v.add(value) ; this.model.addRow(v) ; } public String separator = System.getProperty("path.separator") ; public boolean isPath(String name) { // return (-1 < name.indexOf(this.separator)) ; return name.endsWith(".path") ; } public void addPath(String name, String value) { String[] paths = value.split(this.separator) ; for (int i = 0; i < paths.length; i++) { addRow(name + "[" + i + "]", paths[i]) ; } } }


    See original (unformatted) article

    Feedback

    Verification Image:
    Subject:
    Your Email Address:
    Confirm Address:
    Please Post:
    Copyright: By checking the "Please Post" checkbox you agree to having your feedback posted on notablog if the administrator decides it is appropriate content, and grant compilation copyright rights to the administrator.
    Message Content: