1 Qt中的QDialog

1.1 QDialog简介

对话框的概念:

  • 对话框是与用户进行简短交互的顶层窗口。
  • QDialog是Qt中所有对话框窗口的基类。
  • QDialog继承于QWidget是一种容器类型的组件。
    在这里插入图片描述
    QDialog的意义:
  • QDialog作为一种专用的交互窗口而存在。
  • QDialog不能作为子部件嵌入其它容器中。
  • QDialog是定制了窗口样式的特殊的QWidget。

注意:如果QDialog没有指定parent是不会一直处于最上层的,如果制定了parent则会一直处于最上层。

1.2 模态对话框和非模态对话框

模态对话框(QDialog::exec()):

  • 显示后无法与父窗口进行交互。
  • 是一种阻塞式的对话框调用方式。

非模态对话框(QDialog::show()):

  • 显示后独立存在可以同时与父窗口进行交互。
  • 是一种非阻塞式的对话框调用方式。

一般情况下:

  • 模态对话框用于必须依赖用户选择的场合(80%):
    • 消息提示、文件选择、打印设置等。
  • 非模态对话框用于特殊功能设置的场合(20%):
    • 查找操作、属性设置等。

小技巧:

  • 在栈上创建模态对话框是最简单常用的方式。
  • 一般情况下非模态对话框需要在堆上创建。
  • 通过QDialog::setModal函数可以创建混合特性的对话框(不会阻塞,但是必须做出选择)。
  • 非模态对话框需要指定Qt::WA_DeleteOnClose属性。

测试代码如下:
在这里插入图片描述
Dialog.h:

#ifndef DIALOG_H
#define DIALOG_H

#include <QtGui/QDialog>
#include <QPushButton>

class Dialog : public QDialog
{
    Q_OBJECT
protected:
    QPushButton ModalBtn;
    QPushButton NormalBtn;
    QPushButton MixedBtn;
protected slots:
    void ModalBtn_Clicked();
    void NormalBtn_Clicked();
    void MixedBtn_Clicked();
public:
    Dialog(QWidget *parent = 0);
    ~Dialog();
};

#endif // DIALOG_H

Dialog.cpp:

#include "Dialog.h"

#include <QDebug>

Dialog::Dialog(QWidget *parent) :
        QDialog(parent), ModalBtn(this), NormalBtn(this), MixedBtn(this)
{
    ModalBtn.setText("Modal Dialog");
    ModalBtn.move(20, 20);
    ModalBtn.resize(100, 30);

    NormalBtn.setText("Normal Dialog");
    NormalBtn.move(20, 70);
    NormalBtn.resize(100, 30);

    MixedBtn.setText("Mixed Dialog");
    MixedBtn.move(20, 120);
    MixedBtn.resize(100, 30);

    connect(&ModalBtn, SIGNAL(clicked()), this, SLOT(ModalBtn_Clicked()));
    connect(&NormalBtn, SIGNAL(clicked()), this, SLOT(NormalBtn_Clicked()));
    connect(&MixedBtn, SIGNAL(clicked()), this, SLOT(MixedBtn_Clicked()));

    resize(140, 170);
}

void Dialog::ModalBtn_Clicked()
{
    qDebug() << "ModalBtn_Clicked() Begin";

    QDialog dialog(this);

    dialog.exec();

    qDebug() << "ModalBtn_Clicked() End";
}

void Dialog::NormalBtn_Clicked()
{
    qDebug() << "NormalBtn_Clicked() Begin";

    QDialog* dialog = new QDialog(this);

    dialog->setAttribute(Qt::WA_DeleteOnClose);
    dialog->show();

    qDebug() << "NormalBtn_Clicked() End";
}

void Dialog::MixedBtn_Clicked()
{
    qDebug() << "MixedBtn_Clicked() Begin";

    QDialog* dialog = new QDialog(this);

    dialog->setAttribute(Qt::WA_DeleteOnClose);
    dialog->setModal(true);
    dialog->show();

    qDebug() << "MixedBtn_Clicked() End";
}

