【问题标题】:Optimal location for a modal JDialog to avoid stuck模态 JDialog 避免卡住的最佳位置
【发布时间】:2012-12-25 18:42:51
【问题描述】:

我的 Swing 应用程序必须向用户显示一个模式对话框。很抱歉没有发布 SSCCE。

topContainer 可能是 JFrameJApplet

private class NewGameDialog extends JDialog {
     public NewGameDialog () {
         super(SwingUtilities.windowForComponent(topContainer), "NEW GAME", ModalityType.APPLICATION_MODAL);

         //add components here

         getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

         //TODO:
         setSize(new Dimension(250, 200));
         setLocation(650, 300);
     }
}

我在网络事件中开始这样的对话框

SwingUtilities.invokeLater(new Runnable() {
     @Override
     public void run() {
         NewGameDialog dialog = new NewGameDialog();
         dialog.setVisible(true);
     }
});

问题是为我的对话设置最佳位置。

1) 如果它设置为绝对值,并且我将应用程序框架移动到第二个屏幕,那么对话框会显示在第一个屏幕上,这很奇怪。

2) 如果将其设置为 JFrame 的相对值,则可能会出现用户将应用程序框架移动到屏幕之外,并且相对定位的对话框对用户来说是不可见的。而且因为它是模态的,所以游戏会卡住。

考虑到上述两个问题,最佳解决方案是什么?

【问题讨论】:

  • 从一些 JComponent 中返回 Point
  • @mKorbel,抱歉没听懂
  • see here :-),取消评论 //dialog.setLocation(x, y); 并评论 dialog.setLocationRelativeTo(frame);dialog.setVisible(true); 必须在 invokeLater

标签: java swing jframe jdialog


【解决方案1】:

这让我想起了我在 StackOverflow 上使用 Window.setLocationByPlatform(true) 发表的一篇非常喜欢的帖子。

How to best position Swing GUIs

编辑 1:

您可以将FocusListener 添加到您的JDialogfocusGained(...) 方法上,您可以将setLocationRelativeTo(null) 用于JFrameJDialog,以便它们都位于中心屏幕,无论他们以前在哪里。

import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;

/**
 * Created with IntelliJ IDEA.
 * User: Gagandeep Bali
 * Date: 1/14/13
 * Time: 7:34 PM
 * To change this template use File | Settings | File Templates.
 */
public class FrameFocus
{
    private JFrame mainwindow;
    private CustomDialog customDialog;

    private void displayGUI()
    {
        mainwindow = new JFrame("Frame Focus Window Example");
        customDialog = new CustomDialog(mainwindow, "Modal Dialog", true);
        mainwindow.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        JPanel contentPane = new JPanel();
        JButton mainButton = new JButton(
                "Click me to open a MODAL Dialog");
        mainButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (!customDialog.isShowing())
                    customDialog.setVisible(true);
            }
        });
        contentPane.add(mainButton);
        mainwindow.setContentPane(contentPane);
        mainwindow.pack();
        mainwindow.setLocationByPlatform(true);
        mainwindow.setVisible(true);
    }

    public static void main(String... args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                new FrameFocus().displayGUI();
            }
        });
    }
}


class CustomDialog extends JDialog
{
    private JFrame mainWindow;
    public CustomDialog(JFrame owner, String title, boolean modal)
    {
        super(owner, title, modal);
        mainWindow = owner;
        JPanel contentPane = new JPanel();
        JLabel dialogLabel = new JLabel(
                "I am a Label on JDialog.", JLabel.CENTER);
        contentPane.add(dialogLabel);
        setContentPane(contentPane);
        pack();

        addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
                mainWindow.setLocationRelativeTo(null);
                setLocationRelativeTo(null);
            }

            @Override
            public void focusLost(FocusEvent e) {
                /*
                 * Nothing written for this part yet
                 */
            }
        });
    }
}

编辑 2:

我在这里和那里搜索了一下,在我看来,实际上你的应用程序首先出现在哪个Monitor Screen 上,将确定它是GraphicsConfiguration。虽然当我在 API 中漫游时,对于上述 GraphicsConfiguration 事物只有一个 getter 方法,而没有相同的 setter 方法(您仍然可以通过任何顶级窗口的构造函数指定一个,即 JFrame(...)/JDialog(...) )。

现在你可以用这段代码来占据你的头了,它可以用来确定你想要设置的适当位置,同样,在我看来,你可能必须使用focusGain() 方法,以满足你的条件 2题。看一下附带的代码,虽然不需要创建new JFrame/JDialog,只是看如何获取屏幕坐标(您可以在focusGain()方法中添加以确定整个应用程序的位置。)

GraphicsEnvironment ge = GraphicsEnvironment.
    getLocalGraphicsEnvironment();
