I'm writing an Eclipse RCP and I want to ask the user whether to backup the database when the application is closed. Doing it from the File>Exit menu was easy as I defined a command exit:
public class ExitCommand extends AbstractHandler implements IHandler {
#Override
public Object execute(ExecutionEvent event) throws ExecutionException {
final IWorkbench workbench = PlatformUI.getWorkbench();
if (workbench == null)
return null;
// Ask whether the user wants to back up the information
Shell shell = new Shell(workbench.getDisplay());
MessageBox messageBox = new MessageBox(shell, SWT.ICON_QUESTION
| SWT.YES | SWT.NO);
messageBox.setMessage("You are leaving CatSysPD. Do you want to make a backup of the DataBase? (recommended)");
messageBox.setText("On Exit Backup");
int response = messageBox.open();
if (response == SWT.YES){
new BackupDataBaseAction(shell);
}
final Display display = workbench.getDisplay();
display.syncExec(new Runnable() {
public void run() {
if (!display.isDisposed())
workbench.close();
}
});
return null;
}}
I then linked this to a menu entry called Exit and this work right. However the user could close the application also by pressing the "close window" button. Is there any way of catching this event?
I found a suggestion in a previous topic (see here) using a shutdownHook. However the thread that I want execute has to open a dialog and, as I understand, this cannot be done by an external thread.
Thank you!
Edit
I add here the code for the shutdownHook I'm using. In the Application class:
public class Application implements IApplication {
final double NIDAQmxPortingVersionDependency = 1.001;
public final static String PLUGIN_ID = "CatsysPD";
private static Logger logger = Logger.getLogger(Application.class
.toString());
/*
* (non-Javadoc)
*
* #see org.eclipse.equinox.app.IApplication#start(org.eclipse.equinox.app.
* IApplicationContext)
*/
public Object start(IApplicationContext context) {
logger.info("Starting the application");
Display display = PlatformUI.createDisplay();
systemCheck(display);
initializeApplication(display);
try {
int returnCode = PlatformUI.createAndRunWorkbench(display,
new ApplicationWorkbenchAdvisor());
if (returnCode == PlatformUI.RETURN_RESTART) {
return IApplication.EXIT_RESTART;
}
BackupOnExitHook backupOnExitHook = new BackupOnExitHook(PlatformUI.getWorkbench().getDisplay());
Runtime.getRuntime().addShutdownHook(backupOnExitHook);
return IApplication.EXIT_OK;
} finally {
display.dispose();
}
}
private void systemCheck(Display display) {...}
public void stop() {...}
public void initializeApplication(Display display) {...}
private class BackupOnExitHook extends Thread {
private Display display;
public BackupOnExitHook(Display display){
this.display = display;
}
#Override
public void run(){
display.syncExec(new Runnable(){
#Override
public void run() {
MessageBox messageBox = new MessageBox(new Shell(display), SWT.ICON_QUESTION
| SWT.YES | SWT.NO);
messageBox.setMessage("You are leaving CatSysPD. Do you want to make a backup of the DataBase? (recommended)");
messageBox.setText("On Exit Backup");
int response = messageBox.open();
if (response == SWT.YES){
new BackupDataBaseAction(new Shell(display));
}
}});
}
}
}
The error I get when I try to run it is:
Exception in thread "Thread-5" org.eclipse.swt.SWTException: Device is disposed
at org.eclipse.swt.SWT.error(SWT.java:4083)
at org.eclipse.swt.SWT.error(SWT.java:3998)
at org.eclipse.swt.SWT.error(SWT.java:3969)
at org.eclipse.swt.widgets.Display.error(Display.java:1249)
at org.eclipse.swt.widgets.Display.syncExec(Display.java:4581)
at dk.catsys.pd.Application$BackupOnExitHook.run(Application.java:128)
Thanks again.
How about preWindowShellClose from WorkbenchWIndowAdvisor?
http://help.eclipse.org/helios/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/ui/application/WorkbenchWindowAdvisor.html
Related
I'm using the current version of SWT to build my applications and I want to run it under Mac OS X (Yosemite).
My problem is now that I'm not be able to capture clicks on the "About", "Preferences" and "Quit" menu items which were automatically added to my application.
I already searched a lot and found the following class which seems very helpful to me http://www.transparentech.com/files/CocoaUIEnhancer.java.
And that's my code to initialize it:
import org.eclipse.swt.*;
import org.eclipse.swt.widgets.*;
public class Test {
private Display display;
private Shell shell;
public Test(Display display) {
this.display = display;
initUI();
}
public void open() {
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
private void initUI() {
shell = new Shell(display);
shell.setSize(808, 599);
shell.setText("Test");
AboutHandler aboutHandler = new AboutHandler();
PreferencesHandler preferencesHandler = new PreferencesHandler();
QuitHandler quitHandler = new QuitHandler();
CocoaUIEnhancer uienhancer = new CocoaUIEnhancer("Test");
uienhancer.hookApplicationMenu(display, quitHandler, aboutHandler, preferencesHandler);
}
private class AboutHandler implements Listener {
public void handleEvent(Event e) {
}
}
private class PreferencesHandler implements Listener {
public void handleEvent(Event e) {
}
}
private class QuitHandler implements Listener {
public void handleEvent(Event e) {
}
}
}
I can compile it without any errors but if I start the program then I will get the following exception:
Exception in thread "main" java.lang.NoSuchMethodError: actionProc
at org.eclipse.swt.internal.Callback.bind(Native Method)
at org.eclipse.swt.internal.Callback.<init>(Unknown Source)
at org.eclipse.swt.internal.Callback.<init>(Unknown Source)
at org.eclipse.swt.internal.Callback.<init>(Unknown Source)
at CocoaUIEnhancer.initialize(CocoaUIEnhancer.java:124)
at CocoaUIEnhancer.hookApplicationMenu(CocoaUIEnhancer.java:92)
at Test.initUI(Test.java:50)
at Test.<init>(Test.java:18)
It's probably an error in the native libraries but I can't figure it out!
I didn't use CocoaUIEnhancer at all, as it was causing issues as well.
So here's what I ended up doing in my applications:
/**
* Convenience method that takes care of special menu items (About, Preferences, Quit)
*
* #param name The name of the menu item
* #param parent The parent {#link Menu}
* #param listener The {#link Listener} to add to the item
* #param id The <code>SWT.ID_*</code> id
*/
private void addMenuItem(String name, Menu parent, Listener listener, int id)
{
if (OSUtils.isMac())
{
Menu systemMenu = Display.getDefault().getSystemMenu();
for (MenuItem systemItem : systemMenu.getItems())
{
if (systemItem.getID() == id)
{
systemItem.addListener(SWT.Selection, listener);
return;
}
}
}
/* We get here if we're not running on a Mac, or if we're running on a Mac, but the menu item with the given id hasn't been found */
MenuItem item = new MenuItem(parent, SWT.NONE);
item.setText(name);
item.addListener(SWT.Selection, listener);
}
Just call it with SWT.ID_PREFERENCES, SWT.ID_ABOUT and SWT.ID_QUIT respectively. Hand in a fallback menu item name, a fallback Menu and the actual Listener you want to add to the menu item.
So for example:
addMenuItem("Quit", myMenu, new Listener()
{
#Override
public void handleEvent(Event event)
{
// Close database connection for example
}
}, SWT.ID_QUIT);
It looks like this the actionProc
int actionProc( int id, int sel, int arg0 )
in CocoaUIEnhancer probably needs to use long rather than int for the arguments to work with 64 bit SWT.
You need to modify CocoaUIEnhancer.java, to make it work with pure SWT application as described in this tutorial:
Modify the getProductName() method to return a String when no product is found (instead of null)
Wrap the code in hookWorkbenchListener() in a try-catch (IllegalStateException e) block
Wrap the code in modifyShells() in a try-catch (IllegalStateException e) block
Add some code to the actionProc(...) method, to bring up an About-Dialog and Preferences-Dialog (since we aren’t using commands):
static long actionProc(long id, long sel, long arg0) throws Exception {
// ...
else if (sel == sel_preferencesMenuItemSelected_) {
showPreferences();
} else if (sel == sel_aboutMenuItemSelected_) {
showAbout();
}
return 0;
}
private static void showAbout() {
MessageDialog.openInformation(null, "About...",
"Replace with a proper about text / dialog");
}
private static void showPreferences() {
System.out.println("Preferences...");
PreferenceManager manager = new PreferenceManager();
PreferenceDialog dialog = new PreferenceDialog(null, manager);
dialog.open();
}
// ...
Finally, we add the following lines to our main() method:
public static final String APP_NAME = "MyApp";
public static void main(String[] args) {
//in your case change the Test constructor
Display.setAppName(APP_NAME);
Display display = Display.getDefault();
//insert in initUI method call the earlysetup
if (SWT.getPlatform().equals("cocoa")) {
new CocoaUIEnhancer().earlyStartup();
}
Shell shell = new Shell(display);
shell.setText(APP_NAME);
...
}
Quoted code.
Baz's solution works great! If you'd rather not import OSUtils just to test if you are on a Mac, use instead:
System.getProperty("os.name").contentEquals("Mac OS X")
I am struggling with a problem using TinySound (http://finnkuusisto.github.io/TinySound/). I've made a method to play a sound (i've implemented the Music class, since it allows to be played without a thread sleep limiter). My problem is that the "Play" button in my GUI can be spammed, resulting in the sound or music being played in a stack. I've checked out the setMultiClickThreshold in the Java API, but this do not solve my problem (You never know how long the sound or music-file is going to be).
Has anyone used TinySound, or know a workaround this challenge?
Here is the code for the method (I will provide more if necessary):
public void playSound(String filePath) {
soundFile = new File(filePath);
TinySound.init();
Music sound = TinySound.loadMusic(soundFile);
sound.play(false);
while(sound.done()) {
TinySound.shutdown();
}
}
Consider using a SwingWorker, disabling the JButton on button press, and re-enabling it when the SwingWorker has completed its actions. The re-enabling could be done within a PropertyChangeListener that has been added to your Swingworker and that responds to a PropertyChangeEvent.newValue() of SwingWorker.StateValue.DONE.
For example, your code could look something like,....
public class SwingworkerEg {
// .....
public void playSound(String filePath) {
soundFile = new File(filePath);
TinySound.init();
Music sound = TinySound.loadMusic(soundFile);
sound.play(false);
while (sound.done()) {
TinySound.shutdown();
}
}
// The JButton or menu item's Action or ActionListener class
private class PlayAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
// disable the button or menu item
setEnabled(false);
// create worker to play music in a background thread
// pass in the file path
PlayWorker playWorker = new PlayWorker(filePath);
// listen for when the worker thread is done
playWorker.addPropertyChangeListener(new PlayWorkerListener(this));
// execute the worker (in a background thread)
playWorker.execute();
}
}
// To listen for when the worker is done
class PlayWorkerListener implements PropertyChangeListener {
private PlayAction playAction;
// pass in the Action so we can re-enable it when done
public PlayWorkerListener(PlayAction playAction) {
this.playAction = playAction;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
// if the worker is done
if (evt.getNewValue().equals(SwingWorker.StateValue.DONE)) {
// re-enable the button
playAction.setEnabled(true);
}
}
}
// this is to call playSound in a background thread
class PlayWorker extends SwingWorker<Void, Void> {
private String filePath;
// pass in the file path String
public PlayWorker(String filePath) {
this.filePath = filePath;
}
#Override
protected Void doInBackground() throws Exception {
// this is called in a background thread
playSound(filePath);
return null;
}
}
}
Here's a trivial working example:
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
#SuppressWarnings("serial")
public class SwingWorkerEg2 extends JPanel {
private JSpinner spinner = new JSpinner(new SpinnerNumberModel(3, 3, 10, 1));
public SwingWorkerEg2() {
add(new JLabel("Seconds to wait:"));
add(spinner);
add(new JButton(new FooAction("Please Press Me!")));
}
// The JButton or menu item's Action or ActionListener class
private class FooAction extends AbstractAction {
public FooAction(String name) {
super(name); // set button name
int mnemonic = (int) name.charAt(0); // get first letter as int
putValue(MNEMONIC_KEY, mnemonic); // set button mnemonic for first letter
}
#Override
public void actionPerformed(ActionEvent e) {
// disable the button or menu item
setEnabled(false);
int spinnerValue = ((Integer) spinner.getValue()).intValue();
// create worker to play music in a background thread
FooWorker playWorker = new FooWorker(spinnerValue);
// listen for when the worker thread is done
playWorker.addPropertyChangeListener(new FooWorkerListener(this));
// execute the worker (in a background thread)
playWorker.execute();
}
}
// To listen for when the worker is done
class FooWorkerListener implements PropertyChangeListener {
private FooAction fooAction;
// pass in the Action so we can re-enable it when done
public FooWorkerListener(FooAction fooAction) {
this.fooAction = fooAction;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
// if the worker is done
if (evt.getNewValue().equals(SwingWorker.StateValue.DONE)) {
// re-enable the button
fooAction.setEnabled(true);
}
}
}
// this is to call count down in a background thread
class FooWorker extends SwingWorker<Void, Void> {
private int spinnerValue;
// pass in the file path String
public FooWorker(int spinnerValue) {
this.spinnerValue = spinnerValue;
}
#Override
protected Void doInBackground() throws Exception {
for (int i = 0; i < spinnerValue; i++) {
System.out.println("count is: " + i);
Thread.sleep(1000);
}
System.out.println("count is: " + spinnerValue);
return null;
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SwingWorker Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SwingWorkerEg2());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Make sure to read Concurrency in Swing for more on how to use SwingWorkers
Got it to work! Thank you very much! I will try to implement it in my ActionController class, it is a bit messy keeping everything in the same method in the SoundHandler ;)
here is the final working SoundHandler:
import java.io.File;
import javax.swing.SwingWorker;
import kuusisto.tinysound.Music;
import kuusisto.tinysound.TinySound;
import imports.ActionController;
import imports.GUI;
/**
* This class handles the playing of the sound and extends SwingWorker so that
* the JFrame do not freeze when the sound is played.
*
* #author Gaute Gjerlow Remen
* #version 1.0
*/
public class SoundHandler extends SwingWorker<Void, Void> {
private GUI gui;
private ActionController actionController;
private File soundFile;
public SoundHandler() {
actionController = new ActionController(this);
gui = new GUI(actionController);
}
/**
* Plays the sound file in another thread
* #param filePath
* #throws Exception if the thread is interrupted
* #return null when doInBackground is finished
*/
public void playSound(String filePath) {
soundFile = new File(filePath);
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
#Override
protected Void doInBackground() throws Exception {
TinySound.init();
Music sound = TinySound.loadMusic(soundFile);
sound.play(false);
while (!sound.done()) {
gui.unablePlayButton();
}
gui.enablePlayButton();
TinySound.shutdown();
return null;
}
};
worker.execute();
}
/**
* #return file opened in the GUI
*/
public String openFile() {
return gui.openFile();
}
/**
* Calls the about window in GUI
*/
public void showAbout() {
gui.showAbout();
}
/**
* Calls the showNoSong window in GUI
*/
public void showNoSong() {
gui.showNoSong();
}
/**
* Calls the changeSongLabel window in GUI
*/
public void changeSongLabel() {
gui.changeSongLabel();
}
/**
* A empty method made only for the extending of the class
*/
#Override
protected Void doInBackground() throws Exception {
// TODO Auto-generated method stub
return null;
}
}
I borrowed a design that I found on stackoverflow to redirect console output to a GUI. It worked fine until I started reading from text files in my program. Now when I run the program using the GUI no output is displayed and the GUI freezes and then closes on its own eventually. Here is a slimmed down version of my GUI code:
public class GreenhouseControlsGUI {
public static class MyGui extends JFrame implements ActionListener {
// Start button
JButton Start = new JButton("Start");
/..................................../
/**
* The constructor.
*/
public MyGui(){
super("Greenhouse Controls");
/............................../
bottomPanel.setLayout(new FlowLayout());
bottomPanel.add(Start);
/............................../
getContentPane().add(holdAll, BorderLayout.CENTER);
Start.addActionListener(this);
/............................../
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}
public void actionPerformed(ActionEvent e){
if (e.getSource() == Start)
GreenhouseControls.startMeUp(); // Start program...
/............................../
}
public static void main(String[] args){
MyGui myApplication = new MyGui();
// redirect output to GUI
myApplication.redirectSystemStreams();
// Specify where will it appear on the screen:
myApplication.setLocation(10, 10);
myApplication.setSize(500, 300);
// Show it!
myApplication.setVisible(true);
}
private void updateTextArea(final String text) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myText.append(text);
}
});
}
private void redirectSystemStreams() {
OutputStream out = new OutputStream() {
#Override
public void write(int b) throws IOException {
updateTextArea(String.valueOf((char) b));
}
#Override
public void write(byte[] b, int off, int len) throws IOException {
updateTextArea(new String(b, off, len));
}
#Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
};
System.setOut(new PrintStream(out, true));
System.setErr(new PrintStream(out, true));
}
}
}
I'm pretty sure my problem starts with reading from the 'filename' path in the code below because I don't have this problem when I comment the 'filename' variable declaration out. I thought the methods to redirect console output to my GUI were only redirecting output.... Why is it screwing everything up when I read from a file? I am new to programming and I have probably overlooked something obvious, but I can't figure it out.
Here is the static startMeUp() method invoked inside the GUI class:
public static void startMeUp() {
try {
String option = "-f"; // (-f) to start program or (-d) to restore program
filename = "src/greenhouse/examples3.txt"; // read file from here
dumpFile = "dump.out"; // restore program from here
// if option is invalid invoke corresponding print statement
if ( !(option.equals("-f")) && !(option.equals("-d")) ) {
System.out.println("Invalid option");
printUsage();
}
GreenhouseControls gc = new GreenhouseControls(); // Create GreenhouseControls object
Restart restart = new Restart(0,filename, gc); // Create Restart object
Restore restore = new Restore(0,dumpFile); // Create Restore object
// if the option value is -f ...
if (option.equals("-f")) {
gc.addEvent(restart); // add new Restart object to addEvent()
}
gc.run();
// if the option value is -d ...
if (option.equals("-d")) {
gc.addEvent(restore); // add new Restore object to addEvent()
}
gc.run();
}catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Invalid number of parameters");
printUsage();
}
}
You need to invoke startMeUp in a new thread, because your console program is blocking the event dispatch thread. Like this:
new Thread () {
#Override public void run () {
GreenhouseControls.startMeUp();
}
}.start();
instead of just
GreenhouseControls.startMeUp();
I have a customized JFileChooser
Its approveSelection() method is slightly modified:
public void approveSelection()
{
File file = getSelectedFile();
changeGui();
final Object a = makeALongCalcualtion(file);
if (a != null)
{
super.approveSelection();
SwingWorker<Document, Void> open = new SwingWorker<Document, Void>()
{
#Override
protected Document doInBackground() throws Exception
{
return createADocument(a);
}
#Override
protected void done()
{
try
{
if(get() != null)
{
changeGui();
}
else
{
//TODO error message
changeGui();
}
}
catch (InterruptedException | ExecutionException e)
{
//TODO error message
changeGui();
}
}
};
open.execute();
}
else
{
//TODO error message
changeGui();
}
}
The changeGui() method sets a JProgressBar to indeterminate and updates a JLabel with a new string.
If file provided to makeALongCalcualtion(file) is of invalid type, it will return null, otherwise it returns info that is passed to SwingWorker which can use it to acutally create the representation of a file in the program (the Document object).
However, this doesn't work as it should because makeALongCalcualtion(file) isn't called within SwingWorker method, and that blocks the EDT.
In order to fix the problem, I would have to call makeALongCalcualtion(file) within a SwingWorker. I could move that part of the code into the SwingWorker without any problems, but then I would have to (due to my code logic) move super.approveSelection() along with it.
So the bottom line is, how do I call super.approveSelection() from within doInBackground() for this specific case?
//More info
What is supposed to happen:
User selects and opens a file
JLabel and JProgressBar are updated, indeterminate progress starts to play.
If makeALongCalcualtion(file) return null user is warned with an error window, but the JFileChooser stys open, making it possible to choose again when the error window is closed.
Otherwise, super.approveSelection() is called, allowing the chooser to close.
A document is created (but the method that creates the document return null if something goes wrong).
If everything is fine, JLabel updates and progressBar animation is stopped (indeterminate is set to false).
If something goes wrong same thing happens as in step 6, only with different message in JLabel.
What happens:
same
same
same, but when makeALongCalculation(file); begins, progressBar freezes.
same
same
same, but the animation isn't stopped (since the progressbar is frozen), only the frozen "picture" is removed and progressbar returns to it's previous state.
same
EDIT
I have made some alterations to my program and I now have this:
approveSelection():
public void approveSelection()
{
File file = getSelectedFile();
Main.getStatusBar().startOpen();
final WorkOpen open = new WorkOpen(file);
open.execute();
open.addPropertyChangeListener(new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent evt) {
if ("state".equals(evt.getPropertyName())) {
if (evt.getNewValue().equals("DONE"))
{
if (open.failed())
{
//TODO error message
Main.getStatusBar().endOpen(false);
}
else
{
Main.getStatusBar().endOpen(true);
}
}
}
}
});
}
SwingWorker:
class WorkOpen extends SwingWorker<Document, Void>
{
boolean failed = false;
File file;
public boolean failed()
{
return failed;
}
#Override
protected Document doInBackground() throws Exception
{
ArrayList<String> data = Opener.extractData(file);
if (data != null)
{
//My little path/name/similar managing system
FileComplex fullPath = new FileComplex(file.toString());
return Opener.createDocument(fullPath.getFullName(), fullPath.getFullPath(), data);
}
else
{
failed = true;
return null;
}
}
#Override
protected void done()
{
try
{
if(get() != null)
{
Main.addDocument(get());
}
}
catch (InterruptedException | ExecutionException e)
{
failed = true;
}
}
WorkOpen(File file)
{
this.file = file;
}
}
The problem now is where to call super.approveSelection(). It has to wait for the worker to finish executing, yet I can't call it from the property change listener.
What to do here?
EDIT 2
As HovercraftFullOfEels suggested, I fixed my code and it compiled and ran. But the problem of JProgressBar freezeing remained. Also, I had to introduce something I don't know if I should have:
private void superApproveSelection()
{
super.approveSelection();
}
public void approveSelection()
{
final File file = getSelectedFile();
class OpenWorker extends SwingWorker<Boolean, Void>
{
Document document;
Document getDocument()
{
return document;
}
#Override
protected Boolean doInBackground() throws Exception
{
ArrayList<String> data = Opener.extractData(file);
if (data != null)
{
//I had to start the progressBar here, because if invalid
//file was selected (extractData(file) returns null if it was),
//nothing should happen (maybe an error
//window later, handled with a new Runnable() same as this:
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
Main.getStatusBar().startOpen();
}
});
FileComplex fullPath = new FileComplex(file.toString());
document = Opener.createDocument(fullPath.getFullName(), fullPath.getFullPath(), data);
return true;
}
else
{
return false;
}
}
};
final OpenWorker opener = new OpenWorker();
opener.addPropertyChangeListener(new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent evt)
{
if ("state".equals(evt.getPropertyName()))
{
if (evt.getNewValue() == SwingWorker.StateValue.DONE)
{
if(opener.getDocument() != null)
{
superApproveSelection();
Main.addDocument(opener.getDocument());
Main.getStatusBar().endOpen(true);
}
else
{
try
{
//I'm retrieveing doInBackground()'s value to see if
//progressBar needs stoping (it also displays some
//text, so it must not be called unless the
//progressBar was started).
if (opener.get())
{
Main.getStatusBar().endOpen(false);
}
}
catch (InterruptedException | ExecutionException e)
{
//TODO error something went wrong
}
}
}
}
}
});
opener.execute();
}
"In order to fix the problem, I would have to call makeALongCalcualtion(file) within a SwingWorker. I could move that part of the code into the SwingWorker without any problems, but then I would have to (due to my code logic) move super.approveSelection() along with it."
No, not true at all. super.approveSelection() would not have to be called inside of the SwingWorker.
Why not simply create a SwingWorker, add a PropertyChangeListener to it, and when the SwingWorker's state is done, call super.approveSelection() if indicated?
OK, here is my example below. Explanation to follow:
import java.awt.*;
import java.awt.Dialog.ModalityType;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import javax.swing.*;
import javax.swing.text.*;
#SuppressWarnings("serial")
public class ApproveSelectionTest extends JPanel {
private JTextArea textArea = new JTextArea(30, 60);
public ApproveSelectionTest() {
textArea.setEditable(false);
textArea.setFocusable(false);
JPanel btnPanel = new JPanel();
btnPanel.add(new JButton(new MyGetFileAction("Get Text File Text")));
setLayout(new BorderLayout());
add(new JScrollPane(textArea), BorderLayout.CENTER);
add(btnPanel, BorderLayout.PAGE_END);
}
private class MyGetFileAction extends AbstractAction {
public MyGetFileAction(String text) {
super(text);
}
public void actionPerformed(java.awt.event.ActionEvent arg0) {
MyFileChooser myFileChooser = new MyFileChooser();
int result = myFileChooser.showOpenDialog(ApproveSelectionTest.this);
if (result == JFileChooser.APPROVE_OPTION) {
Document doc = myFileChooser.getDocument();
textArea.setDocument(doc);
}
};
}
private static void createAndShowGui() {
ApproveSelectionTest mainPanel = new ApproveSelectionTest();
JFrame frame = new JFrame("ApproveSelectionTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
#SuppressWarnings("serial")
class MyFileChooser extends JFileChooser {
private WorkOpen workOpen = null;
private JDialog progressDialog = null;
public MyFileChooser() {
}
#Override
public void approveSelection() {
JProgressBar progBar = new JProgressBar();
progBar.setIndeterminate(true);
Window win = SwingUtilities.getWindowAncestor(this);
progressDialog = new JDialog(win, "Checking File", ModalityType.APPLICATION_MODAL);
progressDialog.getContentPane().add(progBar);
progressDialog.pack();
progressDialog.setLocationRelativeTo(null);
File file = getSelectedFile();
workOpen = new WorkOpen(file);
workOpen.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (SwingWorker.StateValue.DONE == pcEvt.getNewValue()) {
if (progressDialog != null) {
progressDialog.dispose();
}
try {
boolean bool = workOpen.get().booleanValue();
if (bool) {
superApproveSelection();
} else {
JOptionPane.showMessageDialog(MyFileChooser.this, "Invalid File Chosen");
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
});
workOpen.execute();
progressDialog.setVisible(true);
}
// ****** this is key *****
private void superApproveSelection() {
super.approveSelection();
}
public Document getDocument() {
if (workOpen == null) {
return null;
} else {
return workOpen.getDocument();
}
}
}
class WorkOpen extends SwingWorker<Boolean, Void> {
private static final long SLEEP_TIME = 4 * 1000;
private Document document = null;
private File file = null;
public WorkOpen(File file) {
this.file = file;
}
#Override
protected Boolean doInBackground() throws Exception {
if (file == null || !file.exists()) {
return Boolean.FALSE;
}
Thread.sleep(SLEEP_TIME);
String fileName = file.getName();
if (fileName.contains(".txt")) {
Scanner scan = new Scanner(file);
StringBuilder stringBuilder = new StringBuilder();
while (scan.hasNextLine()) {
stringBuilder.append(scan.nextLine() + "\n");
}
document = new PlainDocument();
document.insertString(0, stringBuilder.toString(), null);
return Boolean.TRUE;
}
return Boolean.FALSE;
}
public Document getDocument() {
return document;
}
}
Explanation and key points from my example:
This example behaves very simply. You choose a file, and if the file exists and contains ".txt" in its name, then it reads in the document and displays the text in a JTextField.
Else it displays a warning message but leaves the JFileChooser displayed.
Probably the key point: I've given my MyFileChooser class a private superApproveSelection() method that can be called by my PropertyChangeListener. This exposes the super's approveSelection() method to the inner class, one of the problems you were having.
The order of calling code is important in my approveSelection() override.
In this method I first create my JProgressBar and its dialog but don't yet display it immediately. It really doesn't have to be created first, but it sure needs to be displayed last.
I create my SwingWorker, workOpen, but don't yet execute it.
I add my PropertyChangeListener to the SwingWorker before executing the SwingWorker.
I then execute my SwingWorker
I then display my modal JDialog with the indeterminate JProgressBar.
My SwingWorker is structured so that its doInBackground returns a Boolean, not a Document.
I have it create a (very simple) Document if all works out OK that holds the content of the text file, and set a private "doc" field obtainable by a getter method, and then have doInBackground return Boolean.TRUE if all works well.
I've given my doInBackground a Thread.sleep(4000) just to pretend that its action takes a lot of time. Yours of course won't have this.
In the PropertyChangeListener if the SwingWorker is DONE, I'll dispose of the progress bar dialog and then call get() on the SW to get the Boolean result.
If it's Boolean.TRUE, then call the superApproveSelection() method described above.
Else show an error message. Note that since the super's approveSelection() isn't called, the file chooser dialog remains displayed.
If the approveSelection is called then the code that displays my file chooser dialog will get the appropriate return value, will extract the Document from the file chooser and displays the Document in a JTextArea.
I am really needing to understand how parent/child dialogs work.
My users use a OTB Application called Teamcenter. I am writing a add on application that is invoked from a menu selection in the Teamcenter Application.
When they click the menu item, that executes a handler class and that creates the base dialog for my application.
public class AplotDialogHandler extends AbstractHandler {
private static AplotBaseDialog dlg = null;
public AplotDialogHandler() {
}// end Constructor
//////////////////////////////////////////////////////////////////////////
// execute() //
//////////////////////////////////////////////////////////////////////////
#Override
public Object execute(final ExecutionEvent event) throws ExecutionException {
if (dlg == null) {
try {
AbstractAIFApplication app = AIFDesktop.getActiveDesktop().getCurrentApplication();
TCSession session = (TCSession) app.getSession();
TCUserService userService = session.getUserService();
AplotVersion.negotiateVersion(userService);
AplotQueryCapabilities.initialize(userService);
dlg = new AplotBaseDialog(null, session);
}
catch (Exception ex) {
MessageBox.post(HandlerUtil.getActiveWorkbenchWindowChecked(event).getShell(), ex, true);
}
}
dlg.create();
dlg.getShell().setSize(700, 400);
dlg.open();
return null;
}// end execute()
}// end EdiDialogHandler()
Question 1. It seems like my application is not tied to the Teamcenter application. Meaning that I can close Teamcenter and my Application stays open.
Question 2. Should I get the workspace shell and pass it in the base dialog?
But even when my application is open, the user still needs to be able to use the Teamcenter application to select data to send to my application
Question 3. When opening dialogs from my base dialog, should I always pass the base dialog shell to those dialogs?
Question 4. Is there a standard way I should close down the dialogs when the user is done?
You need to pass the parent Shell to the dialog so that when you close parent shell, child shells will also be closed.
You should make your dialog MODELESS ( use SWT.MODELSS as style. Note: it is Hint) so that it will not block your parent shell.
Here is sample code:
public static void main(String[] args) {
Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1, false));
shell.setSize(200, 200);
Button b = new Button(shell, SWT.NONE);
b.setText("Click");
b.addSelectionListener(new SelectionListener() {
#Override
public void widgetSelected(SelectionEvent e) {
CDialog dialog = new CDialog(shell);
dialog.open();
}
#Override
public void widgetDefaultSelected(SelectionEvent e) {
// TODO Auto-generated method stub
}
});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
private static class CDialog extends Dialog
{
/**
* #param parentShell
*/
protected CDialog(Shell parentShell) {
super(parentShell);
}
/* (non-Javadoc)
* #see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
*/
#Override
protected Control createDialogArea(Composite parent) {
Composite comp = (Composite) super.createDialogArea(parent);
Label lbl = new Label(comp, SWT.NONE);
lbl.setText("Test modeless dialog");
return comp;
}
/* (non-Javadoc)
* #see org.eclipse.jface.window.Window#getShellStyle()
*/
#Override
protected int getShellStyle() {
return SWT.DIALOG_TRIM|SWT.MODELESS;
}
}