PropertyChangeSupport for SpinnerNumberModel - java

I want to listen to the changes of the value of the SpinnerNumberModel for a JSpinner.
I create a PropertyChangeSupport and put the model into it.
I need the propertyChangeListener, because it shows me the old and new value of the property.
The snippet doesn't work: the propertyChange method prints nothing, when I click on the JSpinner.
A simple ChangeListener give only the new value, but I need also the old value, how can I get it?
package de.unikassel.jung;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.JFrame;
import javax.swing.JSpinner;
import javax.swing.SpinnerNumberModel;
public class PropertyChangeTest implements PropertyChangeListener {
public static void main(String[] args) {
new PropertyChangeTest();
}
public PropertyChangeTest() {
JFrame frame = new JFrame();
frame.setBounds(100, 100, 450, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
int value = 1;
int min = 0;
int max = 10;
int step = 1;
SpinnerNumberModel spinnerModel = new SpinnerNumberModel(value, min, max, step);
PropertyChangeSupport pcs = new PropertyChangeSupport(spinnerModel);
pcs.addPropertyChangeListener("value", this);
JSpinner spinner = new JSpinner(spinnerModel);
frame.getContentPane().add(spinner);
frame.setVisible(true);
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt);
System.out.println(evt.getSource());
}
}

Instead of listening to the model, listen to the editor's JFormattedTextField, as suggested below.
JSpinner spinner = new JSpinner(new SpinnerNumberModel(1, 0, 10, 1));
JSpinner.DefaultEditor editor = (JSpinner.DefaultEditor) spinner.getEditor();
editor.getTextField().addPropertyChangeListener("value", this);

Monday morning ... classical time for not resisting a couple of comments :-)
#timaschew
"need the propertyChangeListener, because it shows me the old and new value of the property." - (nitpicking - but always have this strong urge to separate requirement and solution :), I think it's the other way round: on a change notification you need access to both the old and new value, a propertyChangeEvent/Listener is a notification type which supports it, there might be others
PropertyChangeSupport is not supposed to be used on part of the the observing code, it's supposed to be used on the obervable's side (just as #Hovercraft did in his example): it's sole responsibility is manage and notify the listeners registered to the observable
occasionally, accessibleContext provides a hook for hacks - nevertheless, it's a hack to hook into it (except you really need to support accessibility, which might well be the case :-) As with all hacks, that's a brittle solution which most probably will cause pain sometime in the future. Much more stable to follow the link about how Action and AbstractButton interact
#Hovercraft
enhancing the model with a richer change notification is the way-to-go (as in: my absolute favourite :-)
just a small detail: if you have a slave let him do all the work - PropertyChangeSupport has methods which take the old/new value, no need to feed to create an event on the observable. It will be thrown away anyway when old and new are equal
for newValue in the notification event, don't use the parameter but instead use getValue again (super might have rejected the change)
#trashgod
haha - you already guessed that I don't like solution: it breaks encapsulation in that it relies on an implementation detail, so don't except when in complete control of the JSpinner creation and are absolutely sure its editor is never changed

For a PropertyChangeSupport to work you need to call its firePropertyChange method, but more importantly the support object needs to have access to the setXXX method of the property that it is listening to, and in that method it needs to call PropertyChangeSupport's firePropertyChange method. And so I think for your idea to work, you'll need to extend the model's class, give it a PropertyChangeSupport object, give it the add and remove listener methods, and be sure to listen to changes made in the model's setValue method which is key. In my example that method looks like this:
#Override
public void setValue(Object newValue) {
// store old value and set the new one
Object oldValue = getValue();
super.setValue(newValue);
// construct the event object using these saved values
PropertyChangeEvent evt = new PropertyChangeEvent(this, VALUE, oldValue,
newValue);
// notify all of the listeners
pcs.firePropertyChange(evt);
}
Here's my sample model class that uses PropertyChangeSupport:
import java.beans.*;
import javax.swing.*;
import javax.swing.event.*;
#SuppressWarnings("serial")
class MySpinnerNumberModel extends SpinnerNumberModel {
public static final String VALUE = "value";
private SwingPropertyChangeSupport pcs = new SwingPropertyChangeSupport(this);
// you will likely need to create multiple constructors to match
// the ones available to the SpinnerNumberModel class
public MySpinnerNumberModel(int value, int min, int max, int step) {
super(value, min, max, step);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
#Override
public void setValue(Object newValue) {
// store old value and set the new one
Object oldValue = getValue();
super.setValue(newValue);
// construct the event object using these saved values
PropertyChangeEvent evt = new PropertyChangeEvent(this, VALUE, oldValue,
newValue);
// notify all of the listeners
pcs.firePropertyChange(evt);
}
}
And finally the test class to test out the above class to see if it is working properly:
import java.beans.*;
import javax.swing.*;
public class TestSpinnerPropChange {
private static void createAndShowUI() {
final MySpinnerNumberModel myModel = new MySpinnerNumberModel(1, 0, 10, 1);
final JSpinner spinner = new JSpinner(myModel);
final JTextField oldValueField = new JTextField(10);
final JTextField newValueField = new JTextField(10);
JPanel panel = new JPanel();
panel.add(spinner);
panel.add(new JLabel("old value:"));
panel.add(oldValueField);
panel.add(new JLabel("new value:"));
panel.add(newValueField);
myModel.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
// checking the property name is overkill here, but is a good habit
// to get into, especially if listening to more than one property.
if (evt.getPropertyName().equals(MySpinnerNumberModel.VALUE)) {
oldValueField.setText(evt.getOldValue().toString());
newValueField.setText(evt.getNewValue().toString());
}
}
});
JFrame frame = new JFrame("TestSpinnerPropChange");
frame.getContentPane().add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}

