登录对话框窗口不会完全丢弃

时间:2014-10-01 15:54:05

标签: java swing dispose jxloginpane

我有一个对话窗口,用于将用户登录到Java中的主程序。但是,我最近发现,如果用户单击“取消”按钮或窗口的本机“关闭”按钮,程序仍会运行,即使登录窗口本身已被丢弃。我必须强行戒掉它。我的直觉告诉我,这与创建LoginService时创建的JXLoginPane有关。请查看下面的(详细记录的)代码并告诉我您的想法:

package info.chrismcgee.sky;

import info.chrismcgee.beans.Login;
import info.chrismcgee.dbutil.LoginConnectionManager;
import info.chrismcgee.login.PasswordHash;
import info.chrismcgee.tables.LoginManager;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.EmptyBorder;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdesktop.swingx.JXLoginPane;
import org.jdesktop.swingx.auth.LoginService;

public class LoginDialog extends JDialog {

    // For logging!
    static final Logger log = LogManager.getLogger(LoginDialog.class.getName());

    /**
     * Serialize, to keep Eclipse from throwing a warning message.
     */
    private static final long serialVersionUID = 52954843540592996L;
    private final JPanel contentPanel = new JPanel(); // default.
    // The login pane is a field because it needs to be called
    // by the "OK" button later.
    private JXLoginPane loginPane;
    // User bean is a field because two inner methods need to access it,
    // and it would be cheaper to only create one bean.
    private Login bean;

    /**
     * Launch the application.
     * Unedited.
     */
    public static void main(String[] args) {

        log.entry("main (LoginDialog)");

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {

                log.entry("run (LoginDialog)");

                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    LoginDialog dialog = new LoginDialog();
                    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
                    dialog.setVisible(true);
                } catch (Exception e) {
                    log.error("Error running the LoginDialog", e);
                }

                log.exit("run (LoginDialog)");
            }
        });

        log.exit("main (LoginDialog)");
    }

    /**
     * Create the dialog.
     */
    public LoginDialog() {
        setBounds(100, 100, 450, 300);
        getContentPane().setLayout(new BorderLayout()); // default.
        contentPanel.setLayout(new FlowLayout()); // default.
        contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); // default.
        getContentPane().add(contentPanel, BorderLayout.CENTER); // default.
        {
            // Create a JXLoginPane using a LoginService
            // to handle the authentication.
            loginPane = new JXLoginPane(new LoginService() {

                // `authenticate` *must* be overridden.
                // We will not be using the "server" string, however.
                @Override
                public boolean authenticate(String name, char[] password, String server)
                        throws Exception {

                    log.entry("authenticate (LoginDialog)");

                    // With the username entered by the user, get the user information
                    // from the database and store it in a Login bean.
                    bean = LoginManager.getRow(name);

                    // If the user does not exist in the database, the bean will be null.
                    if (bean != null)
                        // Use `PasswordHash`'s `validatePassword` static method
                        // to see if the entered password (after being hashed)
                        // matches the hashed password stored in the bean.
                        // Returns `true` if it matches, `false` if it doesn't.
                        return log.exit(PasswordHash.validatePassword(password, bean.getHashPass()));
                    else
                        // If the user doesn't exist in the database, just return `false`,
                        // as if the password was wrong. This way, the user isn't alerted
                        // as to which of the two pieces of credentials is wrong.
                        return log.exit(false);
                }
            });
            // Add the login pane to the main content panel.
            contentPanel.add(loginPane);
        }
        {
            // Create the button pane for the bottom of the dialog.
            JPanel buttonPane = new JPanel();
            buttonPane.setLayout(new FlowLayout(FlowLayout.RIGHT)); // default.
            getContentPane().add(buttonPane, BorderLayout.SOUTH); // default.
            {
                // Create the "OK" button plus its action listener.
                JButton okButton = new JButton("OK");
                okButton.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent evt) {

                        log.entry("OK button pressed. (LoginDialog)");

                        // Several of these will throw exceptions,
                        // so it's in a `try-catch` block.
                        try {
                            // This `if` statement is what calls the `authenticate`
                            // method to see if the credentials match the database.
                            if (loginPane.getLoginService().authenticate(
                                    loginPane.getUserName().toLowerCase(),
                                    loginPane.getPassword(), null))
                            {
                                // If the credentials are in order, close the connection
                                // to the `logins` database, since they won't be needed anymore.
                                LoginConnectionManager.getInstance().close();
                                // Also close the login window; it won't be needed anymore, either.
                                Window window = SwingUtilities.windowForComponent(contentPanel);
                                window.dispose();

                                log.trace("Running Scheduling with access level of " + bean.getAccessLevel());
                                // And finally run the main `Scheduling.java` program,
                                // passing to it the user's access level.
                                String[] args = {Integer.toString(bean.getAccessLevel())};
                                Scheduling.main(args);
                            }
                            else
                            {
                                // If the login credentials fail, let the user know generically.
                                JOptionPane.showMessageDialog(null, "Incorrect username or password.", "Bad Username or Password", JOptionPane.ERROR_MESSAGE);
                            }
                        } catch (Exception e) {
                            log.error("Exception when hitting the 'OK' button.", e);
                        }

                        log.exit("OK button done.");
                    }
                });
                okButton.setActionCommand("OK"); // default.
                // Add the "OK" button the button pane.
                buttonPane.add(okButton); // default.
                getRootPane().setDefaultButton(okButton); // default.
            }
            {
                // Create the "Cancel" button plus its listener.
                JButton cancelButton = new JButton("Cancel");
                cancelButton.setActionCommand("Cancel"); // default.
                cancelButton.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {

                        log.entry("CANCEL button pressed. (LoginDialog)");

                        // Just close the connection to the database,
                        LoginConnectionManager.getInstance().close();
                        // and close the login window.
                        Window window = SwingUtilities.windowForComponent((Component) e.getSource());
                        window.dispose();

//                      System.exit(0);
                        log.exit("CANCEL button done.");
                    }
                });
                // Add the "Cancel" button to the button pane.
                buttonPane.add(cancelButton); // default.
            }
        }
    }

}

