来自 AWT JFrame 的真正应用程序模态 SWT shell

True application modal SWT shell from AWT JFrame

我有一个 Java Swing 应用程序,我正在向它嵌入一个 SWT 小部件。我试图从我的 AWT JFrame 中显示一个 SWT Shell,但无法使其成为应用程序模式。 JFrame 仍然可以聚焦,按钮点击将注册到 EDT。要使 Shell 表现得像模态 AWT 对话框,我需要采取哪些步骤?

我已阅读 this outdated tutorial,但它仅解释了如果应用程序在 SWT 事件线程上 运行 如何执行此操作。我也尝试用模态 JDialog 破解它,但这种行为充其量是丑陋的。从最小工作示例中删除注释以对其进行演示。 SWT.ON_TOP 本身就很麻烦,因为 shell 将停留在每个 window.

之上

This question 没有帮助。

SSCE

public class ModalDialogExample extends JFrame {

public ModalDialogExample()
{
    this.setSize(500, 500);
    this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}

public static void main(String[] args)
{
    JFrame frame = new ModalDialogExample();
    JButton button = new JButton("CLick me");
    button.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
//              JDialog modalDialog = new JDialog();
//              modalDialog.setSize(new Dimension(0,0));
//              modalDialog.setModalityType(ModalityType.APPLICATION_MODAL);
            Display display = new Display();
            Shell shell = new Shell(display, SWT.CLOSE | SWT.TITLE | SWT.BORDER | SWT.OK | SWT.ON_TOP | SWT.APPLICATION_MODAL);
            shell.setSize(200,200);
            shell.open();
            shell.forceActive();
//              modalDialog.setVisible(true);
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch())
                    display.sleep();
            }
            display.dispose();
        }
    });
    frame.getContentPane().add(button);
    frame.setVisible(true);
}
}

这是我想出的。它并不完美,但可以完成工作。回想一下,我有一个 JFrame 在其上我需要一个模态 SWT Shell 表现得像一个模态 JDialog.

  1. 启动的Shell不能在AWT事件派发线程中存活。我们需要访问线程以稍后拦截鼠标和 window 事件。所以在新的 Thread

  2. 中启动 Shell
  3. 我在 JFrame 上放置了一个 GlassPane,因为 Shell 打开了。目的是阻止框架中的其他交互式 Swing 组件及其容器,并拦截所有鼠标事件。我需要鼠标事件,所以我不能简单地做 Frame.setEnabled(false).

  4. 我在框架上使用了 WindowListener,在 GlassPane

  5. 上使用了 MouseListener
  6. 我在 Shell 上使用了 DisposeListener 来检测 JFrame 必须丢失 GlassPane 的时刻以及为Shell 的直播时间。

  7. 为了使用我的 Swing 侦听器控制 Shell 的可见性,我需要访问 SWT 事件线程。 这是通过执行 Runnableshell.getDisplay().syncExec(...)

这就是我启动 Shell、激活 GlassPane、附加 "modality listeners" 并在 Shell 关闭时删除它们的方式。 Shell 在新线程中启动。

new Thread(new Runnable() {

    @Override
    public void run() {

            final Shell shell = myWidget.getShell();

            final EditorWindowListener ewl = new EditorWindowListener(shell);
            myFrame.addWindowListener(ewl);

            final EditorClickListener ecl = new EditorClickListener(shell);
            myFrame.getGlassPane().addMouseListener(ecl);

            myFrame.getGlassPane().setVisible(true);
            shell.addDisposeListener(new DisposeListener() {

            //Remove the disabled status
                @Override
                public void widgetDisposed(DisposeEvent arg0) {
                    myFrame.removeWindowListener(ewl);
                    myFrame.getGlassPane().removeMouseListener(ecl);
                    myFrame.getGlassPane().setVisible(false);
                }
            });
            //The method that starts the shell
            myWidget.show();
        }
}).start();

这就是 myWidget.show() 中发生的事情(标准 SWT 内容,没有修改)

shell.open();   
shell.forceActive();
while (!shell.isDisposed()) {
    if (!display.readAndDispatch())
        display.sleep();
}
display.dispose();

下面是我添加到 frame 和 glassPane 的两个监听器。首先,MouseListener 检测玻璃窗格上的点击,如果捕获到任何事件,则将 Shell 带到顶部。我知道在完美的模态对话框中不需要它,而且在这种情况下也不需要,但是一些双显示器设置导致的问题已通过此侦听器解决。

class EditorClickListener extends MouseAdapter
{
    private Shell shell;

    public EditorClickListener(Shell s)
    {
        this.shell = s;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        shellToFront(shell);
    }
}

然后WindowListener附在框架上。它确保在框架处于活动状态时,shell 跳到顶部。

class EditorWindowListener extends WindowAdapter
{
    Shell shell;
    public EditorWindowListener(Shell s)
    {
        this.shell = s;
    }

    @Override
    public void windowOpened(WindowEvent e) {
        shellToFront(shell);
    }

    @Override
    public void windowDeiconified(WindowEvent e) {
        shellToFront(shell);
    }

    @Override
    public void windowActivated(WindowEvent e) {
        //Set the shell on top of the frame
        //Fixes some problems with dual monitor setups.
        final java.awt.Point framePoint = myFrame.getLocation();
        shellToFront(shell);
        shell.getDisplay().syncExec(new Runnable() {

            @Override
            public void run() {
                shell.setMinimized(false);
                shell.setActive();
                org.eclipse.swt.graphics.Point shellPoint = aikataulu.getLocation();
                shellPoint.x = (int) framePoint.getX();
                shellPoint.y = (int) framePoint.getY();
                shell.setLocation(shellPoint);
            }
        });     
    }
}

最后是将 shell 弹出到您的脸上以创建模态感觉的方法。请注意,所有事件都需要分配才能在 SWT 事件线程中发生。我在将最小化状态设置为 false 时遇到了一些麻烦。所以我不得不添加一个人为的最小化,以防 Shell 没有最小化并且确实落后于其他 windows。这在某些用例中会导致愚蠢的不必要的动画,但现在它会做

private void shellToFront(final Shell shell)
{
    shell.getDisplay().syncExec(new Runnable() {

        @Override
        public void run() {
            if (!shell.getMinimized())
            {
                shell.setMinimized(true);
            }
            shell.setMinimized(false);
            shell.setActive();
        }
    });
}