http://docs.oracle.com/javase/tutorial/javabeans/writing/properties.html#bound
You have to fireThePropertyChange in the setters.

Related

ActionListener call blocks MouseClick event

I have a window with a MenuItem "maddbound3" with the following ActionListener:
maddbound3.addActionListener
(
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
menu_addbound3();
}
}
);
When the menu is clicked this listener calls menu_addbound3() below:
void menu_addbound3()
{
while(getEditMode() != EditMode.NONE)
{
System.out.println("!... " + getEditMode());
synchronized(this)
{
try
{
wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
A MouseClicked event alters the value of the edit mode and issues a notifyAll() so that the while loop should exit. However, tests have shown that when the system is running through the while loop, the MouseClicked event never occurs on clicking the mouse.
Does the ActionListener block the MouseClicked event? How can I resolve this issue?
Thanks
Don't have a while(true) on the Swing event thread, and likewise don't call wait() on the Swing event thread -- you'll freeze the whole GUI making it completely unresponsive. You need to understand that the main Swing event thread or "event dispatch thread" is responsible for all Swing drawing and user interaction, and so if you tie it up with long-running or freezing code, you lock your entire GUI.
Instead, change the state of your program -- perhaps by setting a variable or two, and have the behavior of your program depend on this state. If you need more specific advice, please tell us what behavior you're trying to achieve, and we can perhaps give you a better way of doing it.
For more on the Swing event thread, please read: Lesson: Concurrency in Swing
Edit
You state:
When the user clicks the menu item I want to obtain information via a series of "discrete" mouse clicks from the window. Hence, on clicking the menu, the user would be prompted to "select a point in the window". So, what I need is for my ActionListener function (menu_addbound3) to then wait for a mouse click. Hence the wait/notify setup. A mouse click changes the edit_mode and notifyAll() causes the wait in the while loop to exit which then causes the while loop to exit and I can then prompt for my next bit of information within the menu_addbound3 function, repeating this as as I need to.
Thanks for the clarification, and now I can definitely tell you that you are doing it wrong, that you most definitely do not want to use the while loop or wait or notify. There are many ways to solve this issue, one could be to use some boolean or enum variables to give the program a state and then alter its behavior depending on the state. Your EditMode enum can be used in the MouseListener to let it know that its active, and then you could also give the MouseListener class a boolean variable windowPointSelected, set to false, and then only set it true after the first click has been made.
Edit 2
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class ProgState extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final Color EDIT_COLOR = Color.red;
private EditMode editMode = EditMode.NONE;
private boolean firstPointSelected = false;
private JMenuBar jMenuBar = new JMenuBar();
private JTextField firstPointField = new JTextField(15);
private JTextField secondPointField = new JTextField(15);
public ProgState() {
add(firstPointField);
add(secondPointField);
JMenu menu = new JMenu("Menu");
menu.add(new JMenuItem(new AbstractAction("Edit") {
#Override
public void actionPerformed(ActionEvent arg0) {
setEditMode(EditMode.EDITING);
setFirstPointSelected(false);
}
}));
jMenuBar.add(menu);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mEvt) {
if (getEditMode() == EditMode.EDITING) {
Point p = mEvt.getPoint();
String pStr = String.format("[%d, %d]", p.x, p.y);
if (!isFirstPointSelected()) {
firstPointField.setText(pStr);
setFirstPointSelected(true);
} else {
secondPointField.setText(pStr);
setEditMode(EditMode.NONE);
}
}
}
});
}
public void setEditMode(EditMode editMode) {
this.editMode = editMode;
Color c = editMode == EditMode.NONE ? null : EDIT_COLOR;
setBackground(c);
}
public EditMode getEditMode() {
return editMode;
}
public void setFirstPointSelected(boolean firstPointSelected) {
this.firstPointSelected = firstPointSelected;
}
public boolean isFirstPointSelected() {
return firstPointSelected;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public JMenuBar getJMenuBar() {
return jMenuBar;
}
private static void createAndShowGui() {
ProgState progState = new ProgState();
JFrame frame = new JFrame("EditMode");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(progState);
frame.setJMenuBar(progState.getJMenuBar());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum EditMode {
NONE, EDITING
}
From the discussion it seems that having your class assume a number of states is the best way to proceed. We can achieve this by one or more enum variables. The reason I found this so hard to grasp initially is that I couldn't see the benefit of having all of ones code in the MouseClicked function. This is ugly and unmanageable at best.
However, using multiple enums and splitting processing into a number of external functions, we do indeed achieve a nice system for what we want.

before cell select jtable event

Are there any event that is fired when cell is about to be selected? There is ListSelectionListener, but it has event that is fired only after selection has happened. I need some way to cancel selection event and using ListSelectionListener it is not easy as selection has already happened and I need to have some state variable that indicates if selection is normal or is cancel of a previous selection.
Are there a way to switch off selection notifications? However this is not 100% good solution (there will be problems if some listeners saves selection state in its local storage) this is better than nothing.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.ListSelectionModel;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.JTable;
public class JTableExample extends JFrame {
/**
*
*/
private static final long serialVersionUID = 6040280633406589974L;
private JPanel contentPane;
private JTable table;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
JTableExample frame = new JTableExample();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public JTableExample() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(new BorderLayout(0, 0));
setContentPane(contentPane);
table = new JTable(new MyTableModel());
ListSelectionModel selectionModel = table.getSelectionModel();
selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
selectionModel.addListSelectionListener(new MySelectionListener());
contentPane.add(table, BorderLayout.CENTER);
}
class MyTableModel extends AbstractTableModel {
/**
*
*/
private static final long serialVersionUID = -8312320171325776638L;
public int getRowCount() {
return 10;
}
public int getColumnCount() {
return 10;
}
public Object getValueAt(int rowIndex, int columnIndex) {
return rowIndex * columnIndex;
}
}
class MySelectionListener implements ListSelectionListener {
public void valueChanged(ListSelectionEvent e) {
int selectedRow = table.getSelectedRow();
if (selectedRow == 5) {
System.out.println("I would like this selection never happened.");
}
}
}
}
whatever the goal is that you want to achieve: thinking "mouseEvent" is not enough, selection might change for other reasons (f.i. keyboard input, programmatic trigger, ..). Reverting an unwated change in a listener is not an option: as you already noted that would require to keep a duplicate of the selection and might confuse other listeners.
The only way (that I see, could be others, of course ;-) is not to let it happen in the first place: implement a List SelectionModel which doesn't change the selection if certain conditions are met. My favourite (biased me :-) is a VetoableListSelectionModel It's a subclass of DefaultListSelectionModel which in SingleSelectionMode waits for vetoes from interested parties before actually changing.
Here's a (raw) code snippet using it:
VetoableListSelectionModel vetoableSelection = new VetoableListSelectionModel();
VetoableChangeListener navigationController = new VetoableChangeListener() {
public void vetoableChange(PropertyChangeEvent evt)
throws PropertyVetoException {
// custom method that implements your condition
if (!canSelect((int) evt.getOldValue(), (int) evt.getNewValue()))
throw new PropertyVetoException("uncommitted changes",
evt);
}
};
vetoableSelection.addVetoableChangeListener(navigationController);
myTable.setSelectionModel(vetoableSelection);
The only way to do this that i can think of is handle the MouseEvent and using MouseAdapters, get the coordinates and somehow to check whether the mouse pointer is hovering over a cell or not, if it is, do what you want to do. you probably have to do addMouseListener to get the effect.
Assuming you have set the selection mode of the ListSelectionModel to the desired value and added a listener, you may find it helpful to examine the predicate getValueIsAdjusting(), which "Returns true if the selection is undergoing a series of changes." In practical terms, it is true when the mouse is down, or when it is being dragged in one of the INTERVAL modes.
It may also help to know more about the goal of the this effort, as another approach may be helpful. Naturally, an sscce is always in order.
Addendum: This appears to work, but #kleopatra's approach would prevent losing the previous selection.
private static final int FORBID = 5;
class MySelectionListener implements ListSelectionListener {
public void valueChanged(ListSelectionEvent e) {
int selectedRow = table.getSelectedRow();
if (selectedRow == FORBID) {
selectionModel.removeIndexInterval(FORBID, FORBID);
System.out.println(FORBID + " forbidden.");
}
}
}
user is forbidden to change row if he has not accepted change he made in that row.
You could use a custom CellEditor that conditions stopCellEditing(), as shown in this example.

JInternalFrame selection

I have a JDesktopPane containing some JInternalFrames. I want some menus on the menubar to be activated only when one of the JInternalFrames is selected. I've tried using VetoableChangeListener, with the following code in it:
JInternalFrame selectedFrame = desk.getSelectedFrame();
if ((selectedFrame != null)) {
imageMenu.setEnabled(Boolean.TRUE);
} else {
imageMenu.setEnabled(Boolean.FALSE);
}
But the results are not what I expected - for example, the menu is enabled only the second time I add a frame. when I close all frames, it remains enabled.
How can I make this work?
you have to read basic tutorial about JInternalFrames with link to the InternalFrameListener,
but another and look like as better way is programatically to know those event in all cases and evety times is by adding PropertyChangeListener as shows examples Getting All Frames in a JDesktopPane Container, by adding PropertyChangeListener you can listeng for these events
Add an InternalFrameListener to each internal frame added to the desktop pane, and each time an event is triggered, execute the code you have shown in your question.
This code could be better written though:
setEnabled takes a primitive boolean as argument, not a java.lang.Boolean. Use true and false rather than Boolean.TRUE and Boolean.FALSE.
The expression (selectedFrame != null) evaluates as a boolean. Just write
imageMenu.setEnabled(selectedFrame != null);
instead of
if ((selectedFrame != null)) {
imageMenu.setEnabled(Boolean.TRUE);
} else {
imageMenu.setEnabled(Boolean.FALSE);
}
I would just create a custom event and fire it when a JInternalFrame gets focus (isActivated).
The menu items would listen for this event, intercept it and set their status enabled or disabled accordingly.
The advantage here is that you don't have to handle what menu items should be available for which types of internal frames, just fire the appropriate event. It'll make your life easier if you add more internal frames in the future.
This answer is based on the answer by #mKorbel. This example shows one of the ways to detect focus between internal frames as is demonstrated here:
package com.apexroot.sandbox;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
/**
* author grants unlimited license to modify, reuse and redistribute. based on
* the suggestion by #mKorbel on stackoverflow at
* http://stackoverflow.com/questions/7219860/jinternalframe-selection
* please keep a URL to the original version in the source code.
* http://javajon.blogspot.com/2015/08/windowfocuslistener-for-jinternalframe.html
*
* #author Apexroot
*/
public class InternalFrameFocusListenerExample {
public static final String INTERNAL_FRAME_FOCUS_EVENT_PROPERTY = "selected";
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
final JFrame jFrame = new JFrame();
final JDesktopPane jDesktopPane = new JDesktopPane();
final JInternalFrame[] jInternalFrames = new FocusInternalFrame[3];
for (int i = 0; i < jInternalFrames.length; i++) {
jInternalFrames[i] = new FocusInternalFrame();
}
jFrame.dispose();
jFrame.setContentPane(jDesktopPane);
jDesktopPane.setPreferredSize(new Dimension(400, 200));
jFrame.pack();
jFrame.setVisible(true);
for (int i = 0; i < jInternalFrames.length; i++) {
jDesktopPane.add(jInternalFrames[i]);
jInternalFrames[i].setLocation(10 + 60 * i, 10 + 40 * i);
jInternalFrames[i].setVisible(true);
}
}
});
}
public static class FocusInternalFrame extends JInternalFrame {
public FocusInternalFrame() {
final JLabel jLabel = new JLabel("placeholder for pack();");
setContentPane(jLabel);
pack();
this.addPropertyChangeListener(
INTERNAL_FRAME_FOCUS_EVENT_PROPERTY,
new LabelFocusListener(jLabel));
}
}
private static class LabelFocusListener implements PropertyChangeListener {
private final JLabel jLabel;
public LabelFocusListener(JLabel jLabel) {
this.jLabel = jLabel;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
// please keep a URL to the original version in the source code.
// http://javajon.blogspot.com/2015/08/windowfocuslistener-for-jinternalframe.html
if (INTERNAL_FRAME_FOCUS_EVENT_PROPERTY.equals(
evt.getPropertyName())) {
final Object oldValue = evt.getOldValue();
final Object newValue = evt.getNewValue();
if (oldValue instanceof Boolean
&& newValue instanceof Boolean) {
boolean wasInFocus = (Boolean) oldValue;
boolean isInFocus = (Boolean) newValue;
if (isInFocus && !wasInFocus) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
// focus gained
jLabel.setText("focus gained");
}
});
} else if (wasInFocus && !isInFocus) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
// focus lost
jLabel.setText("focus lost");
}
});
}
}
}
}
}
}

