Event handling in Java (JTree + JButton) - java

private void createEvents()
{
menuFileExit.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
System.exit(0);
}
});
////// Events on tree selection
jtStoryViewer.addTreeSelectionListener(new TreeSelectionListener()
{
public void valueChanged(TreeSelectionEvent arg0)
{
DefaultMutableTreeNode selection = (DefaultMutableTreeNode) jtStoryViewer.getLastSelectedPathComponent();
Object nodeObject = selection.getUserObject();
////// Checks if selected node is a String (only story title is a string)
if(selection.getUserObject().getClass().getName() == "java.lang.String" )
{
tfTitle.setText(nodeObject.toString());
////// Action listener for Change Button
btnChange.addActionListener(new ActionListener()
{
////// Title text swap
public void actionPerformed(ActionEvent arg0)
{
selection.setUserObject(tfTitle.getText());
((DefaultTreeModel)jtStoryViewer.getModel()).nodeChanged(selection);
}
});
}
///// checks if the object is a chapter object
if(selection.getUserObject().getClass().getName() == "ISW.common.Chapter")
{
Chapter chapter = (Chapter) selection.getUserObject();
tfTitle.setText(chapter.toString());
////// Action listener for Change Button
btnChange.addActionListener(new ActionListener()
{
////// Title text swap
public void actionPerformed(ActionEvent arg0)
{
chapter.setTitle(tfTitle.getText());
((DefaultTreeModel)jtStoryViewer.getModel()).nodeChanged(selection);
}
});
}
}
});
}
I am using JTree to display and modify some objects. I added a TreeSelectionListener to get the object data on selection. For now I want to be able to change the title of an object, it works fine on first selection on the tree , I change the value in the text box and the "Change" button works just fine, but when I move on to next objects, the change button also modifies the value of all previously selected objects.
I guess it is caused due to my improper usage of the ActionListeners but I can't tell for sure and at this point I'm stuck.
Will be grateful for any hints.

Don't keep adding an ActionListener to the btnChange JButton within the TreeSelectionListener#valueChanged method.
This will cause the button to call EVERY ActionListener you have previously
Instead, give the btnChange a single ActionListener, when clicked, can act on the currently selected node (by checking the JTree it self). You could have the TreeSelectionListener#valueChanged method enable or disable the btnChange based on the validity of the selection
Also, if(selection.getUserObject().getClass().getName() == "ISW.common.Chapter") isn't how String comparison is done in Java, instead you should use something more like if("ISW.common.Chapter".equals(selection.getUserObject().getClass().getName()))

Related

Detect if JSpinner change comes from typing value or clicking on arrow

