How to set a slider to fix to positions? - java

I have a slider and I require it to fix to int numbers 1-23. So if the user were to move it from 1 they would end up on any number that is 1-23, unlike a double value. The purpose of this slider is to be a clock, you drag it and different times appear.
My first attempt:
private final int MIN_CHANGE = 1;
....
timeSlider.setMax(23);
....
timeSlider.valueProperty().addListener((obs, oldValue, newValue) -> {
if (!timeSlider.isValueChanging()) {
if (Math.abs(newValue.intValue()) > MIN_CHANGE) {
timeSlider.setValue(newValue.intValue());
}
}
});
This more or less works but only for clicking, not for dragging.
How could I get a slider to fix to whole numbers like 1-23 when dragging if the getValue is a double?
Another problem I face is when dragging the pm/am changing is not sudden.
timeSlider.valueChangingProperty().addListener((obs, wasChanging, isChanging) -> {
if (isChanging) {
if(timeSlider.getValue() < 12.99) {
labelTest.textProperty().bind(Bindings.format("%.0f:00am", timeSlider.valueProperty()));
} else {
labelTest.textProperty().bind(Bindings.format("%.0f:00pm", timeSlider.valueProperty()));
}
}
});

Try settings snapToTicks to true and the major tick spacing to 23:
timeSlider.setSnapToTicks(true);
timeSlider.setMajorTickUnit(23.0);
timeSlider.setBlockIncrement(23.0);
timeSlider.setMinorTickCount(0.0); // Disable minor ticks

In order to make it snap to numbers I used the blockIncrement, majorTickUnit and snapToTicks provided in the FXML scene builder which I didn't notice before until now, thanks to ItachiUchiha.
For reference I used the following settings
min="1.0"
max="23.0"
blockIncrement="1.0"
majorTickUnit="1.0"
minorTickCount="0"
showTickMarks="true"
snapToTicks="true"
Lastly, to fix my issue with am/pm not updating I changed my method for am/pm binding to the following
timeSlider.valueProperty().addListener((obs, oldValue, newValue) -> {
if (timeSlider.isValueChanging()) {
if (newValue.intValue() < 12) {
labelTest.textProperty().bind(Bindings.format("%.0f:00am", timeSlider.valueProperty()));
} else {
labelTest.textProperty().bind(Bindings.format("%.0f:00pm", timeSlider.valueProperty()));
}
}
});

Related

How to maintain ratio in values of JavaFX Spinner

I have two spinners and a toggle button in my JavaFX application, and I've also set them editable
Spinner<Integer> hSelector = new Spinner<>(1,15000,1000,1);
Spinner<Integer> wSelector = new Spinner<>(1,15000,1000,1);
hSelector.setEditable(true);
wSelector.setEditable(true);
ToggleButton lock = new ToggleButton("lock");
When this lock toggle button is selected I want to maintain the ratio in values of hSelector and wSelector. So I tried following,
lock.selectedProperty().addListener((observable, oldValue, newValue) -> {
if(newValue){
hSelector.getValueFactory().valueProperty().addListener((ob, ov, nv) -> {
wSelector.getEditor().setText(String.valueOf((nv * wSelector.getValue()) / ov));
});
wSelector.getValueFactory().valueProperty().addListener((ob, ov, nv) -> {
hSelector.getEditor().setText(String.valueOf((nv * hSelector.getValue()) / ov));
});
}
});
But this did not work well. The problem is that when lock is selected and I change the value of one spinner and then I just focus(by clicking in text field of spinner) them both one by one, their value changes automatically even when I don't edit the values.
Now, my questions, What is the correct way to maintain the ratio in values of these spinners?, what is the problem in my method? and how to remove the behavior of maintaining ratio after lock is un-selected.
Do not add a new listener every time the toggle button changes. “Add listener” means the method literally adds a listener to the existing set of listeners, so as your code is written, if the user were to select the toggle button ten times, you would have ten listeners on each spinner.
The correct approach is to add one listener to each Spinner. Each listener should do nothing if the toggle button is not selected. You will want to keep the spinner values’ ratio in a private field, for the listeners to use.
private double ratio = 1;
// ...
lock.selectedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
hSelector.commitValue();
wSelector.commitValue();
ratio = (double) hSelector.getValue() / wSelector.getValue();
}
});
hSelector.getValueFactory().valueProperty().addListener((ob, ov, nv) -> {
if (lock.isSelected()) {
wSelector.getValueFactory().setValue((int) (nv / ratio));
}
});
wSelector.getValueFactory().valueProperty().addListener((ob, ov, nv) -> {
if (lock.isSelected()) {
hSelector.getValueFactory().setValue((int) (nv * ratio));
}
});
You do not want to add a listener to the selectedProperty. Instead, listen for changes to the Spinner values. When the value changes, check if the lock button has been selected and perform your calculations then:
hSelector.valueProperty().addListener((observable, oldValue, newValue) -> {
if (lock.isSelected()) {
wSelector.getValueFactory().setValue(newValue * wSelector.getValue() / oldValue);
}
});
wSelector.valueProperty().addListener((observable, oldValue, newValue) -> {
if (lock.isSelected()) {
hSelector.getValueFactory().setValue(newValue * hSelector.getValue() / oldValue);
}
});
You may need to check the formula you're using to determine the new ratio, however, as it does not seem to work properly; mathematics is not my strong-suit. But this should get your spinners to update properly when changing one's values.

How can I get JavaFx Combobox to respond to user typing?