Dialog::~Dialog()
{
    qDebug() << "~Dialog()";
}

main.cpp:

#include <QtGui/QApplication>
#include <QWidget>
#include <QDialog>
#include <QDebug>
#include "Dialog.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog dlg;
   	dlg.show();
   	
    return a.exec();
}

1.3 对话框的返回值

只有模态对话框才有返回值的概念:

  • 模态对话框的返回值用于表示交互结果。
  • QDialog::exec()的返回值为交互结果:
    • void QDialog::done(int i)关闭对话框并将参数作为交互结果。
    • QDialog::Accepted:用户操作成功。
    • QDialog:Rejected:用户操作失败。

测试代码如下:
QDialog.h的代码和上面一样,就不贴了。

QDialog.cpp:

#include "Dialog.h"

#include <QDebug>

Dialog::Dialog(QWidget *parent) :
        QDialog(parent), ModalBtn(this), NormalBtn(this), MixedBtn(this)
{
    ModalBtn.setText("Modal Dialog");
    ModalBtn.move(20, 20);
    ModalBtn.resize(100, 30);

    NormalBtn.setText("Normal Dialog");
    NormalBtn.move(20, 70);
    NormalBtn.resize(100, 30);

    MixedBtn.setText("Mixed Dialog");
    MixedBtn.move(20, 120);
    MixedBtn.resize(100, 30);

    connect(&ModalBtn, SIGNAL(clicked()), this, SLOT(ModalBtn_Clicked()));
    connect(&NormalBtn, SIGNAL(clicked()), this, SLOT(NormalBtn_Clicked()));
    connect(&MixedBtn, SIGNAL(clicked()), this, SLOT(MixedBtn_Clicked()));

    resize(140, 170);
}

void Dialog::ModalBtn_Clicked()
{
    done(Accepted);
}

void Dialog::NormalBtn_Clicked()
{
    done(Rejected);
}

void Dialog::MixedBtn_Clicked()
{
    done(100);
}

Dialog::~Dialog()
{
    qDebug() << "~Dialog()";
}

main.cpp:

#include <QtGui/QApplication>
#include <QWidget>
#include <QDialog>
#include <QDebug>
#include "Dialog.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog dlg;
    int r = dlg.exec();

    if( r == QDialog::Accepted )
    {
        qDebug() << "Accepted";
    }
    else if( r == QDialog::Rejected )
    {
        qDebug() << "Rejected";
    }
    else
    {
        qDebug() << r;
    }
    
    return r;
}


2 登陆对话框实例分析

2.1 分析

登陆对话框是应用程序中的常用部件,思考:如何开发一个可以在不同项目间复用的登陆对话框?

登陆对话框需求分析:

  • 可复用软件部分。
  • 获取用户名和密码。

附加需求:

  • 随机验证码。

在这里插入图片描述

登陆对话框的设计与架构:
在这里插入图片描述
如何获取用户输入的用户名和密码:

  • 如何在两个不同的对话框之间传递数据?

在这里插入图片描述
对话框之间通过成员变量和成员函数传递数据:

  • 将用户数据保存在私有成员变量中。
  • 通过公有成员函数进行数据传递。

我们话可以进一步开发(这里未完成):

  • 检查用户名和密码是否为空:
    • 当用户名或密码为空时提示错误。
  • 随机验证码:
    • 当验证码输入错误时进行提示。
    • 验证码随机刷新。

2.2 代码实现

代码组织如下:
在这里插入图片描述

QLoginDialog.h:

#ifndef _QLOGINDIALOG_H_
#define _QLOGINDIALOG_H_

#include <QtGui/QDialog>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>

class QLoginDialog : public QDialog
{
    Q_OBJECT
private:
    QLabel UserLabel;
    QLabel PwdLabel;
    QLineEdit UserEdit;
    QLineEdit PwdEdit;
    QPushButton LoginBtn;
    QPushButton CancelBtn;
    QString m_user;
    QString m_pwd;
private slots:
    void LoginBtn_Clicked();
    void CancelBtn_Clicked();
public:
    QLoginDialog(QWidget *parent = 0);
    QString getUser();
    QString getPwd();
    ~QLoginDialog();
};


