Java:Swing:按下按钮后隐藏窗口

4
我有一个Java框架中的按钮,按下按钮后它会从文本字段读取一个值,并使用该字符串作为端口名称来尝试连接串行设备。如果连接成功,则该方法返回true,否则它返回false。如果它返回true,我希望框架消失。在其他类中指定的一系列其他框架将随后出现,其中包含控制串行设备的选项。
我的问题是:按钮连接到一个动作监听器,当按下按钮时就会调用这个方法。如果我尝试使用frame.setVisible(true);方法,Java就会抛出抽象按钮错误,因为我实际上正在告诉它在按钮按下方法退出之前使包含按钮的框架消失。删除frame.setVisible(true);可以让程序正确运行,但是我留下了一个无用的连接框架。
如何在按下按钮时让框架消失?
package newimplementation1;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;


/**
 *
 * @author Zac
 */

public class ConnectionFrame extends JPanel implements ActionListener {


private JTextField textField;
private JFrame frame;
private JButton connectButton;
private final static String newline = "\n";

public ConnectionFrame(){

    super(new GridBagLayout());

    textField = new JTextField(14);
    textField.addActionListener(this);
    textField.setText("/dev/ttyUSB0");

    connectButton = new JButton("Connect");

    //Add Components to this panel.
    GridBagConstraints c = new GridBagConstraints();
    c.gridwidth = GridBagConstraints.REMAINDER;

    c.fill = GridBagConstraints.HORIZONTAL;
    add(textField, c);

    c.fill = GridBagConstraints.BOTH;
    c.weightx = 1.0;
    c.weighty = 1.0;
    add(connectButton, c);



    connectButton.addActionListener(new ActionListener() {

        public void actionPerformed(ActionEvent e)
        {

            boolean success = Main.mySerialTest.initialize(textField.getText());

            if (success == false) {System.out.println("Could not connect"); return;}

            frame.setVisible(false);  // THIS DOES NOT WORK!!

            JTextInputArea myInputArea = new JTextInputArea();
            myInputArea.createAndShowGUI();

            System.out.println("Connected");


        }
    });

}

    public void actionPerformed(ActionEvent evt) {

            // Unimplemented required for JPanel

    }

    public void createAndShowGUI() {

    //Create and set up the window.
    frame = new JFrame("Serial Port Query");
    frame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);


    //Add contents to the window.
    frame.add(new ConnectionFrame());
    frame.setLocation(300, 0);


    //Display the window.
    frame.pack();
    frame.setVisible(true);

            frame.addComponentListener(new ComponentAdapter() {
        @Override
        public void componentHidden(ComponentEvent e) {
            System.out.println("Exiting Gracefully");
            Main.mySerialTest.close();
            ((JFrame)(e.getComponent())).dispose();
            System.exit(0);
        }
    });


}

}

2
应用程序不太可能使用多个 JFrame。小的 UI 元素可以弹出在 JDialogJOptionPane 中,而 CardLayout 或各种 Swing 组件可用于在容器中包含多个 UI(或“屏幕”)。 - Andrew Thompson
1
为了更快地获得更好的帮助,请发布一个SSCCE - Andrew Thompson
4个回答

6

在删除/调整自定义类之后运行您的代码片段,会抛出NPE。原因是您访问的帧为空。这是因为它从未设置过。最好不要依赖任何字段,让按钮找到其顶级祖先并隐藏它,就像这样:

        public void actionPerformed(final ActionEvent e) {

            boolean success = true;
            if (success == false) {
                System.out.println("Could not connect");
                return;
            }

            Window frame = SwingUtilities.windowForComponent((Component) e
                    .getSource());
            frame.setVisible(false); //no problem :-)

        }

这对我很有帮助,谢谢。但是,我有一个问题:这种方法是否可靠?在调用SwingUtilities.windowForComponent(...)时,这种方法是否存在任何已知的缺陷?在我的情况下,我有一个应用程序,其中主框架(JFrame)包含多个JPanel,因此在实现更多之前,我想知道会发生什么。提前感谢您的回答。 - rchrd