When I receive a ChangeEvent from my JSpinner, I'd like to detect if user used the arrows to increase/decrease the number value, or directly typed a new number.
What would be the best approach to do this ?
EDIT: for now my non-reliable solution is just to save the last JSpinner changed value, and if new change value is +/- equals to the step value, then I assume user clicked on an arrow. It works except if user typed a value which is equals to (oldValue +/- step).
EDIT: why ?
I want to reproduce the behavior found in Midi editors of several famous DAWs. The JSpinner represents the velocity (0-127) of selected notes. It shows the velocity of the first selected note. Usually notes velocity differ. When you increase with arrow, you want to increase all selected notes by the same amount. When you type in a new value, you want all velocities to be reset to this value.
Distinguishing the trigger of a value change is not supported - the only value-related event fired by JSpinner is a ChangeEvent which carries no state except the source. We need another type of listener or a combination of listeners.
First thought: listen to changes of the editor's textField, f.i. an actionListener or a propertyChangeListener on the value property. This doesn't work, mainly because
both change- and propertyChangeListener are fired always (change before property)
actionListener is not fired on focusLost
Second thought: go dirty and dig into implementation details to hook into the action fired by the arrow buttons.
The idea:
look up the action for increment/decrement from the spinner's actionMap: this is the same as the arrows' actions and also used by up/down keys (which I assume not counting a "editing")
for each, create a wrapper that sets a flag before delegating to super
put that wrapper into the spinner's actionMap
look up the arrow buttons in the spinner's children and replace their respective actionListener with the wrapper
Client code would change the tweak's changeListener to acts according to the flag as appropriate.
Some code doing the tweaking (beware: not formally tested!):
public static class SpinnerTweaker {
private JSpinner spinner;
private boolean wasButtonAction;
private Object oldValue;
public SpinnerTweaker(JSpinner spinner) {
this.spinner = spinner;
AbstractAction incrementDelegate = createDelegate("increment");
spinner.getActionMap().put("increment", incrementDelegate);
AbstractAction decrementDelegate = createDelegate("decrement");
spinner.getActionMap().put("decrement", decrementDelegate);
// replacing arrow button's action
Component[] components = spinner.getComponents();
for (Component component : components) {
if (component instanceof JButton) {
if (component.getName() == "Spinner.nextButton") {
ActionListener[] actions = ((JButton) component).getActionListeners();
ActionListener uiAction = actions[0];
((JButton) component).removeActionListener(uiAction);
((JButton) component).addActionListener(incrementDelegate);
}
if (component.getName() == "Spinner.previousButton") {
ActionListener[] actions = ((JButton) component).getActionListeners();
ActionListener uiAction = actions[0];
((JButton) component).removeActionListener(uiAction);
((JButton) component).addActionListener(decrementDelegate);
}
}
}
spinner.addChangeListener(e -> {
if (wasButtonAction) {
System.out.println("value changed by button: " + spinner.getValue());
} else {
System.out.println("value changed by editing: " + spinner.getValue());
}
wasButtonAction = false;
});
}
protected AbstractAction createDelegate(String actionCommand) {
// hooking into original button action to set button flag
AbstractAction action = (AbstractAction) spinner.getActionMap().get(actionCommand);
AbstractAction delegate = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
oldValue = spinner.getValue();
wasButtonAction = true;
action.actionPerformed(e);
// hit min/max - TBD: needs testing!
if (oldValue.equals(spinner.getValue())) {
wasButtonAction = false;
}
}
};
return delegate;
}
}
#kleopatra solution works but I found a simpler solution.
The trick is that commitEdit() is only called internally by JSpinner when change results from the increment or decrement action.
public class Spinner2 extends JSpinner
{
// To be checked by ChangeListener after receiving the ChangeEvent
public boolean wasManualEdit=true;
#Override
public void commitEdit() throws ParseException
{
wasManualEdit = false;
super.commitEdit();
}
#Override
protected void fireStateChanged()
{
super.fireStateChanged();
wasManualEdit = true;
}
}

Disable button in Java until all fields filled

I'm new to Java and developing a small project. I'm making a program where the user has to register themselves. I have 3 different tabs on my Tabbed Pane. I want to be able to disable the next button on the first pane making it impossible for the user to continue to pane 2 unless all the text fields on pane 1 have been filled. I have been Searching online and found various examples but none of them would work in run time.
I am using Netbeans.
private void txtFirstNameActionPerformed(java.awt.event.ActionEvent evt) {
if(txtFirstName.getText().trim().length() > 0)
btnNext1.setEnabled(true);
else
btnNext1.setEnabled(false);
}
Create a List of all the text fields on your pane:
List<JTextField> list = new ArrayList<>();
Add all your text fields to that list.
Then, create a universal DocumentListener that listens for text change events, and every time a text update happens, scan through all your text fields to see if they have all been filled:
DocumentListener listener = new DocumentListener() {
#Override
public void removeUpdate(DocumentEvent e) { changedUpdate(e); }
#Override
public void insertUpdate(DocumentEvent e) { changedUpdate(e); }
#Override
public void changedUpdate(DocumentEvent e) {
boolean canEnable = true;
for (JTextField tf : list) {
if (tf.getText().isEmpty()) {
canEnable = false;
}
}
btnNext1.setEnabled(canEnable);
}
};
And add that listener to every text field you have in the list:
for (JTextField tf : list) {
tf.getDocument().addDocumentListener(piecesListener);
}

Editable JCombobox avoid multiple DocumentEvents when the selection change from the popup

