Site iconJava PDF Blog

Converting a Swing application into JavaFX – Listeners and fast scrolling

Recently, I have been working on producing a PDF Viewer, which uses both Swing and JavaFX, in our JPedal PDF library. This has involved a large amount of refactoring to separate out common, shared code and then implementing JavaFX specific functions or optimisations.

One of the features which we make extensive use of in our PDF viewer is listeners. When you scroll inside a PDF file, Java is not fast enough to decode every page in real time as you scroll through the PDF document if you just want to scroll very quickly. If we did this, some documents would be fine but some would be very sluggish and slow. As they scroll, we display the page borders with a white background.We wait for the user to stop scrolling and as soon as they pause, we will decode the visible pages.

If the user open the PDF viewer at the top of the document and then scrolls down to the bottom quickly, we would just decode the top few pages and the bottom few.

Tracking when the user stops is an interesting problem. There are lots of ways to track that the user is moving or components being resized but no real user has stopped event. It is a negative event.  So when the user first starts scrolling, we will start a timer and every time the user scrolls again we restart the timer. The timer will only ever execute once there is a pause (which is the event we want to track).

In Swing we have a JPanel as our base class, so we add a Listener to this with the standard addComponentListener(viewListener) method. Here is our implementation for viewListener class (RefreshLayout) which extends ComponentAdapter.

private class RefreshLayout extends ComponentAdapter {

        //our timer which will trigger event if it finished executing
        //we keep restarting it on each move
        PageMoveTracker tracker=new PageMoveTracker();

        @Override
        public void componentMoved(final ComponentEvent e) {

            //will restart if running
            tracker.startTimer(pages,getPageNumber(),fileAccess); 
        }

        @Override
        public void componentResized(final ComponentEvent e) {

            //will restart if running
            tracker.startTimer(pages,getPageNumber(),fileAccess);
        }

        public void dispose() {
            tracker.dispose();
        }
    }

 

JavaFX has far more flexibility in terms of Listeners and lets you neatly bind listeners onto properties. We are using the same Timer object to track when the user stops scrolling, but this time we attach it to the ScrollPane we display and we implement ChangeListener.

ourScrollPane.viewportBoundsProperty().addListener(viewListener);         
ourScrollPane.vvalueProperty().addListener(viewListener);
ourScrollPane.hvalueProperty().addListener(viewListener);

class RefreshLayout implements ChangeListener {

        PageMoveTracker tracker=new PageMoveTracker();
       
        public void dispose() {
            tracker.dispose();
        }
 
        @Override
        public void changed(ObservableValue ov, Object t, Object t1) {
        
            tracker.startTimer(pages, getPageNumber(), fileAccess);
        }
    }

We are currently looking at writing a series of articles on converting a Swing Application into JavaFX, so if you found this article of interest and would like to see more, please let us know. Or let us have your suggestions and tips on conversion as comments on the article!