#endif

QLoginDialog.cpp:

#include "QLoginDialog.h"

QLoginDialog::QLoginDialog(QWidget *parent)
    : QDialog(parent, Qt::WindowCloseButtonHint), m_lblName(this), m_lblPwd(this), m_editName(this), m_editPwd(this),
      m_btnCancle(this), m_btnLogin(this)
{
    setWindowTitle("Login");
    setFixedSize(285, 170);

    m_lblName.setText("User ID:");
    m_lblName.resize(80, 25);
    m_lblName.move(10, 30);

    m_lblPwd.setText("Password:");
    m_lblPwd.resize(80, 25);
    m_lblPwd.move(10, 65);

    m_editName.move(95, 30);
    m_editName.resize(180, 25);

    m_editPwd.move(95, 65);
    m_editPwd.resize(180, 25);
    m_editPwd.setEchoMode(QLineEdit::Password);

    m_btnCancle.setText("Cancle");
    m_btnCancle.resize(85, 30);
    m_btnCancle.move(95, 110);

    m_btnLogin.setText("Login");
    m_btnLogin.resize(85, 30);
    m_btnLogin.move(190, 110);

    connect(&m_btnCancle, SIGNAL(clicked()), this, SLOT(onBtnCancleClicked()));
    connect(&m_btnLogin, SIGNAL(clicked()), this, SLOT(onBtnLoginClicked()));
}

void QLoginDialog::onBtnCancleClicked()
{
    m_name = "";
    m_pwd = "";

    done(Rejected);
}

void QLoginDialog::onBtnLoginClicked()
{
    m_name = m_editName.text().trimmed();
    m_pwd = m_editPwd.text();

    done(Accepted);
}

QString QLoginDialog::getName()
{
    return m_name;
}

QString QLoginDialog::getPwd()
{
    return m_pwd;
}

QLoginDialog::~QLoginDialog()
{
    
}

Widget.h:

#ifndef _WIDGET_H_
#define _WIDGET_H_

#include <QtGui/QWidget>
#include <QPushButton>

class Widget : public QWidget
{
    Q_OBJECT
private:
    QPushButton TestBtn;
private slots:
    void TestBtn_Clicked();
public:
    Widget(QWidget *parent = 0);
    ~Widget();
};

#endif

Widget.cpp:

#include "Widget.h"
#include "QLoginDialog.h"
#include "QDebug"

Widget::Widget(QWidget *parent) :
    QWidget(parent), m_btnTestLoginDialog(this)
{
    m_btnTestLoginDialog.setText("Test login dialog");

    setFixedSize(300, 200);

    connect(&m_btnTestLoginDialog, SIGNAL(clicked()), this, SLOT(onBtnTest()));
}


void Widget::onBtnTest()
{
    QLoginDialog loginDlg(this);

    if (loginDlg.exec() == QDialog::Accepted)
    {
        qDebug() << loginDlg.getName();
        qDebug() << loginDlg.getPwd();
    }
}

main.cpp:

#include <QtGui/QApplication>
#include "QLoginDialog.h"
#include "Widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    
    return a.exec();
}

2.3 登录对话框的改进

分析下如上对话框存在的问题:

  • 没有实现验证码功能,容易被恶意程序攻击,盗取用户名和密码。

改进思路-验证码机制:

  1. 随机产生验证码。
  2. 用户后填写。
  3. 判断用户识别的正确性。

需求:

  • 验证码必须能够有效避开恶意程序的识别!

关于验证码和恶意程序:

  • 自动测试原理:
    • 利用一些特殊的系统函数能够通过代码控制程序,从而模拟用户操作。
  • 恶意程序:
    • 使用自动测试原理对目标程序进行控制,从而盗取信息或进行攻击。
  • 验证码:
    • 随机产生,用户容易识别,程序难以识别,从而有效避免恶意攻击。