Java JList refresh only after minimized or maximized

I have a JList connected to a collection.
When collection change, I need the JList to be automatically refreshed. But I can see that my JList will refresh only after I minimized (or maximized) the JFrame.
Why?
I'm new with Java and I'm trying to learn.
I have this collection for the list model:
public class UserCollection extends Vector<User> implements ListModel{
private static final long serialVersionUID = 2668410577023194442L;
#Override
public void addListDataListener(ListDataListener arg0) {
System.out.println("add list! --- " +arg0.toString());
}
#Override
public Object getElementAt(int index) {
return this.get(index).getName();
}
#Override
public int getSize() {
return(this.elementCount);
}
#Override
public void removeListDataListener(ListDataListener arg0) {}
}
In another class, I populate the collection with userList.add(u1); or userList.remove(u1);
In the JFrame class I have:
JList list = new JList();
list.setModel(xmppManager.userList);
I can see on console the collection changing, and if I minimize the JFrame and/or maximize, the JList is refreshed properly...
If you can, I suggest you use a DefaultListModel as your JList's model. This model will automate the change of the view (the JList) as the model changes and will make your life much easier. If you absolutely must use a collection of your own making, then see if you can have the class that holds it extend an AbstractListModel. If you do this, be sure to call the appropriate fireXXXX() method whenever you change data in the model.
Once your collection has changed, call a refresh on your JList:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
jlist.revalidate(); // triggers a repaint of all the items in the JList.
jlistContainer.repaint(); // Not sure if this one is needed
}
});
How your JList model is updated?
[EDIT] Now that we have your code, you should look at AbstractListModel and implements your model in the same way, or better, extends AbstractListModel.
Actually, you add data to your collection but the model is not notified of this change!
When you added or removed items using the add and remove methods, you were calling methods in Vector. This updated your model but the actual JList had no way to know that the model was updated and that it needed to refresh itself.
(I think others have adequately explained how to fix the problem)
Finally I have resolved this thanks to your help!
This is what I did:
1) In the class doing the work I have
public DefaultListModel userList;
and I populate it with userList.addElement(user) and userList.removeElement(user)
2) in the JFrame class, I have:
JList list = new JList();
list.setCellRenderer(new UsersRenderer());
list.setModel(xmppManager.userList);
3) and this is my UsersRenderer:
import java.awt.Color;
import java.awt.Component;
import javax.swing.*;
public class UsersRenderer extends javax.swing.JPanel implements ListCellRenderer {
/**
*
*/
private static final long serialVersionUID = -9210143012283239644L;
public UsersRenderer() {
initComponents();
}
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
User person = (User) value;
labelUser.setText(person.getName() + " " + person.getAddress());
if (isSelected) {
setBackground(Color.red);
setForeground(Color.white);
} else {
setBackground(Color.white);
setForeground(Color.black);
}
return this;
}
private void initComponents() {
labelUser = new javax.swing.JLabel();
labelUser.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N
labelUser.setText("testo");
add(labelUser);
}
private javax.swing.JLabel labelUser;
}