GraphicsDevice[] gs = ge.getScreenDevices();
for (int j = 0; j < gs.length; j++) {
    GraphicsDevice gd = gs[j];
    GraphicsConfiguration[] gc =
            gd.getConfigurations();
    for (int i=0; i < gc.length; i++) {
        JFrame f = new
        JFrame(gs[j].getDefaultConfiguration());
        Canvas c = new Canvas(gc[i]);
        Rectangle gcBounds = gc[i].getBounds();
        int xoffs = gcBounds.x;
        int yoffs = gcBounds.y;
        f.getContentPane().add(c);
        f.setLocation((i*50)+xoffs, (i*60)+yoffs);
        f.show();
    }
}

编辑 3:

尝试改变这一点:

int x = loc.getX() + (mainWindow.getWidth() - getWidth()) / 2;
int y = loc.getY() + (mainWindow.getHeight() - getHeight()) / 2;
setLocation(x, y);

只是:

setLocationRelativeTo(mainWindow);

为了测试上述内容,我按原样使用了我的 FrameFocus 类,尽管我已将您的更改添加到我的 CustomDialog 方法中,如修改后的 CustomDialog 类所示。

class CustomDialog extends JDialog
{
    private JFrame mainWindow;
    public CustomDialog(JFrame owner, String title, boolean modal)
    {
        super(owner, title, modal);
        mainWindow = owner;
        JPanel contentPane = new JPanel();
        JLabel dialogLabel = new JLabel(
                "I am a Label on JDialog.", JLabel.CENTER);
        contentPane.add(dialogLabel);
        setContentPane(contentPane);
        pack();

        addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
                //mainWindow.setLocationRelativeTo(null);
                //setLocationRelativeTo(null);
                GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
                GraphicsDevice[] gs = ge.getScreenDevices();
                for (int j = 0; j < gs.length; j++) {
                    GraphicsDevice gd = gs[j];
                    GraphicsConfiguration[] gc = gd.getConfigurations();
                    for (int i=0; i < gc.length; i++) {
                        Rectangle gcBounds = gc[i].getBounds();

                        Point loc = mainWindow.getLocationOnScreen();
                        if (gcBounds.contains(loc)) {
                            System.out.println("at " + j + " screen");

                            int x = gcBounds.x + (gcBounds.width - mainWindow.getWidth()) / 2;
                            int y = gcBounds.y + (gcBounds.height - mainWindow.getHeight()) / 2;
                            mainWindow.setLocation(x, y);

                            //x = (int) (loc.getX() + (mainWindow.getWidth() - CustomDialog.this.getWidth()) / 2);
                            //y = (int) (loc.getY() + (mainWindow.getHeight() - CustomDialog.this.getHeight()) / 2);
                            //CustomDialog.this.setLocation(x, y);
                            CustomDialog.this.setLocationRelativeTo(mainWindow);

                            break;
                        }
                    }
                }
            }

            @Override
            public void focusLost(FocusEvent e) {
                /*
                 * Nothing written for this part yet
                 */
            }
        });
    }
}

【讨论】:

  • 我将 JFrame 移动到第二个屏幕,但 JDialog 出现在第一个屏幕上。它没有解决问题 #1。
  • @NikolayKuznetsov :如果您将 FocusListener 添加到您的 JDialog 并执行我在 focusGained() 方法的编辑中提到的步骤会怎样?我希望这种解决方法适用于这两种情况。
  • @NikolayKuznetsov :请观看最新的编辑,看看这些新信息是否对您有任何帮助:-)
  • 我认为使用 EDIT-2 和 getLocation 我需要确定应用程序正在运行的屏幕,然后将 JFrameJDialog 移动到该屏幕的中间。
  • 请看我对这个问题的回答。你觉得这段代码有什么问题吗?
【解决方案2】:

我认为,最好将对话框置于当前屏幕中间,如 here 所述。

Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension screenSize = toolkit.getScreenSize();
int x = (screenSize.width - d.getWidth()) / 2;
int y = (screenSize.height - d.getHeight()) / 2;
d.setLocation(x, y);

这总是有效的,如果它位于屏幕的中心,用户怎么看不到它?并且setLocationRelativeTo也可以使用,但是你需要invoke it at the right time

【讨论】:

  • d.setLocationRelativeTo(null); 怎么样?这是链接下答案的主线。
  • 也可以使用,但另请参阅“怪癖”下的链接。
  • IMO 使用 setLocationRelativeTo 完全没有怪癖,必须在 正确的时间 调用,如 here 所示。
  • 也许,最好说“你需要在正确的时间调用它”。
  • 我在第一个屏幕上启动应用程序,然后将其移至第二个屏幕,但对话框仍然出现在第一个屏幕上。
【解决方案3】:

使用JDialog.setLocation()JDialog 移动到所需的Point on the screen

import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;

public class JDialogAtPoint {

    private JFrame frame = new JFrame();
    private JPanel panel = new JPanel();
    private JDialog dialog;
    private Point location;

    public JDialogAtPoint() {
        createGrid();
        createDialog();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(panel);
        frame.setLocation(100, 100);
        frame.pack();
        frame.setVisible(true);
    }