需要注意的问题:

  • 验证码必须动态随机产生。
  • 验证码的显示避开使用标准组件(标签、文本框等)。
  • 验证码应该附带足够多的障碍增加程序识别难度。

解决方案:

  1. 随机产生目标验证码。
  2. 将验证码直接绘制于登录对话框。
  3. 验证码中的字符颜色随机改变。
  4. 在验证码区域随机绘制噪点。

关于随机数:

  • 计算机无法产生真正意义上的随机数。
  • 计算机只能模拟随机数序列(伪随机数)。
  • 随机种子决定每次产生的随机序列是否相同。

在这里插入图片描述
随机产生验证码:
在这里插入图片描述
验证码绘制:
在这里插入图片描述
改进后的代码如下:

QLoginDialog.h:

#ifndef _QLOGINDIALOG_H_
#define _QLOGINDIALOG_H_

#include <QtGui/QDialog>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QTimer>

class QLoginDialog : public QDialog
{
    Q_OBJECT
private:
    QLabel UserLabel;
    QLabel PwdLabel;
    QLabel CaptLabel;
    QLineEdit UserEdit;
    QLineEdit PwdEdit;
    QLineEdit CaptEdit;
    QPushButton LoginBtn;
    QPushButton CancelBtn;
    QString m_user;
    QString m_pwd;
    QString m_captcha;
    Qt::GlobalColor* m_colors;
    QTimer m_timer;
private slots:
    void LoginBtn_Clicked();
    void CancelBtn_Clicked();
    void Timer_Timeout();
protected:
    void paintEvent(QPaintEvent *);
    QString getCaptcha();
    Qt::GlobalColor* getColors();
public:
    QLoginDialog(QWidget *parent = 0);
    QString getUser();
    QString getPwd();
    ~QLoginDialog();
};


#endif

QLoginDialog.cpp:

#include "QLoginDialog.h"
#include <QPainter>
#include <QTime>
#include <QDebug>
#include <QMessageBox>

QLoginDialog::QLoginDialog(QWidget* parent) : QDialog(parent, Qt::WindowCloseButtonHint),
    UserLabel(this), PwdLabel(this), CaptLabel(this),
    UserEdit(this), PwdEdit(this), CaptEdit(this),
    LoginBtn(this), CancelBtn(this)
{
    UserLabel.setText("User ID:");
    UserLabel.move(20, 30);
    UserLabel.resize(60, 25);

    UserEdit.move(85, 30);
    UserEdit.resize(180, 25);

    PwdLabel.setText("Password:");
    PwdLabel.move(20, 65);
    PwdLabel.resize(60,25);

    PwdEdit.move(85, 65);
    PwdEdit.resize(180, 25);
    PwdEdit.setEchoMode(QLineEdit::Password);

    CaptLabel.setText("Captcha:");
    CaptLabel.move(20, 100);
    CaptLabel.resize(60, 25);

    CaptEdit.move(85, 100);
    CaptEdit.resize(85, 25);

    CancelBtn.setText("Cancel");
    CancelBtn.move(85, 145);
    CancelBtn.resize(85, 30);

    LoginBtn.setText("Login");
    LoginBtn.move(180, 145);
    LoginBtn.resize(85, 30);

    m_timer.setParent(this);

    setWindowTitle("Login");
    setFixedSize(285, 205);

    connect(&m_timer, SIGNAL(timeout()), this, SLOT(Timer_Timeout()));
    connect(&LoginBtn, SIGNAL(clicked()), this, SLOT(LoginBtn_Clicked()));
    connect(&CancelBtn, SIGNAL(clicked()), this, SLOT(CancelBtn_Clicked()));

    qsrand(QTime::currentTime().second() * 1000 + QTime::currentTime().msec());

    m_captcha = getCaptcha();
    m_colors = getColors();

    m_timer.start(100);
}