我假设LoginService在创建对话框时创建了一个线程。如果是这种情况,结束这个帖子的正确方法是什么?如果情况并非如此,那么发生了什么以及如何解决?

10/01/14 2:42 pm编辑:我已经采纳了dic19的建议和简化的事情。很大。我现在使用他刚刚坚持使用内置JXLoginDialog的建议创建了一个准系统对话框。它没有实际登录到我的主程序所需的所有方法,但是这是简化的对话框,应该足以看到在按下“取消”按钮后程序是否继续运行:

package info.chrismcgee.sky;

import info.chrismcgee.beans.Login;
import info.chrismcgee.login.PasswordHash;
import info.chrismcgee.tables.LoginManager;

import javax.swing.JDialog;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdesktop.swingx.JXLoginPane;
import org.jdesktop.swingx.auth.LoginService;

public class LoginDialog {

    static final Logger log = LogManager.getLogger(LoginDialogOriginal.class.getName());
    private static Login bean;
    private static LoginService loginService = new LoginService() {

        @Override
        public boolean authenticate(String name, char[] password, String server)
                throws Exception {

            log.entry("authenticate (LoginDialogOriginal)");

            bean = LoginManager.getRow(name);

            if (bean != null)
                return log.exit(PasswordHash.validatePassword(password, bean.getHashPass()));
            else
                return log.exit(false);
        }
    };

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        try {
            JXLoginPane loginPane = new JXLoginPane(loginService);
            JXLoginPane.JXLoginDialog dialog = new JXLoginPane.JXLoginDialog(new JDialog(), loginPane);
            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            dialog.setVisible(true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

我已删除评论以稍微收紧代码。

可悲的是,点击“取消”确实处理了对话框,程序仍在内存中,我必须像以前一样强制退出它。也许PasswordHash中有一个线程或其他内容?我或多或少只是从CrackStation's post解除了代码。

10/02/14 3:32 pm编辑:我已经尝试更新该简化程序,基于dic19的建议创建一个JFrame,然后根据成功显示或处理登录对话框。我担心在用户点击“取消”后,仍然更改程序。它仍然在记忆中,仍然需要强行退出。以下是使用dic19修改的更新代码:

package info.chrismcgee.sky;

import java.awt.event.WindowEvent;

import info.chrismcgee.beans.Login;
import info.chrismcgee.login.PasswordHash;
import info.chrismcgee.tables.LoginManager;

import javax.swing.JDialog;
import javax.swing.JFrame;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdesktop.swingx.JXLoginPane;
import org.jdesktop.swingx.auth.LoginService;

public class LoginDialog {

    static final Logger log = LogManager.getLogger(LoginDialogOriginal.class.getName());
    private static Login bean;
    private static LoginService loginService = new LoginService() {

        @Override
        public boolean authenticate(String name, char[] password, String server)
                throws Exception {

            log.entry("authenticate (LoginDialogOriginal)");

            bean = LoginManager.getRow(name);

            if (bean != null)
                return log.exit(PasswordHash.validatePassword(password, bean.getHashPass()));
            else
                return log.exit(false);
        }
    };

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        try {
            JFrame frame = new JFrame("Welcome!"); // A non-visible JFrame which acts as the parent frame.
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            JXLoginPane loginPane = new JXLoginPane(loginService);
            JXLoginPane.JXLoginDialog dialog = new JXLoginPane.JXLoginDialog(frame, loginPane);
            dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
            dialog.setVisible(true);

            if (dialog.getStatus() != JXLoginPane.Status.SUCCEEDED)
                frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
            else
                frame.setVisible(true);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

同样,我为了简洁起见删除了评论。知道为什么在单击“取消”后仍然导致程序在内存中挂起?

3 个答案:

答案 0 :(得分:3)

围绕JXLoginPane的登录/身份验证框架旨在异步执行LoginService和登录窗格之间的交互,以便在后台线程中完成登录过程时为视图设置动画。

被告知,尽管authenticate(username, password, server)方法是公开的,但你绝不应该明确地称之为。您应该使用startAuthentication(username, password, server)并使用适当的LoginListener进行验证。

okButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent evt) {
        ...
        loginPane.getLoginService().startAuthentication(
                                loginPane.getUserName().toLowerCase(),
                                loginPane.getPassword(), null);
        ...
    }
});

另一方面,取消按钮应通过调用cancelAuthentitacion()方法取消(惊讶)登录过程。否则,此过程仍在运行,直到完成(如果有的话)并且您的应用程序不会退出,这实际上是您正在经历的症状。所以,翻译成代码:

cancelButton.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        ...
        loginPane.getLoginService().cancelAuthentication();
        ...
    }
});

