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. ;)
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 am writing a board game which has a 20x20 grid.
This is in my board class:
private final Position[][] grid = new Position[GRID_SIZE][GRID_SIZE];
each position has :
public class Position {
private final Coordinates pos;
private Player player;
private final static double RECTANGLE_SIZE = 40.0;
private final Rectangle rect = new Rectangle(RECTANGLE_SIZE, RECTANGLE_SIZE);
}
so basically I have 20x20 Positions and each positions has a Rectangle
This is what I do to display the grid
for (int cols = 0; cols < GRID_SIZE; ++cols) {
for (int rows = 0; rows < GRID_SIZE; ++rows) {
grid.add(gameEngine.getBoard().getGrid()[cols][rows].getRect(), cols, rows);
}
}
Anyway, the grid is initialized and works properly. What I want to do is to make the rectangle objects clickable and to be able to return their Coordinates when they are clicked.
This is how I handle the mouse click
private void setUpRectangle() {
rect.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
rect.setFill(Color.BLACK);
}
});
}
What this code does is to change the color of the rectangle to black, but how could I return the Coordinates.
Basically, I can edit the onclick function to return the coordinates of this position, but how can I acquire them later?
This is not a JavaFX question as much as it is a design question. You have a container (Position) of 2 objects (Coordinates and Rectangle) and you want one of them to know about the other. That is, the rectangle should know the coordinates of its position.
There are a few approaches here, and depending on the bigger picture, one might be better than the others. James_D mentioned a couple in a comment.
Keep a reference of the position object in the rectangle object. This is useful if rectangle needs to access various datum in the container from various places. You would do something like rectangle.getPosition().getCoordinates() or .getPlayer().
Keep a reference of the coordinates object in the rectangle object. This is a more specific approach of 1 useful if you only need that object. You would do something like rectangle.getCoordinates().
Pass the coordinates to your setUpRectangle method. This is useful if you rectangle doesn't need access to this data from various places, it's a local solution. Then in the handle method you would return the coordinates you passed to setUpRectangle, though we can't see what class this method is in.
Use external help. You can keep something like Map<Rectangle, Coordinates> and then call map.get(rectangle). You can hide this map in a method Coordinates getCoordinatesForRectangle(Rectangle rectangle) instead of calling it directly.
You could store this data as userData (or use properties in case userData is preserved for something else in your program):
private final Rectangle rect;
public Position() {
rect = new Rectangle(RECTANGLE_SIZE, RECTANGLE_SIZE);
rect.setUserData(this);
}
rect.setOnMouseClicked((MouseEvent event) -> {
Position pos = (Position) ((Node) event.getSource()).getUserData();
...
});
You could also use a listener that knows about the position:
class CoordinateAwareListener implements EventHandler<MouseEvent> {
private final int coordinateX;
private final int coordinateY;
public CoordinateAwareListener(int coordinateX, int coordinateY) {
this.coordinateX = coordinateX;
this.coordinateY = coordinateY;
}
#Override
public void handle(MouseEvent event) {
// do something with the coordinates
}
}
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()));
}
}
});
I have a ScrollView with a lot buttons. Each button is enabled when the user unlocks that button/level. I would like to focus the ScrollView on the latest unlocked button/level. See the screenshot below.
I found some functions like scrollTo() that could focus the ScrollView either at top, button or something like that but I would like to focus the ScrollView at certain places like the button ID that says Level 8 in the screenshot below. How would I go about doing this?
I think this should be good enough :
yourButtonView.getParent().requestChildFocus(yourButtonView,yourButtonView);
public void RequestChildFocus (View child, View focused)
child - The child of this ViewParent that wants focus. This view will contain the focused view. It is not necessarily the view that actually has focus.
focused - The view that is a descendant of child that actually has focus
First you need to know the position of item in scroll that has to get focus once you know that you can use following code to make that item focus
final int x;
final int y;
x = rowview[pos].getLeft();
y = rowView[pos].getTop();
yourScrollView.scrollTo(x, y);
refer this question
I agree with the benefits of previous answers. However, by experience, I have found this to be more complex than this. The setSelectionFromTop is very sensitive and often breaks if it is executed too early. This may depend on different reasons but the two primary reasons are that
If executed from within Activity lifecycle methods the views have not been loaded/configured yet.
View modifications triggered after the list move action seems to break the move. Probably overwriting some value before the move has been finalized due to a recalculation of the views.
The reasoning seems to apply for both setSelectionFromTop and setSelection() methods although
I tested mostly with setSelectionFromTop. smoothScrollToPosition() seems to be more robust, probably because it by definition changes the list position delayed whn doing the smooth scrolling.
Look at this example source code:
// Wee need to pospone the list move until all other view setup activities are finished
list.post(new Runnable(){
#override
public void run() {
list.setSelectionFromTop(selectedPosition, Math.max(0, Math.round(list.getHeight() / 3))); // Make sure selection is in middle of page, if possible.
// Make sure you do not modify (this or other) parts of the view afterwards - it may break the list move
// activityButtons.setVisibility(View.GONE);
}});
see: http://developer.android.com/reference/android/widget/ListView.html#setSelectionFromTop(int, int)
'int selectedLevel' holds the currently selected level, information is held in a Level class.
'values' is a list with all your levels, displayed by the list.
Example:
ListView lv = (ListView)findViewById(android.R.id.list);
int i = 0;
for (Level l : values) {
i++;
if (l.level == selectedLevel) {
lv.setSelectionFromTop(i, 200);
break;
}
}
try this
sc.post(new Runnable() {
public void run() {
sc.smoothScrollTo(x, y);
}
});
x the position where to scroll on the X axis
y the position where to scroll on the Y axis
Use this code, taken from this answer
private final void focusOnView(){
new Handler().post(new Runnable() {
#Override
public void run() {
your_scrollview.scrollTo(0, your_EditBox.getBottom());
}
});
}
Use this code, taken from my another answer
scrollViewSignup.post(new Runnable() {
#Override
public void run() {
int scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, 0);
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, true);
int new_scrollY = scrollViewSignup.getScrollY();
scrollViewSignup.scrollTo(0, scrollY);
scrollViewSignup.smoothScrollTo(0, new_scrollY);
}
});
This code tries to smooth scroll and uses the standard behaviour of system to position to an item. You can simply change smoothScrollTo with scrollTo if you don't want the system to smooth scroll to the item. Or, you can use the code below only.
scrollViewSignup.post(new Runnable() {
#Override
public void run() {
scrollViewSignup.scrollTo(0, 0);
final Rect rect = new Rect(0, 0, view.getWidth(), view.getHeight());
view.requestRectangleOnScreen(rect, true);
}
});
Use the desired code block after trying, or, according to the need.
Suppose I have 4 squares colored blue, white, red and green (myComponent) associated with the mouse press event. At one point, the mouse is pressed over one of them - say, the yellow one - and the event is activated.
Now, the control flux is inside the event handling function. How do I get the MyComponent - the yellow square - that caused this from here?
EDIT
I have another question. Is there a way to tell the position of the component? My problem is a bit more complicated than what I said.
Basically, I have a grid full of squares. When I click one of the squares, I have to know which one it is, so I can update my matrix. The thing is, if I calculate it myself, it only works on a given resolution.
I have a GridBagLayout, and inside it are the myComponents. I have to know which one of the components exactly - like, component[2][2] - caused the interruption.
I mean, I can tell which one of the components did it, but not where in the matrix it is located.
MouseEvent.getSource() returns the object on which the event initially occurred.
I have a GridBagLayout, and inside it
are the myComponents. I have to know
which one of the components exactly -
like, component[2][2] - caused the
interruption.
You could store the indices, e.g. (2,2), inside each myComponent when you add them to the matrix. That way, given the component, you can always identify its position in the matrix.
class MyComponent extends JButton
{
final int i; // matrix row
final int j; // matrix col
// constructor
MyComponent(String text, int i, int j)
{
super(text);
this.i = i;
this.j = j;
}
...
}
By adding a MouseListener (or alternatively, a MouseAdapter, if you don't need to override all the MouseListener' methods) to each of your colored boxes, when an event such as a mouse click occurs, theMouseListenerwill be called with a [MouseEvent`]3, which can be used to obtain the component that was clicked.
For example:
final MyBoxComponent blueBox = // ... Initialize blue box
final MyBoxComponent whiteBox = // ... Initialize white box
final MyBoxComponent redBox = // ... Initialize red box
final MyBoxComponent greenBox = // ... Initialize green box
MouseListener myListener = new MouseAdapter() {
public void mouseClicked(MouseEvent e)
{
// Obtain the Object which caused the event.
Object source = e.getSource();
if (source == blueBox)
{
System.out.println("Blue box clicked");
}
else if (source == whiteBox)
{
System.out.println("White box clicked");
}
// ... and so on.
}
};
blueBox.addMouseListener(myListener);
whiteBox.addMouseListener(myListener);
redBox.addMouseListener(myListener);
greenBox.addMouseListener(myListener);