Skip to main content

Swing Application Framework update

Posted by alexfromsun on August 6, 2008 at 10:10 AM PDT

As you probably know, a few weeks ago I became the new specification lead for the Swing Application Framework project (JSR-296). This project has been fairly silent for the last little while, so it is high time to continue with working on this framework and complete it timely.

The main goal of this project is quite ambitious: create a Swing framework which allows to quickly create a simple Swing application
and which is also flexible and extensible for the big commercial applications. The key to success it to recognize the common patterns and the best practices that help to create a good Swing application and implement them in the Swing Application Framework.

In this blog entry I want to describe the recent changes and give the reasons why it was done this way and bring up some issues to think over. Don't hesitate to share your opinion, we make it for you and your feedback is very important for us. The project is now in transition state and the API may be changed. The latest source files you can find in the Subversion reporsitory, zipped files are also available,
the previous stable version can be found here.

One frame application

The framework is to be friendly to beginners and creating an application with a single frame should be very easy.
Remember the time when you were writing your first Swing application - JFrame with a main component, toolBar, statusBar and menu what questions did you have?

Here is my list:

  • How to create a JFrame?
  • How to place components properly to the frame?
  • How to show the frame?

Let me show you how it can done with the latest SAF:

public class SingleFrameExample2 extends SingleFrameApplication {
    @Override
    protected Component createMainComponent() {
        JLabel label = new JLabel("JLabel");
        label.setFont(new Font("LucidaSans", Font.PLAIN, 32));
        return label;
    }

    @Override
    protected JMenuBar createJMenuBar() {
        JMenuBar bar = new JMenuBar();
        JMenu menu = new JMenu("JMenu");
        menu.add(new JMenuItem("JMenuItem"));
        menu.add(new JMenuItem("JMenuItem"));
        bar.add(menu);
        return bar;
    }

    @Override
    protected JToolBar createJToolBar() {
        JToolBar toolBar = new JToolBar();
        toolBar.add(new JButton("JButton"));
        return toolBar;
    }

    public static void main(String[] args) {
        launch(SingleFrameExample2.class, args);
    }
}

By running this small example you'll see the frame with the menu, toolbar and the label as the main component. This frame will have the size sufficient to all its subcomponent and be shown at the center of the screen.

Open issues

With the provided test case you can easily create one frame, but what about more realistic multi frame applications? The SingleFrameApplication creates the main frame and exits when the frame is closed. It may be not the case if there are more frames in your application, for example you may want your application to stay open until it has at least one frame visible. It is also important for the modern application to save the state of a frame (like its size and position) when it is closed and restore it when the application is started again.

All that mean that we can't just create a frame or dialog and show it. We need a way to let the application set it up and add closing listeners or set the frame's bounds. The current SingleFrameApplication has the

public void configureWindow(Window root, Object key)

which should be called after you create any secondary window in your application. The first parameter is the window itself and the second one is the identifier of this window, which is used to save the window's state.

This design is not very flexible, for example it is difficult to set which window should close the application

Proposed API

Introduce a new method in the Application class

public void registerWindow(Window w, WindowContext c)

and add a new WindowContext class which will keep the identifier, know how to configure this window and whether window's listener should close the application when this window is closed

abstract public class WindowContext {

    abstract public Object getIdentifier();
   
    public void configure(Window w){
    }
   
    //Is it a good name for this method?
    public boolean isClosingApplication() {
        return false;
    }
}

saveState() and restoreState() methods are also candidates to the WindowContext class

That's all for today, the next blogs will be about the View class and the Application singleton.
I am looking forward to your comments.




Thanks

alexp

Related Topics >>

Comments

how to add a editor pane

This code does not look like the code when you start a new desktop project. I have downloaded netbeans 6.7.1 and the desktop app runs but of coarse it does almost nothing. How about showing your code in context to the code generated by netbeans? Isn't that how most people will run swing application framework? How can I add some control to the application? I only know the simple apps in the swing tutorial and I am trying to understand this framework. thanks.