I have an editable JComboBox with a single listener on it.
It is a documentListener that execute some code when the user insert or remove some text inside the combobox textfield:
((JTextComponent)combobox.getEditor().getEditorComponent()).getDocument().addDocumentListener(..)
My problem is that when the user select an element from the popup and the content of the combobox textfield changes there are two events executed into the documentListener, one is a removeUpdate() corresponding to the deletion of the previous content and the other is a insertUpdate() corresponding to the insertion of the new value.
I want that only one execution of my code is done and not two. How can I avoid that the code is executed two times when the user select an entry from the popup?
I tried various combination of different listener but for now without result.
What I want in the end is that my code is execute only one time when:
- The user change the text into the combobox textfield.
- The user select an element from the combobox popup
Thanks in advance.
[EDIT 1]
As requested I updated adding SSCCE
myCombobox = new javax.swing.JComboBox<String>();
myCombobox.setEditable(true);
((JTextComponent)myCombobox.getEditor().getEditorComponent()).getDocument().addDocumentListener(
new DocumentListener(){
#Override
public void insertUpdate(DocumentEvent e) {
System.out.println("insert performed");
}
#Override
public void removeUpdate(DocumentEvent e) {
System.out.println("remove performed");
}
#Override
public void changedUpdate(DocumentEvent e) {
System.out.println("change performed");
}
});
myCombobox.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent event) {
if (event.getStateChange() == ItemEvent.SELECTED) {
System.out.println("Action performed");
}
}
});
Note that in this case I have an ItemEvent instead of an ActionEvent because I'm continuing to modify my code searching for a solution in any case the behavior should not be influenced by this.
You can check ((JTextComponent)combobox.getEditor().getEditorComponent()).hasFocus() to be sure user types in the editor.

Editable JComboBox with dynamic entries

I made my JComboBox editable using:
myCombo.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
I encounter multible problems with the following task:
When writing into the combobox, the new content of the box should be taken and compared with a list and all entries starting with this text should be shown in the popupmenu.
So if i have a list with: "Aban" "Aben" "Aber" "Acen" "Aden"
and enter "Ab" into the box, then the pop-up should display the first 3 entries.
When clicking one of the entries (Either by keyboard selecting and pressing enter/tab or by clicking with a mouse) The ComboBox should get that value and the Popup should hide. I need to find this action as some of the elements have a note at the end (In backets which I require) but only when one of the entries is selected
Here are the most imporant parts of my code:
final JTextComponent tcA = (JTextComponent) myCombo.getEditor().getEditorComponent();
tcA.getDocument().addDocumentListener(new DocumentListener() {
public void methodUsedByinsertUpdateAndremoveUpdate(DocumentEvent e) {
String item = ((JTextComponent) myCombo.getEditor().getEditorComponent()).getText();
//Routine to get the new list in a vector, not pasted for readability
DefaultComboBoxModel newMyComboModel = new DefaultComboBoxModel(myVectorList);
myCombo.setModel(newMyComboModel);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myCombo.showPopup();
}
});
}
}
myCombo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(myCombo.getModel().getSize() == 1) {
//Special logic to find out if the selected item has a note
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myCombo.hidePopup();
}
});
}
}
With this, i have:
Trouble with the first character (Caret position not working correctly)
Popup not automatically shown and hides when entering new character into the field
Problems with Swing GUI not being actualised
If you require more information just ask
The Glazed List recommended by peeskillet did exactly what I wanted

Why is itemStateChanged on JComboBox is called twice when changed?

