ScrollPane не обновляет свое содержимое

РЕЗЮМЕ

Добрый день, сообщество 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, тоже вызывает запрос на пульс, но не работает.

РЕЗУЛЬТАТЫ ИСПЫТАНИЙ

Странно то, что все возвращается к норме, когда размер окна моего приложения изменяется. Вот видео, которое показывает, что происходит с моей программой:

ВИДЕО и ЗЕРКАЛО 1

Я уже не знаю, что еще делать. Может ли кто-нибудь помочь мне с этим, пожалуйста?

РЕДАКТИРОВАТЬ (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)

Щедрость была начата.


person Loa    schedule 07.02.2014    source источник
comment
Где находится файл класса TestesUtil в вашем проекте   -  person Reegan Miranda    schedule 08.02.2014
comment
@Reegan Я отредактировал свой оригинальный пост. Пожалуйста, проверь это. Спасибо за внимание. :')   -  person Loa    schedule 08.02.2014
comment
Я попробовал ваш код и могу подтвердить это странное поведение для Windows 7: при запуске вашего примера с 32-разрядной версией JRE 8 b128 прокрутка курсора отключается, если кто-то случайно перемещает курсор по экрану очень быстро в течение некоторого времени. После изменения размера кадра он внезапно снова работает. С другой стороны, при использовании JDK 1.7.0_51 ваш пример отлично работает. Так что это, безусловно, ошибка, о которой следует сообщить разработчикам JavaFX. Я могу только предложить использовать JDK 7, пока эта ошибка не будет исправлена. Удачи - и кстати: Очень хорошее описание проблемы с большим усилием!   -  person Balder    schedule 10.02.2014
comment
@Бальдер Спасибо, Бальдер. Я думаю, что это то, чем мы, программисты, должны делиться, чтобы мы могли разрабатывать более качественные и функциональные приложения. Я больше недели с этой проблемой. Я рассмотрел несколько альтернатив, все из которых привели бы к чему-то плохому и непригодному для сопровождения. К сожалению, мне нужно использовать JDK 8, потому что я могу использовать новый CSS API. Я совершенно не знаю, как уведомить разработчиков JavaFX об ошибке. На самом деле, я не знаю, является ли это ошибкой. Я верю, что может быть, но не могу сказать.   -  person Loa    schedule 11.02.2014
comment
Если эта проблема действительно считается ошибкой, я буду считать правильным ответ, который показывает, что это ошибка, и разработчики JavaFX были уведомлены. Я очень хочу, чтобы это было решено в будущем, потому что мне очень нужно использовать эту функцию (как и всем нам). JDK 8 планируется завершить и доставить в середине 2014 года. До тех пор, я надеюсь, что это решено. Но еще раз говорю... Я до сих пор не знаю, баг ли это. Необходимо уведомить об этом разработчиков JavaFX. Я также создал обсуждение в сообществе Oracle. Пока ничего.   -  person Loa    schedule 11.02.2014
comment
Я добавил описание ошибки и хакерский способ решения проблемы в своем ответе. Но вы действительно должны сами сообщить об ошибке, потому что вы гораздо больше связаны с ней и с JavaFX. Конечно, не стесняйтесь использовать части моего описания по своему усмотрению. Вы можете сообщить об ошибке здесь: javafx-jira.kenai.com/secure/Dashboard.jspa . Также ссылка на это обсуждение, возможно, некоторые другие пользователи также могут подтвердить это.   -  person Balder    schedule 11.02.2014


Ответы (1)


ОБНОВЛЕНИЕ

Эта ошибка исправлена ​​для JavaFX 8u20.

Описание ошибки

Это ошибка, которую можно легко проверить, выполнив следующий код с помощью JavaFx JRE 8:

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application {

final ScrollPane sp = new ScrollPane();
final Image[] images = new Image[5];
final ImageView[] pics = new ImageView[5];
final VBox vb = new VBox();
final Label fileName = new Label();
final String [] imageNames = new String [] {"fw1.jpg", "fw2.jpg",
        "fw3.jpg", "fw4.jpg", "fw5.jpg"};

@Override
public void start(Stage stage) {
    VBox box = new VBox();
    Scene scene = new Scene(box, 180, 180);
    stage.setScene(scene);
    stage.setTitle("Scroll Pane");
    box.getChildren().addAll(sp, fileName);
    VBox.setVgrow(sp, Priority.ALWAYS);

    fileName.setLayoutX(30);
    fileName.setLayoutY(160);

    for (int i = 0; i < 5; i++) {
        images[i] = new Image(getClass().getResourceAsStream(imageNames[i]));
        pics[i] = new ImageView(images[i]);
        pics[i].setFitWidth(100);
        pics[i].setPreserveRatio(true);
        vb.getChildren().add(pics[i]);
    }

    sp.setVmax(440);
    sp.setPrefSize(115, 150);
    sp.setContent(vb);
    sp.vvalueProperty().addListener(new ChangeListener<Number>() {
        public void changed(ObservableValue<? extends Number> ov,
            Number old_val, Number new_val) {
                fileName.setText(imageNames[(new_val.intValue() - 1)/100]);
        }
    });

    stage.show();
}

public static void main(String[] args) {
    launch(args);
}
}

