Adding ExitListeners to Netbeans generated Desktop Application?

R

Rexx Magnus

I've got a bit of a problem with an application that I'm writing - I'm
using Netbeans 6.1 (JDK5). The app needs to be totally cross-platform, and
as such because I work on it under Mac, Windows and Linux, I don't want to
be bound to one particular platform to compile it.
The reason that this is an issue is because when I quit the application,
I'm catching the window close event and saving various settings to a file
before it exits. On Windows and Linux this should be fine, however on the
Mac, if you exit it using the menu or command-q, it bypasses the
windowclose event, therefore not saving the data.

I'm assuming that the generic catch-all is to add an ExitListener to the
application, but because of the way that the Netbeans project is written
and generated, I'm not sure where to put the relevant code. I've tried
overriding the shutdown() method in the (App) and putting in a simple
system.out.println statement to see if it catches it. That doesn't appear
to work.

I've also tried putting code in the (View) in order to attach the
ExitListener, much in the same way as the WindowListener, but that doesn't
seem to work either.

The main program code is stored in the (View), as are the variables and
methods I need to access on shutdown. When I've tried using methods in the
(App) class, I can't call them due to not actually having a handle on the
instance - I'm guessing it's because of the way that Netbeans sets up the
GUI with show(new DesktopApplication1View(this));

Any help would be appreciated!
I want to avoid using the menu hooks and OS detection to catch the use of
the Apple menus, because this requires importing of apple packages,
meaning I'll have to alter it if I compile on other platforms (I'm
desperately trying to keep this platform agnostic).




The code that usually comes up for a GUI is something like this (with my
edits in):
(View)
-------------------
public class DesktopApplication1View extends FrameView {

public DesktopApplication1View(SingleFrameApplication app) {
super(app);

initComponents();

//I've added this for the window listener and also to make it
non-resizable
this.getFrame().setResizable(false);
this.getFrame().setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
this.getFrame().addWindowListener(new Closer());

//status bar code etc. that I didn't need taken out.

}

//about box code here - something else I don't use
//along with all the other gui generated code.

//my window closer
public class Closer extends WindowAdapter{

@Override public void windowClosing(WindowEvent e){
System.out.println("saving");
saveFile();
System.exit(0);
}
}

}
-------------------

Then you've got the code that actually calls the gui code:
(App)
-------------------
package desktopapplication1;

import org.jdesktop.application.Application;
import org.jdesktop.application.SingleFrameApplication;

/**
* The main class of the application.
*/
public class DesktopApplication1 extends SingleFrameApplication {

/**
* At startup create and show the main frame of the application.
*/
@Override protected void startup() {
show(new DesktopApplication1View(this));
}

/**
* This method is to initialize the specified window by injecting
resources.
* Windows shown in our application come fully initialized from the GUI
* builder, so this additional configuration is not needed.
*/
@Override protected void configureWindow(java.awt.Window root) {
}

/**
* A convenient static getter for the application instance.
* @return the instance of DesktopApplication1
*/
public static DesktopApplication1 getApplication() {
return Application.getInstance(DesktopApplication1.class);
}

/**
* Main method launching the application.
*/
public static void main(String[] args) {
launch(DesktopApplication1.class, args);
}
}
-----------------
 
T

Thomas Kellerer

Rexx Magnus, 07.08.2008 11:41:
I want to avoid using the menu hooks and OS detection to catch the use
of the Apple menus, because this requires importing of apple packages,

Not necessarily. You can add a hook without needing the Apple libraries at compile time.
Of course if anything changes in the Apple interfaces this will stop working, but I don't think there is a way around that.

Here is the code.

I removed error handling and logging, but I guess you get the Idea.
I simply check for the OS at startup (before initializing any GUI!) and then call installApplicationHandler()