I'm writing a program which involves having a user type in a combo box and the list of items should change to reflect the text in the box (similar to autocomplete when you type into Google)
However, my combo box will not update until I press Enter. It doesn't seem to update when regular keys are typed. I have tried adding all kinds of listeners to the combo box but none of them fix the problem.
Here is the code snippet that has been the most successful. It is called from the fxml code: onKeyReleased="#keyReleased". It works properly, but still only executes when Enter is pressed.
public void keyReleased() throws SQLException, ClassNotFoundException
{
String coname = custconame_combo.getValue();
scriptHandler = new ScriptHandler();
custconame_combo.getItems().clear();
int i = 0;
for (String s : scriptHandler.searchCustomer(coname))
{
System.out.println(s);
custconame_combo.getItems().add(s);
custconame_combo.show();
i += 1;
}
}
I have searched high and low and still can't seem to solve this issue.
Since I've solved my problem, I'll share what I found.
Third party libraries provided the easiest solution. I went with the autocompletion class from JFoenix. It has exactly the functionality I was looking for and didn't feel like I was trying to reinvent the wheel.
This answer was very helpful in my search: JavaFX TextField Auto-suggestions
Just had a similiar porblem. The onKeyReleased method doesn't respond as needed. Use the EventHandler.
Here is my code (just tested and works well):
currencySymbolComboBox.setOnKeyPressed(event -> {
if(currencySymbolComboBox.isShowing()) {
if(event.getCode().isLetterKey()) {
currencyComboBoxKeysTyped += event.getCode().getName();
Optional<String> os = currecnySymbolsObservableList.stream()
.filter(symbol -> symbol.startsWith(currencyComboBoxKeysTyped))
.findFirst();
if (os.isPresent()) {
int ind = currecnySymbolsObservableList.indexOf(os.get());
ListView<String> lv = ((ComboBoxListViewSkin) currencySymbolComboBox.getSkin()).getListView();
lv.getFocusModel().focus(ind);
lv.scrollTo(ind);
currencySymbolComboBox.getSelectionModel().select(ind);
} else {
currencyComboBoxKeysTyped = currencyComboBoxKeysTyped
.substring(0, currencyComboBoxKeysTyped.length() - 1);
}
}
else if(event.getCode() == KeyCode.BACK_SPACE) {
if(currencyComboBoxKeysTyped.length() > 0) {
currencyComboBoxKeysTyped = currencyComboBoxKeysTyped
.substring(0, currencyComboBoxKeysTyped.length() - 1);
}
}
}
});
currencySymbolComboBox.showingProperty().addListener((observable, oldValue, newValue) -> {
if(!currencySymbolComboBox.isShowing()) {
currencyComboBoxKeysTyped = "";
}
});

Javafx Slider event listener being called before it snaps to the nearest tick

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
}
});

JavaFX TextArea: how to set tabulation width

How do I set tab width of JavaFX TextArea ?
When I use tabulation (tab key) in TextArea, the width of the tabulation is wide. I want to control the width, i.e., use 4 spaces. In the documentation I could not find a method to do this.
I tried this code (where taInput is a TextArea), but it is not working as it should:
taInput.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent e) {
if (e.getCode() == KeyCode.TAB) {
// TAB SPACES
StringBuilder sb = new StringBuilder(config.getTabSpacesCount());
for (int i=0; i<config.getTabSpacesCount(); i++) {
sb.append(' ');
}
taInput.insertText(taInput.getCaretPosition(), sb.toString());
e.consume();
}
}
});
Finally I found a way to do this.
It seems that the setOnKeyPressed() method is not good for this task because the event is handled after the keyPress action is executed.
The addEventFilter() handles the events before their actions are executed, so you can manipulate the events.
My new code:
taInput.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent e) {
if (e.getCode() == KeyCode.TAB) {
String s = StringUtils.repeat(' ', config.getTabSpacesCount());
taInput.insertText(taInput.getCaretPosition(), s);
e.consume();
}
}
});
#tenotron
your code also executes same logic for combination of TAB key with set of modifiers ( shift, control, alt, meta or shortcut). Meaning
In TextArea
Pressing TAB key = Ctrl(modifier) + TAB = .... = your logic.
To fix this issue , you have to use KeyCombination
Sample Code :
textArea.addEventFilter(KeyEvent.KEY_PRESSED,
new EventHandler<KeyEvent>() {
final KeyCombination combo = new KeyCodeCombination(
KeyCode.TAB);
#Override
public void handle(KeyEvent event) {
// check for only tab key
if (combo.match(event)) {
textArea.insertText(textArea.getCaretPosition(),
"I am not real TAB");
event.consume();
}
}
});
now Pressing TAB key results "I am not Real TAB" , ctrl+TAB will highlight the next Node on the scene.
Reference :
Correctly Checking KeyEvents
KeyCombination
From JavaFX 14 onward, the best way to deal with this is to use CSS to change the tab width, as shown in my answer to Setting the tab spacing/size visualization for a JavaFX TextArea
Replacing tab characters with multiple spaces doesn't have the same effect as tabs advance to the next tab stop, they don't add a fixed-width gap. Even if you adjusted for the characters preceding the tab, when not using a fixed-width font, an integer number of actual spaces may not give you the correct position.
Try making what you want displayed as a String. Then use s.replace("\t", " ");
if you want four spaces. This worked for me.

JSlider question: Position after leftclick

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. ;)

Categories

Resources