Skip navigation
1 2 3 Previous Next

alexfromsun

37 posts

Swing makes it very easy to control and modify simple components like JLabel or JButton. It gets trickier when you customize a compound component like JTree or JTable. Unlike a simple component, JTable consists of multiple subcomponents like table header, renderer and editor, so actually JTable is not a single component but a container with several descendants.

Imagine that you want to customize a JLabel to change its border when you move the mouse cursor over it. You just add a mouse listener and switch the border there back and forth. Now let's make the task a bit more challenging - you want make the same trick with a JPanel which contains a JButton inside.

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class JPanelTest extends JFrame { 
    public JPanelTest() {
        super("JPanel test");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final JPanel panel = new JPanel();
        add(panel);
        
        panel.addMouseListener(new MouseAdapter() {
            public void mouseEntered(MouseEvent e) {
                panel.setBorder(
                BorderFactory.createLineBorder(Color.GREEN, 5));
            }

            public void mouseExited(MouseEvent e) {
                panel.setBorder(null);
            }
        });
        
        panel.setLayout(new GridLayout(0, 2));
        panel.add(new JButton("JButton"));
        
        setSize(300, 300);
        setLocationRelativeTo(null);
    }

    public static void main(String... args) throws Exception {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new JPanelTest().setVisible(true);
            }
        });
    }
}

 Running this test you will see that when the mouse cursor enters the child button the parent panel receives the mouseExited event and the border is gone.

http://swinghelper.java.net/bin/blog/listeners/border.png http://swinghelper.java.net/bin/blog/listeners/noborder.png

To fix it you should add the same listener to the button and to all of the possible panel's children recursively.
It may sound as a minor problem, but it gets complicated if the hierarachy is changing. In this case you should add this listener to the newly added component and remove it accordingly.

By the way, adding a mouseListener may considerably change the component's behaviour as I illustrated in my very first entry. So implementing a simple animation for a compound component is quite a challenging task in Swing.

Missing API

Compound components is a key part of any GUI toolkit so it is important to support them in a better way. In Swing I am missing a simple solution which would give me a chance to listen for the input events not for a single component but for a component and all its subcomponents. JLayer component added in JDK 7, can catch all the events from its children, but this is a topic for another entry.

This was an entry from the Swing in a better world series

Thanks
alexp

In the list of the things that needs improving in Swing, the implementation of listeners takes the first place. The problem is the fact that the order in which listeners are notified is not specified  and it is not even guaranteed that your listeners will be notified after the Swing system listeners.  Actually all listeners can be mixed together with any possible combination and it leads to two main problems:

Examining the problem

 Let's write some code and check if a JButton is in the pressed state when MouseListener.mousePressed() is notified:

import javax.swing.*;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class ListenersOrder extends JFrame {

    public ListenersOrder() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new FlowLayout());

        final JButton button = new JButton("JButton");
        
        button.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                System.out.println("button.getModel().isPressed() = "
                        + button.getModel().isPressed());
            }
        });

        add(button);
        pack();
    }

    public static void main(String... args) throws Exception {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new ListenersOrder().setVisible(true);
            }
        });
    }
}

When you press the button you can see that it is in the pressed state, as we may expect.
button.getModel().isPressed() = true
Let's add some more code and change the system Look and Feel after the button is created:

import javax.swing.*;
import java.awt.FlowLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class ListenersOrder extends JFrame {

    public ListenersOrder() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new FlowLayout());

        final JButton button = new JButton("JButton");
        
        button.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                System.out.println("button.getModel().isPressed() = "
                        + button.getModel().isPressed());
            }
        });

        add(button);
        pack();
        
        changeLaF();
    }

    private void changeLaF() {
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        // we don't forget to update all the components
        SwingUtilities.updateComponentTreeUI(this);
    }
    
    public static void main(String... args) throws Exception {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new ListenersOrder().setVisible(true);
            }
        });
    }
}

This simple update changes the mouse listener's order and now, surprisingly, when you press the button you'll see:
button.getModel().isPressed() = false

How to explain it?

Let's examine the  mouse listeners' added to the button, corresponding to every relevant line:

                                                                                                                                                                                                               
Line of codeMouseListener added to the button
JButton button = new JButton("JButton");Listener from MetalLaF
button.addMouseListener(new MouseAdapter() {});user's listener
changeLaF();Listener from MetalLaF
changeLaF();Listener from NimbusLaF

So you can see that in the first test the ui listener sets the pressed flag before the user's listener, when in the second test the listener's order is reversed. 
It means that the actual state of the button seeing from a user's mouseListener is unspecified and you shouldn't rely on any particular order.

Modal dialogs

Showing a modal dialog from a listener is the best way to observe your component in a transition state. If you run the following test and click an unselected list's item the modal dialog will come up and you'll be able to see the two selected items for a list with the SINGLE_SELECTION mode.

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.FlowLayout;

public class ModalityTest extends JFrame {

    public ModalityTest() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new FlowLayout());

        String[] data = {"One", "Two", "Three", "Four"};
        JList list = new JList(data);
        list.setSelectionMode(
                ListSelectionModel.SINGLE_SELECTION);
        list.setSelectedIndex(0);

        list.addListSelectionListener(
                new ListSelectionListener() {
                    public void valueChanged(ListSelectionEvent e) {
                        
                        // Showing a modal dialog blocks the listener's notification
                        JOptionPane.showConfirmDialog(null,
                                "Do you see two selected lines ?");
                    }
                });

        add(list);
        setSize(200, 300);
    }

    public static void main(String... args) throws Exception {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new ModalityTest().setVisible(true);
            }
        });
    }
}

 

http://swinghelper.java.net/bin/blog/modality/modality.png

 

This example shows why you shoudn't stop the listener's notification somewhere in the middle with a modal dialog and should always show it after all the listeners are notified. The recommended pattern is to always use SwingUtilities.invokeLater() when you need to show a modal dialog from your listener:

list.addListSelectionListener(
                new ListSelectionListener() {
                    public void valueChanged(ListSelectionEvent e) {

                        SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                                // Showing a modal dialog with invokeLater 
                                // doesn't leave the GUI in a transition state 
                                JOptionPane.showConfirmDialog(null,
                                        "Do you see two selected lines ?");
                            }
                        });
                    }
                });

How should it have been implemented?

     We seriously thought about improving the listeners notification several years ago but after heated disputes we didn't come up to one proposal.  I offered to mark the Swing UI listeners with the existing UIResource interface and gurantee their notification before the user's listeners.   The other propsal was to add a conception of listeners "weight" which will control the order in which a listener will be notified.  So you would use the new add*Listener() methods with the second parameter - button.addMouseListener(listener, weight);

As a matter of fact the multiple listeners approach brings more problems than solutions. It would have been better to make the users override methods like processMouseEvent and automatically notify the same kind of method in the ComponentUI.

This was an entry from the Swing in a better world series 
Thanks alexp

Checked exceptions are painful. I could write a long article about it, but there are more than enough good blogs describing this problem.
My favorite article is written by Rod Waldhoff.

From my point of view the existence of the InvocationTargetException clearly shows the problem.
Imagine you call a method which throws an exception, you properly catches it
and then you decide to rewrite the code and call the same method via refection.
After that you have to catch the same exception in quite a different manner: 

public class Test {

    public static void main(String[] args) {
        PrinterJob job = PrinterJob.getPrinterJob();
        
        // Call it in the usual way
        
        try {
            // this throws checked PrinterException
            job.print();
        } catch (PrinterException e) {
            // handle the printer exception
        }

        // Call the same method via reflection

        try {
            // this throws InvocationTargetException instead
            getPrintMethod().invoke(job);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } 
        
        catch (InvocationTargetException e) {
            if(e.getCause() instanceof PrinterException) {
                // handle the printer exception;
                // a little bit more complicated than it used to be
            }
        }
    }

