РЕЗЮМЕ
Добрый день, сообщество StackOverflow.
Некоторое время я пытался разработать программу, позволяющую пользователям помещать объекты в область, позволяющую перемещать эту область с помощью мыши. Для этого типа программы я решил использовать ScrollPane, потому что пользователь может добавлять различное содержимое в область, которую я называю холстом. Почему-то в моей программе происходит что-то странное.
ОБЪЯСНЕНИЕ ПРОГРАММЫ
Что я в основном сделал, так это создал группу объектов и определил эту группу как содержимое ScrollPane. Внутри группы есть объект Rectangle, который был добавлен в качестве границ холста. Этот объект имеет большие размеры (например, 1500 x 1000) и используется в вычислениях, предотвращающих выход узлов за его пределы. Это просто логика существующего большого прямоугольника в моей программе, но на самом деле нет объекта Node с движением мыши. Что существует, так это случайное распределение объектов Shape по площади прямоугольника.
Поскольку полосы прокрутки ScrollPane перемещены, я использую setHvalue setVvalue методы. К сожалению, для моих целей этот метод не изменяет положение области просмотра ScrollPane со значениями в пикселях, а со значениями в диапазоне от 0f до 1f. Чтобы я мог перемещать область просмотра с помощью мыши, я использовал уравнение, известное как Правило 3 (здесь, в моей стране, насколько я знаю), которое мы приравниваем значения и умножаем крест.
Например, предположим, что я хочу переместить окно просмотра ScrollPane с помощью мыши по горизонтали, и что моя область холста имеет ширину 2000 пикселей. Чтобы узнать, как далеко (в пикселях) мышь была перетащена из одной точки в другую, мне нужно знать, как это значение представлено в диапазоне от 0f до 1f. Предположим, я перетащил мышь на 3 пикселя, я мог бы найти представление от 0f до 1f со следующим сравнением:
2000 px ---- 1f
3 px ---- xf
Умножив скрещенные, я получу следующий результат:
xf = 3 / 2000
xf = 0.0015
Примечание. Думаю, вы все это знаете. Я никого не учу математике, просто хочу объяснить логику моей задачи.
ИСХОДНЫЙ КОД
Вот мой класс программы:
import testes.util.TestesUtil;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.StrokeType;
import javafx.stage.Stage;
public class ScrollTest4 extends Application
{
// #########################################################################################################
// MAIN
// #########################################################################################################
public static void main(String[] args)
{
Application.launch(args);
}
// #########################################################################################################
// INSTÂNCIAS
// #########################################################################################################
// OUTSIDE
private BorderPane root;
private Button but_moreH;
private Button but_lessH;
private Button but_moreV;
private Button but_lessV;
// LOG
private VBox vbox_south;
private Label lab_hValue;
private Label lab_vValue;
private Label lab_viewport;
// INSIDE
private Rectangle rec_canvas;
private ScrollPane scroll;
private Group grp_objects;
// MOUSE
private double mouse_x = 0;
private double mouse_y = 0;
// MISC
private AnimationTimer timer;
// EDITED - 08/02/2014
private boolean moreH = false;
private boolean moreV = false; // Purposely unused.
private boolean lessH = false;
private boolean lessV = false; // Purposely unused.
// #########################################################################################################
// INÍCIO FX
// #########################################################################################################
@Override public void start(Stage estagio) throws Exception
{
this.iniFX();
this.confFX();
this.adFX();
this.evFX();
Scene cenario = new Scene(this.root , 640 , 480);
estagio.setScene(cenario);
estagio.setTitle("Programa JavaFX");
estagio.show();
}
protected void iniFX()
{
// OUTSIDE
this.root = new BorderPane();
this.but_moreH = new Button();
this.but_lessH = new Button();
this.but_moreV = new Button();
this.but_lessV = new Button();
// LOG
this.vbox_south = new VBox();
this.lab_hValue = new Label();
this.lab_vValue = new Label();
this.lab_viewport = new Label();
// INSIDE
this.scroll = new ScrollPane();
this.grp_objects = new Group();
this.rec_canvas = new Rectangle();
// MISC
this.timer = new AnimationTimer()
{
@Override public void handle(long now)
{
// EDITED - 08/02/2014
if(but_moreH.isArmed() || moreH)
{
// scroll.hvalueProperty().set(scroll.hvalueProperty().get() + 0.003f);
scroll.setHvalue(scroll.getHvalue() + 0.003f);
}
// EDITED - 08/02/2014
if(but_lessH.isArmed() || lessH)
{
// scroll.hvalueProperty().set(scroll.hvalueProperty().get() - 0.003f);
scroll.setHvalue(scroll.getHvalue() - 0.003f);
}
if(but_moreV.isArmed())
{
scroll.setVvalue(scroll.getVvalue() + 0.003f);
}
if(but_lessV.isArmed())
{
scroll.setVvalue(scroll.getVvalue() - 0.003f);
}
}
};
this.timer.start();
}
protected void confFX()
{
// OUTSIDE
this.but_moreH.setText("More H");
this.but_moreH.setMaxHeight(Double.MAX_VALUE);
this.but_lessH.setText("Less H");
this.but_lessH.setMaxHeight(Double.MAX_VALUE);
this.but_moreV.setText("More V");
this.but_moreV.setMaxWidth(Double.MAX_VALUE);
this.but_lessV.setText("Less V");
this.but_lessV.setMaxWidth(Double.MAX_VALUE);
// LOG
this.updateHvalue();
this.updateVvalue();
this.updateViewport();
// INSIDE
this.rec_canvas.setWidth(1200);
this.rec_canvas.setHeight(1000);
this.rec_canvas.setFill(Color.INDIANRED);
this.rec_canvas.setStroke(Color.RED);
this.rec_canvas.setStrokeType(StrokeType.INSIDE);
this.rec_canvas.setStrokeWidth(1);
}
protected void adFX()
{
// LOG
this.vbox_south.getChildren().add(this.but_moreV);
this.vbox_south.getChildren().addAll(this.lab_hValue , this.lab_vValue , this.lab_viewport);
// OUTSIDE
this.root.setCenter(this.scroll);
this.root.setTop(this.but_lessV);
this.root.setBottom(this.vbox_south);
this.root.setRight(this.but_moreH);
this.root.setLeft(this.but_lessH);
// INSIDE
this.grp_objects.getChildren().add(this.rec_canvas);
this.scroll.setContent(this.grp_objects);
// MISC
StrokeType[] strokes = {StrokeType.CENTERED , StrokeType.INSIDE , StrokeType.OUTSIDE};
for(int cont = 0 ; cont < 20 ; cont++)
{
Rectangle node = new Rectangle(Math.random() * 100 + 50 , Math.random() * 100 + 50);
node.setFill(TestesUtil.getCorAleatoria(false));
node.setStroke(TestesUtil.getCorAleatoria(false));
node.setStrokeType(strokes[(int) (Math.random() * 2)]);
node.setStrokeWidth(Math.random() * 9 + 1);
node.setRotate(Math.random() * 360);
node.setMouseTransparent(true);
// EDITED - 08/02/2014
TestsUtil.putRandomlyIn(
node ,
rec_canvas.getBoundsInParent().getMinY() ,
rec_canvas.getBoundsInParent().getMinY() + rec_canvas.getBoundsInParent().getHeight() ,
rec_canvas.getBoundsInParent().getMinX() + rec_canvas.getBoundsInParent().getWidth() ,
rec_canvas.getBoundsInParent().getMinX() );
this.grp_objects.getChildren().add(node);
}
}
protected void evFX()
{
// ##########################
// SCROLL PROPERTIES
// ##########################
this.scroll.hvalueProperty().addListener(new ChangeListener<Number>()
{
@Override public void changed(ObservableValue<? extends Number> observable,Number oldValue, Number newValue)
{
updateHvalue();
updateViewport();
}
});
this.scroll.vvalueProperty().addListener(new ChangeListener<Number>()
{
@Override public void changed(ObservableValue<? extends Number> observable,Number oldValue, Number newValue)
{
updateVvalue();
updateViewport();
}
});
this.scroll.setOnKeyPressed(new EventHandler<KeyEvent>()
{
@Override public void handle(KeyEvent e)
{
if(e.getCode() == KeyCode.RIGHT)
{
moreH = true;
}
else if(e.getCode() == KeyCode.LEFT)
{
lessH = true;
}
}
});
this.scroll.setOnKeyReleased(new EventHandler<KeyEvent>()
{
@Override public void handle(KeyEvent e)
{
if(e.getCode() == KeyCode.RIGHT)
{
moreH = false;
}
else if(e.getCode() == KeyCode.LEFT)
{
lessH = false;
}
}
});
// ##########################
// CANVAS
// ##########################
this.rec_canvas.setOnMousePressed(new EventHandler<MouseEvent>()
{
@Override public void handle(MouseEvent e)
{
// The XY distance from the upper left corner of the canvas.
mouse_x = e.getX();
mouse_y = e.getY();
}
});
this.rec_canvas.setOnMouseDragged(new EventHandler<MouseEvent>()
{
@Override public void handle(MouseEvent e)
{
// ##########################
// PIXELS
// ##########################
// The distance between mouse movements (drag events).
double xPixelsMoved = e.getX() - mouse_x;
// double yPixelsMoved = e.getY() - mouse_y;
// ##########################
// TO 1F
// ##########################
double h_of_1f = xPixelsMoved / rec_canvas.getBoundsInParent().getWidth();
double h_of_1f_inverted = h_of_1f * -1;
double currentH = scroll.getHvalue();
scroll.setHvalue(currentH + h_of_1f);
// scroll.hvalueProperty().set(scroll.getHvalue() + h_de_x);
// scroll.vvalueProperty().set(scroll.getVvalue() + v_de_y);
// ##########################
// DEBUG
// ##########################
System.out.printf("xPixelsMoved: %f , h_of_1f: %f , h_of_1f_inverted: %f %n",
xPixelsMoved , h_of_1f , h_of_1f_inverted);
// ##########################
// UPDATE FROM
// EVENT TO EVENT
// ##########################
// Writes last mouse position to update on new motion event.
mouse_x = e.getX();
mouse_y = e.getY();
}
});
}
// #########################################################################################################
// MISC.
// #########################################################################################################
protected void updateViewport()
{
Bounds vport = this.scroll.getViewportBounds();
this.lab_viewport.setText(String.format("Viewport - [X: %f , Y: %f , W: %f , H: %f]",
vport.getMinX() , vport.getMinY() , vport.getWidth() , vport.getHeight() ));
}
protected void updateHvalue()
{
this.lab_hValue.setText("H value: " + this.scroll.getHvalue() );
}
protected void updateVvalue()
{
this.lab_vValue.setText("V value: " + this.scroll.getVvalue() );
}
}
ЭТА ПРОБЛЕМА
Щелкнув кнопкой мыши по области холста и перетащив ее, вы увидите, что программа перемещает область просмотра ScrollPane по горизонтали. Программа вроде работает отлично (или нет). Однако что-то идет не так в тот момент, когда мышь дергается иногда резко (...или нет!). В определенные моменты окно просмотра ScrollPane визуально не обновляется. Это странное поведение, потому что даже если область просмотра визуально не обновляется, полосы прокрутки все равно обновляются.
Я использую другие способы перемещения области просмотра ScrollPane по горизонтали, используя тот же метод, и по какой-то причине только подход с использованием мыши делает это возможным. Я думал, что это можно решить, сделав запрос макета с помощью requestLayout, тоже вызывает запрос на пульс, но не работает.
РЕЗУЛЬТАТЫ ИСПЫТАНИЙ
Странно то, что все возвращается к норме, когда размер окна моего приложения изменяется. Вот видео, которое показывает, что происходит с моей программой:
Я уже не знаю, что еще делать. Может ли кто-нибудь помочь мне с этим, пожалуйста?
РЕДАКТИРОВАТЬ (02.08.2014, 10:08 по Гринвичу – 3:00)
Оригинальный исходный код моего приложения написан на португальском языке, поэтому вы можете увидеть что-то неизвестное. По сути, TestesUtil — это служебный класс со статическими методами, определяющими ярлыки для других клиентских классов. Я изменил вызов из моего исходного кода, показанного здесь ранее, и теперь добавляю некоторые методы моего класса TestesUtil, переведенные на английский язык как TestsUtil:
public static void putRandomlyIn(Node node , double northPoint , double southPoint , double eastPoint , double westPoint)
{
node.setLayoutX(Math.random() * pontoLeste);
node.setLayoutY(Math.random() * pontoSul);
fixEasternBoundary(node , eastPoint);
fixNorthernBoundary(node , northPoint);
fixWesternBoundary(node , westPoint);
fixSouthernBoundary(node , southPoint);
}
Здесь нет никакой тайны. Этот метод просто вычисляет значение из интервала и определяет свойства LayoutXY для аргумента Node. Методы "исправить..." просто проверяют границы boundsInParent узла по сравнению с точкой в аргументе, а затем корректируют свойства layoutXY из объекта Node. Даже если убрать случайное распределение объектов, проблема все равно возникает. Так что я уверен, что эта проблема не вызвана этим.
Исходный код оригинального поста был изменен с добавлением возможности перемещать полосы прокрутки с помощью клавиш со стрелками. Даже если это уже существующая функция ScrollPane, добавление которой может воспроизвести ошибку, наблюдаемую с помощью мыши (теперь со стрелками). Некоторые вещи также были переведены на английский язык для лучшего понимания сообществом.
Пожалуйста, прошу помощи. У меня кружится голова, я не знаю, что делать. Такая ситуация может происходить из-за какой-то ошибки в JavaFX? Ahhrr... Пожалуйста, кто-нибудь, помогите мне в этом. :'(
Спасибо за внимание в любом случае.
РЕДАКТИРОВАТЬ (02.09.2014, 10:50 по Гринвичу – 3:00)
Забыл упомянуть... Моя программа изначально была написана и протестирована с использованием JDK 8 b123. В настоящее время я установил версию JDK 8 b128 и все еще сталкиваюсь с той же проблемой. Моя операционная система Windows 7 64x.
Я почти уверен, что это баг. Вы, ребята, получаете тот же результат, что и я? Или я один нашел такую проблему? Если это ошибка, какую процедуру следует предпринять?
Спасибо за внимание.
РЕДАКТИРОВАТЬ (02.10.2014, 09:45 по Гринвичу – 3:00)
Щедрость была начата.