【问题标题】:Java swing: Why does the mouse cursor not update for the first paint?Java swing:为什么第一次绘制时鼠标光标不更新?
【发布时间】:2020-08-27 15:06:14
【问题描述】:

我编写了一些 Java Swing 代码来收集一些输入,进行一些计算,然后显示结果。

预期结果:

  1. 对于每个用户输入:
  2. 用户转到文件 -> 新建 -> 输入一些数据。
  3. 计算计算时鼠标光标变成微调器。
  4. 显示结果。
  5. 鼠标光标恢复默认值。

实际结果:

  1. 对于第一个用户输入:
  2. 用户转到文件 -> 新建 -> 输入一些数据。
  3. 计算计算时鼠标光标保持不变。
  4. 显示结果。
  5. 对于每个后续使用,结果都是上述预期结果。

我对 Swing 还很陌生,我一直在尝试深入了解这一点,但运气不佳。我已将错误范围缩小到代码的特定行。如果我注释掉该行 JOptionPane.showConfirmDialog(null, null, "Enter new maze parameters", JOptionPane.OK_CANCEL_OPTION); 在代码中,然后微调器按预期第一次出现。如果我把线路留在里面,那么我就会遇到所描述的问题。

请注意,为了消除实际程序中的大量 U/I 流,我已大幅缩减此示例,但此示例确实如所述那样中断。我无法进一步简化示例代码。

简化代码

package ca.pringle.maze.ui;

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Set;

import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;

import ca.pringle.maze.logic.Edge;
import ca.pringle.maze.logic.MazeConfig;
import ca.pringle.maze.logic.MazeMaker;
import ca.pringle.maze.logic.PathFinder;
import ca.pringle.maze.util.Pair;

public final class MazeDrawer extends JFrame {

    private final MazePanel mazePanel;

    public MazeDrawer() {
        mazePanel = new MazePanel();
    }

    public void init() {
        final JScrollPane scrollPane = new JScrollPane(mazePanel);
        final JMenuBar menuBar = createMenuBar((actionEvent) -> generateNewMaze());
        setJMenuBar(menuBar);

        getContentPane().setLayout(new BorderLayout());
        getContentPane().add(scrollPane, BorderLayout.CENTER);

        setSize(100, 100);
        setVisible(true);
        repaint();
    }

    void generateNewMaze() {
        // if I comment out  this line below, then the spinner appears on the first run as expected.
        // if I leave this line in, then the spinner does not appear on the first run, only on subsequent runs
        JOptionPane.showConfirmDialog(null, null, "Enter new maze parameters", JOptionPane.OK_CANCEL_OPTION);

        final MazeConfig mazeConfig = new MazeConfig(1000, 1000, 1);

        final MazeMaker mazeMaker = new MazeMaker(mazeConfig);
        final PanelDimensions panelDimensions = new PanelDimensions(mazeConfig.getRows(), mazeConfig.getColumns(), 15, 15);

        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

        // the two lines below can take a long time depending on the inputs. I tried removing
        // them and replacing with a sleep to simulate and simplify the problem, but no luck,
        // so I left them in, even if I did not include the code.
        final Set<Edge> edges = mazeMaker.generateUndirectedMazeEdges();
        final Pair<Integer, Integer> startAndEndNodes = new PathFinder().findLongestPath(edges, mazeConfig);
        mazePanel.update(edges, startAndEndNodes, panelDimensions);
        mazePanel.setPreferredSize(new Dimension(panelDimensions.panelWidth, panelDimensions.panelHeight));
        mazePanel.repaint();

        setSize(Math.min(1200, panelDimensions.panelWidth + 11), Math.min(700, panelDimensions.panelHeight + 53));
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }

    private JMenuBar createMenuBar(final ActionListener menuListener) {

        final JMenuBar bar = new JMenuBar();

        final JMenu fileMenu = new JMenu("File");
        fileMenu.setMnemonic(KeyEvent.VK_F);

        final JMenuItem newMenuItem = new JMenuItem("New", KeyEvent.VK_N);
        newMenuItem.addActionListener(menuListener);
        fileMenu.add(newMenuItem);
        bar.add(fileMenu);

        return bar;
    }
}

感谢任何帮助。请假设我是一个完整的 Swing 菜鸟,所以一个更具体的答案/解释比假设我对 Swing 有所了解的高级答案更有可能有所帮助。当然,我会阅读任何建议的文档。

如果有任何问题或需要澄清,请告诉我,我很乐意根据需要多次更新帖子。

【问题讨论】:

  • 第一次执行代码时,GUI 甚至还不可见,所以我猜没有组件关注 setCursor() 方法什么都不做。我猜你应该先创建一个使框架可见,然后再开始执行其他代码以获取用户输入。
  • @camickr 听起来很合理。我确实有 setVisible(true);和重绘();在我的init方法中,它被称为第一件事。我想另一个问题是您的理论能否解释为什么删除该提示会导致代码按预期工作(除非我显然想要提示)。
  • 这能回答你的问题吗? Swing GUI doesn't update during data processing
  • 长时间运行的任务在你改变光标后执行,但仍在事件派发线程内;您需要在光标更改生效之前从更改光标的方法返回。您需要将迷宫计算移动到另一个线程,然后,当它完成后,您使用 SwingUtilities.invokeLater 将光标改回。

标签: java swing


【解决方案1】:

我怀疑您的 init()generateNewMaze() 正在从另一个线程调用并产生不可预测的结果。你不应该从 EventDispachThread 之外调用 swing 代码或操作 swing 对象。如果您实际上不是从另一个线程调用,则不需要外部调用者包装器。

这里有一个更好的方法来做你想做的事情:

void generateNewMaze() {
    SwingUtilities.invokeLater(() -> {
        JOptionPane.showConfirmDialog(null, null, "Enter new maze parameters", JOptionPane.OK_CANCEL_OPTION);

        setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
        mazePanel.setEnabled(false);

        new Thread(() -> {
            try {
                // Do the long running maze task; this sleep is to simulate that task
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            SwingUtilities.invokeLater(() -> {
                setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                mazePanel.setEnabled(true);
            });
        }).start();
    });
}

您也可以考虑使用 Glass 窗格:Wait cursor and disable java application

【讨论】:

  • 感谢您的回答。我会尝试一下,以及一些变化。我一直在尝试其他建议,但到目前为止还没有运气。我仍然计划阅读完整的挥杆教程。我也想过尝试玻璃窗格,但感觉就像是一个创可贴,没有解决根本原因(即我糟糕的 Swing 代码)。
  • 您可能会遇到的麻烦是您可能想在迷宫生成时阻止与面板/框架的交互,这听起来像是您想要“禁用”它,但摇摆不会呈现光标一个组件(如果它被禁用),这就是玻璃窗格的来源;它提供了另一层来更改光标,而组件“显示”
  • 啊,这很有道理,你说得对——可以说,在生成迷宫时,用户应该坐下来放松一下。星期五我有时间试试你的建议,我会看看结果如何。我真的很想再次感谢您提供实际的示例代码,这将节省我大量时间尝试我认为的含义的 30 种变体。顺便说一句,我在发帖之前确实检查过,一切都在 EventDispachThread 上发生。
  • @MichaPringle 运气好吗?
  • 信不信由你,我还没有机会尝试一下。我会尽快的。
猜你喜欢
  • 1970-01-01
  • 2012-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-24
相关资源
最近更新 更多