简体   繁体   English

Java swing:为什么鼠标 cursor 第一次绘制时不更新?

[英]Java swing: Why does the mouse cursor not update for the first paint?

I have written some Java Swing code that collects some input, does some calculations, and then displays the result.我写了一些 Java Swing 代码,收集一些输入,做一些计算,然后显示结果。

Expected result:预期结果:

  1. For every user input:对于每个用户输入:
  2. User goes to File -> New -> Enters some data.用户转到文件 -> 新建 -> 输入一些数据。
  3. Mouse cursor turns into spinner as calculations are computed.随着计算的进行,鼠标 cursor 变成微调器。
  4. Result is displayed.显示结果。
  5. Mouse cursor returns to default.鼠标 cursor 返回默认值。

Actual result:实际结果:

  1. For first user input:对于第一个用户输入:
  2. User goes to File -> New -> Enters some data.用户转到文件 -> 新建 -> 输入一些数据。
  3. Mouse cursor remains unchanged as calculations are computed.鼠标 cursor 在计算时保持不变。
  4. Result is displayed.显示结果。
  5. For each subsequent usage, the result is the expected result described above.对于后续的每次使用,结果都是上面描述的预期结果。

I am pretty new to Swing, and I have been trying to get to the bottom of this without much luck.我是 Swing 的新手,我一直在努力弄清楚这个问题,但运气不佳。 I have narrowed down the error to a specific line of the code.我已将错误缩小到代码的特定行。 If I comment out the line JOptionPane.showConfirmDialog(null, null, "Enter new maze parameters", JOptionPane.OK_CANCEL_OPTION);如果我注释掉行JOptionPane.showConfirmDialog(null, null, "Enter new maze parameters", JOptionPane.OK_CANCEL_OPTION); in the code, then the spinner appears the first time through as expected.在代码中,然后微调器按预期第一次出现。 If I leave the line in, then I have the problem as described.如果我留下线路,那么我就会遇到所描述的问题。

Please note that I have heavily trimmed this sample down to eliminate a lot of the U/I flow that is in the actual program, but this sample does break as described.请注意,我已大幅削减此示例以消除实际程序中的大量 U/I 流,但此示例确实如所述那样中断。 I was not able to simplify my sample code further.我无法进一步简化示例代码。

The simplified code简化代码

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;
    }
}

Any help is appreciated.任何帮助表示赞赏。 Please assume I am a complete Swing noob, so a more specific answer/explanation is more likely to help than a high level one that assumes I know something about Swing. I will read any suggested documentation of course.请假设我是一个完整的 Swing 菜鸟,所以更具体的答案/解释比假设我对 Swing 有所了解的高级答案/解释更有帮助。我当然会阅读任何建议的文档。

If there are any problems or clarifications needed, please let me know, I am happy to update the post as many times as required.如果有任何问题或需要澄清,请告诉我,我很乐意根据需要多次更新帖子。

I suspect your init() and generateNewMaze() are being called from another thread and giving unpredictable results.我怀疑您的init()generateNewMaze()正在从另一个线程调用并给出不可预测的结果。 You should never call swing code or manipulate swing objects from outside the EventDispachThread.永远不要调用 swing 代码或从 EventDispachThread 外部操作 swing 对象。 If you aren't in fact calling from another thread, you don't need the outer invokelater wrapper.如果您实际上不是从另一个线程调用,则不需要外部 invokelater 包装器。

Here's a better way to do what you are trying to do:这是做您想做的事情的更好方法:

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();
    });
}

You might also consider using the Glass pane: Wait cursor and disable java application您也可以考虑使用玻璃面板: 等待 cursor 并禁用 java 应用程序

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM