Tutorial: beautifying the dialogs in Swing applications

Jul 18, 2012 by     18 Comments    Posted under: Tutorials

Today, a tutorial for old-school application designers using the Swing technology. Swing produces UIs that look like windows98 interfaces, but with a bit of tweaking, it’s still a very powerful technology, at least until JavaFX gets bundled in the JRE for all platforms.

Working on a new application, I was getting a bit tired of the dialogs I use everywhere in my apps. They look a bit old-fashioned, and I dislike the modal dialogs in desktop applications as much as the popups in websites. Of course, I though about using layers and glass panels, but I faced one issue: an opengl canvas is a heavy beast, and cannot be masked with any other swing component. Since I nearly always include an opengl canvas (using libGDX LwjglCanvas) in my applications, I’m stuck with dialogs. Fine, so let’s make them more attractive!

Following images show (1) a basic modal dialog, and (2) the same dialog, tweaked.

The method presented here is really simple to implement, and no magic tricks are involved. It can be easily modified to fit any style: just remove what you dislike (shadow, title, etc) and add other elements instead.

1. The method

What I want to do is to avoid having to manually change all my already-made dialogs. Hopefully, this method can take any existing dialog and convert them into the expected result, with only a few (3) lines of code put into the dialog code.

A basic Swing dialog comes as shown in the following illustration. Like every top-level container, it is made of a root panel (JRootPane) which holds a content panel, a layer panel, and a glass panel. A top-level container, like a window or a dialog, is by default opaque and surrounded by a system frame (which holds the title and some buttons to close/minimize/maximize it).

A swing dialog without any changes

To bring the desired effect in place, we need to change that, as shown in next figure. The dialog will be made as large as the underlying window, and placed on top of it. Its content panel will be replaced by a custom panel. This custom panel will hold the original content panel, as well as some labels to display the dialog title for instance. It will also be made transparent, and a shadow image is painted on it, right behind the original content panel.

The same dialog, after applying the method

Therefore, the application frame has not changed at all, everything comes from the dialog itself. This is mandatory to be able to mask every possible widgets, including heavy ones like an opengl canvas. All the code will go in the dialog itself. Of course, we’ll place everything in a utility class, for easy reuse in every dialog.

2. Implementation

When you look at the previous picture, implementation seems trivial. You’re in luck: it is. The following code extract places everything in a utility class with a static method. This single method does all the job: it creates the new dialog transparent back panel, position the components over it, and replaces the original dialog content panel by this new panel. Finally, it sizes and positions the dialog over the underlying window.

public class SwingUtils {
	/**
	 * Centers the dialog over the given parent component. Also, creates a
	 * semi-transparent panel behind the dialog to mask the parent content.
	 * The title of the dialog is displayed in a custom fashion over the dialog
	 * panel, and a rectangular shadow is placed behind the dialog.
	 */
	public static void createDialogBackPanel(JDialog dialog, Component parent) {
		DialogBackPanel newContentPane = new DialogBackPanel(dialog);
		dialog.setContentPane(newContentPane); // dialog content is replaced by the panel
		dialog.setSize(parent.getSize()); // the dialog is made as wide as the underlying window
		dialog.setLocation(parent.getLocationOnScreen()); // the dialog is placed over this window
	}

	// -------------------------------------------------------------------------

	private static class DialogBackPanel extends JPanel {
		private static final Paint fill = new Color(0xAAFFFFFF, true);
		private static final ImageIcon shadowImage = new ImageIcon(SwingUtils.class.getResource("/res/gfx/dialogShadow.png"));
		private final Component cmp;
		private final JLabel title = new JLabel();
		private final JLabel info = new JLabel("Hit 'ESC' to close the dialog");

		public DialogBackPanel(JDialog dialog) {
			this.cmp = dialog.getContentPane();

			 // Misc
			setOpaque(false); // the panel is transparent
			title.setText(dialog.getTitle());

			 // Layout of components
			setLayout(null); // absolute layout
			add(cmp);
			add(title);
			add(info);

			// Style the components (here I use my Universal CSS Engine,
			// but you can just use common "setForeground()" type methods.
			Style.registerCssClasses(cmp, ".dialogPanel");
			Style.registerCssClasses(title, ".dialogTitleLabel");
			Style.registerCssClasses(info, ".dialogInfoLabel");
			Style.apply(this, new Style(Res.getUrl("css/style.css")));

			// Size the components (required for absolute layouts)
			title.setSize(title.getPreferredSize());
			info.setSize(info.getPreferredSize());
			cmp.setSize(cmp.getPreferredSize());
		}

