I have a list of Map.Entry<String,Integer>s that I am looping through, and for each one, making a JLabel/JSpinner representing that particular entry.
How can I make it so that when the ChangeListener fires on the JSpinner, it updates that entry to reflect the new value?
My code looks like
for (Map.Entry<String,Integer> entry : graph.getData()) {
SpinnerNumberModel model = new SpinnerNumberModel(
entry.getValue(),(Integer)0,(Integer)100,(Integer)5);
JSpinner spinner = new JSpinner(model);
JLabel label = new JLabel(entry.getKey());
spinner.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
entry.setValue((Integer)((JSpinner)e.getSource()).getValue());
}
});
// more stuff
}
However, this does not work, because entry either needs to be final or object-level, and it is neither.
Is there an easier way, like C#'s dataSource that directly binds the object to the spinner? Or how can I make this work?
There are several options if you want to stick with individual JLabel and JSpinner. Use #trashgod's answer if the map could get large.
WRONG Per #Suresh Kumar's comment: final Map.Entry<String,Integer> entry
WRONG Add spinner.putClientProperty("MyEntryKey", entry), then in your ChangeListener get the entry with spinner.getClientProperty.
Add spinner.putClientProperty("MyEntryKey", entry.getKey()), then in your ChangeListener graph.put(spinner.getClientProperty(), ...).
From #Michael's answer (not sure if this is ethical): Add final String key = entry.getKey(), then in your ChangeListener graph.put(key, ...).
Let your enclosing class implement ChangeListener to avoid one ChangeListener per JSpinner:
public void stateChanged(ChangeEvent e) {
JSpinner spinner = (JSpinner)e.getSource();
graph.put((String)spinner.getClientProperty("MyEntryKey"), (Integer)spinner.getValue());
}
BTW graph.getData() looks a bit odd. If graph implements Map, you should use graph.entrySet().
Particularly if the number of Map entries is large, consider using a JTable, with your Map as the core of the data model and a custom render/editor extending JSpinner. There's a related example here.
#Suresh is right about using final in the for loop, but since Map.Entry values aren't viable outside of the iterator, this would lead to trouble. Another possibility for this one-way binding would be
for (Map.Entry<String, Integer> entry : graph.getData()) {
...
final String key = entry.getKey();
spinner.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
graph.put(key, (Integer)((JSpinner)e.getSource()).getValue());
}
});
For a more complete data binding, you may want a more complex object to bind against. See the jgoodies binding API's ValueModel and SpinnerAdapterFactory, for example.
Related
My question is quite simple, unfortunately I can't find an answer.
Basically, I have a Swing window, with 2 JSpinners. The second JSpinner has to define the high bound of the first JSpinner. So for example, if the user puts the value 4 on the second JSpinner, they mustn't be able to put a value higher than 4 in the first JSpinner. And if the value currently displayed in the first JSpinner is higher than 4 I'll set it to 4.
So I've built my two JSpinners with SpinnerNumberModel in the constructor to set the initial bounds:
// First spinner
SpinnerNumberModel spinnerLimits_sante = new SpinnerNumberModel(3, 0.25, 3, 0.25);
JSpinner spin_sante = new JSpinner(spinnerLimits_sante);
// Second spinner
JSpinner spin_sante_max = new JSpinner(spinnerLimits_sante_max);
SpinnerNumberModel spinnerLimits_sante_max = new SpinnerNumberModel(3, 3, 32767, 1);
To get the value of the second JSpinner when it changes, I thought about using a ChangeListener on it. So each time it changes, I can get the new value, recreate a SpinnerNumberModel object using the new bounds (the low-bound and the interval wouldn't change, however the high-bound will be updated and the value will be updated if it is higher than the high-bound).
ChangeListener listenerSanteMax = new ChangeListener() {
public void stateChanged(ChangeEvent e) {
int newBound = spin_sante_max.getValue();
// ...
}
};
spin_sante_max.addChangeListener(listenerSanteMax);
Unfortunately this is where I'm stuck: JSpinner has no getter that would return a SpinnerNumberModel object nor its values.
And even if there was one, it seems there's no setter either.
So, my question:
Is it possible to dynamically change the range and value of a spinner?
Thanks in advance!
In the ChangeListener, instead of creating a new SpinnerNumberModel, just update the maximum value of the existing model. Here's two ways to do it.
If the ChangeListener can see the spinner models, you can do the update directly.
ChangeListener listenerSanteMax = new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
Number newMaximum = spinnerLimits_sante_max.getNumber().doubleValue();
spinnerLimits_sante.setMaximum((Comparable) newMaximum);
}
};
If the ChangeListener can't see the spinner models, you can do a cast.
JSpinner has a getModel() method the returns a SpinnerModel. You can cast it to a SpinnerNumberModel, but you should use instanceof to check that the cast will work.
ChangeListener listenerSanteMax = new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
Object source = e.getSource();
if (source instanceof JSpinner) {
JSpinner spinner = (JSpinner) source;
SpinnerModel sm = spinner.getModel();
if (sm instanceof SpinnerNumberModel) {
SpinnerNumberModel model = (SpinnerNumberModel) sm;
Number newMaximum = model.getNumber().doubleValue();
spinnerLimits_sante.setMaximum((Comparable) newMaximum);
}
}
}
};
In either case, newMaximum is a Number, and all subclasses supported by SpinnerNumberModel are Comparable.
Use getMode() to obtain the model from the JSpinner and type-cast it to SpinnerModelNumber like this:
SpinnerNumberModel snm = (SpinnerNumberModel)spinner.getModel();
how do i go do this, based on input in textfield, you get some results inside jlist, after you select option in jlist you then get an action, code examples would be appreciated... this is what i got so far:
final DefaultListModel<String> locations = new DefaultListModel<String>();
getTextFieldSearch().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
for(int i=0;i<10;++i) {
locations.add(i, "blah");
}
}
});
JList<String> list_racentRaces = new JList<String>(locations);
Start by taking a look at How to Use Lists, which has lots of awesome code examples.
The basic idea would be to...
When your actionPerformed method is triggered, create a new DefaultListModel, assuming you don't have your own implementation, fill it with all the new items you need and apply it to the instance of list_racentRaces
If you want to maintain what was previously in the list, you should consider starting with a DefaultListModel and simply add the new items to it as you need to...
Then, attach a ListSelectionListener to list_racentRaces and when the valueChanged event is triggered, find the selected item(s) and do what ever you need to based on these result(s)
You can find more details and examples through How to Write a List Selection Listener
I have a JPanel on which I've dynamically added quite a few JButtons. All of this is working perfectly. Later on in my program execution, I need to refer back to these buttons and pull out the button text. I'm having trouble figuring out how to refer back to them.
When I created each button, I gave it a unique name. Let's say this is the code where I created the button:
public void createButton(Container parent, String btnName) {
JButton btn = new JButton("xyz");
btn.setName(btnName);
btn.addActionListner(new ActionListner() {
//code
}
parent.add(btn);
}
In another method, I'm trying to retrieve the label on the button since it may have changed at run time. Do I need to keep an array of these buttons as they are created? Or is there a way that I can refer back to them directly?
This is what I was working on, but it's stupid. Can anyone suggest a correct approach?
public String getBtnLabel(String btnName) {
JButton btn = (JButton) btnName;
return btn.getText();
}
If the answer is that I just need to create the array and then iterate over it, that's fine. Just looking for other options.
You need to use a Map<String, JButton> so when you create your dynamic buttons you give them some sort of unqiue name:
//somewhere at the top of your class
private final Map<String, JButton> myButtonMap = new HashMap<>();
public void createButton(Container parent, String btnName) {
JButton btn = new JButton("xyz");
btn.setName(btnName);
btn.addActionListner(new ActionListner() {
//code
}
parent.add(btn);
myButtonMap.put(btnName, btn);
}
And then simply get from the map
public String getBtnLabel(String btnName) {
return myButtonMap.get(btnName).getText();
}
This will obviously throw an NPE if the button isn't defined...
Also you will need to delete from your map when you're done with it otherwise you're asking for a memory leak...
I suggest you to use a Map< String, JButton >.
At creation time you put new button into it with buttons.put( name, btn )
In event handler you use JButton btn = buttons.get( name )
Yes you need to keep references to the buttons. An array would be an option, but since arrays are awkward to use, you should prefer a List.
If you have a a reference to the JPanel containing the buttons, you could get them from it. but that is likely to be rather bothersome.
I would recommend keeping a list of your buttons or a reference to them in a map, however you could do this:
for (Component i : parent.getComponents()) {
if (i.getName().equals(btnName)) {
JButton b = (JButton) i;
// do stuff..
}
}
Using the parent component and iterating over the added components.
I have a JFrame with a menubar, in which i'd like some dynamic menus, as in, depending on the size of the ArrayList with HashLists. The problem here is that i then got a dynamic amount of JMenuItems, so i need a way to get as much variables as HashLists. In this case i made the name of the variable static, but obviously that's not working on the ActionListener, since all MenuItems will have the same ActionListener.
I'm wondering how to solve this, and how to get a menu with a dynamic amount of menuitems which all have unique actionlisteners.
private ArrayList<HashMap> menuLijst;
.
for (HashMap h : menuLijst) {
String vraag = (String) h.get("vraag");
JMenuItem qMenu = new JMenuItem(vraag);
informatie.add(qMenu);
qMenu.addActionListener(this);
}
Thanks in advance.
Depending on what you want to do in your ActionListener, you can either use this, as you do right now and in the actionPerformed you can then use ((JMenutItem)event.getSource()) to see which menu item has been clicked. Alternatively, you could register as many ActionListeners as there are menus, like this:
for (final HashMap h : menuLijst) {
final String vraag = (String) h.get("vraag");
final JMenuItem qMenu = new JMenuItem(vraag);
informatie.add(qMenu);
qMenu.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
// here event.getSource() is the same as qMenu
System.out.println("Menu "+qMenu+" with label "+vraag+" has been pressed");
System.out.println("HashMap is "+h);
}
});
}
But to me (and also seeing your previous questions), it seems that you are abusing the usage of HashMap instead of using appropriate new objects. I don't know what else is in your HashMap, let's say that you have 3 keys: "vraag", "answer", "grade", you could create the following class:
public class MyClass {
private String vraag;
private String answer;
private int grade;
// And getters and setters here.
}
And have a List<MyClass> instead of List<HashMap>.
I don't see why you want to use a HashMap for your Strings. If you save an ArrayList of Strings, and loop over them to add them all to the menu, you can add actionListeners to all of them, just as you are doing now.
In your ActionListener, check which button is clicked by looping through the ArrayList, and comparing to the name of the clicked button.
I have a JList which uses a DefaultListModel.
I then add values to the model which then appear in the JList. I have created a MouseListener which (when double clicked) allows the user to edit the current user number of that person they have selected.
I have checked that the actual object of that record is being changed, and it is. The only issue I'm having is getting the actual Jlist to update to show the new values of that object.
Snippets of the current code I have are:
Creating the JList and DefaultTableModel:
m = new DefaultListModel();
m.addListDataListener(this);
jl = new JList(m);
jl.addMouseListener(this);
Updating the object:
String sEditedNumber = JOptionPane.showInputDialog(this, "Edit number for " + name, number);
if (sEditedNumber != null) {
directory.update (name, sEditedNumber);
}
And (when jl is the JList and m is the DefaultTableModel):
public void contentsChanged(ListDataEvent arg0) {
jl.setModel(m);
}
Instead of setModel(), update your existing model using one of the DefaultListModel methods such as setElementAt(), which will fireContentsChanged() for you.
You need to call fireContentsChanged() on the ListModel.
You need to call DefaultListModel.fireContentsChanged(). But since this method is protected (I really wonder why), you can't do that directly. Instead, make a small subclass:
class MinoListModel<T> extends DefaultListModel<T>
{
public void update(int index)
{
fireContentsChanged(this, index, index);
}
}
Use it as your list model:
m = new MinoListModel<>();
jl = new JList(m);
After updating a user number, update the corresponding entry: m.update(theIndex);
Alternatively, if you don't want a subclass, you can just replace the JList element after the user number changed: m.setElementAt(theSameElement, theIndex);. Though this is somewhat cumbersome and having a subclass seems the cleaner approach.