【问题标题】:JavaFX: interaction between AnimationTimer and MenuBarJavaFX:AnimationTimer 和 MenuBar 之间的交互
【发布时间】:2014-06-28 21:05:44
【问题描述】:

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

但是,我注意到当AnimationTimer 运行时,我的软件中的菜单栏没有正确显示。当我将鼠标悬停在其他菜单项上时,一些菜单项丢失了。这张图片说明了这一点:

左侧是菜单通常的样子,右侧是AnimationTimer 运行时的样子。如您所见,缺少“保存”菜单项,而是显示我的实时图像的背景。此外,当我打开一个新窗口(在新的Scene 上)时,当我将鼠标悬停在任何类型的Node(一个按钮、一个复选框......)上时,背景变成黑色。在初始化Scene 时,我可以通过将深度缓冲区布尔值设置为 true 来解决此问题。但是我不知道如何修复这个菜单栏错误,我认为这些错误表明我所做的可能是不正确的。

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

问题:

  1. 这个错误真的是从哪里来的吗?
  2. 有没有办法改进我的代码,例如使用不同于 AnimationTimer 的东西?

这是一个重现该错误的代码 sn-p。将 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);
        }
    }
}

【问题讨论】:

    标签: java javafx imageview menubar


    【解决方案1】:

    我想问题在于,由于您每帧都重新绘制图像,因此您将菜单弹出窗口与图像重叠。这似乎是一个错误,但您也要求 FX 应用程序线程的工作量超出您的需要。

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

    有关避免因过多的Platform.runLater(...) 调用而淹没 FX 应用程序线程的另一种方法,请参阅Throttling javafx gui updates

    【讨论】:

    • 读起来很有趣,虽然该错误似乎仍在使用这种策略。
    • 我有一个来自摄像机的实时信息,所以我需要至少 10-12 FPS 才能与它正确交互。我尝试使用 Observable 模式,并在每次获得新图像时触发更新。更新时,我使用您描述的策略调用 Platform.runLater()。问题依然存在。我认为我显示的图像太大(2560*2160 像素)
    • javafx-jira.kenai.com 提交错误。包含一个完整的示例来演示该问题。
    【解决方案2】:

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

    如果我采用上面发布的 MCVE,可能会是这样(未经测试,可能存在更清洁的解决方案):

        public class MainWindow {
    
            private BorderPane pane;
            private MenuBar menuBar;
            private ImageView displayImage;
            private Button startRefreshingButton;
    
            private ImageContent imageContent;
            private AnimationTimer animationTimer;
    
            // Here's the value to bind
            private ObservableValue<WritableImage> imageProperty;
    
            public MainWindow(ImageContent imageContent) {
                //initialization stuff 
    
                this.imageProperty = new ObservableValue<>(imageContent.getWritableImage());
                displayImage.imageProperty().bind(imageProperty);
    
                //The image is reset at each frame
                animationTimer = new AnimationTimer() {
                    @Override
                    public void handle(long now) {
                        SwingFXUtils.toFXImage(imageContent.getBufferedImage(), image.getWritableImage());
                    }
                };
            }
        }
    
    
        public class ImageContent {
    
            private BufferedImage imageContent;
            private WritableImage writableImage;
    
            //Initializes bufferedimage with the path specified
            public ImageContent(String pathToImage) throws IOException {
                imageContent = ImageIO.read(new File(pathToImage));
    
                //Get the width and height values from your image
                int width = imageContent.getWidth();
                int height = imageContent.getHeight();
                writableImage = new WritableImage(width, height);
            }
    
            public void setImage(BufferedImage newImage) {
                imageContent = newImage;
            }
    
            public WritableImage getWritableImage() {
                return writableImage;
            }
    
            public BufferedImage getBufferedImage() {
                return imageContent;
            }
        }
    

    但是,现在这似乎很占用内存,我会研究一下。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-06
      • 2016-11-11
      • 2016-05-15
      • 2015-03-31
      • 2015-07-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多