public class MacOSHelper
implements InvocationHandler
{
private Object proxy;

public MacOSHelper()
{
}

public void installApplicationHandler()
{
Class appClass = Class.forName("com.apple.eawt.Application");
Object application = appClass.newInstance();
if (application != null)
{
Class listener = Class.forName("com.apple.eawt.ApplicationListener");
this.proxy = Proxy.newProxyInstance(listener.getClassLoader(), new Class[] { listener },this);
Method add = appClass.getMethod("addApplicationListener", new Class[] { listener });
if (add != null)
{
// Register the proxy as the ApplicationListener. Calling events on the Listener
// will result in calling the invoke method from this class.
add.invoke(application, this.proxy);
}

// Now register for the Preferences... menu
Method enablePrefs = appClass.getMethod("setEnabledPreferencesMenu", boolean.class);
enablePrefs.invoke(application, Boolean.TRUE);
}
}

public Object invoke(Object prx, Method method, Object[] args)
throws Throwable
{
String methodName = method.getName();
if ("handleQuit".equals(methodName))
{
// According to the Apple docs, one should call ApplicationEvent.setHandled(false);
// in order to be able to cancel exiting.
// See http://developer.apple.com/samplecode/OSXAdapter/listing2.html
setHandled(args[0], false);
System.exit(0);
}
else if ("handleAbout".equals(methodName))
{
// Show about dialog
setHandled(args[0], true);
}
else if ("handlePreferences".equals(methodName))
{
// Show Options dialog
setHandled(args[0], true);
}
else
{
LogMgr.logInfo("MacOSHelper.invoke()", "Ignoring unknown event.");
}
return null;
}

private void setHandled(Object event, boolean flag)
{
Method setHandled = event.getClass().getMethod("setHandled", boolean.class);
setHandled.invoke(event, Boolean.valueOf(flag));
}

}
 
A

Andrew Thompson

Rexx Magnus, 07.08.2008 11:41:
..
...You can add a hook ..

How does the Mac. react to a Runtime.addShutDownHook(Thread)?

The docs state that one of the reasons the thread will
be invoked is "...in response to a user interrupt, such
as typing ^C, or a system-wide event, such as user logoff
or system shutdown."
 
T

Thomas Kellerer

Andrew Thompson, 07.08.2008 12:22:
How does the Mac. react to a Runtime.addShutDownHook(Thread)?

The docs state that one of the reasons the thread will
be invoked is "...in response to a user interrupt, such
as typing ^C, or a system-wide event, such as user logoff
or system shutdown."

I think that (the shutdown hook) will only be called if you explicitely call System.exit().
To my understanding this was not what the user wanted, but he rather wanted to hook into the system menus from the OS.

I have no Mac, but the code I posted seems to work fine. One Mac user tested it, and he said that the menu items from the Mac System menu work fine.

Thomas
 
R

Rexx Magnus

Andrew Thompson, 07.08.2008 12:22:

I think that (the shutdown hook) will only be called if you explicitely
call System.exit(). To my understanding this was not what the user
wanted, but he rather wanted to hook into the system menus from the OS.

I have no Mac, but the code I posted seems to work fine. One Mac user
tested it, and he said that the menu items from the Mac System menu work
fine. Thomas

Actually, now I've looked at it in a bit more detail, I've managed to get
it to work. Of course, because Netbeans generates the menus and sticks
guarded code in, I can't prevent it from creating them in the first place
on the Mac. Maybe there's a way I can hide them after initial creation if
it's running on a Mac.

Thanks for the help!
 
J

John B. Matthews

Andrew Thompson said:
If the problem statement requires you to mention
your IDE, it might be best to go to a forum specific
to the IDE.
<http://www.netbeans.org/community/lists/top.html>

Capital idea; but in this case, the OP is mistaken: the problem has
nothing to do with NetBeans. The solution, which I proposed here

<http://groups.google.com/group/comp.lang.java.gui/msg/a13d5b6cb4daec37?h
l=en>

builds and runs fine with the included ant script:

<http://developer.apple.com/samplecode/OSXAdapter/index.html>
 
J

John B. Matthews

"Rexx Magnus said:
Indeed it did, I was barking up the wrong tree at the time.

Ah, I see. The problem isn't NetBeans itself; it's (protected) GUI code
generated by NetBeans. You only need to invoke setQuitHandler(), but you
need to do it in the JFrame's constructor.

FWIW, the ant target "package," which builds a Mac application bundle,
is somewhat more transparent than the corresponding Xcode facility.

I look forward to hearing how this progresses.
 
D

Daniele Futtorovic

I've got a bit of a problem with an application that I'm writing - I'm
using Netbeans 6.1 (JDK5). The app needs to be totally cross-platform,
and as such because I work on it under Mac, Windows and Linux, I don't
want to be bound to one particular platform to compile it.
The reason that this is an issue is because when I quit the application,
I'm catching the window close event and saving various settings to a
file before it exits. On Windows and Linux this should be fine, however
on the Mac, if you exit it using the menu or command-q, it bypasses the
windowclose event, therefore not saving the data.

I assume "windowclose event" means WindowListener#windowClosed(WindowEvent)

Out of curiosity, does it also "bypass"
WindowListener#windowClosing(WindowEvent) ?
 
R

Rexx Magnus