不要重新发明轮子......

请注意,您实际上没有必要做所有这些事情。您可以使用this answer中举例说明的JXLoginDialog,并避免几乎所有启动/取消实施。在一个片段中:

    LoginService loginService = new LoginServiceImp(); // your login service implementation

    JXLoginPane loginPane = new JXLoginPane(loginService);

    JXLoginPane.JXLoginDialog dialog = new JXLoginPane.JXLoginDialog(parentFrame, loginPane);
    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
    dialog.setVisible(true);

最后,如果您不喜欢JXLoginDialog,则可以通过其操作地图访问默认登录窗格的登录/取消操作:

    JXLoginPane loginPane = new JXLoginPane(loginService);

    ActionMap map = loginPane.getActionMap();
    Action loginAction = map.get(JXLoginPane.LOGIN_ACTION_COMMAND);
    Action cancelAction = map.get(JXLoginPane.CANCEL_LOGIN_ACTION_COMMAND);

    JButton okButton = new JButton(loginAction);
    JButton cancelButton = new JButton(cancelAction);

然后将这些按钮放在自定义对话框中的任意位置。 代码wiki中也解释了所有这一过程。


修改

在测试更新后的代码10分钟后(10月1日下午2:42编辑),我意识到了这一点:

    JXLoginPane.JXLoginDialog dialog = new JXLoginPane.JXLoginDialog(new JDialog(), loginPane);

现在你的申请没有结束,因为对话的父母仍然是#34;活着"。您应该同时处理两个对话框:登录对话框及其父对象。