4

您的问题出在这一行:

  frame.add(new ConnectionFrame());

你正在创建一个新的ConnectionFrame对象,因此你的按钮试图关闭的框架与正在显示的框架不同,这是问题的根源。
如果你将其更改为:
  //!! frame.add(new ConnectionFrame());
  frame.add(this);

为了让两个JFrames成为同一个,事情可能会更加顺利。但是,说了这么多,你的整个设计都有点不好,我建议重新考虑一下,采用更多面向对象、更少静态的方式。此外,在需要对话框时请使用对话框,而不是框架;而且,与其使用对话框,还不如通过CardLayout交换视图(JPanels)来实现更好的效果。
就我个人而言,我会为此创建一个“愚蠢”的GUI,它创建一个JPanel(在我的示例中,它扩展了一个JPanel以简化代码,但如果不必要,我会避免扩展),然后让调用此代码的人通过某些控件决定如何处理信息。例如:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

@SuppressWarnings("serial")
public class ConnectionPanel extends JPanel {

   private JTextField textField;
   private JButton connectButton;
   private ConnectionPanelControl control;

   public ConnectionPanel(final ConnectionPanelControl control) {
      super(new GridBagLayout());
      this.control = control;

      ActionListener listener = new ActionListener() {
         public void actionPerformed(ActionEvent e) {
            if (control != null) {
               control.connectButtonAction();
            }
         }
      };

      textField = new JTextField(14);
      textField.addActionListener(listener);
      textField.setText("/dev/ttyUSB0");

      connectButton = new JButton("Connect");

      GridBagConstraints c = new GridBagConstraints();
      c.gridwidth = GridBagConstraints.REMAINDER;

      c.fill = GridBagConstraints.HORIZONTAL;
      add(textField, c);

      c.fill = GridBagConstraints.BOTH;
      c.weightx = 1.0;
      c.weighty = 1.0;
      add(connectButton, c);

      connectButton.addActionListener(listener);
   }

   public String getFieldText() {
      return textField.getText();
   }

}

再次强调,文本框中的文本以及显示此 JPanel 的 GUI 可能需要由简单 GUI 之外的某些内容进行决策处理:

public interface ConnectionPanelControl {

   void connectButtonAction();

}

此外,为了避免冻结用户界面,您可能会在后台线程中进行任何连接,通常是使用SwingWorker。也许可以这样做:
import java.awt.event.ActionEvent;
import java.util.concurrent.ExecutionException;

import javax.swing.*;

@SuppressWarnings("serial")
public class MyMain extends JPanel {
   public MyMain() {
      add(new JButton(new ConnectionAction("Connect", this)));
   }

   private static void createAndShowGui() {
      JFrame frame = new JFrame("My Main");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(new MyMain());
      frame.pack();
      frame.setLocationRelativeTo(null);
      frame.setVisible(true);
   }

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

@SuppressWarnings("serial")
class ConnectionAction extends AbstractAction {
   private MyMain myMain;
   private ConnectionPanel cPanel = null;
   private JDialog dialog = null;

   public ConnectionAction(String title, MyMain myMain) {
      super(title);
      this.myMain = myMain;
   }

   @Override
   public void actionPerformed(ActionEvent e) {
      if (dialog == null) {
         dialog = new JDialog(SwingUtilities.getWindowAncestor(myMain));
         dialog.setTitle("Connect");
         dialog.setModal(true);
         cPanel = new ConnectionPanel(new ConnectionPanelControl() {

            @Override
            public void connectButtonAction() {
               final String connectStr = cPanel.getFieldText();
               new MySwingWorker(connectStr).execute();
            }
         });
         dialog.getContentPane().add(cPanel);
         dialog.pack();
         dialog.setLocationRelativeTo(null);
      }
      dialog.setVisible(true);
   }

   private class MySwingWorker extends SwingWorker<Boolean, Void> {
      private String connectStr = "";

      public MySwingWorker(String connectStr) {
         this.connectStr = connectStr;
      }

      @Override
      protected Boolean doInBackground() throws Exception {
         // TODO: make connection and then return a result
         // right now making true if any text in the field
         if (!connectStr.isEmpty()) {
            return true;
         }
         return false;
      }

      @Override
      protected void done() {
         try {
            boolean result = get();
            if (result) {
               System.out.println("connection successful");
               dialog.dispose();
            } else {
               System.out.println("connection not successful");
            }
         } catch (InterruptedException e) {
            e.printStackTrace();
         } catch (ExecutionException e) {
            e.printStackTrace();
         }
      }
   }
}

1
哇,谢谢所有迅速回复的人。我发了这个问题,然后走开了15分钟,以为幸运的话可能会有1个回复......但是居然有4个! - Zac

1

如果您将JFrame实例命名为xxxFrame,将JPanel实例命名为xxxPanel,则代码将更易读。将JPanel实例命名为xxxFrame会使事情变得非常混乱。

如果您粘贴异常的堆栈跟踪,也会有所帮助。

我怀疑问题出在frame为空的事实上。这是因为frame字段仅在createAndShowGUI方法中初始化,但此方法不显示当前连接面板,而是显示一个新面板,因此具有空帧字段:

ConnectionFrame firstPanel = new ConnectionFrame();
// The firstPanel's frame field is null
firstPanel.createAndShowGUI();
// the firstPanel's frame field is now not null, but
// the above call opens a JFrame containing another, new ConnectionFrame, 
// which has a null frame field

createAndShowGUI 的代码应该包含:

frame.add(this);

而不是

frame.add(new ConnectionFrame());

1

对于 Swing GUI,最好只创建一次 JFrame,而另一个顶级容器可以是JDialogJWindow(默认情况下未装饰)。

这里有一个简单的例子:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class SuperConstructor extends JFrame {

    private static final long serialVersionUID = 1L;

    public SuperConstructor() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setPreferredSize(new Dimension(300, 300));
        setTitle("Super constructor");
        Container cp = getContentPane();
        JButton b = new JButton("Show dialog");
        b.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent evt) {
                FirstDialog firstDialog = new FirstDialog(SuperConstructor.this);
            }
        });
        cp.add(b, BorderLayout.SOUTH);
        JButton bClose = new JButton("Close");
        bClose.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent evt) {
                System.exit(0);
            }
        });
        add(bClose, BorderLayout.NORTH);
        pack();
        setVisible(true);
    }

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

            @Override
            public void run() {
                SuperConstructor superConstructor = new SuperConstructor();
            }
        });
    }

    private class FirstDialog extends JDialog {

        private static final long serialVersionUID = 1L;

        FirstDialog(final Frame parent) {
            super(parent, "FirstDialog");
            setPreferredSize(new Dimension(200, 200));
            setLocationRelativeTo(parent);
            setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            setModalityType(Dialog.ModalityType.DOCUMENT_MODAL);
            JButton bNext = new JButton("Show next dialog");
            bNext.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent evt) {
                    SecondDialog secondDialog = new SecondDialog(parent, false);
                }
            });
            add(bNext, BorderLayout.NORTH);
            JButton bClose = new JButton("Close");
            bClose.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent evt) {
                    setVisible(false);
                }
            });
            add(bClose, BorderLayout.SOUTH);
            pack();
            setVisible(true);
        }
    }
    private int i;

    private class SecondDialog extends JDialog {

        private static final long serialVersionUID = 1L;

        SecondDialog(final Frame parent, boolean modal) {
            //super(parent); // Makes this dialog unfocusable as long as FirstDialog is visible
            setPreferredSize(new Dimension(200, 200));
            setLocation(300, 50);
            setModal(modal);
            setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            setTitle("SecondDialog " + (i++));
            JButton bClose = new JButton("Close");
            bClose.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent evt) {
                    setVisible(false);
                }
            });
            add(bClose, BorderLayout.SOUTH);
            pack();
            setVisible(true);
        }
    }
}

更好的做法是重复使用顶级容器,因为在运行时创建大量的顶级容器可能会导致内存不足。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接