DefaultListModel.clear errors

I'm trying to display a list of items and, when the user clicks on an item, to clear the list and display another list.
If I run this and click on the first entry on the displayed list, the program dies with a long trail of runtime exceptions. If I remove the clear() line (commented below), it runs fine. Adding try/catch didn't reveal any information useful to me. Apologies for the long code, but I couldn't figure out how to shorten and still generate the errors.
What at I doing wrong?
import java.util.*;
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
class ListGui extends JPanel implements ListSelectionListener {
private static JList list;
private static DefaultListModel listModel = new DefaultListModel();
public ListGui() {
super(new BorderLayout());
list = new JList(listModel);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.addListSelectionListener(this);
JScrollPane listScrollPane = new JScrollPane(list);
add(listScrollPane, BorderLayout.CENTER);
}
public static void Populate(List<String> lines) {
listModel.clear();
for(String line : lines) {
listModel.addElement(line);
}
}
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() == false) {
List<String> out = new ArrayList<String>();
out.add("three");
out.add("four");
Populate(out);
}
}
}
public class TestClear {
static JComponent newContentPane = new ListGui();
private static void createAndShowGUI() {
JFrame frame = new JFrame("toast");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(newContentPane);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
List<String> out = new ArrayList<String>();
createAndShowGUI();
out.add("one");
out.add("two");
ListGui.Populate(out);
}
}
I'm trying to display a list of items and, when the user clicks on an item, to clear the list and display another list.
That doesn't sound like the best design to me. The selection will change whenever you click on an item or when you use the arrow keys to move up or down the list. I'm sure for users that like to use the keyboard you don't want the list to change every time you use an arrow key.
The normal design would be to invoke an Action on the list on a "double click" or when the user users "Enter" from the keboard. This is easily implemente using the List Action concept.
However, if you really do want to update the list on every selection then I would use code like:
list.removeListSelectionListener( this );
populate(...);
list.addListSelectionListener(this);
The problem is that you are calling Populate() in valueChanged() which triggers valueChanged() and hence the stackoverflow.
The simplest solution is to have a flag to prevent reentry.
boolean busy = false;
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() == false && !busy) {
busy = true;
List<String> out = new ArrayList<String>();
out.add("three");
out.add("four");
Populate(out);
busy = false;
}
}
If your code could be accessed by multiple threads, you should be looking into ReentrantLock

Categories

Resources