		@Override
		protected void paintComponent(Graphics g) {
			super.paintComponent(g);

			int w = getWidth();
			int h = getHeight();

			// Location of the components:
			// - the dialog original content panel is centered
			// - the title label is placed over it, aligned left
			// - the info label is placed over it, aligned right
			int shadowX = w/2 - (cmp.getWidth()+100)/2;
			int shadowY = h/2 - (cmp.getHeight()+100)/2;
			cmp.setLocation(w/2-cmp.getWidth()/2, h/2-cmp.getHeight()/2);
			title.setLocation(w/2-cmp.getWidth()/2, h/2-cmp.getHeight()/2-title.getHeight());
			info.setLocation(w/2+cmp.getWidth()/2-info.getWidth(), h/2-cmp.getHeight()/2-info.getHeight());

			// Paint
			Graphics2D gg = (Graphics2D) g.create();
			gg.setPaint(fill);
			gg.fillRect(0, 0, w, h);
			gg.drawImage(shadowImage.getImage(), shadowX, shadowY, cmp.getWidth()+100, cmp.getHeight()+100, null);
			gg.dispose();
		}
	}
}

So how do you use this code? Trivial, you just need to add one line of code at the end of your dialog code. You can even add it outside the dialog code, after you instantiated it with new MyDialog(theParentWindow). Well, actually, I lied a bit: there are 2 other mandatory lines, required to enable the dialog transparency, and to remove the system frame around it. That’s actually 3 lines :)

These first two lines need to be included inside the dialog code, and before anything, else you will get an exception.

public MyDialog(JFrame parent) {
	super(parent, true);

	setUndecorated(true); // remove system frame
	AWTUtilities.setWindowOpaque(this, false); // enable opacity

	... dialog code ...

	SwingUtils.createDialogBackPanel(this, parent.getContentPane());
}

Et voilà. Your dialogs are up and ready. Well, not totally, we can polish them a bit more, with stuff that cannot be shown in screenshots, but which contribute a lot to the overall user experience.

3. Polishing with fade in/out

For now, when you call setVisible(true) on the dialog, it immediately pops out of nowhere. Indeed, as long as a dialog uses a system frame, the system is able to smoothly fade it in, like for every window of every program. However, as soon as you remove the system frame, dialogs and windows just pop in, without any animation. Cool, that means we can use any animation we want!

The transition I propose is a very basic, linear, opacity fade in/out, so feel free to modify it to be better. Implementation is as follows:

public class SwingUtils {
	/**
	 * Creates an animation to fade the dialog opacity from 0 to 1.
	 */
	public static void fadeIn(final JDialog dialog) {
		final Timer timer = new Timer(10, null);
		timer.setRepeats(true);
		timer.addActionListener(new ActionListener() {
			private float opacity = 0;
			@Override public void actionPerformed(ActionEvent e) {
				opacity += 0.25f;
				dialog.setOpacity(Math.min(opacity, 1));
				if (opacity >= 1) timer.stop();
			}
		});

		dialog.setOpacity(0);
		timer.start();
		dialog.setVisible(true);
	}

	/**
	 * Creates an animation to fade the dialog opacity from 1 to 0.
	 */
	public static void fadeOut(final JDialog dialog) {
		final Timer timer = new Timer(10, null);
		timer.setRepeats(true);
		timer.addActionListener(new ActionListener() {
			private float opacity = 1;
			@Override public void actionPerformed(ActionEvent e) {
				opacity -= 0.25f;
				dialog.setOpacity(Math.max(opacity, 0));
				if (opacity 					timer.stop();
					dialog.dispose();
				}
			}
		});

		dialog.setOpacity(1);
		timer.start();
	}
}

To use it, instead of calling dialog.setVisible(true), just call SwingUtils.fadeIn(dialog). And to fade out, do the same at the place you used dialog.dispose() or dialog.setVisible(false).

4. Polishing with “Hit escape to close”

Another feature that is very welcome is to let the users close a dialog by pressing the ESC key, no matter what component has the focus. It is especially important for dialogs without system frame, since buttons are not shown. Moreover, in the method presented in this article, there is no way to move the dialog around, so if the “ok” or “close” button you may have placed in the dialog is placed outside the screen (because the user opened the dialog while the window is not fully visible), there is no way for him to close it.

