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.
I want to create a system that has a time in/time out feature.
I tried this code as a trial before including the the feature to my system:
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class Time extends JFrame implements ActionListener {
Date now = new Date();
private JLabel time;
private JButton getTime;
private SimpleDateFormat dateFormatter = new SimpleDateFormat("hh:mm:ss");
public Time()
{
setLayout(null);
setSize(500,300);
JLabel time = new JLabel("00:00:00");
time.setSize(100,100);
time.setLocation(40,40);
JButton getTime = new JButton("GET TIME");
getTime.addActionListener(this);
getTime.setSize(90,30);
getTime.setLocation(90,70);
Container pane = getContentPane();
pane.add(time);
pane.add(getTime);
setVisible(true);
}
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand() == "GET TIME")
{
JOptionPane.showMessageDialog(null, "Time "+dateFormatter.format(now),
"Time.",JOptionPane.INFORMATION_MESSAGE);
}
}
public static void main(String[] args) {
new Time();
}
}
It gets the current time, but it still gives the same time when I click again the button. It only changes when I close the UI.
Canned answer:
Don't compare Strings using ==. Use the equals(...) or the equalsIgnoreCase(...) method instead. Understand that == checks if the two objects are the same which is not what you're interested in. The methods on the other hand check if the two Strings have the same characters in the same order, and that's what matters here. So instead of
if (fu == "bar") {
// do something
}
do,
if ("bar".equals(fu)) {
// do something
}
or,
if ("bar".equalsIgnoreCase(fu)) {
// do something
}
Uncanned answer:
So in other words, change this:
if (e.getActionCommand() == "GET TIME")
to this:
if ("GET TIME".equals(e.getActionCommand())
or better still, use String constants.
Also, you'll want to avoid using null layout in your Swing GUI's as you'll find out that that is the hard way to lay things out. Much easier and more robust is to use layout managers.
Edit
You need to get the time from within your actionPerformed method. In other words, now should be created in the actionPerformed method before calling the JOptionPane.
public void actionPerformed(ActionEvent e)
{
if (e.getActionCommand() == "GET TIME")
{
now = new Date();
JOptionPane.showMessageDialog(null, "Time "+dateFormatter.format(now),
You're getting the same time because you've declared the Date variable as a global variable and initialized it globally as well. You should change the implementation of your actionPerformed as follows:
public void actionPerformed(ActionEvent e) {
Date now = new Date(); //Create a new instance of date
if ("GET TIME".equalsIgnoreCase(e.getActionCommand())) {
JOptionPane.showMessageDialog(null,
"Time " + dateFormatter.format(now), "Time.",
JOptionPane.INFORMATION_MESSAGE);
}
}
I've got a DocumentListener to look for any changes in the JTextField:
public class MyDocumentListener implements DocumentListener {
static String text;
public void insertUpdate(DocumentEvent e) {
updateLog(e);
}
public void removeUpdate(DocumentEvent e) {
updateLog(e);
}
public void changedUpdate(DocumentEvent e) {
//Plain text components do not fire these events
}
public static String passText() {
System.out.println("string that will be passed is: "+text);
return text;
}
public void updateLog(DocumentEvent e) {
Document doc = (Document)e.getDocument();
int length = e.getLength();
try {
text = doc.getText(0, length);
} catch (BadLocationException e1) {
e1.printStackTrace();
}
System.out.println("you typed "+text);
}
}
And then, in the other class:
String info = MyDocumentListener.passText();
The problem is I'm getting only one character, instead of the whole String. Any suggestions?
You're getting the length of the change instead of the length of the document:
int length = e.getLength(); // probably 1
should be
int length = doc.getLength();
int getLength()javadoc
The answer provided by paislee is indeed correct. You would like to add just another way to do the same thing. You can use bindings, which adds the concept of ValueHolders, variables that will store and reflect imediatley any property changes of your graphical components. It can provide a very effective way to implement MVC design pattern with Swing since the communication between Model-Controller-View is much more affective and decoupled.
JGoodies has an excellent and open source implementation for it. If you can spend sometime and want to improve your design, don't hesitate to take a look.
I found this blocking GlassPane class on the web and I'm curious to know if some of you see any problem with it.
public final class BlockingGlassPane extends JComponent implements AWTEventListener {
// Events will be consumed for this window.
private Window parentWindow;
// Focus will be returned to this component.
private Component lastFocusOwner;
private final Toolkit toolkit;
public BlockingGlassPane() {
super();
setOpaque(false);
addMouseListener(new MouseAdapter() {
});
addKeyListener(new KeyAdapter() {
});
setInputVerifier(new InputVerifier() {
#Override
public boolean verify(JComponent anInput) {
return false;
}
});
toolkit = Toolkit.getDefaultToolkit();
}
#Override
public void setVisible(boolean b) {
if (b) {
if (parentWindow == null) {
parentWindow = SwingUtilities.windowForComponent(this);
}
Component focusOwner = parentWindow.getFocusOwner();
if (focusOwner != this) {
lastFocusOwner = focusOwner;
}
toolkit.addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
toolkit.addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK);
requestFocus();
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
} else {
toolkit.removeAWTEventListener(this);
if (lastFocusOwner != null) {
lastFocusOwner.requestFocus();
lastFocusOwner = null;
}
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
super.setVisible(b);
}
#SuppressWarnings("unchecked")
public void eventDispatched(AWTEvent e) {
Object source = e.getSource();
if (e instanceof EventObject && source instanceof Component) {
Component src = (Component) source;
EventObject ev = e;
if (SwingUtilities.windowForComponent(src) == parentWindow) {
try {
Class[] cls = {};
Object[] args = {};
ev.getClass().getMethod("consume", cls).invoke(ev, args);
} catch (Exception ex) {
// ex.printStackTrace();
}
}
}
}
Just at a glance, I see several problems here, mostly in and around the eventDispatched() method.
First, why are you implementing AWTEventListener at all, since you never add this object to anything as an AWTEventListener? Did you mean to add this object to itself as an event listener? Are you adding it as an event listener somewhere else in code that isn't shown here?
Second, why do you test e instanceof EventObject? I cut-and-pasted your code into Eclipse, which immediately warned me that all AWTEvent objects are instances of EventObject. So, you can get rid of that test - It will always be true.
Third, why on earth are you resorting to reflection? It looks as if you are trying to use a Swing-only method on AWT events that don't have it. That approach won't work - Trying to reflectively call a nonexistent method will just throw an exception, which this code will silently catch and ignore.
Finally, why are you reinventing the wheel? Some quick Googling reveals some simpler examples and some more complicated examples that you could use as a starting point for your work and which would likely get you much closer to what you really want here.
I am writing a GWT app that involves interacting with an external document in an iframe. As a proof of concept, I am trying to attach a click handler to a button.
The following works in javascript
var iframe = document.getElementById("rawJSIFrame");
var doc = iframe.contentDocument;
var body = doc.body;
var button = doc.getElementsByTagName("input").namedItem("submit");
button.onclick = function() {
alert("Clicked!");
};
Trying to do the equivalent in GWT, I did the following:
public void addClickHandlerToSubmitButton(String buttonElementName, ClickHandler clickHandler) {
IFrameElement iframe = IFrameElement.as(frame.getElement());
Document frameDocument = getIFrameDocument(iframe);
if (frameDocument != null) {
Element buttonElement = finder(frameDocument).tag("input").name(buttonElementName).findOne();
ElementWrapper wrapper = new ElementWrapper(buttonElement);
HandlerRegistration handlerRegistration = wrapper.addClickHandler(clickHandler);
}
}
private native Document getIFrameDocument(IFrameElement iframe)/*-{
return iframe.contentDocument;
}-*/;
The following is the ElementWrapper class:
public class ElementWrapper extends Widget implements HasClickHandlers {
public ElementWrapper(Element theElement) {
setElement(theElement);
}
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
}
The code to find the button works fine but the actual click event handler is not getting invoked. Has anybody had a similar issue before, and how did you resolve it?
Thanks in advance,
Tin
Hilbrand is right about the problem being that the GWT method onAttach() was not called.
I implemented your original solution, adding the following method to ElementWrapper:
public void onAttach() {
super.onAttach();
}
And called added wrapper.onAttach() after the ElementWrapper is created. Works like a charm!
I expect the problem is that the GWT method onAttach() is not called when you use the wrapping as in your first example. You can try to use the static wrap method on the Button widget. Although to use this the input must be of type button. Or have a look at the implementation of the wrap method. Here is the modified code when using the wrap method:
Element buttonElement = finder(frameDocument).tag("input").name(buttonElementName).findOne();
Button button = Button.wrap(buttonElement);
HandlerRegistration handlerRegistration = button.addClickHandler(clickHandler);
After researching this further, I found that the iframe is irrelevant. The same behaviour doesn't work on a normal button on the host page.
I basically fixed it by using JSNI to replicate part of GWT's event handling mechanism. The following works:
Element buttonElement = DOM.getElementById("externalButton");
new CustomElementWrapper(buttonElement).addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
Window.alert("GWT hooked into button");
}
});
Where CustomElementWrapper is:
public class CustomElementWrapper extends Widget implements HasClickHandlers {
private ClickEventManager clickEventManager;
public CustomElementWrapper(Element theElement) {
setElement(theElement);
clickEventManager = new ClickEventManager(theElement);
}
public HandlerRegistration addClickHandler(ClickHandler handler) {
//The 'right' way of doing this would be the code below. However, this doesn't work
// A bug in GWT?
//
// return addDomHandler(handler, ClickEvent.getType());
return clickEventManager.registerClickHandler(handler);
}
void invokeClickHandler() {
clickEventManager.invokeClickHandler();
}
public boolean isClickHandlerRegistered() {
return clickEventManager.isClickHandlerRegistered();
}
}
Finally, the ClickEventManager, where the actual work happens is:
public class ClickEventManager {
private boolean clickHandlerRegistered = false;
private ClickHandler clickHandler;
private Element element;
public ClickEventManager(Element element) {
this.element = element;
}
public void invokeClickHandler() {
//This shouldn't really be null but we are bypassing GWT's native event mechanism
//so we can't create an event
clickHandler.onClick(null);
}
public boolean isClickHandlerRegistered() {
return clickHandlerRegistered;
}
HandlerRegistration registerClickHandler(ClickHandler handler) {
clickHandler = handler;
if (!clickHandlerRegistered) {
registerClickHandlerInJS(element);
clickHandlerRegistered = true;
}
return new HandlerRegistration() {
public void removeHandler() {
//For now, we don't support the removal of handlers
throw new UnsupportedOperationException();
}
};
}
private native void registerClickHandlerInJS(Element element)/*-{
element.__clickManager = this;
element.onclick
= function() {
var cm = this.__clickManager;
cm.#com.talktactics.agent2.client.widgets.ClickEventManager::invokeClickHandler()();
}
}-*/;
}
Personally, I hate this solution because I appear to be duplicating GWT's event handling and quite possibly introducing nasty javascript memory leaks. Any ideas on why my first post doesn't work (remembering that the iframe aspect is a red herring), would be appreciated.
Thanks,
Tin
You may find this helpful:
import com.google.gwt.dom.client.Element;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.AbsolutePanel;
public class DirectPanel extends AbsolutePanel implements HasClickHandlers {
public DirectPanel(Element elem) {
super(elem.<com.google.gwt.user.client.Element> cast());
onAttach();
}
#Override
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
}
You will then be able to make arbitrary containers into widget containers:
Element root = Document.get().getElementById("target");
DirectPanel p = new DirectPanel(root);
Button register = new Button("Register");
register.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
// ...
}
});
p.add(register);
And bind events to arbitrary elements:
Element root = Document.get().getElementById("target");
DirectPanel p = new DirectPanel(root);
p.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
// ...
}
});
Specifically in your case, try this:
IFrameElement frm = Document.get().createIFrameElement();
Document d = frm.getContentDocument();
NodeList<Element> inputs = d.getElementsByTagName("input");
InputElement target = null;
for(int i = 0; i < inputs.getLength(); ++i) {
Element e = inputs.getItem(0);
if (e.getNodeName().equals("submit")) {
target = InputElement.as(e);
break;
}
}
if (target != null) {
DirectPanel p = new DirectPanel(target);
p.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
// TODO Auto-generated method stub
}
});
}
It's always mystified me that GWT makes doing this so difficult and poorly documented.
Instead of using iframes i suggest you simply make a http request from GWT via com.google.gwt.http.client.RequestBuilder. Like so:
private void getHtml(String url) {
RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, url);
rb.setCallback(new RequestCallback() {
#Override
public void onResponseReceived(Request request, Response response) {
HTMLPanel html = new HTMLPanel(response.getText());
// Now you have a widget with the requested page
// thus you may do whatever you want with it.
}
#Override
public void onError(Request request, Throwable exception) {
Log.error("error " + exception);
}
});
try {
rb.send();
} catch (RequestException e) {
Log.error("error " + e);
}
}
You could use JSNI to reuse your JavaScript piece of code. Your javascript code would call a gwt method on an object that would throw it on behalf of the button in the iframe.
As to why GWT code does not work -- I guess that is because they use some layer on top of regular browser events that probably cannot span more than 1 frame. That's just a guess though. You could file this as a feature/bug request agains GWT team. If I am right your code looks just fine.
Please see my previous answer. A slight modification to your original solution will make it work.