I want the message box to appear immediately after the user changes the value in the textfield. Currently, I need to hit the enter key to get the message box to pop out. Is there anything wrong with my code?
textField.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent e) {
if (Integer.parseInt(textField.getText())<=0){
JOptionPane.showMessageDialog(null,
"Error: Please enter number bigger than 0", "Error Message",
JOptionPane.ERROR_MESSAGE);
}
}
}
Any help would be appreciated!
Add a listener to the underlying Document, which is automatically created for you.
// Listen for changes in the text
textField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
warn();
}
public void removeUpdate(DocumentEvent e) {
warn();
}
public void insertUpdate(DocumentEvent e) {
warn();
}
public void warn() {
if (Integer.parseInt(textField.getText())<=0){
JOptionPane.showMessageDialog(null,
"Error: Please enter number bigger than 0", "Error Message",
JOptionPane.ERROR_MESSAGE);
}
}
});
The usual answer to this is "use a DocumentListener". However, I always find that interface cumbersome. Truthfully the interface is over-engineered. It has three methods, for insertion, removal, and replacement of text, when it only needs one method: replacement. (An insertion can be viewed as a replacement of no text with some text, and a removal can be viewed as a replacement of some text with no text.)
Usually all you want is to know is when the text in the box has changed, so a typical DocumentListener implementation has the three methods calling one method.
Therefore I made the following utility method, which lets you use a simpler ChangeListener rather than a DocumentListener. (It uses Java 8's lambda syntax, but you can adapt it for old Java if needed.)
/**
* Installs a listener to receive notification when the text of any
* {#code JTextComponent} is changed. Internally, it installs a
* {#link DocumentListener} on the text component's {#link Document},
* and a {#link PropertyChangeListener} on the text component to detect
* if the {#code Document} itself is replaced.
*
* #param text any text component, such as a {#link JTextField}
* or {#link JTextArea}
* #param changeListener a listener to receieve {#link ChangeEvent}s
* when the text is changed; the source object for the events
* will be the text component
* #throws NullPointerException if either parameter is null
*/
public static void addChangeListener(JTextComponent text, ChangeListener changeListener) {
Objects.requireNonNull(text);
Objects.requireNonNull(changeListener);
DocumentListener dl = new DocumentListener() {
private int lastChange = 0, lastNotifiedChange = 0;
#Override
public void insertUpdate(DocumentEvent e) {
changedUpdate(e);
}
#Override
public void removeUpdate(DocumentEvent e) {
changedUpdate(e);
}
#Override
public void changedUpdate(DocumentEvent e) {
lastChange++;
SwingUtilities.invokeLater(() -> {
if (lastNotifiedChange != lastChange) {
lastNotifiedChange = lastChange;
changeListener.stateChanged(new ChangeEvent(text));
}
});
}
};
text.addPropertyChangeListener("document", (PropertyChangeEvent e) -> {
Document d1 = (Document)e.getOldValue();
Document d2 = (Document)e.getNewValue();
if (d1 != null) d1.removeDocumentListener(dl);
if (d2 != null) d2.addDocumentListener(dl);
dl.changedUpdate(null);
});
Document d = text.getDocument();
if (d != null) d.addDocumentListener(dl);
}
Unlike with adding a listener directly to the document, this handles the (uncommon) case that you install a new document object on a text component. Additionally, it works around the problem mentioned in Jean-Marc Astesana's answer, where the document sometimes fires more events than it needs to.
Anyway, this method lets you replace annoying code which looks like this:
someTextBox.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
doSomething();
}
#Override
public void removeUpdate(DocumentEvent e) {
doSomething();
}
#Override
public void changedUpdate(DocumentEvent e) {
doSomething();
}
});
With:
addChangeListener(someTextBox, e -> doSomething());
Code released to public domain. Have fun!
Just create an interface that extends DocumentListener and implements all DocumentListener methods:
#FunctionalInterface
public interface SimpleDocumentListener extends DocumentListener {
void update(DocumentEvent e);
#Override
default void insertUpdate(DocumentEvent e) {
update(e);
}
#Override
default void removeUpdate(DocumentEvent e) {
update(e);
}
#Override
default void changedUpdate(DocumentEvent e) {
update(e);
}
}
and then:
jTextField.getDocument().addDocumentListener(new SimpleDocumentListener() {
#Override
public void update(DocumentEvent e) {
// Your code here
}
});
or you can even use lambda expression:
jTextField.getDocument().addDocumentListener((SimpleDocumentListener) e -> {
// Your code here
});
Be aware that when the user modify the field, the DocumentListener can, sometime, receive two events. For instance if the user selects the whole field content, then press a key, you'll receive a removeUpdate (all the content is remove) and an insertUpdate.
In your case, I don't think it is a problem but, generally speaking, it is.
Unfortunately, it seems there's no way to track the content of the textField without subclassing JTextField.
Here is the code of a class that provide a "text" property :
package net.yapbam.gui.widget;
import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
/** A JTextField with a property that maps its text.
* <br>I've found no way to track efficiently the modifications of the text of a JTextField ... so I developed this widget.
* <br>DocumentListeners are intended to do it, unfortunately, when a text is replace in a field, the listener receive two events:<ol>
* <li>One when the replaced text is removed.</li>
* <li>One when the replacing text is inserted</li>
* </ul>
* The first event is ... simply absolutely misleading, it corresponds to a value that the text never had.
* <br>Anoter problem with DocumentListener is that you can't modify the text into it (it throws IllegalStateException).
* <br><br>Another way was to use KeyListeners ... but some key events are throw a long time (probably the key auto-repeat interval)
* after the key was released. And others events (for example a click on an OK button) may occurs before the listener is informed of the change.
* <br><br>This widget guarantees that no "ghost" property change is thrown !
* #author Jean-Marc Astesana
* <BR>License : GPL v3
*/
public class CoolJTextField extends JTextField {
private static final long serialVersionUID = 1L;
public static final String TEXT_PROPERTY = "text";
public CoolJTextField() {
this(0);
}
public CoolJTextField(int nbColumns) {
super("", nbColumns);
this.setDocument(new MyDocument());
}
#SuppressWarnings("serial")
private class MyDocument extends PlainDocument {
private boolean ignoreEvents = false;
#Override
public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
String oldValue = CoolJTextField.this.getText();
this.ignoreEvents = true;
super.replace(offset, length, text, attrs);
this.ignoreEvents = false;
String newValue = CoolJTextField.this.getText();
if (!oldValue.equals(newValue)) CoolJTextField.this.firePropertyChange(TEXT_PROPERTY, oldValue, newValue);
}
#Override
public void remove(int offs, int len) throws BadLocationException {
String oldValue = CoolJTextField.this.getText();
super.remove(offs, len);
String newValue = CoolJTextField.this.getText();
if (!ignoreEvents && !oldValue.equals(newValue)) CoolJTextField.this.firePropertyChange(TEXT_PROPERTY, oldValue, newValue);
}
}
I know this relates to a really old problem, however, it caused me some problems too. As kleopatra responded in a comment above, I solved the problem with a JFormattedTextField. However, the solution requires a bit more work, but is neater.
The JFormattedTextField doesn't by default trigger a property change after every text changes in the field. The default constructor of JFormattedTextField does not create a formatter.
However, to do what the OP suggested, you need to use a formatter which will invoke the commitEdit() method after each valid edit of the field. The commitEdit() method is what triggers the property change from what I can see and without the formatter, this is triggered by default on a focus change or when the enter key is pressed.
See http://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html#value for more details.
Create a default formatter (DefaultFormatter) object to be passed to the JFormattedTextField either via its constructor or a setter method. One method of the default formatter is setCommitsOnValidEdit(boolean commit), which sets the formatter to trigger the commitEdit() method every time the text is changed. This can then be picked up using a PropertyChangeListener and the propertyChange() method.
An elegant way is to add the listener to the caret position, because it changes every time something is typed/deleted, then just compare old value with current one.
String oldVal = ""; // empty string or default value
JTextField tf = new JTextField(oldVal);
tf.addCaretListener(e -> {
String currentVal = tf.getText();
if(!currentVal.equals(oldVal)) {
oldVal = currentVal;
System.out.println("Change"); // do something
}
});
(This event is also being triggered every time a user just clicks into a TextField).
textBoxName.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent e) {
onChange();
}
#Override
public void removeUpdate(DocumentEvent e) {
onChange();
}
#Override
public void changedUpdate(DocumentEvent e) {
onChange();
}
});
But I would not just parse anything the user (maybe on accident) touches on his keyboard into an Integer. You should catch any Exceptions thrown and make sure the JTextField is not empty.
If we use runnable method SwingUtilities.invokeLater() while using Document listener application is getting stuck sometimes and taking time to update the result(As per my experiment). Instead of that we can also use KeyReleased event for text field change listener as mentioned here.
usernameTextField.addKeyListener(new KeyAdapter() {
public void keyReleased(KeyEvent e) {
JTextField textField = (JTextField) e.getSource();
String text = textField.getText();
textField.setText(text.toUpperCase());
}
});
it was the update version of Codemwnci. his code is quite fine and works great except the error message. To avoid error you must change the condition statement.
// Listen for changes in the text
textField.getDocument().addDocumentListener(new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
warn();
}
public void removeUpdate(DocumentEvent e) {
warn();
}
public void insertUpdate(DocumentEvent e) {
warn();
}
public void warn() {
if (textField.getText().length()>0){
JOptionPane.showMessageDialog(null,
"Error: Please enter number bigger than 0", "Error Massage",
JOptionPane.ERROR_MESSAGE);
}
}
});
You can use even "MouseExited" to control.
example:
private void jtSoMauMouseExited(java.awt.event.MouseEvent evt) {
// TODO add your handling code here:
try {
if (Integer.parseInt(jtSoMau.getText()) > 1) {
//auto update field
SoMau = Integer.parseInt(jtSoMau.getText());
int result = SoMau / 5;
jtSoBlockQuan.setText(String.valueOf(result));
}
} catch (Exception e) {
}
}
Use a KeyListener (which triggers on any key) rather than the ActionListener (which triggers on enter)
DocumentFilter ? It gives you the ability to manipulate.
[ http://www.java2s.com/Tutorial/Java/0240__Swing/FormatJTextFieldstexttouppercase.htm ]
Sorry. J am using Jython (Python in Java) - but easy to understand
# python style
# upper chars [ text.upper() ]
class myComboBoxEditorDocumentFilter( DocumentFilter ):
def __init__(self,jtext):
self._jtext = jtext
def insertString(self,FilterBypass_fb, offset, text, AttributeSet_attrs):
txt = self._jtext.getText()
print('DocumentFilter-insertString:',offset,text,'old:',txt)
FilterBypass_fb.insertString(offset, text.upper(), AttributeSet_attrs)
def replace(self,FilterBypass_fb, offset, length, text, AttributeSet_attrs):
txt = self._jtext.getText()
print('DocumentFilter-replace:',offset, length, text,'old:',txt)
FilterBypass_fb.replace(offset, length, text.upper(), AttributeSet_attrs)
def remove(self,FilterBypass_fb, offset, length):
txt = self._jtext.getText()
print('DocumentFilter-remove:',offset, length, 'old:',txt)
FilterBypass_fb.remove(offset, length)
// (java style ~example for ComboBox-jTextField)
cb = new ComboBox();
cb.setEditable( true );
cbEditor = cb.getEditor();
cbEditorComp = cbEditor.getEditorComponent();
cbEditorComp.getDocument().setDocumentFilter(new myComboBoxEditorDocumentFilter(cbEditorComp));
I am brand new to WindowBuilder, and, in fact, just getting back into Java after a few years, but I implemented "something", then thought I'd look it up and came across this thread.
I'm in the middle of testing this, so, based on being new to all this, I'm sure I must be missing something.
Here's what I did, where "runTxt" is a textbox and "runName" is a data member of the class:
public void focusGained(FocusEvent e) {
if (e.getSource() == runTxt) {
System.out.println("runTxt got focus");
runTxt.selectAll();
}
}
public void focusLost(FocusEvent e) {
if (e.getSource() == runTxt) {
System.out.println("runTxt lost focus");
if(!runTxt.getText().equals(runName))runName= runTxt.getText();
System.out.println("runText.getText()= " + runTxt.getText() + "; runName= " + runName);
}
}
Seems a lot simpler than what's here so far, and seems to be working, but, since I'm in the middle of writing this, I'd appreciate hearing of any overlooked gotchas. Is it an issue that the user could enter & leave the textbox w/o making a change? I think all you've done is an unnecessary assignment.
Here is a Kotlin port of #Boann's answer, which is a great solution that has been working well for me.
import java.beans.*
import javax.swing.*
import javax.swing.event.*
import javax.swing.text.*
/**
* Installs a listener to receive notification when the text of this
* [JTextComponent] is changed. Internally, it installs a [DocumentListener] on the
* text component's [Document], and a [PropertyChangeListener] on the text component
* to detect if the `Document` itself is replaced.
*
* #param changeListener a listener to receive [ChangeEvent]s when the text is changed;
* the source object for the events will be the text component
*/
fun JTextComponent.addChangeListener(changeListener: ChangeListener) {
val dl: DocumentListener = object : DocumentListener {
private var lastChange = 0
private var lastNotifiedChange = 0
override fun insertUpdate(e: DocumentEvent) = changedUpdate(e)
override fun removeUpdate(e: DocumentEvent) = changedUpdate(e)
override fun changedUpdate(e: DocumentEvent) {
lastChange++
SwingUtilities.invokeLater {
if (lastNotifiedChange != lastChange) {
lastNotifiedChange = lastChange
changeListener.stateChanged(ChangeEvent(this))
}
}
}
}
addPropertyChangeListener("document") { e: PropertyChangeEvent ->
(e.oldValue as? Document)?.removeDocumentListener(dl)
(e.newValue as? Document)?.addDocumentListener(dl)
dl.changedUpdate(null)
}
document?.addDocumentListener(dl)
}
You can use it on any text component as follows:
myTextField.addChangeListener { event -> myEventHandler(event) }
Like his code, also public domain.
In my application I have a screen where I use Accelerators. I'm using the function key F3 to execute an operation in my application. It works fine everytime, but when I click in any TextField on this screen the function key doesn't execute.
Here is the code where I set the Accelerator:
scene.getAccelerators().put(
new KeyCodeCombination(KeyCode.F3),
new Runnable() {
#Override
public void run() {
// do sth here
}
}
);
When I click my textfield and then hit the F3 function key it doesn't work.
Someone knows the solution?
This works for me using Java 1.8.0_45. However I encounter a similar issue with an editable Combobox field.
Edited:
Upon further investigation it does seem to occur with text field as well. I worked around it using the following custom class in place of text field:
public class ShortcutFriendlyTextField extends TextField{
public ShortcutFriendlyTextField() {
super();
addEventHandler(KeyEvent.KEY_RELEASED,new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
//handle shortcuts if defined
Runnable r=getScene().getAccelerators().get(new KeyCodeCombination(event.getCode()));
if(r!=null) {
r.run();
}
}
});
}
}
This answer is based on tikerman's. I added the code to handle modifier keys.
if (!event.getCode().isModifierKey()) {
Consumer<KeyCombination.Modifier[]> runAccelerator = (modifiers) -> {
Runnable r = getScene().getAccelerators().get(new KeyCodeCombination(event.getCode(), modifiers));
if (r != null) {
r.run();
}
};
List<KeyCombination.Modifier> modifiers = new ArrayList<>();
if (event.isControlDown()) modifiers.add(KeyCodeCombination.CONTROL_DOWN);
if (event.isShiftDown()) modifiers.add(KeyCodeCombination.SHIFT_DOWN);
if (event.isAltDown()) modifiers.add(KeyCodeCombination.ALT_DOWN);
runAccelerator.apply(modifiers.toArray(new KeyCombination.Modifier[modifiers.size()]));
}
Edit: fix consumer spelling.
If you have accelerators setup else where, such as a MenuBar, using some of the other solutions will cause the shortcut to be executed twice.
In my case, using modifiers with my shortcut (SHIFT + F3) works while in focus in the text area, but not while using just an accelerator such as just F3.
I set mine up to only use the event handler for shortcuts that do not use a modifier.
public static final double JAVA_VERSION = Double.parseDouble(System.getProperty("java.specification.version"));
public static void makeInputFieldShortCutFriendly(Node node) {
//this bug wasn't fixed until 9.0
if (JAVA_VERSION < 9) {
node.addEventHandler(KeyEvent.KEY_RELEASED, (KeyEvent event) -> {
if (!event.getCode().isModifierKey()) {
if (!event.isAltDown() && !event.isShiftDown() && !event.isAltDown()) {
Runnable r = node.getScene().getAccelerators().get(new KeyCodeCombination(event.getCode(), KeyCodeCombination.SHORTCUT_ANY));
if (r != null) {
r.run();
}
}
}
});
}
}
Edit: No modifier shortcuts was fixed in Java 9. Given the popularity of JDK 8 right now, we should probably provide some backwards capatability or request users update there JRE to 9+.
I you are using JavaFx+Scene builder then just make a function that redirect all the event to the respective functions and then add this function to every element in the scene with OnKeyReleased event
I've met a problem and I've been struggling last 2 days. I have a compiled program that runs some simulations and visualizes the results in an SVG file. The file is replaced every 2 seconds with a new one, until the simulation is done.
Wanting to visualize the results, I made a java swing program which uses batik and JSVGCanvas to display the svg file and update it every 2 seconds.
The code I use is:
// In the main part of my code
svgCanvas = new JSVGCanvas();
oneLineInnerPanel.add("Center", svgCanvas);
(new Thread() {
#Override
public void run() {
try {
Thread.sleep(2000);
File f = new File("file_that_shows_simulation_still_running");
while (f.exists()) {
svgReloadButtonActionPerformed(null);
Thread.sleep(2000);
}
} catch (InterruptedException ex) {
Logger.getLogger(testUI.class.getName()).log(Level.SEVERE, null, ex);
}
}
}).start();
// -----------------------------------------------
private void svgReloadButtonActionPerformed(java.awt.event.ActionEvent evt) {
try {
svgCanvas.loadSVGDocument(SVGFile.toURL().toString());
} catch (MalformedURLException ex) {
Logger.getLogger(testUI.class.getName()).log(Level.SEVERE, null, ex);
}
}
It works fine with the exception that every 30-40 updates, it happens that the loadSVGDocument tries to read the document while it's being written by the simulator and thus I get a jdialog error:
XML document structures must start and end within the same entity.
org.apache.batik.dom.util.SAXIOException: XML document structures must start and end within the same entity.
at org.apache.batik.dom.util.SAXDocumentFactory.createDocument(SAXDocumentFactory.java:437)
at org.apache.batik.dom.util.SAXDocumentFactory.createDocument(SAXDocumentFactory.java:349)
at org.apache.batik.dom.svg.SAXSVGDocumentFactory.createDocument(SAXSVGDocumentFactory.java:200)
at org.apache.batik.dom.svg.SAXSVGDocumentFactory.createSVGDocument(SAXSVGDocumentFactory.java:124)
at org.apache.batik.bridge.DocumentLoader.loadDocument(DocumentLoader.java:106)
at org.apache.batik.swing.svg.SVGDocumentLoader.run(SVGDocumentLoader.java:84)
Caused by: org.xml.sax.SAXParseException; systemId: file:/tmp/tempDir9189341730639722289/svgOut.svg; lineNumber: 272; columnNumber: 2; XML document structures must start and end within the same entity.
at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source)
at org.apache.batik.dom.util.SAXDocumentFactory.createDocument(SAXDocumentFactory.java:431)
... 5 more
This doesn't affect the whole procedure, but it's ugly. I get 2-3 of these jdialogs throughout the simulation. I cannot lock the file I/O, because I don't have access to the simulator code. If I lock from java only, the simulator crashes saying it cannot access the file.
What I want is that if there is an error while loading the svg file to somehow catch it internally and not have a jdialog. I can accept missing an update every 30-40 times (60-80 secs).
Now, JSVGCanvas gives you the option to provide a useragent and overwrite the displayError() method to do as you like. I tried that but the dialogs still occur. The problem is that the dialogs are not produced by the svgUserAgent I provide but by an internal BridgeUserAgent:
public JSVGComponent(SVGUserAgent ua, boolean eventsEnabled,
boolean selectableText) {
super(eventsEnabled, selectableText);
svgUserAgent = ua;
userAgent = new BridgeUserAgentWrapper(createUserAgent());
addSVGDocumentLoaderListener((SVGListener)listener);
addGVTTreeBuilderListener((SVGListener)listener);
addSVGLoadEventDispatcherListener((SVGListener)listener);
if (updateOverlay != null)
getOverlays().add(updateOverlay);
}
public void loadSVGDocument(String url) {
String oldURI = null;
if (svgDocument != null) {
oldURI = svgDocument.getURL();
}
final ParsedURL newURI = new ParsedURL(oldURI, url);
stopThenRun(new Runnable() {
public void run() {
String url = newURI.toString();
fragmentIdentifier = newURI.getRef();
loader = new DocumentLoader(userAgent);
nextDocumentLoader = new SVGDocumentLoader(url, loader);
nextDocumentLoader.setPriority(Thread.MIN_PRIORITY);
Iterator it = svgDocumentLoaderListeners.iterator();
while (it.hasNext()) {
nextDocumentLoader.addSVGDocumentLoaderListener
((SVGDocumentLoaderListener)it.next());
}
startDocumentLoader();
}
});
}
Can anyone help me get out of this mess, please?
Thanks in advance!
Finally solved the issue. Extended JSVGCanvas, override the createUserAgent() method to provide a bridgeuseragent of my own. This useragent extends the JSVGCanvas user agent but overrides the displayError methods. Here's the code:
import org.apache.batik.bridge.UserAgent;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.util.XMLConstants;
/**
*
* #author
*/
public class myJSVGCanvas extends JSVGCanvas{
#Override
protected UserAgent createUserAgent() {
return new myCanvasUserAgent();
}
protected class myCanvasUserAgent extends CanvasUserAgent
implements XMLConstants {
/**
* Displays an error message in the User Agent interface.
*/
#Override
public void displayError(String message) {
if (svgUserAgent != null) {
super.displayError(message);
} else {
System.out.println(message);
// JOptionPane pane =
// new JOptionPane(message, JOptionPane.ERROR_MESSAGE);
// JDialog dialog =
// pane.createDialog(myJSVGCanvas.this, "ERROR");
// dialog.setModal(false);
// dialog.setVisible(true); // Safe to be called from any thread
}
}
/**
* Displays an error resulting from the specified Exception.
*/
#Override
public void displayError(Exception ex) {
if (svgUserAgent != null) {
super.displayError(ex);
} else {
ex.printStackTrace();
// JErrorPane pane =
// new JErrorPane(ex, JOptionPane.ERROR_MESSAGE);
// JDialog dialog = pane.createDialog(myJSVGCanvas.this, "ERROR");
// dialog.setModal(false);
// dialog.setVisible(true); // Safe to be called from any thread
}
}
}
}
Hope it helps somebody. If anyone has a better idea for dealing with the problem, than just hiding it, I'd be glad to hear it!
What is the proper way to dispose of SWT Shells? I have created some Dialogs with success following the template given in the Dialog API docs. The SWT API for Dialog says:
The basic template for a user-defined dialog typically looks something
like this:
public class MyDialog extends Dialog {
Object result;
public MyDialog (Shell parent, int style) {
super (parent, style);
}
public MyDialog (Shell parent) {
this (parent, 0); // your default style bits go here (not the Shell's style bits)
}
public Object open () {
Shell parent = getParent();
Shell shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL);
shell.setText(getText());
// Your code goes here (widget creation, set result, etc).
shell.open();
Display display = parent.getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
return result;
}
}
The Dialogs I have created do not have their own taskbar icon on Windows, as I would expect for a Dialog. I now want to create some Shells, which if I understand correctly will receive their own taskbar entry on Windows?
In contrast to the directions given in the above API docs, I have also seen an article which seems to suggest that having a while loop as shown in the API docs is the wrong way to do it. The article instead suggests disposing the Shell by using the close event listener.
What is the correct way to dispose of SWT Shells (and while were on the topic, Dialogs as well)?
Hopefully I can help explain what's going on here.
Dialog is basically just a convenient class to subclass, because Shell itself is not supposed to be subclassed. You can create shells without using Dialog at all, if you want to. In this case, your MyDialog class is a class that others can use to open the same kind of dialog repeatedly.
This piece of code drives the SWT event loop as long as the shell is open (clicking the close button on the shell disposes the shell by default):
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
You have to call Display.readAndDispatch periodically to keep your SWT application from "locking up". Basically it causes all of the incoming events from the operating system (keyboard and mouse events, repaint events, etc.) to be correctly processed by your application. readAndDispatch basically takes an event from the application's event queue and invokes the correct listeners. In an Eclipse RCP application, the workbench is normally responsible for "pumping" the event loop and you don't have to mess with it. Here's a little more info about the event loop.
The purpose of "manually" pumping the event loop in this context is to prevent MyDialog.open from returning while the shell is not disposed, but still keep the application from "hanging". If your MyDialog.open method tried to wait for the shell to be disposed, but it didn't pump the event loop, your application would "lock up" because without the event loop running, there's no way for the shell to ever be notified that it should be disposed!
You can create shells without using this pattern. Here's an example of a very simple SWT application that opens a ton of shells all at once and keeps running as long as at least one of them is still open (I've omitted the package declaration and imports):
public class Shells {
private static int numDisposals = 0;
public static void main(String[] args) {
Display d = Display.getDefault();
for (int i = 0; i < 5; i++) {
Shell s = new Shell(d);
s.open();
s.addDisposeListener(new DisposeListener() {
#Override
public void widgetDisposed(DisposeEvent arg0) {
numDisposals++;
}
});
}
while (numDisposals < 5) {
while (!d.readAndDispatch()) {
d.sleep();
}
}
}
}
Note that I add a DisposeListener to each shell so I can take some action when the shell is closed. You can also use IShellListener to listen more directly for the close event and even actually prevent it (which you might want to do if the shell contains unsaved work, for example). Here's an annoying modification to the first program that starts 5 shells and randomly prevents you from closing them:
public class Shells {
private static Random r = new Random();
private static int numDisposals = 0;
public static void main(String[] args) {
Display d = Display.getDefault();
for (int i = 0; i < 5; i++) {
Shell s = new Shell(d);
s.open();
s.addShellListener(new ShellAdapter() {
#Override
public void shellClosed(ShellEvent e) {
boolean close = r.nextBoolean();
if (close) {
System.out.println("Alright, shell closing.");
} else {
System.out.println("Try again.");
}
e.doit = close;
}
});
s.addDisposeListener(new DisposeListener() {
#Override
public void widgetDisposed(DisposeEvent arg0) {
numDisposals++;
}
});
}
while (numDisposals < 5) {
while (!d.readAndDispatch()) {
d.sleep();
}
}
}
}
Hopefully this has helped make things more clear!
Edited to add: I'm not totally sure why you aren't getting a windows taskbar item for your shell, but I suspect it has something to do with the style flags you're passing into the shell's constructor. The shells in my example have no style flags and they all get a taskbar icon.