Этот код взят непосредственно из учебника по JavaFX ScrollPane.

Если очень быстро перемещать мышью вертикальную полосу прокрутки случайным образом, то в какой-то момент экран зависнет и перестанет обновляться. Хотя по-прежнему можно перемещать полосу прокрутки, отображаемые изображения останутся фиксированными. Только если изменить размер кадра, отображение изображений будет обновлено, а ScrollPane вернется в свое предыдущее состояние. Обратите внимание, что эта ошибка возникает только в JRE 8 и не воспроизводится в JRE 7.

Единственный обходной путь, который я смог найти, это добавить

sp.snapshot(new SnapshotParameters(), new WritableImage(1, 1));

слушателю:

    sp.vvalueProperty().addListener(new ChangeListener<Number>() {
        public void changed(ObservableValue<? extends Number> ov,
            Number old_val, Number new_val) {
                fileName.setText(imageNames[(new_val.intValue() - 1)/100]);
                sp.snapshot(new SnapshotParameters(), new WritableImage(1, 1));
        }
    });

Вызов моментального снимка в ScrollPane, по-видимому, вызывает обновление каждый раз, когда изменяется vvalueProperty. Кажется, это известный обходной путь для нескольких проблем с обновлением с JavaFX - -set">см. здесь.

person Balder    schedule 10.02.2014
comment
Я создал новую задачу в системе JIRA. Я не знаю, правильно ли я сделал, потому что я действительно не знаю, как использовать JIRA. Я думаю, что проблема еще не ответила. Не могли бы вы сказать мне, правильно ли я поступил? Вы могли бы также сказать мне, что мне теперь делать? Чего я хочу, как я уже сказал, так это того, чтобы проблема была замечена разработчиками, и в конечном итоге она была решена. Если да, то я буду считать этот вопрос чем-то прошлым и буду спокоен. Мне нужна эта уверенность. Ссылка на открытый вопрос находится здесь: javafx-jira.kenai.com/browse/ - person Loa; 12.02.2014
comment
Я только что обменялся некоторыми комментариями с Джонатаном Джайлсом из JIRA, и после некоторых первоначальных проблем он смог воспроизвести ошибку. Он сказал, что рассмотрит это. - person Balder; 12.02.2014
comment
@Loa Вы могли бы также сказать мне, что мне теперь делать? Я бы посоветовал следить за развитием ошибки и использовать обходной путь, о котором я упоминал выше, пока он не будет исправлен. Не ожидайте слишком многого от разработчиков JavaFX — поиск подобной ошибки может занять некоторое время... - person Balder; 12.02.2014
comment
У меня нет слов, чтобы описать, как я благодарю вас за помощь. Бальдер, спасибо. Вы не только помогли мне, но и приложили больше усилий для решения моей проблемы. Я рад, что в этом сообществе есть такие люди, как вы. Я завершаю эту проблему как реальную ошибку в языке JavaFX. Сами разработчики это признали и сейчас находятся в процессе решения. - person Loa; 12.02.2014
comment
Мне показался интересным предложенный вами альтернативный подход, но он показался немного грязным и может повлиять на производительность моего приложения. Пока что делать нечего. Мне нужно подождать, пока эта ошибка не будет решена. Еще раз благодарю вас за все, вы хороший человек. :) - person Loa; 12.02.2014
comment
Спасибо, Лоа, я рад, что смог помочь. Невероятно, как быстро работают разработчики JavaFX - я только что видел, что они уже отследили ошибку до проблем. - person Balder; 12.02.2014
comment
@Loa На заметку: не то чтобы я хотел быть жадным, но, поскольку вы приняли мой ответ, возможно, вы также подумали бы о присуждении мне открытой награды? Я верю, что ты забыл... - person Balder; 12.02.2014
comment
Ах! Мне жаль! Это первый раз, когда я создаю награду. Я думал, что мне просто нужно поставить ваш ответ как правильный. Простите меня. - person Loa; 12.02.2014