JavaFX:AnimationTimer和MenuBar之间的交互

时间:2014-05-11 13:25:52

标签: java javafx imageview menubar

我正在开发一个软件,它从相机中获取图像并在JavaFX ImageView中实时显示。我有一个获取最后一个图像的线程(在本例中为BufferedImage),以及一个将AnimationTimer分配给ImageView的线程。我继续使用AnimationTimer的原因似乎是每次获得新图像时使用Runnable填充平台更好。刷新工作正常,FPS也不错。

但是,我注意到当AnimationTimer运行时,我的软件中的菜单栏显示不正确。当我迷过其他菜单时,有些菜单项丢失了。这张照片解释了它: enter image description here

在左侧,您可以看到菜单通常的样子,右侧是AnimationTimer运行时的样子。如你所见," Save"缺少菜单项,而是显示我的实时图像的背景。此外,当我打开一个新窗口(在新的Scene上)时,当我在任何类型的Node(一个按钮,一个复选框......)上移动时,背景变为黑色。通过在初始化Scene时将depth-buffer boolean设置为true,我能够解决这个问题。我不知道如何修复这个菜单栏错误,我认为这些错误表明我正在做的事情可能不对。

我想也许JavaFX应用程序线程已经饱和了要显示的新图像,并且基本上花费了太多时间来绘制其他元素(例如菜单项)。

问题:

  1. 这真的是这个bug的来源吗?
  2. 有没有办法改进我的代码,例如使用与AnimationTimer不同的东西?

  3. 这是一个重现错误的代码段。将start函数中的两个字符串更改为图像的路径。图像应该相对较大(几MB)。

    点击"开始"按钮启动动画计时器。然后尝试打开"文件"菜单和鼠标在菜单项上。该错误没有系统地出现,尝试重复上下移动鼠标,它应该出现在某个点上。

    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javafx.animation.AnimationTimer;
    import javafx.application.Application;
    import javafx.embed.swing.SwingFXUtils;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.Menu;
    import javafx.scene.control.MenuBar;
    import javafx.scene.control.MenuItem;
    import javafx.scene.image.Image;
    import javafx.scene.image.ImageView;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.Pane;
    import javafx.stage.Stage;
    import javax.imageio.ImageIO;
    
    public class ImageRefresher extends Application {
    
        @Override
        public void start(Stage primaryStage) {
    
            //Here change the 2 Strings to a path to an image on your HDD
            //The bug appears more easily with large images (>3-4MB)
            String pathToImage1 = "/path/to/your/first/image";
            String pathToImage2 = "/path/to/your/second/image";
    
            try {
                //Image content (contains buffered image, see below)
                ImageContent image = new ImageContent(pathToImage1);
    
                //If this line is commented, the bug does not appear
                image.setImage(ImageIO.read(new File(pathToImage2)));
    
                //JavaFX class containing nodes (see below)
                MainWindow window = new MainWindow(image);
    
                Scene scene = new Scene(window.getPane(), 300, 250);
                primaryStage.setTitle("Menu refresh");
                primaryStage.setScene(scene);
                primaryStage.show();
            } catch (IOException ex) {
                Logger.getLogger(ImageRefresher.class.getName()).log(Level.SEVERE, null, ex);
            }
    
        }
    
        public static void main(String[] args) {
            launch(args);
        }
    
        public class MainWindow {
    
            private BorderPane pane;
            private MenuBar menuBar;
            private ImageView displayImage;
            private Button startRefreshingButton;
    
            private ImageContent imageContent;
            private AnimationTimer animationTimer;
    
            public MainWindow(ImageContent imageContent) {
                this.imageContent = imageContent;
    
                //Builds the window's components
                buildGraphic();
    
                //The image is reset at each frame
                animationTimer = new AnimationTimer() {
                    @Override
                    public void handle(long now) {
                        displayImage.setImage(imageContent.getDisplayableImage());
                    }
                };
            }
    
            private void buildGraphic() {
                pane = new BorderPane();
                menuBar = new MenuBar();
    
                Menu menu = new Menu("File");
                menu.getItems().addAll(new MenuItem("Save"),
                        new MenuItem("Open"),
                        new MenuItem("Close"));
                menuBar.getMenus().add(menu);
    
                displayImage = new ImageView();
    
                startRefreshingButton = new Button("Start");
                startRefreshingButton.setOnAction((event) -> {
                    animationTimer.start();
                });
    
                pane.setTop(menuBar);
                pane.setCenter(displayImage);
                pane.setBottom(startRefreshingButton);
            }
    
            public Pane getPane() {
                return pane;
            }
        }
    
        public class ImageContent {
    
            private BufferedImage imageContent;
    
            //Initializes bufferedimage with the path specified
            public ImageContent(String pathToImage) throws IOException {
                imageContent = ImageIO.read(new File(pathToImage));
            }
    
            public void setImage(BufferedImage newImage) {
                imageContent = newImage;
            }
    
            //Function called by the animation timer to
            //get a JavaFX image from a bufferedimage
            public Image getDisplayableImage() {
                return SwingFXUtils.toFXImage(imageContent, null);
            }
        }
    }
    

2 个答案:

答案 0 :(得分:2)

我想问题是,因为你每帧重新绘制图像,所以你将菜单弹出窗口覆盖在图像上。这似乎是一个错误,但你也要求从FX应用程序线程中获得比你需要更多的工作。

理想情况下,您应该找到一种方法来检查是否确实存在新图像,并且只有在确实存在新文件时才更新图像。 (考虑使用java.nio.file.Path来表示文件并调用Files.getLastModifiedTime(path)。)

另一种方法是避免使用Platform.runLater(...)次呼叫过多来淹没FX应用程序主题,请参阅Throttling javafx gui updates

答案 1 :(得分:0)

最后,我没有在jira上提出任何问题,因为我能够解决我的问题。问题来自我调用SwingFXUtils.toFXImage(imageContent, null)的方式。我在每一帧都返回了这个函数的结果,我不确定细节,但这可能每次创建一个新对象。避免这种情况的一种简单方法是传递WritableImage作为参数,并将ImageProperty的{​​{1}}值绑定到它。

如果我把MCVE发布在上面这可能是这样的(没有经过测试,可能是更清洁的解决方案):

ImageView

然而,现在这似乎是内存密集型的,我会调查它。