然而,恕我直言,最好让一个不可见的JFrame作为登录对话框的父级,并在关闭后查看对话框的状态(就像我们使用JOptionPane一样):如果状态为SUCCEEDED,则显示框架,否则也处置框架:

    JFrame frame = new JFrame("Welcome!");
    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    ...
    JXLoginPane.JXLoginDialog dialog = new JXLoginPane.JXLoginDialog(frame, loginPane);
    dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
    dialog.setVisible(true);

    if (dialog.getStatus() != JXLoginPane.Status.SUCCEEDED) {
        frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
    } else {
        // make frame visible here
    }

答案 1 :(得分:1)

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  // To quit the whole GUI.

dialog.dispose(); // After user clicks cancel.

但由于您未明确创建取消按钮,因此无法直接调用dialog.dispose()。 你仍然可以。有3行额外的代码。 调用dialog.dispose(),在对话框中添加一个窗口监听器(扩展java.awt.event.WindowAdapter)。并覆盖windowClosing()方法。并在其正文中写下dialog.dispose()

摘录:

dialog.addWindowListener(

    new WindowAdapter() {

        @Override
        public void windowClosing(WindowEvent event) {

            dialog.dispose();
        }
    }
);  

答案 2 :(得分:0)

最终代码:

package info.chrismcgee.sky;

import info.chrismcgee.beans.Login;
import info.chrismcgee.dbutil.LoginConnectionManager;
import info.chrismcgee.login.PasswordHash;
import info.chrismcgee.tables.LoginManager;

import java.awt.EventQueue;
import java.awt.event.WindowEvent;
import java.text.ChoiceFormat;
import java.text.Format;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.ResourceBundle;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.UIManager;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdesktop.swingx.JXLoginPane;
import org.jdesktop.swingx.auth.LoginAdapter;
import org.jdesktop.swingx.auth.LoginEvent;
import org.jdesktop.swingx.auth.LoginListener;
import org.jdesktop.swingx.auth.LoginService;

public class LoginDialog {

    // FIELD LIST
    // For logging!
    static final Logger log = LogManager.getLogger(LoginDialogOriginal.class.getName());
    // A non-visible JFrame which acts as the parent frame.
    private JFrame frame;
    // A simple int to track number of failure attempts.
    private int failedAttemptsCount = 1;
    // User bean is a field because two inner methods need to access it,
    // and it would be cheaper to only create one bean.
    private Login bean;

    private String failureMessage(Locale currentLocale, int attemptNumber)
    {
        // Because the message text must be translated,
        // isolate it in a ResourceBundle.
        ResourceBundle bundle = ResourceBundle.getBundle("info.chrismcgee.components.ChoiceBundle", currentLocale);

        // Create a Message Formatter and set its locale.
        MessageFormat messageForm = new MessageFormat("");
        messageForm.setLocale(currentLocale);

        // For the upcoming ChoiceFormatter, set two arrays.
        // The first array denotes the range of numbers possible.
        double[] attemptLimits = {0, 1, 2};
        // The second array maps to the first, setting the variable names in the Choice.
        String[] attemptStrings = {
                bundle.getString("noAttempts"),
                bundle.getString("oneAttempt"),
                bundle.getString("multipleAttempts")
        };

        // Now finally create the ChoiceFormat, which maps the arrays together.
        ChoiceFormat choiceForm = new ChoiceFormat(attemptLimits, attemptStrings);

        // Retreive the message pattern from the bundle,
        // applying it to the MessageFormat object.
        String pattern = bundle.getString("pattern");
        messageForm.applyPattern(pattern);

        // Now assign the ChoiceFormat object to the MessageFormat object.
        Format[] formats = {choiceForm, NumberFormat.getInstance()};
        messageForm.setFormats(formats);

        // Now that everything is set up, let's prepare the message arguments.
        Object[] messageArguments = {new Integer(attemptNumber), new Integer(attemptNumber)};
        String result = messageForm.format(messageArguments);

        log.debug("Result of the failureMessage method is:");
        log.debug(result);

        // And return the message.
        return result;
    }

    /**
     * The main method that is called from main. It handles all of the work
     * of creating a login dialog and showing it.
     */
    private void showLoginDialog()
    {
        // Attempt to set the UI to match the current OS.
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception err) {
            log.error("Exception thrown when attempting to set the look and feel for the current OS.", err);
        }