I'm using a JComboBox with an ItemListener on it. When the value is changed, the itemStateChanged event is called twice. The first call, the ItemEvent is showing the original item selected. On the second time, it is showing the item that has been just selected by the user. Here's some tester code:
public Tester(){
JComboBox box = new JComboBox();
box.addItem("One");
box.addItem("Two");
box.addItem("Three");
box.addItem("Four");
box.addItemListener(new ItemListener(){
public void itemStateChanged(ItemEvent e){
System.out.println(e.getItem());
}
});
JFrame frame = new JFrame();
frame.getContentPane().add(box);
frame.pack();
frame.setVisible(true);
}
So when I changed the Combo box once from "One" to "Three" the console shows:
One
Three
Is there a way I can tell using the ItemEvent maybe, that it's the second item (ie. the user selected item)? And if someone can explain why it gets called twice, that would be nice too!
Thanks
Have a look at this source:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Tester {
public Tester(){
JComboBox box = new JComboBox();
box.addItem("One");
box.addItem("Two");
box.addItem("Three");
box.addItem("Four");
box.addItemListener(new ItemListener(){
public void itemStateChanged(ItemEvent e){
System.out.println(e.getItem() + " " + e.getStateChange() );
}
});
JFrame frame = new JFrame();
frame.getContentPane().add(box);
frame.pack();
frame.setVisible(true);
}
public static void main(String [] args) {
Tester tester = new Tester();
}
}
Use the getStateChange to determine if an item is selected or deselected
According to this thread,
It gets tripped when you leave one result and then called again when set to another result
Don't listen for itemStateChanged. Use an ActionListener instead, which is good for handling events of the combo.
You need a ItemStateListener if you need to separately handle deselection / selection depending on the item involved.
Changing the state of the item within itemStateChanged causes itemStateChanged to be fired... this called "reentrance".
I wanted to get the index string after selected and set in combobox
comboBox1.addItemListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
if(e.getStateChange() == ItemEvent.SELECTED) {
comboBox1ItemStateChanged();
}
}
});
Yo can do it like this:
import java.awt.event.*;
jComboBox1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Hello");
}
});
private void dropDown_nameItemStateChanged(java.awt.event.ItemEvent evt) {
if(evt.getStateChange() == ItemEvent.SELECTED)
{
String item = (String) evt.getItem();
System.out.println(item);
}
}
Good Luck!
The code is:
public class Tester {
private JComboBox box;
public Tester() {
box = new JComboBox();
box.addItem("One");
box.addItem("Two");
box.addItem("Three");
box.addItem("Four");
box.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == 1) {
JOptionPane.showMessageDialog(box, e.getItem());
System.out.println(e.getItem());
}
}
});
JFrame frame = new JFrame();
frame.getContentPane().add(box);
frame.pack();
frame.setVisible(true);
}
}
Have a look here,
box.addItemListener(new ItemListener(){
public void itemStateChanged(ItemEvent e){
if(e.getStateChange()== ItemEvent.SELECTED) {
//this will trigger once only when actually the state is changed
JOptionPane.showMessageDialog(null, "Changed");
}
}
});
When you select a new option, it will only once call the JOptionPane, indicating that the code there will be called once only.
Quote from Java Tutorial:
"Only one item at a time can be selected in a combo box, so when the user makes a new selection the previously selected item becomes unselected. Thus two item events are fired each time the user selects a different item from the menu. If the user chooses the same item, no item events are fired."
When the anyitem is selected from the combo box, it internally triggers selection change, i.e. it will call the function setSelectedItem.
If an explicit itemStateChanged event listener is implemented, the setSelectedItem will call itemStateChanged. So, when an item is selected it calls setSelectedItem then it calls itemStateChanged.
As the value of the combo box changes, even that too triggers itemStateChanged and hence itemStateChanged gets called.
I had written listener for item change to handle change in value of combo box when set internally from the code and that caused the function getting called twice.
Here are the 2 back traces, which gets invoked when a value is selected from combo box.
1st time on actual value change:
dataMgr.MainInterface.jComboBoxPaymentStatusValueChangeHandle(MainInterface.java:1431),
dataMgr.MainInterface.jComboBoxPaymentStatusItemStateChanged(MainInterface.java:1676),
dataMgr.MainInterface.access$600(MainInterface.java:28),
dataMgr.MainInterface$7.itemStateChanged(MainInterface.java:437),
javax.swing.JComboBox.fireItemStateChanged(JComboBox.java:1223),
javax.swing.JComboBox.selectedItemChanged(JComboBox.java:1271),
javax.swing.JComboBox.contentsChanged(JComboBox.java:1330),
javax.swing.AbstractListModel.fireContentsChanged(AbstractListModel.java:118),
javax.swing.DefaultComboBoxModel.setSelectedItem(DefaultComboBoxModel.java:93),
javax.swing.JComboBox.setSelectedItem(JComboBox.java:576), javax.swing.JComboBox.setSelectedIndex(JComboBox.java:622), javax.swing.plaf.basic.BasicComboPopup$Handler.mouseReleased(BasicComboPopup.java:852), java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:290), java.awt.Component.processMouseEvent(Component.java:6533), javax.swing.JComponent.processMouseEvent(JComponent.java:3324), javax.swing.plaf.basic.BasicComboPopup$1.processMouseEvent(BasicComboPopup.java:501), java.awt.Component.processEvent(Component.java:6298), java.awt.Container.processEvent(Container.java:2236), java.awt.Component.dispatchEventImpl(Component.java:4889), java.awt.Container.dispatchEventImpl(Container.java:2294), java.awt.Component.dispatchEvent(Component.java:4711), java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4888), java.awt.LightweightDispatcher.processMouseEvent(Container.java:4525), java.awt.LightweightDispatcher.dispatchEvent(Container.java:4466), java.awt.Container.dispatchEventImpl(Container.java:2280), java.awt.Window.dispatchEventImpl(Window.java:2746), java.awt.Component.dispatchEvent(Component.java:4711), java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758), java.awt.EventQueue.access$500(EventQueue.java:97), java.awt.EventQueue$3.run(EventQueue.java:709), java.awt.EventQueue$3.run(EventQueue.java:703), java.security.AccessController.doPrivileged(Native Method), java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76), java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86), java.awt.EventQueue$4.run(EventQueue.java:731), java.awt.EventQueue$4.run(EventQueue.java:729), java.security.AccessController.doPrivileged(Native Method), java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76), java.awt.EventQueue.dispatchEvent(EventQueue.java:728), java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201), java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116), java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105), java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101), java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93), java.awt.EventDispatchThread.run(EventDispatchThread.java:82)]
2nd time from the due to operation on combobox
dataMgr.MainInterface.jComboBoxPaymentStatusValueChangeHandle(MainInterface.java:1431),
dataMgr.MainInterface.jComboBoxPaymentStatusItemStateChanged(MainInterface.java:1676),
dataMgr.MainInterface.access$600(MainInterface.java:28),
dataMgr.MainInterface$7.itemStateChanged(MainInterface.java:437),
javax.swing.JComboBox.fireItemStateChanged(JComboBox.java:1223),
javax.swing.JComboBox.selectedItemChanged(JComboBox.java:1280),
javax.swing.JComboBox.contentsChanged(JComboBox.java:1330),
javax.swing.AbstractListModel.fireContentsChanged(AbstractListModel.java:118),
javax.swing.DefaultComboBoxModel.setSelectedItem(DefaultComboBoxModel.java:93),
javax.swing.JComboBox.setSelectedItem(JComboBox.java:576),
javax.swing.JComboBox.setSelectedIndex(JComboBox.java:622),
javax.swing.plaf.basic.BasicComboPopup$Handler.mouseReleased(BasicComboPopup.java:852),
java.awt.AWTEventMulticaster.mouseReleased(AWTEventMulticaster.java:290),
java.awt.Component.processMouseEvent(Component.java:6533),
javax.swing.JComponent.processMouseEvent(JComponent.java:3324),
javax.swing.plaf.basic.BasicComboPopup$1.processMouseEvent(BasicComboPopup.java:501),
java.awt.Component.processEvent(Component.java:6298), java.awt.Container.processEvent(Container.java:2236),
java.awt.Component.dispatchEventImpl(Component.java:4889), java.awt.Container.dispatchEventImpl(Container.java:2294),
java.awt.Component.dispatchEvent(Component.java:4711), java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4888),
java.awt.LightweightDispatcher.processMouseEvent(Container.java:4525), java.awt.LightweightDispatcher.dispatchEvent(Container.java:4466),
java.awt.Container.dispatchEventImpl(Container.java:2280), java.awt.Window.dispatchEventImpl(Window.java:2746),
java.awt.Component.dispatchEvent(Component.java:4711), java.awt.EventQueue.dispatchEventImpl(EventQueue.java:758),
java.awt.EventQueue.access$500(EventQueue.java:97), java.awt.EventQueue$3.run(EventQueue.java:709),
java.awt.EventQueue$3.run(EventQueue.java:703), java.security.AccessController.doPrivileged(Native Method),
java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76),
java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86),
java.awt.EventQueue$4.run(EventQueue.java:731), java.awt.EventQueue$4.run(EventQueue.java:729),
java.security.AccessController.doPrivileged(Native Method), java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76), java.awt.EventQueue.dispatchEvent(EventQueue.java:728), java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201), java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116), java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105), java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101), java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93), java.awt.EventDispatchThread.run(EventDispatchThread.java:82)]
JComboBox.setFocusable(false) will do the trick.

Categories