Ah, I see. The problem isn't NetBeans itself; it's (protected) GUI code
generated by NetBeans. You only need to invoke setQuitHandler(), but you
need to do it in the JFrame's constructor.

FWIW, the ant target "package," which builds a Mac application bundle,
is somewhat more transparent than the corresponding Xcode facility.

I look forward to hearing how this progresses.

It's proven to be simpler than expected (for handling Mac menu events,
that is). Instructions on how I got the hooks to work easily are below.
Right at the bottom is the problem I still haven't yet fixed (regards to
catching the close action).

In the variables section of your application's GUI class (usually
something-View in the case of netbeans):

-----
public static boolean MAC_OS_X =
(System.getProperty("os.name").toLowerCase().startsWith("mac os x"));
final static int MENU_MASK =
Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
-----

They detect which OS you're running, and the format of the shortcut keys
(not sure how necessary the latter is).

After the netbeans generated Code at the beginning of your GUI
constructor...
-----
public MyAppView(SingleFrameApplication app) {
super(app);
initComponents();
-----

I simply place:
-----
registerForMacOSXEvents();
if (MAC_OS_X){
//hide the menus if it's running on a Mac.
menuBar.removeAll();
}
-----

This enables you to check at runtime whether you're running on a Mac and
then destroy the menu so it fits the usual look and feel of Mac OS X. It's
a bit of a kludge, but it seemed to be the only way of removing the menus
since the construction code is guarded (not that I'm complaining).



In the IDE generated code for the GUI (after the constructor, not within
it obviously), you can simply insert the registerForMacOSXEvents() method
and then include the OSXAdapter.java class in your project, replacing the
default package name with the one for your project.
That then provides for the About, Preferences, File (open) and Quit menus
which are the usual items for Mac apps.

My version of the method is trimmed down, as I only need a Quit/Exit and
About menu:
-----
public void registerForMacOSXEvents() {
if (MAC_OS_X) {
try {
// Generate and register the OSXAdapter, passing it a hash
of all the methods we wish to
// use as delegates for various
com.apple.eawt.ApplicationListener methods
OSXAdapter.setQuitHandler(this,
getClass().getDeclaredMethod("exitProperly", (Class[])null));
OSXAdapter.setAboutHandler(this,
getClass().getDeclaredMethod("about", (Class[])null));
} catch (Exception e) {
System.err.println("Error while loading the OSXAdapter:");
e.printStackTrace();
}
}
}
-----
The OSXAdapter.setXXXXHandler methods simply get given the actual name of
the method you wish to call from your GUI's class. In my example, I'm
calling exitProperly() - note that there are no brackets in the string
passed as the method names above.



The problem I'm having now is that when I have attached a WindowListener
which then points to my exitProperly() method, it carries out those
actions, but is still closing the window if I add a "Do you wish to quit?"
dialog. It seems that the default close action is not being set to do
nothing.
I had the following code just after the initComponents() line:
-----
this.getFrame().setResizable(false);
this.getFrame().setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
this.getFrame().addWindowListener(new Closer());

//I've also tried this, but this gives an error saying it can't be
applied.
//app being mentioned at the start of the constructor and as super(app);
app.addExitListener(new Closer());
-----

Now this works fine for the non-resizing window and also adds the
listener, which gets executed when you close the window. However it is
still exiting the program after performing my specified task in the
exitProperly() method. The only reason I can see for this is that I am not
setting the default close operation for the right 'thing'. If I could work
out what to apply the default close operation to, I won't need to add an
exitlistener (which I don't think would help, as I need to be able to
totally abort the close operation).
The official Mac example is of no use as it basically closes the window,
but not the menu when you shut it (via the window close button) - then if
you quit via the menu which is the normal mac method for multiwindow apps,
it pops back up but then closes. They don't seem to have catered for
single-window applications in that regard, but that's because it basically
uses the method I have below - it seems that if it returns true, it quits,
if it doesn't then it doesn't quit, or at least it shouldn't - if you
override the default close operation.

I am truly poking around in the dark on this one, as I don't understand
the way that Netbeans has formed the application.

-----
public boolean exitProperly() {
boolean val = false;
if ((JOptionPane.showConfirmDialog(null, "Are you sure?", "Quit",
JOptionPane.YES_NO_OPTION)) == JOptionPane.YES_OPTION) {
val = true;
saveFile();
System.out.println("yes came up, I saved");
System.exit(0);
} else {
System.out.println("no came up");
}

return val;
}

public class Closer extends WindowAdapter {

@Override
public void windowClosing(WindowEvent e) {
System.out.println("Windowclosing");
exitProperly();
}
}
-----
 
D

Daniele Futtorovic

-----
public boolean exitProperly() {
boolean val = false;
if ((JOptionPane.showConfirmDialog(null, "Are you sure?",
"Quit", JOptionPane.YES_NO_OPTION)) == JOptionPane.YES_OPTION) {
val = true;
saveFile();
System.out.println("yes came up, I saved");
System.exit(0);

Don't exit a GUI via System.exit(). dispose() its main Window.
 
R

Rexx Magnus

I assume "windowclose event" means
WindowListener#windowClosed(WindowEvent)

Out of curiosity, does it also "bypass"
WindowListener#windowClosing(WindowEvent) ?

The menu method of exiting does indeed bypass the
WindowClosing(WindowEvent). I haven't actually checked for WindowClosed,
as I need to be able to stop it from closing now if I so wish.
I've hooked the menu to an exit method now, so it performs the actions I
need before exiting, but with the code generated by Netbeans, I can't find
out how to do setDefaultCloseOperation, as applying this to the main
frame of the application seems to have no effect (it gets set, but it
still closes after doing the exit routine, even if you're not calling
System.exit(0).
 
D

Daniele Futtorovic

I've hooked the menu to an exit method now, so it performs the actions I
need before exiting, but with the code generated by Netbeans, I can't
find out how to do setDefaultCloseOperation, as applying this to the
main frame of the application seems to have no effect

Look, you're not going to run your application in NetBeans, ultimately.
So if the GUI builder causes trouble, scrap it. Find the main and start
that.
 
J

John B. Matthews

[...]
After the netbeans generated Code at the beginning of your GUI
constructor...
-----
public MyAppView(SingleFrameApplication app) {
super(app);
initComponents();
-----

I simply place:
-----
registerForMacOSXEvents();
if (MAC_OS_X){
//hide the menus if it's running on a Mac.
menuBar.removeAll();
}
-----

This enables you to check at runtime whether you're running on a Mac and
then destroy the menu so it fits the usual look and feel of Mac OS X. It's
a bit of a kludge, but it seemed to be the only way of removing the menus
since the construction code is guarded (not that I'm complaining).
[...]

Instead of removing the menu bar, why not put your menus and dock name
at the top of the screen in OSXAdapter.app/Contents/Info.plist:

<key>Java</key>
<dict>
<key>ClassPath</key>
<string>$JAVAROOT/OSXAdapter.jar</string>
<key>JVMVersion</key>
<string>1.5+</string>
<key>MainClass</key>
<string>apple.dts.samplecode.osxadapter.MyApp</string>
<key>Properties</key>
<dict>
<key>apple.laf.useScreenMenuBar</key>
<string>true</string>
</dict>
<key>VMOptions</key>
<string>-Xdock:name=MyApp</string>
</dict>

Or, from the command line:

java -Xdock:name=MyApp -Dapple.laf.useScreenMenuBar=true \
-jar jars/OSXAdapter.jar
 
R

Rexx Magnus

John said:
[...]
After the netbeans generated Code at the beginning of your GUI
constructor...
-----
public MyAppView(SingleFrameApplication app) {
super(app);
initComponents();
-----

I simply place:
-----
registerForMacOSXEvents();
if (MAC_OS_X){
//hide the menus if it's running on a Mac.
menuBar.removeAll();
}
-----

This enables you to check at runtime whether you're running on a Mac and
then destroy the menu so it fits the usual look and feel of Mac OS X. It's
a bit of a kludge, but it seemed to be the only way of removing the menus
since the construction code is guarded (not that I'm complaining).
[...]

Instead of removing the menu bar, why not put your menus and dock name
at the top of the screen in OSXAdapter.app/Contents/Info.plist:

<key>Java</key>
<dict>
<key>ClassPath</key>
<string>$JAVAROOT/OSXAdapter.jar</string>
<key>JVMVersion</key>
<string>1.5+</string>
<key>MainClass</key>
<string>apple.dts.samplecode.osxadapter.MyApp</string>
<key>Properties</key>
<dict>
<key>apple.laf.useScreenMenuBar</key>
<string>true</string>
</dict>
<key>VMOptions</key>
<string>-Xdock:name=MyApp</string>
</dict>

Or, from the command line:

java -Xdock:name=MyApp -Dapple.laf.useScreenMenuBar=true \
-jar jars/OSXAdapter.jar

The menus appear at the top by using the OSXAdapter anyway, without
further modifications, so I'm happy with that. I'll have a look at your
method for renaming the app though.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top