Thanks to the nice new features of Java7, there is now a JLayer component available, that is meant to let you paint over a component, or intercept some of its events. The last feature is the one we need.

public class SwingUtils {
	/**
	 * Adds a glass layer to the dialog to intercept all key events. If the
	 * espace key is pressed, the dialog is disposed (either with a fadeout
	 * animation, or directly).
	 */
	public static void addEscapeToCloseSupport(final JDialog dialog, final boolean fadeOnClose) {
		LayerUI layerUI = new LayerUI() {
			private boolean closing = false;

			@Override
			public void installUI(JComponent c) {
				super.installUI(c);
				((JLayer) c).setLayerEventMask(AWTEvent.KEY_EVENT_MASK);
			}

			@Override
			public void uninstallUI(JComponent c) {
				super.uninstallUI(c);
				((JLayer) c).setLayerEventMask(0);
			}

			@Override
			public void eventDispatched(AWTEvent e, JLayer l) {
				if (e instanceof KeyEvent && ((KeyEvent) e).getKeyCode() == KeyEvent.VK_ESCAPE) {
					if (closing) return;
					closing = true;
					if (fadeOnClose) fadeOut(dialog);
					else dialog.dispose();
				}
			}
		};

		JLayer layer = new JLayer<>(dialog.getContentPane(), layerUI);
		dialog.setContentPane(layer);
	}
}

So that’s another static method in our utility class. This one replaces the dialog content panel with the JLayer panel, so be sure to call it after createDialogBackPanel. You can also call it directly inside the latter, for more ease.

5. Conclusion

Eventually, we’ve got a nice utility class filled with easy-to-use methods that will bring cool effects instantly in your UIs. I hope that can give you some ideas. The JLayer class seems really powerful, I’m eager to use it a bit more to draw overlays of all sorts.

Demo of the tutorial (with executable jar and sources):
http://www.aurelienribon.com/tutorials/data/dialog-enhancer-demo.zip

Side note: the Universal CSS Engine has greatly evolved since the 1.0 version. I never made any public release of the new stuff, but I’ll try to do that as soon as I get the time. I think it is now sufficiently mature to be used without fear.

Below are some images showing the steps I went through, from the basic dialog to the target.



18 Comments + Add Comment

  • could you please share your complete project for this example ?

    • Well, the complete project is a whole particle editor, so I think it will be overkill just to learn how dialogs are managed. The editor will be released as open-source in the next days, I still need to implement some stuff.

      However, I’m considering building a small example project to showcase this tutorial. I’ll upload something soon :)

    • And here you are :)
      http://www.aurelienribon.com/tutorials/data/dialog-enhancer-demo.zip

      The demo includes an executable jar, as well as the corresponding sources.

      • Perfect. Thats what i was requesting for. I always had bad experience building Swing apps. It might increase my knowledge.

  • The FX editor looks really nice. Can’t wait to test it. :)

    • Thank, it should be released quite soon, it’s nearly complete now :)

  • Great news. Will it be compatible to the libGDX particle implementation (i guess… yes ;))?

    • Yes and no :)

      Actually, I built this application in a few hours, just to let me add some simple effects to my libgdx games (hence the name “lil”). I wasn’t able to use the official particle effect class to fit with meter units, so I designed my own effect class in a few minutes.

      The tool will ship with this custom, easy-to-use, particle effect class API, but I want to make it able to export to various implementations, like the official libgdx one (which is by far more powerful than mine).

  • Hi :)

    Thanks for this awesome tutorial. I’m just starting with Swing and I’m trying to make
    my interface as beautiful as possible.
    Could you please answer a few questions?
    What Look and Feel are you using? What did you customize?

  • Any news about particle editor? :)

    Thank you for all these great tools.

    • I first want to complete the next major update of gdx-setup, and then I’ll release the particle editor :)

      • really looking forward to particle editor :)

  • [...] would have been modal dialogs, like what I do in nearly every other tool I made. I even wrote a tutorial about how to create pretty dialogs. However, this requires Java7, and I’m quite bored of dialogs. I could also use a tabbed [...]

  • thank you for this tutorial.. ^_^

  • sir…
    plz give some easy and understandable simple code to make project attractive……

  • Aw, this was a very nice post. Finding the time and actual effort to
    produce a good article… but what can I say… I put things off a whole lot and don’t seem to get
    anything done.

Got anything to say? Go ahead and leave a comment!

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>