void QLoginDialog::LoginBtn_Clicked()
{
    qDebug() << "LoginBtn_Clicked() Begin";

    QString captcha = CaptEdit.text().replace(" ", "");

    if( m_captcha.toLower() == captcha.toLower() )
    {
        m_user = UserEdit.text().trimmed();
        m_pwd = PwdEdit.text();

        if( m_user == "" )
        {
            QMessageBox::information(this, "Info", "User ID can NOT be empty!");
        }
        else if( m_pwd == "" )
        {
            QMessageBox::information(this, "Info", "Password can NOT be empty!");
        }
        else
        {
            done(Accepted);
        }
    }
    else
    {
        QMessageBox::critical(this, "Error", "The captcha is NOT matched!");

        m_captcha = getCaptcha();

        CaptEdit.selectAll();
    }

    qDebug() << "LoginBtn_Clicked() End";
}

void QLoginDialog::CancelBtn_Clicked()
{
    qDebug() << "CancelBtn_Clicked() Begin";

    done(Rejected);

    qDebug() << "CancelBtn_Clicked() End";
}

QString QLoginDialog::getUser()
{
    return m_user;
}

QString QLoginDialog::getPwd()
{
    return m_pwd;
}

void QLoginDialog::Timer_Timeout()
{
    m_colors = getColors();

    update();
}

void QLoginDialog::paintEvent(QPaintEvent *)
{
    QPainter painter(this);

    painter.fillRect(180, 100, 84, 24, Qt::white);

    painter.setFont(QFont("Comic Sans MS", 12));

    for(int i=0; i<150; i++)
    {
        painter.setPen(m_colors[i%4]);
        painter.drawPoint(180 + qrand() % 84, 100 + qrand() % 24);
    }

    for(int i=0; i<4; i++)
    {
        painter.setPen(m_colors[i]);
        painter.drawText(180 + 20 * i, 100, 20, 24, Qt::AlignCenter, QString(m_captcha[i]));
    }
}

QString QLoginDialog::getCaptcha()
{
    QString ret = "";

    for(int i=0; i<4; i++)
    {
        int c = (qrand() % 2) ? 'a' : 'A';

        ret += static_cast<QChar>(c + qrand() % 26);
    }

    return ret;
}

Qt::GlobalColor* QLoginDialog::getColors()
{
    static Qt::GlobalColor colors[4];

    for(int i=0; i<4; i++)
    {
        colors[i] = static_cast<Qt::GlobalColor>(2 + qrand() % 16);
    }

    return colors;
}

QLoginDialog::~QLoginDialog()
{

}


Widget.h:

#ifndef _WIDGET_H_
#define _WIDGET_H_

#include <QtGui/QWidget>
#include <QPushButton>

class Widget : public QWidget
{
    Q_OBJECT
private:
    QPushButton TestBtn;
private slots:
    void TestBtn_Clicked();
public:
    Widget(QWidget *parent = 0);
    ~Widget();
};

#endif

Widget.cpp:

#include "Widget.h"
#include "QLoginDialog.h"

#include <QDebug>

Widget::Widget(QWidget *parent) : QWidget(parent), TestBtn(this)
{
    TestBtn.setText("Test Login Dialog");

    setFixedSize(200, 50);

    connect(&TestBtn, SIGNAL(clicked()), this, SLOT(TestBtn_Clicked()));
}

void Widget::TestBtn_Clicked()
{
    QLoginDialog dlg;

    if( dlg.exec() == QDialog::Accepted )
    {
        qDebug() << "User: " + dlg.getUser();
        qDebug() << "Pwd: " + dlg.getPwd();
    }
}

Widget::~Widget()
{
    
}

main.cpp:

#include <QtGui/QApplication>
#include "widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    
    return a.exec();
}


3 Qt种的标准对话框

标准对话框:

  • Qt为开发者提供了一些可复用的对话框类型。
  • Qt提供的可复用对话框全部继承自QDialog类。
    在这里插入图片描述
    Qt中的标准对话框遵循相同的使用方式:
    在这里插入图片描述

参考资料:

  1. QT实验分析教程
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