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().
Related
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.
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 trying to make a calculator which is basicly like this: user enters about 18-19 values, then he presses a button and the result is equal to his input sum divided by the number of fields.
However, it's far more complex than that. The user can specify some options which add more input fields (Basicly make an option where he makes a jformattedtextfield editable, when by default it's non-editable), this can be a huge timewaster since I have to write a huge IF statement, which I'd hate doing. Basicly the user can activate some jSpinners which make more jFormattedTExtFields editable than the default options, significantly. What I'm asking is, how can you check which jformattedtextfields are editable, which aren't, and then perform operations with the ones which are editable?
add DocumentListener to all JFormattedTextFields and take value from Number instance, for eample for Float
if (someTextField.isEditable()){
float newValue = (((Number) resistorValue.getValue()).floatValue());
}
First of all, determine container where jformattedtextfield is placed, then just use JContainer API to traverse all child components and filter all jformattedtextfield components using instanceof.
For example:
public static int calcIfEnabled(Container container) {
int finalResult = 0;
for (Component c : container.getComponents()) {
if (c instanceof JFormattedTextField && c.isEnabled() && ((JFormattedTextField) c).isEditable()) {
finalResult += Integer.parseInt(((JFormattedTextField) c).getText());
}
}
return finalResult;
}
UPD: Of course you can include all child component using recursion and pass main Container (JFrame), but it will be not so good from performance point.
public static int calcIfEnabled(Container container) {
int finalResult = 0;
for (Component c : container.getComponents()) {
if (c instanceof JFormattedTextField && c.isEnabled() && ((JFormattedTextField) c).isEditable()) {
finalResult += Integer.parseInt(((JFormattedTextField) c).getText());
} else if (c instanceof Container) {
finalResult += calcIfEnabled((Container) c);
}
}
return finalResult;
}
Simplest would be to add the JFormattedTextField to an array, whenever you create a new one.
And then when required, iterator over the array and check whether it is editable.
It sounds like your model may be sufficiently complex to warrant using the Model View Controller pattern. Your model would specify which fields make sense for the view to display for a given controller state. It would also ensure that the displayed answer was similarly consistent. This example may offer additional guidance.
I have swt browser widget in which user can type with keyboard, I need for certain character user press change it to others.
for example when user press x, I change it y.
I add key listener where I can block user input with doit = false;
but now I can't pass my character.
here is what I am doing:
browser_1.addListener(SWT.KeyDown, new Listener() {
public void handleEvent(Event arg0) {
if(arg0.character=='x')
{
arg0.doit=false;
//now here how to send y as a charachter to browser widget
}
}
});
In other words can I somehow change character to other without using arg0.doit=false;
So after some search, here is the solution
In SWT you can add to display 'filter' listener instance which can modify pretty anything in the event (see docs for details).
Caution from Javadoc: Setting the type of an event to SWT.None from within the handleEvent() method can be used to change the event type and stop subsequent Java listeners from running. Because event filters run before other listeners, event filters can both block other listeners and set arbitrary fields within an event. For this reason, event filters are both powerful and dangerous. They should generally be avoided for performance, debugging and code maintenance reasons.
Here's the code (changes any typed key to 'l' character and wrote that in console, when the event actually arise)
browser.addListener(SWT.KeyDown, new Listener() {
public void handleEvent(Event event) {
System.out.println(event.character);
}
});
display.addFilter(SWT.KeyDown, new Listener() {
public void handleEvent(Event event) {
if(event.widget instanceof Browser) {
event.character = 'l';
}
}
});
IMHO it's really dirty solution, implementation on browser side (by JavaScript) is much more prettier
Also when I'm looking to your code (don't know if it's just some testing, proof-of-concept code, anyway), using variables with something_number or arg0 makes me sad. It makes code so much unreadable and obscure, try to avoid them ;]..
You can do following:
Text textControl = new Text(...);
textControl.addKeyListener(this);
...
public void keyPressed(KeyEvent e) {
if (e.character == 'x' && (e.stateMask & SWT.CONTROL) == 0) {
e.doit = false;
textControl.insert("y");
}
}
Some comments to this code:
We check for e.stateMask because we still need keep CTRL+X as cut-function. Please note, if you use instead of current code this one (that just check that no special button is pressed):
if (e.character == 'x' && e.stateMask == 0)
You will get a bug when CapsLock is on. In this case user should press Shift+X to get lower x.
Method insert("y") inserts charater to the place of cursor. When some text is seleted, the whole selection will be replaced by "y".
Current example changes only lower case of "x". You should change it if needed to handle also change upper X to Y.
I had a similar requirement: converting a decimal separator press on the keypad (a dot) to the decimal separator of our locale (a comma). I tried the same idea as Sorceror, but it didn't work for me either. What does work is setting event.doit = false and posting a new event that is the clone of the original event with the character replaced:
#Override
public void handleEvent(Event event) {
if (event.widget instanceof Browser && event.character == 'x') {
Event eventClone = cloneEvent(event);
eventClone.character = 'y';
event.doit = false;
display.post(eventClone);
}
}
(If display is a local variable you need to make it final.)
I created a small utility method to create a clone of the event:
/**
* #return a clone of the given {#link Event}
*/
public static Event cloneEvent(Event event) {
Event clone = new Event();
clone.display = event.display;
clone.widget = event.widget;
clone.type = event.type;
clone.detail = event.detail;
clone.item = event.item;
clone.index = event.index;
clone.gc = event.gc;
clone.x = event.x;
clone.y = event.y;
clone.width = event.width;
clone.height = event.height;
clone.count = event.count;
clone.time = event.time;
clone.button = event.button;
clone.character = event.character;
clone.keyCode = event.keyCode;
clone.keyLocation = event.keyLocation;
clone.stateMask = event.stateMask;
clone.start = event.start;
clone.end = event.end;
clone.text = event.text;
clone.doit = event.doit;
clone.data = event.data;
clone.touches = event.touches;
clone.xDirection = event.xDirection;
clone.yDirection = event.yDirection;
clone.magnification = event.magnification;
clone.rotation = event.rotation;
return clone;
}
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. ;)