        // Initialize the invisible Frame and set its default close behavior.
        frame = new JFrame("Welcome!");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // Login pane for the Login dialog.
        JXLoginPane loginPane = new JXLoginPane();
        // Login listener that tracks failure attempts, successes, and canceled events.
        LoginListener loginListener = new LoginAdapter() {
            // The error message to display to the user.
            String message;

            /* (non-Javadoc)
             * @see org.jdesktop.swingx.auth.LoginAdapter#loginCanceled(org.jdesktop.swingx.auth.LoginEvent)
             */
            @Override
            public void loginCanceled(LoginEvent source) {
                // Close the connection to the `logins` database, since it won't be needed anymore.
                LoginConnectionManager.getInstance().close();
                // And close out this dialog.
                frame.dispose();
            }

            /* (non-Javadoc)
             * @see org.jdesktop.swingx.auth.LoginAdapter#loginFailed(org.jdesktop.swingx.auth.LoginEvent)
             */
            @Override
            public void loginFailed(LoginEvent source) {
                if (failedAttemptsCount < 4)
                    message = failureMessage(Locale.US, (4 - failedAttemptsCount++));
                else
                {
                    // Close the connection to the `logins` database, since it won't be needed anymore.
                    LoginConnectionManager.getInstance().close();
                    frame.dispose();
                }

                loginPane.setErrorMessage(message);
            }

            /* (non-Javadoc)
             * @see org.jdesktop.swingx.auth.LoginAdapter#loginSucceeded(org.jdesktop.swingx.auth.LoginEvent)
             */
            @Override
            public void loginSucceeded(LoginEvent source) {
                // If the credentials are in order, close the connection
                // to the `logins` database, since it won't be needed anymore.
                LoginConnectionManager.getInstance().close();
                // Also close the login window; it won't be needed anymore, either.
                frame.dispose();

                log.trace("Running Scheduling with access level of " + bean.getAccessLevel());
                // And finally run the main `Scheduling.java` program,
                // passing to it the user's access level.
                String[] args = {Integer.toString(bean.getAccessLevel())};
                Scheduling.main(args);
            }
        };

        // The login service which will have the actual validation logic.
        LoginService loginService = new LoginService() {

            // `authenticate` *must* be overridden.
            // We will not be using the "server" string, however.
            @Override
            public boolean authenticate(String name, char[] password, String server)
                    throws Exception {

                log.entry("authenticate (LoginDialog)");

                // With the username entered by the user, get the user information
                // from the database and store it in a Login bean.
                bean = LoginManager.getRow(name);

                // If the user does not exist in the database, the bean will be null.
                if (bean != null)
                    // Use `PasswordHash`'s `validatePassword` static method
                    // to see if the entered password (after being hashed)
                    // matches the hashed password stored in the bean.
                    // Returns `true` if it matches, `false` if it doesn't.
                    return log.exit(PasswordHash.validatePassword(password, bean.getHashPass()));
                else
                    // If the user doesn't exist in the database, just return `false`,
                    // as if the password was wrong. This way, the user isn't alerted
                    // as to which of the two pieces of credentials is wrong.
                    return log.exit(false);
            }
        };

        loginService.addLoginListener(loginListener);
        loginPane.setLoginService(loginService);

        JXLoginPane.JXLoginDialog dialog = new JXLoginPane.JXLoginDialog(frame, loginPane);
        dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        dialog.setVisible(true);

        // If the loginPane was cancelled or closed or otherwise did not succeed,
        // then the main JFrame needs to be disposed to exit the application.
        if (dialog.getStatus() != JXLoginPane.Status.SUCCEEDED)
            frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
        else
            frame.dispose();
    }

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new LoginDialog().showLoginDialog();
            }

        });
    }

}

这有点长,是的,但那是因为我决定加倍努力并添加一个可以适应当前语言环境并处理复数形式的自定义失败消息。再次,非常感谢@ dic19和@Aditya帮助他们正常工作并正常关闭! ;〜)

如果您想在此处提出更好的解决方法,请随意添加评论。它对我自己和其他人都有帮助!