    static Method getPrintMethod() {
        try {
            return Class.forName("PrinterJob").getDeclaredMethod("print");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
I would love it if Java doesn't require me to catch the InvocationTargetException and let me work with the PrinterException the same way.

Let me give you some examples that illustrate what kind of mess it brings to the Swing code base.
Fortunately there are not many checked exceptions used in Swing code, but when you implement printing you can't avoid PrinterException and PrinterAbortException.
Since printing must be done in the background to not block the GUI,
we have to properly re-throw the exceptions back to the Event Dispatching Thread thread.

So in the implementation of the JTable.print() method you can find the following construction: 

       // this runnable will be used to do the printing
        // (and save any throwables) on another thread
        Runnable runnable = new Runnable() {
            public void run() {
                try {
                    // do the printing
                    job.print(copyAttr);
                } catch (Throwable t) {
                    // save any Throwable to be rethrown
                    synchronized(lock) {
                        printError = t;
                    }
                } finally {
                    // we're finished - hide the dialog
                    printingStatus.dispose();
                }
            }
        };
and then we have to understand what kind of exception was thrown and how we can re-throw it back: 

       // check the type of error and handle it
        if (pe != null) {
            // a subclass of PrinterException meaning the job was aborted,
            // in this case, by the user
            if (pe instanceof PrinterAbortException) {
                return false;
            } else if (pe instanceof PrinterException) {
                throw (PrinterException)pe;
            } else if (pe instanceof RuntimeException) {
                throw (RuntimeException)pe;
            } else if (pe instanceof Error) {
                throw (Error)pe;
            }

            // can not happen
            throw new AssertionError(pe);
        }
This boilerplate code can't be avoided, you can find the same pattern in the implementation of JTable.print().
If the methods that are called on the background thread threw another checked exceptions, we would have to add another instanceof checks there.
More checked exceptions - more lines of code.

If there were no checked exceptions in java, that kind of code would be unnecessary,
we could freely re-throw any exception no matter what their type is.
I would definitely remove the checked exceptions from Java.

This was an entry from the Swing in a better world series

Thanks
alexp

We have all read the "Effective Java" book and know that we should prefer interfaces over abstract classes.
This is a known and respected pattern which should be used wherever possible.

However the years in the JDK team tauhgt me not to blindly trust
to good practices from the world of application programming.

A distinctive feature of the JDK is backward compatibility.
All programms written in public JDK API must compile and run
with every next JDK versions. It makes it really different.

Comptibility is good for Java programmers but not so good for JDK developers,
java interfaces is a part of a problem.

If you want to add an interface to the JDK API you have to be sure
that you know in advance all the methods that are to be included there.
You cannot make any mistakes because it will be impossible to add a new method
to an interface without breaking the user's code.

In every JDK release we add new methods to some of the existing classes.
Let's have a look what happens when it turned out that we need to expand an interface.

The LayoutManager interface is widely used across Swing/AWT toolkit.
This interface contains 5 methods and that was enough at the beginning.
Later on it was discovered that a complex layout needs more methods there,
but it was decided to intrdoduce a new interface,
because nobody wants to implement too many empty methods for a simple layout.

So here comes the LayoutManager2 interface
and now the layout related methods from java.awt.Container look like this:

public float getAlignmentX() {
        float xAlign;
        if (layoutMgr instanceof LayoutManager2) {
            synchronized (getTreeLock()) {
                LayoutManager2 lm = (LayoutManager2) layoutMgr;
                xAlign = lm.getLayoutAlignmentX(this);
            }
        } else {
            xAlign = super.getAlignmentX();
        }
        return xAlign;
    }

 However the layout setter and getter still work with the old LayoutManger type: 

public void setLayout(LayoutManager mgr) {
        layoutMgr = mgr;
        invalidateIfValid();
    }

so you have to read the spec to know that the new LayoutManager2 exists.

If it happens that more methods should be added in a next release,
the LayoutManager3 will appear, adding more mess to the Swing API.

So now you can see why I don't like java interfaces as much as I should do.

 If I created Java...


Discussing the features of a programming language is very popular among programmers,
everybody knows how to make it in a right way, here is my try:

The interfaces in java are too restrictive and I wonder if they could be implemented more friendly for programmers.
I am dreaming about Java that allows implementing an interface without being made to implement every single method of it.

Look at the KeyAdapter class, we have it only because it is too inconvenient to implement every 3 methods
when you need only one of them. I am sure that in a perfect java there is no need in classes
like MouseAdapter, HierarchyBoundsAdapter and so on.

You don't want to type empty methods for a KeyListener?
You should use KeyAdapter.

don't want to type empty methods for a MouseListener?
Then you should use MouseAdapter.

But what if I want my class to implement KeyListener *and* MouseListener and I don't need most of the methods from them?
The only solution is to manually generate the empty stubs (thanks to my IDE which can do it automatically)

In the java of my dreams the compiler can generate empty stubs for a method from an interface your class is implementing,
so you can override only those methods that you really need.

The "empty" methods from a super interface have empty bodies and return null for reference types and zero for numbers.

Sometime it is desirable to remind a developer that a method just needs to be implemented
and abstract methods in Java classes ensure it. Currently all methods in an interface are considered abstract
no matter if they have the "abstract" keyword in their signature or not.

I think it should be more meaningful support of this keyword for java interfaces,
only methods with the "abstract" keyword must be implemented
when compiler takes care of all the rest.

With this proposal the LayoutManager interface would be implemented like this:

interface LayoutManager {

    // methods that are currently in java.awt.LayoutManager
    // the user must implement them (note the abstract keyword)
    abstract void addLayoutComponent(String name, Component comp);
    abstract void removeLayoutComponent(Component comp);
    abstract Dimension preferredLayoutSize(Container parent);
    abstract Dimension minimumLayoutSize(Container parent);
    abstract void layoutContainer(Container parent);

    // optional methods from java.awt.LayoutManager2
    // they don't have to be implemented
    void addLayoutComponent(Component comp, Object constraints);
    Dimension maximumLayoutSize(Container target);
    float getLayoutAlignmentX(Container target);
    float getLayoutAlignmentY(Container target);
    void invalidateLayout(Container target);
}

 
This was an entry from the Swing in a better world series

Thanks and see you soon
alexp 

I truly love the Swing GUI toolkit, I enjoy its flexibility, opennes and great abilities.

I know that some people say that Swing is too difficult to learn,
and I partly accept it because it took me several years working in the Swing team
to get the whole picture of the AWT, Java 2D and the Swing itself.

The Swing  history counts more then 10 years and it is definitely not about end.
The new features have already been added to JDK 7
and the new blogs are coming to describe them.

Swing is the best graphical toolkit I have ever worked with,
anyway it could have been implemented even better.

Even the best piece of software in the world would have bugs
and design glitches. At this point I want to stop for a moment,
to look back and remember the cases when
I wanted Swing to be designed in a different way.

This information may be useful for all developers
who are working on any other graphical toolkit.

Swing is written in Java,
so its API is tightly bound with Java language peculiarities
and event high level decisions of the Swing architecture are affected by the language sometimes.

So I'll cover the java related issues first
and then touch some exclusively Swing features.

Java related issues

Swing design

Thanks
alexp

 

Hello Swing community

 

While the SAF project is on hold, the Swing team welcomes the active development of the alternative implementations of  the Swing framework. I found a few promising projects and put the links to them at the SAF project main page:          and don't forget about the mature Netbeans Platform

 

I am happy to promote those projects. If there is anything I can help with, please don't hesitate to contact with me, I'd be glad to get in touch with the development teams.

 

Let me know if there are more good projects to be added in this list.

 

Thanks and keep up the good work!
alexp
alex_spb

SAF and JDK7 Blog

Posted by alex_spb Aug 19, 2009

Hello Swing community

After much discussion it's become clear that the Swing Application Framework API as it is today hasn't reached consensus and we feel still needs further design work done.

Since the SAF API was committed to milestone 5 of JDK7 and that time is already here, this date is now impossible, and we need to decommit SAF from any specific JDK 7 milestone.

Thanks
alexp

I know this is not good to disappear for a long time from blogging and SAF mail aliases, I am sorry about that. This happened because Swing team had some urgent temporary tasks to work on. The good news is that most of the tasks are completed and Swing team has returned to its primary goal - Swing library.

I should say that this time I have really come back to SAF and this project is currently #1 priority for the Swing team. We organized a little team to move SAF further and now working on schedule. My team mates asked me what problems with the current SAF code come to my head straightway and how I envision the "ideal Swing Application Framework".

This blog is written to start the conversation about those issues, it is intended not only for my colleagues but for all Swing programmers who are very welcome to comment.

Existing code

The "singleton problem"

Static method Application.launch() saves the current application instance to the static field and returns it with Application.getInstance()

This prevents using Application inside one JVM but from different AppContexts. Imagine that two applets on the same html page launch an applicaiton, they will likely to be run inside one JVM so they will share static data of any class, so an applet can't use static Applicaiton.getInstance() to access to its own application instance.

Design of the View class

Let's look at the the class' javadoc: 
 * A View encapsulates a top-level Application GUI component, like a JFrame
 * or an Applet, and its main GUI elements: a menu bar, tool bar, component, 
 * and a status bar.  All of the elements are optional (although a View without
 * a main component would be unusual).
Class View has methods like getMenuBar()/setMenuBar(), getToolBar()/setToolbar() and getRootPane().

This works fine for a MDI applicaiton where each view is operated by its own frame and each frame can have its own menu bar. This is exactly like most of the native application on Windows or *nix work. However a good framework must support SDI applications as well, on Mac OS all application's view shares one menu bar and it is better to let the view customize the "main" menu bar rather than having one menu bar per a view. It is also not unusual for a Mac application to have a "view" with a menu bar but with no main component.

SingleFrameApplication class

SingleFrameApplication is bound with JFrame.

A good framework should be friendly with IDE, let's say I want to build a SingleFrameApplication in my favorite IDE designer. In this case I shouldn't explicitly use classes like JFrame or JDialog because it is very difficult for an IDE to control and design a real JFrame.

Applets is the more obvious example, it should be as easy to run a SingleFrameApplication as an applet as a standalone application. The common pattern here is to provide all the data that required to build a JFrame but don't explicitly create it. It will allow the different view containers to show the view in a different way.

Lack of flexible menu support

I am not a Mac user, so I was really impressed when I knew how difficult it is to make a Swing application look like native on Mac. The menu bar is one of the main issue, it is totally different on Mac, please see this article for details. There is no need to say that SAF should automatically solve this kind of problems.

The ideal framework

It is small but very flexible, any part of its functionality can be easily overridden. For example, if you don't like the implementation of LocalStorage, it is easy to plug in your own implementation. It is free from all mentioned problem and knows how an application should look like on a particular OS. 

The question of the day

I mentioned only a few problems with the current SAF to start a discussion. What do you think about the problems I mentioned and what is your list of features that SAF must have?
note: don't forget that SAF is supposed to be a small framework. 


I am looking forward for your comments
It won't take a half a year for the next blog, I promise.

alexp