I have a JSpinner that has all integers as model. I want it's value to increase with the down arrow and value to decrease with up arrow,which is the exact opposite of the default usage.
I have already done this using a variable that has the previous value and added a Change Listener. Whenever a value changes i compare it to the previous. If it's about to increase i instead decrease it and vice versa.
I wanted to know if there is another way of doing it. Like a listener who knows which arrow is pressed.
SpinnerNumberModel model = new SpinnerNumberModel(5, 1, 100, -1); // Note: -ve step
Andrew's answer is the way to go. This is another way to achieve it via a ChangeListener, since you mentioned it.
A ChangeListener fires every time the value changes in the spinner field.
The steps are:
Keep a field reference of its current value.
Detect the new value in listener
If the new value is higher than the old value, it means the ↑ arrow pressed
Otherwise, it means the ↓ arrow pressed
According to the change, set the opposite value
An SSCCE of what I mean:
public class SpinnerExample extends JFrame {
private int oldValue;
public SpinnerExample() {
super("");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
JSpinner spinner = new JSpinner();
spinner.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
int newValue = (int) spinner.getValue();
spinner.removeChangeListener(this);
if (newValue > oldValue) //next value button pressed
spinner.setValue(oldValue - (newValue - oldValue));
else
spinner.setValue(oldValue + (oldValue - newValue));
oldValue = (int) spinner.getValue();
spinner.addChangeListener(this);
}
});
add(spinner);
setLocationByPlatform(true);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new SpinnerExample().setVisible(true));
}
}
Note that the listener is removed before changing the value and after the change is added again. If you dont remove-change-add the listener, you will end up with a StackOverFlow error since changing the value will fire the listener again and again.
Also, remember that this in line spinner.removeChangeListener(this); refers to the ChangeListener. Converting this listener to a lambda expression:
spinner.addChangeListener(e -> {
int newValue = (int) spinner.getValue();
spinner.removeChangeListener(this);
if (newValue > oldValue) //next value button pressed
spinner.setValue(oldValue - (newValue - oldValue));
else
spinner.setValue(oldValue + (oldValue - newValue));
oldValue = (int) spinner.getValue();
System.out.println("oldValue:" + oldValue);
spinner.addChangeListener(this);
});
you will not able to remove the listener, since the this will be a reference to SpinnerExample instance and not the listener. Long story short, keep it an anonymous class and not a lambda.
Another way to accomplish is to switch the handlers/listeners of these buttons (probably reflection will have to interfere), but it will require a lot more effort. Plus there is no guarantee that will work for other look and feels, since they are initiated in <LookAndFeel>-ComboBoxUI.
Related
I use JSlider in my project. And in somewhere I need to get the value of slider but getValue() method does not return the current slider value. It always returns the default value.
Here is the related part of my code:
this.slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
// This two lines below are about Cytoscape framework. I am taking a column of a table and convert it to List<String>. There are not doing anything about slider.
CyColumn timeColumn = table.getColumn("startTime"); // Getting start time column
List<String> timeList = filter.getTimeFromColumn(timeColumn); // Gets value of start time column without null values
// I printed out slider.getValue() value and it returns default slider value all the time.
sliderLabel.setText(timeList.get(slider.getValue()));
}
});
In another part of my code, I need to change the min and max values of the slider. For that, I use the code below:
public void reCreateSlider(){
// Activity size returns correct.
ArrayList<CyNode> activities = filter.FilterRowByNodeType("activity", "nodeType");
this.slider = new JSlider(0, activities.size());
}
Why slider.getValue() returns default value all the time? Thanks a lot,
Actually I just change the reCreateSlider() method like below and it worked.
public void reCreateSlider(){
// Activity size returns correct.
ArrayList<CyNode> activities = filter.FilterRowByNodeType("activity", "nodeType");
slider.setMinimum(0);
slider.setMaximum(activities.size());
}
If anyone has the same problem, can do this way.
I'm quite a newbie on JavaFX and I need to bind the visible property of a Label in a way that, if the value it represents reaches 0, the Label should be invisible. Also, it needs to be updated when the bounded integerProperty value changes.
This is my code:
#FXML
private Label kingRewardLabel;
// many other stuff between
IntegerProperty kingBonus = mainApp.getLocalModel().getMap().kingBonus();
kingBonus.addListener((observable, oldValue, newValue) -> {
if (newValue.equals(0)) {
kingRewardLabel.setVisible(false);
} else {
kingRewardLabel.setText(String.valueOf(newValue.intValue()));
}
});
// testing the listener
kingBonus.setValue(25);
I have already tried to debug a little but everything seems fine, no error, no exception thrown, just the listener does not work, or at least not as I expect, because the Label still show the default text "Label", instead of "25"
You can use simply bindings to achieve this:
kingRewardLabel.textProperty().bind(kingBonus.asString());
kingRewardLabel.visibleProperty().bind(kingBonus.greaterThan(0));
The Label kingRewardLabel will display the value of the IntegerProperty kingBonus and it is only visible if the displayed value is greater than zero.
But, if you want to stay with listeners:
kingBonus.addListener((obs, oldVal, newVal) -> {
kingRewardLabel.setVisible(newVal.intValue() > 0);
kingRewardLabel.setText(newVal.toString());
});
This is almost the same as your listener in the question, but in that case, if the Label became invisible, it will never become visible again as kingRewardLabel.setVisible(true) is never called.
Finally, to answer your question about why the listener is "not working" - there can be two possible reasons:
1) The Label, which one is displayed is not the Label stored in kingRewardLabel
2) At the time when you call kingBonus.setValue(25);, the value stored in kingBonus is already 25, no changed event will be fired, therefore the listener is not executed at all.
You can go like this:
kingBonus.addListener(l -> {
int value = kingBonus.getValue().intValue();
System.out.println("Entered listener for value:" + value);
if (value == 0)
kingRewardLabel.setVisible(false);
else
kingRewardLabel.setText(value+"");
});
});
I am trying to update some information every time the Slider value is changed, so I have the code set up like this:
int betAmount = 0;
Slider betSlider = new Slider();
betSlider.setMinorTickCount(4);
betSlider.setMajorTickUnit(250);
betSlider.setSnapToTicks(true);
betSlider.valueChangingProperty().addListener((obs, wasChanging, isChanging) -> {
if (!isChanging) {
betAmount = (int) betSlider.getValue();
update(); //update method, not relevant to problem
}
});
The problem that I am having is that the getValue() method on my slider is being called before it snaps to the nearest tick. Because of this, I am getting the incorrect value stored in my betAmount variable. I was wondering if there was any way to get the slider's value after it has finished snapping to the nearest tick.
Try using valueProperty() in place of valueChangingProperty()
betSlider.valueProperty().addListener((obs, oldValue, newValue) -> {
betAmount = newValue.intValue());
});
valueChanging -> It provides notification that the value is changing.
value -> The current value represented by this Slider.
The way I've done this in the past is to use the slider's valueProperty() and filter out any new values that occur while the slider is still changing.
int betAmount = 0;
Slider betSlider = new Slider();
betSlider.setMinorTickCount(4);
betSlider.setMajorTickUnit(250);
betSlider.setSnapToTicks(true);
betSlider.valueProperty().addListener((obs, oldValue, newValue) -> {
if(newValue != null && !newValue.equals(oldValue) && !betSlider.isValueChanging()) {
betAmount = newValue.intValue();
update(); //update method, not relevant to problem
}
});
I used the code below when initialising my sliders:
//Gets the corresponding slider value, from it's represented value.
int curr = valueToSlider(min, max, current, scale, q, grains);
final JSlider slider = new JSlider(JSlider.VERTICAL, 0, grains, curr);
slider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent event) {
double value = sliderToValue(min, max, slider.getValue(), scale, q, grains);
String str = "";
if (valueType == SliderValueType.FLOAT)
str = String.format("%.2f",value);
if (valueType == SliderValueType.INTEGER)
str = String.format("%.0f", value);
valueLabel.setText(str);
callCommand(c, value);
}
});
Now I need to trigger the changed event to get that valueLabel label to set in its correct format.
slider.setValue(curr);
That doesn't trigger the changed event, I'm guessing because the value hasn't changed. A simple hacky fix is to just do something like:
slider.setValue(1);
slider.setValue(curr);
But you could imagine that in some code, that triggering the changed event with a random value, might have unwanted consequences.
I could reproduce that setText method in my initialisation method.
if (valueType == SliderValueType.FLOAT)
str = String.format("%.2f",curr);
if (valueType == SliderValueType.INTEGER)
str = String.format("%.0f", curr);
valueLabel.setText(str);
(Which sounds like the best solution here to be honest).
But just wondering - is there a way to trigger an changed event another way?
Refactor the contents of stateChanged() into a separate method, say updateLabel(). Call that method from both the stateChanged() method and your initialisation code.
I agree, it would be wrong to set the value to some arbitrary value just to trigger a change.
Note: you can manually trigger a change through JSlider.fireStateChanged(), however that's still less clear to a future code maintainer than simply calling updateLabel().
Whenever I click a JSlider it gets positioned one majorTick in the direction of the click instead of jumping to the spot I actually click. (If slider is at point 47 and I click 5 it'll jump to 37 instead of 5). Is there any way to change this while using JSliders, or do I have to use another datastructure?
As bizarre as this might seem, it's actually the Look and Feel which controls this behaviour. Take a look at BasicSliderUI, the method that you need to override is scrollDueToClickInTrack(int).
In order to set the value of the JSlider to the nearest value to where the user clicked on the track, you'd need to do some fancy pants translation between the mouse coordinates from getMousePosition() to a valid track value, taking into account the position of the Component, it's orientation, size and distance between ticks etc. Luckily, BasicSliderUI gives us two handy functions to do this: valueForXPosition(int xPos) and valueForYPosition(int yPos):
JSlider slider = new JSlider(JSlider.HORIZONTAL);
slider.setUI(new MetalSliderUI() {
protected void scrollDueToClickInTrack(int direction) {
// this is the default behaviour, let's comment that out
//scrollByBlock(direction);
int value = slider.getValue();
if (slider.getOrientation() == JSlider.HORIZONTAL) {
value = this.valueForXPosition(slider.getMousePosition().x);
} else if (slider.getOrientation() == JSlider.VERTICAL) {
value = this.valueForYPosition(slider.getMousePosition().y);
}
slider.setValue(value);
}
});
This question is kind of old, but I just ran across this problem myself. This is my solution:
JSlider slider = new JSlider(/* your options here if desired */) {
{
MouseListener[] listeners = getMouseListeners();
for (MouseListener l : listeners)
removeMouseListener(l); // remove UI-installed TrackListener
final BasicSliderUI ui = (BasicSliderUI) getUI();
BasicSliderUI.TrackListener tl = ui.new TrackListener() {
// this is where we jump to absolute value of click
#Override public void mouseClicked(MouseEvent e) {
Point p = e.getPoint();
int value = ui.valueForXPosition(p.x);
setValue(value);
}
// disable check that will invoke scrollDueToClickInTrack
#Override public boolean shouldScroll(int dir) {
return false;
}
};
addMouseListener(tl);
}
};
This behavior is derived from OS. Are you sure you want to redefine it and confuse users? I don't think so. ;)