Make Your Swing App Go Native, Part 1 Blog


    Java applications, in particular those written with the Swing toolkit, have a reputation for feeling clunky and out of place, as if they don't belong on your computer. Often your users can't put their finger on what it is that doesn't feel quite right. It is often the look of the buttons, subtle menu bar differences, and a lack of a good launching mechanism. Swing applications often look and feel d ifferent than native applications.

    This is the first of a three part article series in which we will build a simple application from the ground up and make it look and feel native. This article will cover designing the menus and adding visual alerts. The second article will focus on buildin g native executables and adding file type associations. The third part will complete the series with icons, dialog boxes, splashscreens, and a checklist of finishing touches. Throughout the series, we will make use of several libraries and techniques that automate the process and make building high-quality Swing applications as easy as possible.

    To illustrate each of these techniques, we will overhaul a faux instant messenger called The Mad Chatter. I've chosen to do use an IM program as our ongoing example because it is complex enough to benefit from native integration while being simple enough to fit in a series of articles. The Mad Chatter will use the basic File and Edit menus to save log files and edit text. Being a chat application, it will also need a way of alerting the user, so we get to exercise dock bouncing and window flashing.

    Over the course of these three articles, the Mad Chatter will get a complete makeover, including:

    • A menu bar that feels native
    • Global key combos
    • Alerts when a new message arrives
    • Installer free native packaging
    • Registering file type associations
    • Custom icons
    • A splashscreen
    • Native file dialogs

    Some of these tips have been covered in more detail elsewhere, so I'll try not to rehash them. Instead, I'll cover them briefly and then go into how you can cleanly integrate them into your development process.

    What is a Native-Feeling Application?

    Now first let's talk about what it means to feel native. A simple explanation would be that the user can't tell the difference between your Swing application and a native one. But that doesn't tell us the whole story. Like good photography, feeling native isn't really about doing anything. It's about not doing a whole bunch of things. Not being slow, not having widgets that look different, not requiring special installation procedures. In general, it's about not feeling any different than any other application. This includes key combos, menu placement, icons, packaging, and the correct terminology (Quit vs.Exit, Preferences vs.Options, etc.). It means all of the little details that have nothing to do with what the program actually does, but affects how the program feels. And this makes a huge difference to the users.

    Most users have a favorite platform and pre-existing expectations about what buttons go where, which actions are associated with various keyboard accelerators, and hundred of other UI niceties that they take for granted. Although you can create a cross-platform application that is as simple to use and as reliable as a native application, your application is more likely to be adopted by end users if its UI meets their expectations. You will need to consider the expectations of users on each of your target platforms.

    Setting the Look and Feel

    The first step to making the application feel native is setting the current Look and Feel. Swing has the concept of a Look and Feel (L&F), which is a set of classes and images that completely control how the standard Swing components look and behave. By default, Swing applications have a cross-platform L&F called Metal (Figure 1), which many consider quite ugly.

    The standard Metal L&F
    Figure 1. The standard Metal L&F

    L&Fs are used for theming, but they can also be used to make on-screen components, from buttons to menus, look just like their native counterparts. Apple ships an Aqua L&F that makes a Swing app on Mac OS X virtually indistinguishable from a native OS X application. Sun ships a similar L&F for Windows. By telling Swing to use the system Look and Feel, the one provided by the JVM, we help maintain the illusion that this is a native application.

    On OS X, this is already done for us, so we don't need to worry. On Windows, this is done by getting the system Look and Feel from the UIManager, as seen below:

    try { UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName()); } catch (UnsupportedLookAndFeelException ex) { System.out.println("Unable to load native look and feel"); }

    This throws an exception if the system L&F isn't available (or if there isn't one), so we have to catch it instead of letting the program quit. Now our application looks like Figure 2 or 3 instead of Figure 1.

    The OS X Aqua L&F
    Figure 2. The OS X Aqua L&F

    The Windows L&F
    Figure 3. The Windows L&F

    Managing the Menu Bar

    Now that we've got the Components looking native, we need to work on the menu bar. The main differences between platforms are the single menu bar and key combos on the Mac.

    Unlike Windows, the Macintosh has always given applications a single menu bar for the entire program instead of one menu bar at the top of each frame. Since Swing assumes one menu bar per frame, Apple has designed their Swing L&F to always use the menu bar of the active frame. To make the menu bar appear as if there is really only one will require some cleverness as we build up our menu. But I'm getting ahead of myself.

    First: we can tell OS X to use a single menu bar by adding a system property to the command line:

    java -Dapple.laf.useScreenMenuBar=true 

    And now we have a single menu bar, as shown in Figure 4:

    The Mac OS X single menu bar
    Figure 4. The Mac OS X single menu bar

    The next Mac-specific item is the Application menu. All OS X programs have a special menu, titled with the name of the program, that contains the About,Preferences, and Quit menu items, along with a few provided by the operating system (likeHide).

    To set the name of our application, and have it appear at the top of the application menu, we use another system property, You can put this on the command line with another -Dargument. If you're using the Project Builder IDE, you'll want to put all of these Java system properties in your Info.plistfile, where they'll look like this:

    <key></key> <string>MadChatter</string>

    The Application menu now looks like Figure 5:

    OS X Application Name
    Figure 5. OS X Application Name

    Setting Key Combos

    The next step is creating our menu items with the right key combos. Key combos are keypress combinations that perform menu actions without actually selecting the menu. An example isControl-S for Save. Each platform has default key combos for common functions, and our application should match these defaults wherever possible. Swing also lets us set the menu item name along with the key combo.

    We can set each key combo by testing for the platform and using the right accelerator key (i.e., Control orCommand), but we also have to consider cases where the name of the menu item is different, such as Quit vs. Exit. This is a lot of conditional logic to develop, much of which is reusable across applications. To avoid rebuilding this code in each program, I have created a library called XP for (Cross (X) Platform). This library is itself built upon Steve Roy's MRJAdapter library. (Thanks, Steve.)

    Instead of manually creating each menu item appropriately for each platform, the xp object will create them for us. We just need to add them to menus and set theActionListeners. The XP library will also create the Cut, Copy, and Paste handlers to make sure that native-to-Java clipboard actions work properly. To use the library, we just get anXPHelper from the factory and start pulling out menu items.

    XPHelper xp = XPFactory.getXPHelper(); JMenu file = new JMenu("File"); JMenuItem neww = xp.getNewMenu(); JMenuItem open = xp.getOpenMenu(); JMenuItem close = xp.getCloseMenu(); JMenuItem save = xp.getSaveMenu(); JMenuItem quit = xp.getQuitMenu(); file.add(neww); file.add(open); file.add(close); file.add(save); if(!xp.isMac()) { file.add(quit); } JMenu edit = new JMenu("Edit"); edit.add(xp.getCutAction()); edit.add(xp.getCopyAction()); edit.add(xp.getPasteAction()); menubar.add(edit);

    The Quit menu item is conditionally added to the menu because OS X will automatically add it to the Application menu, while under Windows, we need to do it manually.

    Attaching the Event Listeners

    To make these menus fully functional, we need to addActions or ActionListeners. This can be a little tricky because some actions are tied to a particular window and some are global. Then we have to add in the complexity of the Application menu under OS X. This gives us three cases to deal with:

    1. An action that is local to a particular window (Save).
    2. An action that is global and is attached to a normal menu item (Help).
    3. An action that is global and may go under the Mac Application menu (Quit).

    We'll do this like we would in any normal Swing application, with just a few changes.

    For normal actions, we allocate our ActionListenerwhen we create the JFrame.

    ActionListener open_list = new OpenListener(panel,frame); ActionListener save_list = new SaveListener(panel,frame); ActionListener close_list = new ActionListener() { public void actionPerformed(ActionEvent evt) { frame.hide(); frame.dispose(); } }; open.addActionListener(open_list); save.addActionListener(save_list); close.addActionListener(close_list);

    For global options, we do the same thing, but wrap them in an initialization flag that makes sure they only get initialized once. After they are created, we add them to each frame.

    if(!initialized) { /* add event handlers */ cut = xp.getCutAction(); copy =xp.getCopyAction(); paste = xp.getPasteAction(); new_list = new ActionListener() { public void actionPerformed(ActionEvent evt) { Startup.newWindow(); } }; // more global actions } JMenu edit = new JMenu("Edit"); edit.add(cut); edit.add(copy); edit.add(paste);

    For global options that are under the Application menu, we create the actions globally as before, but we add them conditionally. If it's under Windows, we add them to each frame. If we are running under OS X, then we add them once to the application menu.

    JMenuItem about = xp.getAboutMenu(); // only manually add on non-mac if(!xp.isMac()) { JMenu help = new JMenu("Help"); help.add(about); menubar.add(help); // and add the about for every window about.addActionListener(about_list); } else { if(!initialized) { about.addActionListener(about_list); } }

    Since this conditional menu stuff can get hairy, I recommend creating functions for each step of the process:init_menu_handlers(),create_menu_items(),attach_menu_handlers().

    Now we have a set of menus that completely hide their cross-platform nature from the rest of the application. Let's take a look at the results in Figures 6 and 7.

    The finished menu for the Mac
    Figure 6. The finished menu for the Mac

    The finished menu for Windows
    Figure 7. The finished menu for Windows

    Native User Alerts for the Mac

    One native feature that virtually all instant messenger applications have is alerts. When a new message comes in, the user may not have our application active. They may have switched to another application, so we need to send an alert to notify the user that something is waiting for them. Each platform has their own way of doing this. In OS X, the dock icon will bounce. In Windows, the taskbar icon will flash. Sadly, neither Apple nor Microsoft has provided an API to do this from Java, so what to do?

    On the Mac, there is a third party library called Notifications, written by Gregory Guerin. His library uses JDirect, Apple's form of Java native integration, to call the OS X Carbon APIs that control the dock. It is thoughtfully released under the Artistic License, so we'll use it here.

    The Alert API uses an object called a Notification. This object can be configured and then posted to send the alert to the user. Once posted, the alert can berescinded to remove the alert and start over. Thenotification is actually an abstract object with concrete implementations below it. Gregory has provided implementations for Mac OS X and the Classic Mac OS. We just need to tell it which one to load. Here's the basic code to do it:

    Notification.SetFactory( "glguerin.notification.imp.mac.ten.TenNotification"); note = Notification.MakeOne(); note.setMarker(true); note.setIcon(0); note.setSound(0); note.setAlert(null);;

    First we initialize the factory with the correct implementation classname. Then we create a new Notification and configure it. For our purposes, we set the marker totrue so we get dock bouncing and turn off all other options. Once it's set up, we call post() to send it to the user. That's it!

    I should mention that this library only works under JDirect, not the Java Native Interface (JNI), which is provided as part of the 1.3 JDK (but not 1.4). It shouldn't matter how you compile, but make sure you use the 1.3 JVM to run it. Hopefully, Gregory will port it to JNI in the future. When we do the packaging in the next article, you will need to be sure to use the 1.3JavaApplicationStub.

    So how can we test this puppy? Since we aren't actually running on an IM network, we'll have to create a fake event with a Test Alert item in the Tools menu. This will tell the user that an alert will be sent in five seconds, then launch it. Here's some quick and dirty code to do it; nothing fancy, since it's just for testing. Note that the actual alert and screen updates are sent by an separate thread so that we don't block the main event thread.

    public class AlertListener implements ActionListener { public void actionPerformed(ActionEvent evt) { new Thread(new Runnable() { public void run() { try { Thread.currentThread().sleep(5000); } catch (Exception ex) { System.out.println("error: " + ex); } final XPHelper xp = XPFactory.getXPHelper(); if(xp.isMac()) { macAlert(); } } }).start(); } }

    Compile, package, and run, and we get Figure 8:

    The OS X Dock bouncing alert
    Figure 8. The OS X Dock bouncing alert

    That's it for the Mac side; not too bad. With some more work, we could package that all into one clean .jar and have a nice Java-Mac API that handles everything. Now on to Windows.

    Native User Alerts for Windows

    Things aren't so rosy here. No one has made a reusable object for controlling the window; it looks like we'll have to roll our own. Another Google search turns up the FlashWindowAPI. It's a Win32 function that flashes the task bar icon of the provided HWND (a Windows-specific object from the C/C++ world). To make this work, we will need to create a JNI library from scratch. Don't worry, it's not that hard. It just requires a little planning.

    To use JNI, we create a Java class with an empty method markednative. Then we create a C function to implement the empty Java method with native C code. Finally, we compile and link it into a native library. Our Java method will look like this:
    package org.joshy.jni; import java.awt.Component; import javax.swing.*; public class WindowUtil extends Canvas { static { System.loadLibrary("WindowUtil"); } public native void flash(Component c, boolean flash); }

    That's it. Just one method. We'll pass in a component contained in the window we want to flash, and then a Boolean to turn flashing on and off. The static initializer is to load the native library into which we will compile our C code.

    #include <assert.h> #include "jawt_md.h" #include "org_joshy_jni_WindowUtil.h" JNIEXPORT void JNICALL Java_org_joshy_jni_WindowUtil_flash( JNIEnv * env, jobject canvas, jobject component, jboolean bool) { /* do native stuff here */ }

    The C file has one function with a matching signature. The package and class names have been added to the method name to come up with Java_org_joshy_jni_JNITest_flash. TheJNIEnv is for extra environment information. Thecanvas is the object making the call (theWindowUtil class above) and the componentis the frame that we want to flash. The jboolean is for the flash variable.

    At the top of the C file, you'll notice three includes.assert.h and jawt_md.h are part of the JDK and the Windows SDK, both required for this code to compile.org_joshy_jni_WindowUtil.h is the header file of our Java API in Instead of writing it by hand, this header file is generated by javah, which we can call with the javah Ant task.

    <javah destdir="jni" class="org.joshy.jni.JNITest" classpath="jni"/>

    So far, so good. We've got a Java class, a C file, and a header to tie them together. Now let's make the C file do something. I found the following code on Sun's JNI forum; I've adapted it to make it a little safer.

    JAWT awt; JAWT_DrawingSurface* ds; JAWT_DrawingSurfaceInfo* dsi; JAWT_Win32DrawingSurfaceInfo* dsi_win; jboolean result; jint lock; // Get the AWT awt.version = JAWT_VERSION_1_3; result = JAWT_GetAWT(env, &awt); assert(result != JNI_FALSE); // Get the drawing surface ds = awt.GetDrawingSurface(env, component); if(ds == NULL) return; // Lock the drawing surface lock = ds->Lock(ds); assert((lock & JAWT_LOCK_ERROR) == 0); // Get the drawing surface info dsi = ds->GetDrawingSurfaceInfo(ds); // Get the platform-specific drawing info dsi_win = (JAWT_Win32DrawingSurfaceInfo*)dsi->platformInfo; FlashWindow(dsi_win->hwnd,bool); // Free the drawing surface info ds->FreeDrawingSurfaceInfo(dsi); // Unlock the drawing surface ds->Unlock(ds); // Free the drawing surface awt.FreeDrawingSurface(ds);

    This is mostly boilerplate. JAWT_GetAWT() lets us get a reference to the AWT subsystem, which we can get use to get a drawing surface. From there, we can pull out the drawing surface info and extract an hwnd, which is handle to the window itself. We can pass this handle to theFlashWindow function to actually flash the window. Once that's done, we clean up the surfaces and return. Now we just need to compile and link. C'est facile, non? Non!

    I'd just like to have a quick rant here. I hate dealing with JNI on Windows. Not that I hate JNI, but I hate C. Specifically, C compilers for Windows. I spent many hours trying to get this example to compile using GCC and later, CodeWarrior, not wanting to purchase Visual C++ just to compile a single library. It took way too much time on Google looking for help, trying to figure out linking errors, debugging DLL symbols, and generally making myself crazy. Oh how I longed to be back in the Java world, where I had only the classpath to worry about. Oh the humanity! Finally, when I gave up and asked a friend to compile it for me, I discovered that you can get Microsoft's command-line compiler for free as part of the .NET SDK. But to install that, you need the .NET framework runtime. And of course, you'll need the libraries and header files for Windows itself, so now download the Windows SDK. About three hundred megs of download later (I'm not kidding), I could compile this single file. Now I remember why I switched from C to Java eight years ago. As I go through the compilation step, I don't recommend you try this at home.

    cl.exe, Microsoft's command-line compiler, can do the compile and linking in one step, so we just need a single command to do it:

    cl.exe \ /Z7 /Od /nologo \ -I$JDK\\include \ -I$JDK\\include\\win32 \ -I"$MVS\\Vc7\\include" \ -I"$SDK\\include" \ WindowUtilImp.c \ -FeWindowUtil.dll \ -LDd \ -MD \ -link \ -libpath:"$SDK\\lib" \ -libpath:"$MVS\\Vc7\\lib" \ -libpath:"$JDK\\lib" \ jawt.lib user32.lib gdi32.lib

    Now doesn't that look easy!? :) Okay. I'll step through it. I have no idea what /Z7 /Od and /nologo do. (I pulled these from examples on the Web.) I think /Odis "optimize debug." I've created variables for the path to the Java SDK ($JDK), Microsoft's compiler ($MVS), and the Windows SDK ($SDK).-I adds an include directory, so I've added the include directories for JNI, the Windows-specific JNI, the compiler headers, and the standard Windows headers. The next two lines are the C file and the DLL we want to make. -LDd tellscl.exe that we want to link and build a DLL as well. I'm not sure about -MD, but many of the examples I based this on used it. -link tells the compiler to pass all of the arguments after it on to the linker, which is why I placed all of the library stuff after this argument. The-libpath: arguments add library paths, so we've pulled in the JNI libraries, the compiler libraries, and the Windows SDK libraries. Finally, we list the specific libraries we want included. It's very important that jawt.lib is in there, since it's the hook that lets the C side get a reference to theJFrame and extract an HWND.

    Okay. Now that we've worked out all of the compilation difficulties, we can drop it into a script and call it from Ant.

    <copy file="${scripts}/" todir="jni"/> <copy file="${csrc}/WindowUtilImp.c" todir="jni"/> <exec executable="bash" dir="jni"> <arg value=""/> </exec> <copy file="jni/WindowUtil.dll" todir="."/>

    This code copies the scripts and code into a temp directory, compiles it, and then copies the resulting DLL to the root directory.

    So, compile, link, run, and it works!

    Windows taskbar flashing
    Figure 9. Windows taskbar flashing

    This code only does a single flash. To make it a little bit more usable, I've added an overloaded function to theWindowUtil class that lets you set the time between the on and off of the flash and the time between flashes. It also loops count times. I've put it in a separate thread so that it won't block the event loop, but this isn't required.

    public void flash(final JFrame frame, final int intratime, final int intertime, final int count) { new Thread(new Runnable() { public void run() { try { // flash on and off each time for(int i=0; i<count; i++) { flash(frame,true); Thread.sleep(intratime); flash(frame,true); Thread.sleep(intertime); } // turn the flash off flash(frame,false); } catch (Exception ex) { System.out.println(ex.getMessage()); } }}).start(); }

    So, finally, we have a native method that flashes the window. While this works, it's a very cumbersome way to do it. It's my hope that in the future, Sun or the Java community will start building a cross-platform framework for features like these. Then we could all benefit from native hooks without having to invest so much time, bandwidth, or money in native compiler tools.

    The Beginning of our Transformation

    I hope you've enjoyed this first installment of the Native Swing series. We have covered getting the menus and toolkit to both look and behave the same as native applications. Then we added a native feature, visual alerts, for both the Windows and Macintosh versions. All of these create a more enjoyable experience for users of each platform.

    Special thanks to Kim Swartz of Squeaky Clean Designfor creating the consistent icon and logo used in the Mad Chatter.

    In the next article I will focus on packaging. We will use Ant and some third-party utilities to create platform-specific executables and add another native feature, file type associations. I hope you will join me for the next installment.

    Editor's Note: Downloadable sample code for this article and others in the series will be provided at the conclusion of the series. You can also participate in the Mad Chatter project, part of's Java Desktopcommunity.