Hello, after a new bug detected, here's a new setSessionState (i hope tht's the last !) : public void setSessionState( Component c, Object state ) { checkComponent(c); if ((state != null) && !(state instanceof WindowState)) { throw new IllegalArgumentException("invalid state"); } Window w = (Window) c; if (!w.isLocationByPlatform() && (state != null)) { WindowState windowState = (WindowState) state; Rectangle frameBounds = windowState.getBounds(); Rectangle gcBounds0 = windowState.getGraphicsConfigurationBounds(); if (gcBounds0 != null) { // problem !!! if (!gcBounds0.contains(frameBounds.getLocation())) { Point loc = gcBounds0.getLocation(); loc.x = (gcBounds0.width - frameBounds.width) / 2 + gcBounds0.x; loc.y = (gcBounds0.height - frameBounds.height) / 2; frameBounds.setLocation(loc); } if (getVirtualGraphicsBounds().contains(gcBounds0.getLocation())) { boolean resizable = true; if (w instanceof Frame) { resizable = ((Frame) w).isResizable(); } else if (w instanceof Dialog) { resizable = ((Dialog) w).isResizable(); } if (resizable) { w.setBounds(frameBounds); // -------------------------- added JCS 27.2.2009 ---------------------- String clientPropertyKey = "WindowState.normalBounds"; ((JFrame) c).getRootPane().putClientProperty(clientPropertyKey, frameBounds); // -------------------------- added JCS 27.2.2009 ---------------------- } } } if (w instanceof Frame) { ((Frame) w).setExtendedState(windowState.getFrameState()); } } } }

A better setSessionState (revisited this day after a bug) in SessionStorage : public void setSessionState( Component c, Object state ) { checkComponent(c); if ((state != null) && !(state instanceof WindowState)) { throw new IllegalArgumentException("invalid state"); } Window w = (Window) c; if (!w.isLocationByPlatform() && (state != null)) { WindowState windowState = (WindowState) state; Rectangle frameBounds = windowState.getBounds(); Rectangle gcBounds0 = windowState.getGraphicsConfigurationBounds(); if (gcBounds0 != null) { // problem !!! if (!gcBounds0.contains(frameBounds.getLocation())) { Point loc = gcBounds0.getLocation(); loc.x = (gcBounds0.width - frameBounds.width) / 2 + gcBounds0.x; loc.y = (gcBounds0.height - frameBounds.height) / 2; frameBounds.setLocation(loc); } if (getVirtualGraphicsBounds().contains(gcBounds0.getLocation())) { boolean resizable = true; if (w instanceof Frame) { resizable = ((Frame) w).isResizable(); } else if (w instanceof Dialog) { resizable = ((Dialog) w).isResizable(); } if (resizable) { w.setBounds(frameBounds); } } } if (w instanceof Frame) { ((Frame) w).setExtendedState(windowState.getFrameState()); } } } }

Hi I have modified methods "getSessionState" and "setSessionState" for a better support for two or more monitors and fullscreen modes. I give you the code here or i can send you "SessionStorage.java" : public Object getSessionState( Component c ) { checkComponent(c); int frameState = Frame.NORMAL; if (c instanceof Frame) { frameState = ((Frame) c).getExtendedState(); } GraphicsConfiguration gc = c.getGraphicsConfiguration(); Rectangle gcBounds = (gc == null) ? null : gc.getBounds(); Rectangle frameBounds = c.getBounds(); /* If this is a JFrame created by FrameView and it's been maximized, * retrieve the frame's normal (not maximized) bounds. More info: * see FrameStateListener#windowStateChanged in FrameView. */ if ((c instanceof JFrame) && (0 != (frameState & Frame.MAXIMIZED_BOTH))) { String clientPropertyKey = "WindowState.normalBounds"; Object r = ((JFrame) c).getRootPane().getClientProperty(clientPropertyKey); if (r instanceof Rectangle) { frameBounds = (Rectangle) r; } // -------------------------- modified JCS 25.2.2009 ---------------------- if (!gcBounds.contains(frameBounds.getLocation())) { Point loc = gcBounds.getLocation(); loc.x = (gcBounds.width - frameBounds.width) / 2 + gcBounds.x; loc.y = (gcBounds.height - frameBounds.height) / 2; frameBounds.setLocation(loc); } // -------------------------- modified JCS 25.2.2009 ---------------------- } return new WindowState(frameBounds, gcBounds, getScreenCount(), frameState); } ----------------------------------------------------------------- ADDED: private Rectangle computeVirtualGraphicsBounds() { Rectangle virtualBounds = new Rectangle(0, 0, 0, 0); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); GraphicsDevice[] gd = ge.getScreenDevices(); for (int i = 0; i < gd.length; i++) { GraphicsConfiguration gc = gd[i].getDefaultConfiguration(); debug("Screen " + (i + 1), gc.getBounds()); virtualBounds = virtualBounds.union(gc.getBounds()); } debug("Virt. screen", virtualBounds); return virtualBounds; } MODIFIED: public void setSessionState( Component c, Object state ) { checkComponent(c); if ((state != null) && !(state instanceof WindowState)) { throw new IllegalArgumentException("invalid state"); } Window w = (Window) c; if (!w.isLocationByPlatform() && (state != null)) { WindowState windowState = (WindowState) state; Rectangle gcBounds0 = windowState.getGraphicsConfigurationBounds(); if (gcBounds0 != null && computeVirtualGraphicsBounds().contains( gcBounds0.getLocation())) { boolean resizable = true; if (w instanceof Frame) { resizable = ((Frame) w).isResizable(); } else if (w instanceof Dialog) { resizable = ((Dialog) w).isResizable(); } if (resizable) { Rectangle r = windowState.getBounds(); if (r.x < 0) { r.x = 0; } if (r.y < 0) { r.y = 0; } if (r.width < 1) { r.width = 640; } if (r.height < 1) { r.height = 480; } w.setBounds(r); } } if (w instanceof Frame) { ((Frame) w).setExtendedState(windowState.getFrameState()); } } } } --------------------------------- Bye

I'm very interesting for this development. It's a good framework to start with my students. I wait for a long time to a new version (some bugs with 2 monitors). Thank's to continue this developpement. Is there a new version available and where. I use the 1.0.3. Bye.

I hope this project continues. Having done a lot of swing and built my own framework for my DD Poker game, I know the value of this. I've started using it at a new job. I've posted some lessons learned here: http://wiki.donohoedigital.com/wiki/Wiki.jsp?page=Swing%20Notes -Doug

My suggestions : * For the SingleFrameApplication class, it probably isn't necessary to a WindowContext object. "As the application has a single frame" so the name SingleFrameApplication, all the developer needs is to access the frame object to configure it without bothering with other abstractions to access properties and methods. * For application with multiple frames which would be some kind of MultiFrameApplication class, a WindowContext would be nice but only for registration to retrieve the window later. staticWindowContext.getContext(windowID).putProperty(key, value); staticWindowContext.getContext(windowID).doSomethingICouldDoWithoutTheFrameWork(); A windowContext wouldn't be useful at least to me really as : I would want to change many kind of properties => Can I access all properties through wrappers? I would want to call different methods => addWindowListener, addComponentListener, repaint, etc? To sum up, in a SingleFrameApplication, the frame window should be accessible and in a multi-frame application it would be better to let the user create the frames and deal with them manually. Some helpers static methods could easily create the menubar and toolbar through properties file or XML descriptors, using reflection. But if you offer that, people will ask why there isn't a XUL or WPF style framework included in appframework. Yves Zoundi, VFSJFileChooser : http://vfsjfilechooser.sf.net XPontus XML Editor : http://xpontus.sf.net

Hi Alex, Glad to hear SAF is back up and running. I think it's a big and important project. Seems to me that many Swing developers have had to write their own frameworks to deal with exactly the sort of issues you're addressing in SAF, I know I have. I work with an application that has multiple frames and have had to devise my own structure for closing them. I have a hierarchical structure to the frames in my application. I think it's important to be able to register parent/child frame relationships so when a parent frame is closed, so are all it's children. This probably won't fit all applications so maybe it's an extension to the norm. Also, when persisting application state (windows, size) it's important to be able to associate other data with that state. In my application I reuse generic frames with different data models so I associate a the important data model information with the frame information when it's persisted. Finally on the persisting I think you should allow extensions so I can save my application context to a database, maybe using JPA. Good luck with SAF. Cheers, Paul.

You could also reverse the logic: instead of having a true false on instantiation, ask each window if it is ok the close the application when the framework intends to. This will also allow for a "save document" alike popup on each window.

Hi Alex, registerWindow(...) sounds better. Not sure about WindowContext.isClosingApplication() though. Wouldn't want to be implementing boiler plate code unecessarily, e.g. for 30 or more window types. How many windows in a given application trigger a shutdown when they close? In our case there are at least 30 window types for various things including chooser dialogs etc, but only 1 is the main application window. We don't use S.A.F -- yet :) Have you also considered an 'opt in' approach, where only those few windows whose closure represents a shutdown hook need register this interest with the application? E.g. Application.registerShutdownHookContext(WindowContext wc); Not such a good method name, but you see what I mean :) Many thanks.

What's the name of the maven archetype for this? Or at least the artifactid and repo?

Very excited to see this progressing. This and also with Beans Binding as well, there's going to be a lot to like in JDK 7 for Swing developers.

Thought I would pile on: great news that SAF hasn't been abandoned. Good luck in your endeavor.

Yeah nice to see the project is no longer derailed by JavaFX. Not sure I understand the need for isClosingApplication, the JSR-296 based applications I have working uses implements the ExitListener to the same vetoing effect: addExitListener( new ExitListener(){ public boolean canExit(EventObject arg0) { // Check if we have dirty data and prompt save dialog... } public void willExit(EventObject arg0) { // NOP } }); Also. What is the chance we'll get some sort of restart capability along the side of launch and end. This would be very handy to circumvent the singleton resource caching going on. For instance, it is not possible to change Locale in a JSR-296 app (Swing has no LocaleChangeEvent). I am thinking along the lines of starting in a new isolated ClassLoader instance: public void restart() { try { URLClassLoader parentClassLoader = (URLClassLoader)this.getClass().getClassLoader(); URLClassLoader classLoader = URLClassLoader.newInstance( parentClassLoader.getURLs(), parentClassLoader ); Class loadedClass = classLoader.loadClass( this.getClass().getName() ); Class[] argTypes = { String[].class}; Object[] args = { new String[]{""}}; Method method = loadedClass.getMethod("main", argTypes); method.invoke(null, args); } catch (Exception ex) { Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex); } } Note: I have actually tried making this case with Hans before, to no effect though. Regards, Casper

Thank you Alexander. It's good to know that the project is still alive. The proposed API seems good to me. What about adding docking to the framework? This will make it a killer framework as no framework is complete without providing docking facilities.

Good to see the framework is being put back on the rails. As my first experience with OO programming was Think Pascal and the Think Class Library, I certainly appreciate a solid framework for creating applications - both document-based and single-frame style. Keep up the good work!

Good work - it's great to know that usefull stuff like this isn't getting abandoned. @roboss - the Preferences registry problem isn't really an issue with this framework - it's an issue with the Preferences system itself. All somebody needs to do is write another implementation for use with windows ( file based perhaps ) ? I may tackle this job myself one day, as I'm getting a little tired of registry cruft myself. I may go completely oldschool and write a preferences implementation that uses .INI files. ;-)

It was a nice example. Is the SAF using the Preferences API to save settings? I really do like the Preferences API, but I really do not like that it saves values to the registry on the Windows implementation. Unless you're planning on including some kind of uninstaller tool with the SAF that will clean up the registry when the application that saved values is no longer being used. Otherwise you'll just end up adding more cruft to the already crufty registry. I would really like a way to specify that the backing store should be an XML document stored in an appropriate, well-known location. (Even better, make this the default behavior in SAF, and let the user explicitly ask to save in the registry if that is what they really want. ) Often these locations are relative to the user's home directory. I know where to save these kinds of preferences on Windows (XP at least) and Mac OS X. I am also quite confident a standard location could be determined for other *nix-based platforms.

Yep, good to know that the project is in good hands!

Good to know this JSR is advancing! This feature for notifying (asking for permission as well) application closing is really interesting. It fits in cases where you have unsaved work in multiple windows, etc. Will this follow the same principle as in a PropertyVetoException ? You've mentioned a statusbar as an example in your post. Does this JSR concern widgets (like a JStatusBar) or it is only about application infrastructure? Good luck on the JSR

Hi Alex, I was wondering if you have any plans on implementing the createJMenuBar, createJToolBar methods to use the action names. Currently I do something like this in my version of createJMenuBar: String[] fileMenuActionKeys = { Keywords.NEW_SETTINGS, Keywords.OPEN_SETTINGS, Keywords.SAVE_SETTINGS, "---", Keywords.SAVE_IMAGE, "---", Keywords.PLOT, "---", Keywords.QUIT }; String[] viewMenuActionKeys = { Keywords.EDIT_SETTINGS, "---", "viewTabularData", "---", "edit-header-footer" }; String[] helpMenuActionKeys = { "about" }; menuBar.add(this.createMenu("File", fileMenuActionKeys)); menuBar.add(this.createMenu("View", viewMenuActionKeys)); menuBar.add(this.createMenu("Help", helpMenuActionKeys)); It would be nice to simply have properties that allow you to create menus, and toolbars like this: Application.menubar.File.name.en = File Application.menubar.File.Open = Open Application.menubar.File.Save = Save Application.toolbar. ... Open.Name.en = Open Open.ShortDescription.en = Open a File ... Regards, Mark

Hi Alex, Please add OSGi support to the framework - thank you!

Great to see you in charge of this development effort. I have been watching this JSR for a while, and have avoided getting involved up until now because it seems to be associated with netbeans (not sure if it is intentionally or not). It is my hope that at some point all references to a particular IDE could be removed... then I think it might get wider acceptance from folks like me who do not want to be tied to one IDE or the other. -- sorry for ranting, I will get back on track with my post now :) I am using a homegrown application class that does similar things with a few other features Like the Swing Application Framework, I have a toolbar, menu bar and status bar. I have also added 1. Integrated logging (log4j) 2. A properties file loading manager, 3. An integrated splash screen loader 4. I have also added a content area which contains a navigation tree on the left, and a panel area on the right where custom panels are drawn in a card panel, overlaid with a JXlayer. Selecting navigation links on the left tree then causes the corresponding panel on the right to be displayed. 5. I have added a thread manager that simplifies the processing of data threads which can change progress bars to indicate thread progress (completion % and status note updating etc) 6. My application class has a lot more stuff like serial communication classes and a database manager but that does not belong in a GUI framework. While mine is all working, I am eager to see if I can do better using the new Swing Application Framework as it becomes more stable. No sense re-inventing the wheel if this project can do it as well. Not sure if any of these ideas would be good for others but it is food for thought. In any case, congratulations Alex on this new role!