    private void createGrid() {
        panel.setLayout(new GridLayout(3, 3, 4, 4));
        int l = 0;
        int row = 3;
        int col = 3;
        JButton buttons[][] = new JButton[row][col];
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                buttons[i][j] = new JButton("");
                buttons[i][j].putClientProperty("column", i + 1);
                buttons[i][j].putClientProperty("row", j + 1);
                buttons[i][j].setAction(updateCol());
                panel.add(buttons[i][j]);
                l++;
            }
        }
    }

    private void createDialog() {
        dialog = new JDialog();
        dialog.setAlwaysOnTop(true);
        dialog.setModal(true);
        dialog.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
        JPanel pane = (JPanel) dialog.getContentPane();
        pane.setBorder(new EmptyBorder(20, 20, 20, 20));
        dialog.pack();
    }

    public Action updateCol() {
        return new AbstractAction("Display JDialog at Point") {

            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                JButton btn = (JButton) e.getSource();
                System.out.println("Locations coordinates" + btn.getLocation());
                System.out.println("clicked column "
                        + btn.getClientProperty("column")
                        + ", row " + btn.getClientProperty("row"));
                if (!dialog.isVisible()) {
                    showingDialog(btn.getLocationOnScreen());
                }
            }
        };
    }

    private void showingDialog(final Point loc) {
        dialog.setVisible(false);
        location = loc;
        int x = location.x;
        int y = location.y;
        dialog.setLocation(x, y);
        Runnable doRun = new Runnable() {

            @Override
            public void run() {//dialog.setLocationRelativeTo(frame);
                dialog.setVisible(true);
            }
        };
        SwingUtilities.invokeLater(doRun);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JDialogAtPoint cf = new JDialogAtPoint();
            }
        });
    }
}

【讨论】:

  • 我对该帖子的 +1,虽然我必须等待 10 个小时才能做到这一点:(
  • 在这里的大部分时间我都遇到了同样的问题 :-)
  • 请看我对这个问题的回答。你觉得这段代码有什么问题吗?
  • 可能是,注意是否有多个GraphicsDevice,然后必须检查JOptionPane,有旧Bug,在某些版本中是JOptionPane 移到第一个。设备
【解决方案4】:

在所有 3 位回答者的帮助下,我提出了似乎正是我需要的代码。首先,JFrame 被放置在当前屏幕的中间,然后 JDialog 相应地。

GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] gs = ge.getScreenDevices();
for (int j = 0; j < gs.length; j++) {
    GraphicsDevice gd = gs[j];
    GraphicsConfiguration[] gc = gd.getConfigurations();
    for (int i=0; i < gc.length; i++) {
        Rectangle gcBounds = gc[i].getBounds();

        Point loc = mainWindow.getLocationOnScreen();
        if (gcBounds.contains(loc)) {
            System.out.println("at " + j + " screen");

            int x = gcBounds.x + (gcBounds.width - mainWindow.getWidth()) / 2;
            int y = gcBounds.y + (gcBounds.height - mainWindow.getHeight()) / 2;
            mainWindow.setLocation(x, y);

            int x = loc.getX() + (mainWindow.getWidth() - getWidth()) / 2;
            int y = loc.getY() + (mainWindow.getHeight() - getHeight()) / 2;
            setLocation(x, y);

            break;
        }
    }
}

【讨论】:

  • 再看这段代码,你的First Monitor Screen 将出现JFrame,将成为GraphicsConfiguration 的一部分,因此遍历第二个值将毫无意义。即使您将这个JFrame 带到另一个屏幕,它仍然会再次进入第一个屏幕。所以在这种情况下,focusGained() 方法再次可以与setLocationRelativeTo(null) 一起使用。虽然我会在某个时候尝试此代码,但当我回到家时,我可以确定这种方法有多好。
  • @GagandeepBali,你是对的。这是我的原始版本,在测试期间得到了大幅更新。
  • @NikolayKuznelsov :啊哈,虽然更新的答案确实有效,但在某些时候,如果我将mainWindow 带到我的第二台显示器的最底部,然后尝试按下@987654331 @ 如我的示例所示,然后使用您的代码,JDialog 会到达 mainWindow 的这个位置,尽管 mainWindow 本身会到达中心。如果您遇到同样的问题,那么我已经用 EDIT 3 更新了我的答案,看看,这可能也可以解决这个问题,我猜... +1 为您的努力,放置你的答案:-)
  • 当然可以,非常欢迎您并保持微笑:-) 简单来说,我的意思是,只需找到您的JFrame 的位置(就像您已经完成的那样),然后简单地将JDialog 相对于你的JFrame 放在这之后(使用setLocationRelativeTo(mainWindow)),而不是通过计算为你的JDialog 找到新的坐标。
猜你喜欢
  • 2011-01-24
  • 1970-01-01
  • 1970-01-01
  • 2020-10-05
  • 1970-01-01
  • 2022-01-15
  • 1970-01-01
  • 2016-10-15
  • 2016-03-28
相关资源
最近更新 更多