QT 實(shí)現(xiàn)隨機(jī)驗(yàn)證碼功能
1.界面實(shí)現(xiàn)效果
以下是具體的項(xiàng)目需要用到的效果展示,用于驗(yàn)證字母。
2.簡介
自定義CaptchaMovableLabel,繼承自QLabel類:
中間的4個(gè)字母,就是CaptchaMovableLabel類來實(shí)例化的對象。
主要功能如下:
1.顯示字母;
2.實(shí)現(xiàn)了鼠標(biāo)移動事件,使字母可拖動;
3.存在定時(shí)器,不斷改變字母顏色;
4.繪制字母時(shí),可旋轉(zhuǎn)一定角度;
#ifndef CAPTCHAMOVABLELABEL_H #define CAPTCHAMOVABLELABEL_H #include <QLabel> #include <QTime> #include <QPropertyAnimation> #include <QDebug> #include <QMouseEvent> #include <QPainter> #include <QPainterPath> #include <QApplication> #include <QGraphicsDropShadowEffect> #include <cmath> #include <QTimer> #define CAPTCHA_REFRESH_DURATION 300 // 刷新的動畫時(shí)長 #define CAPTCHA_CHAR_ANGLE_MAX 20 // 最大旋轉(zhuǎn)角:20° #define CAPTCHA_SHADOW_BLUR_MAX 80 // 最大的陰影模糊半徑 class CaptchaMovableLabel : public QLabel { Q_OBJECT Q_PROPERTY(int refreshProgress READ getRefreshProgress WRITE setRefreshProgress) Q_PROPERTY(int pressProgress READ getPressProgress WRITE setPressProgress) public: CaptchaMovableLabel(QWidget* parent); void setAngle(int angle); void setColor(QColor color); void setText(QString ch); void startRefreshAnimation(); void setMoveBorder(QRect rect); QString text(); protected: void paintEvent(QPaintEvent *) override; void mousePressEvent(QMouseEvent *ev) override; void mouseMoveEvent(QMouseEvent *ev) override; void mouseReleaseEvent(QMouseEvent *ev) override; private: void startPressAnimation(int end); void setRefreshProgress(int g); int getRefreshProgress(); inline bool isNoAni(); void setPressProgress(int g); int getPressProgress(); private slots: //設(shè)置rgb顏色 void slotMovePos(); private: QPoint press_pos; bool dragging = false; bool moved = false; QGraphicsDropShadowEffect effect; QString ch; QColor color; int angle = 0; int refreshProgress = 100; QString prevCh; QColor prevColor; int prevAngle = 0; QString prevChar; int pressProgress = 0; bool inited = false; QTimer movingTimer; int moveR, moveG, moveB; }; #endif // CAPTCHAMOVABLELABEL_H #include "captchamovablelabel.h" CaptchaMovableLabel::CaptchaMovableLabel(QWidget *parent) : QLabel(parent) { effect.setOffset(0, 0); // effect.setBlurRadius(8); setGraphicsEffect(&effect); movingTimer.setInterval(30); movingTimer.setSingleShot(false); connect(&movingTimer, SIGNAL(timeout()), this, SLOT(slotMovePos())); } void CaptchaMovableLabel::setAngle(int angle) { this->prevAngle = this->angle; this->angle = angle; } void CaptchaMovableLabel::setColor(QColor color) { this->prevColor = this->color; this->color = color; moveR = qrand() % 5; moveG = qrand() % 5; moveB = qrand() % 5; movingTimer.start(); } void CaptchaMovableLabel::setText(QString text) { this->prevCh = this->ch; this->ch = text; // 計(jì)算合適的高度 QFontMetrics fm(this->font()); double w = fm.horizontalAdvance(text)+2; double h = fm.height(); const double PI = 3.141592; int xieHalf = sqrt(w*w/4+h*h/4); // 斜邊的一半 double a = atan(w/h) + CAPTCHA_CHAR_ANGLE_MAX * PI / 180; // 最大的傾斜角度 int w2 = xieHalf * sin(a) * 2; a = atan(w/h) - CAPTCHA_CHAR_ANGLE_MAX * PI / 180; int h2 = xieHalf * cos(a) * 2; resize(w2, h2); } void CaptchaMovableLabel::startRefreshAnimation() { if (!inited) // 第一次,直接顯示,取消動畫 { inited = true; return ; } QPropertyAnimation* ani = new QPropertyAnimation(this, "refreshProgress"); ani->setStartValue(0); ani->setEndValue(100); ani->setDuration(qrand() % (CAPTCHA_REFRESH_DURATION / 3) + CAPTCHA_REFRESH_DURATION / 3); ani->start(); connect(ani, SIGNAL(finished()), ani, SLOT(deleteLater())); connect(ani, SIGNAL(finished()), &movingTimer, SLOT(start())); } QString CaptchaMovableLabel::text() { return ch; } void CaptchaMovableLabel::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setFont(this->font()); painter.setRenderHint(QPainter::SmoothPixmapTransform); int w2 = width()/2, h2 = height()/2; painter.translate(w2, h2); // 平移到中心,繞中心點(diǎn)旋轉(zhuǎn) if (isNoAni()) // 不在動畫中,直接繪制 { painter.setPen(color); painter.rotate(angle); painter.drawText(QRect(-w2, -h2, width(), height()), Qt::AlignCenter, ch); return ; } // 動畫里面,前后漸變替換 double newProp = refreshProgress / 100.0; double oldProp = 1.0 - newProp; double a = prevAngle * oldProp + angle * newProp + 0.5; painter.save(); painter.rotate(a); QColor c = prevColor; c.setAlpha(c.alpha() * oldProp); // 舊文字漸漸消失 painter.setPen(c); painter.drawText(QRect(-w2,-h2,width(),height()), Qt::AlignCenter, prevCh); c = this->color; c.setAlpha(c.alpha() * newProp); // 新文字漸漸顯示 painter.setPen(c); painter.drawText(QRect(-w2, -h2, width(), height()), Qt::AlignCenter, ch); painter.restore(); } void CaptchaMovableLabel::mousePressEvent(QMouseEvent *ev) { if (ev->button() == Qt::LeftButton) { // 開始拖拽 press_pos = ev->pos(); dragging = true; moved = false; this->raise(); movingTimer.stop(); startPressAnimation(200); return ev->accept(); } QLabel::mousePressEvent(ev); } void CaptchaMovableLabel::mouseMoveEvent(QMouseEvent *ev) { if (dragging && ev->buttons() & Qt::LeftButton) { if (!moved && (ev->pos() - press_pos).manhattanLength() < QApplication::startDragDistance()) { return QLabel::mouseMoveEvent(ev); // 還沒到這時(shí)候 } moved = true; move(this->pos() + ev->pos() - press_pos); ev->accept(); return ; } QLabel::mouseMoveEvent(ev); } void CaptchaMovableLabel::mouseReleaseEvent(QMouseEvent *ev) { if (dragging) { // 結(jié)束拖拽 dragging = false; movingTimer.start(); startPressAnimation(0); } if (moved) return ev->accept(); QLabel::mouseReleaseEvent(ev); } void CaptchaMovableLabel::startPressAnimation(int end) { QPropertyAnimation* ani = new QPropertyAnimation(this, "pressProgress"); ani->setStartValue(pressProgress); ani->setEndValue(end); ani->setDuration(CAPTCHA_REFRESH_DURATION << 1); ani->start(); connect(ani, SIGNAL(finished()), ani, SLOT(deleteLater())); } void CaptchaMovableLabel::setRefreshProgress(int g) { this->refreshProgress = g; update(); } int CaptchaMovableLabel::getRefreshProgress() { return refreshProgress; } bool CaptchaMovableLabel::isNoAni() { return refreshProgress == 100; } void CaptchaMovableLabel::setPressProgress(int g) { this->pressProgress = g; double off = g / 100; effect.setBlurRadius(g / 20.0); effect.setOffset(-off, off); } int CaptchaMovableLabel::getPressProgress() { return pressProgress; } void CaptchaMovableLabel::slotMovePos() { if (refreshProgress < 100) return ; int val = color.red() + moveR; if ( val > 255) { val = 255; moveR = - qrand() % 5; } else if (val < 0) { val = 0; moveR = - qrand() % 5; } color.setRed(val); val = color.green() + moveG; if ( val > 255) { val = 255; moveG = - qrand() % 5; } else if (val < 0) { val = 0; moveG = - qrand() % 5; } color.setGreen(val); val = color.blue() + moveB; if ( val > 255) { val = 255; moveB = - qrand() % 5; } else if (val < 0) { val = 0; moveB = - qrand() % 5; } color.setBlue(val); }
自定義CaptchaLabel類,此類繼承QWidget用于存在上面的4個(gè)字母。
主要功能如下:
1.畫噪音點(diǎn),背景上繪制無數(shù)個(gè)隨機(jī)顏色的點(diǎn);
2.畫噪音線,這個(gè)線條是動態(tài)的,隨時(shí)間更改起漸變顏色,線條位置;
3.鼠標(biāo)點(diǎn)擊,生成隨機(jī)字母。
#ifndef CAPTCHALABEL_H #define CAPTCHALABEL_H #include "captchamovablelabel.h" #include <QTimer> #define CAPTCHAR_COUNT 4 // 驗(yàn)證碼字符數(shù)量 class CaptchaLabel : public QWidget { Q_OBJECT Q_PROPERTY(int refreshProgress READ getRefreshProgress WRITE setRefreshProgress) public: CaptchaLabel(QWidget* parent = nullptr); void refresh(); bool match(QString input); private: void initView(); void initData(); void setRefreshProgress(int g); int getRefreshProgress(); bool isNoAni(); private slots: void moveNoiseLines(); protected: void paintEvent(QPaintEvent* ) override; void mouseReleaseEvent(QMouseEvent *event) override; private: CaptchaMovableLabel* charLabels[CAPTCHAR_COUNT]; // Label控件 QList<QPoint> noisePoints; // 噪音點(diǎn) QList<QColor> pointColors; // 點(diǎn)的顏色 QList<QPointF> lineStarts; // 噪音線起始點(diǎn) QList<QPointF> lineEnds; // 噪音先結(jié)束點(diǎn) QList<QPointF> startsV; // 起始點(diǎn)的移動速度(帶方向) QList<QPointF> endsV; // 結(jié)束點(diǎn)的速度(帶方向) QList<QColor> lineColor1s; // 線的漸變色1 QList<QColor> lineColor2s; // 線的漸變色2 QList<int> lineWidths; QTimer movingTimer; int refreshProgress = 100; QList<QPoint> noisePoints2; // 新的位置 int autoRefreshMax = 2; // match錯(cuò)誤幾次后就自動刷新 int matchFailCount = 0; // match錯(cuò)誤次數(shù) int matchFailAndRefreshCount = 0; // 失敗且導(dǎo)致刷新的次數(shù),強(qiáng)行刷新 }; #endif // CAPTCHALABEL_H #include "captchalabel.h" CaptchaLabel::CaptchaLabel(QWidget *parent) : QWidget(parent) { initView(); // 這里延遲,等待布局結(jié)束 QTimer::singleShot(0, [=]{ initData(); refresh(); }); } void CaptchaLabel::initView() { // 初始化控件 for (int i = 0; i < CAPTCHAR_COUNT; i++) { charLabels[i] = new CaptchaMovableLabel(this); charLabels[i]->move(0, 0); } // 初始化時(shí)鐘 movingTimer.setInterval(30); movingTimer.setSingleShot(false); movingTimer.start(); connect(&movingTimer, SIGNAL(timeout()), this, SLOT(moveNoiseLines())); } void CaptchaLabel::initData() { // 初始化噪音線 auto getRandomColor = [=]{ return QColor(qrand() % 255, qrand() % 255, qrand() % 255); }; int w = width(), h = height(); int count = 20/*w * h / 400*/; int penW = qMin(w, h) / 15; for (int i = 0; i < count; i++) { lineStarts.append(QPointF(qrand() % w, qrand() % h)); lineEnds.append(QPointF(qrand() % w, qrand() % h)); startsV.append(QPointF((qrand() % 30 - 15) / 10.0, (qrand() % 30 - 15) / 10.0)); endsV.append(QPointF((qrand() % 30 - 15) / 10.0, (qrand() % 30 - 15) / 10.0)); lineWidths.append(qrand() % penW + 1); lineColor1s.append(getRandomColor()); lineColor2s.append(getRandomColor()); } } void CaptchaLabel::setRefreshProgress(int g) { this->refreshProgress = g; update(); } int CaptchaLabel::getRefreshProgress() { return refreshProgress; } bool CaptchaLabel::isNoAni() { return refreshProgress == 100; } void CaptchaLabel::moveNoiseLines() { int w = width(), h = height(); double vBase = 100.0; // 大概最快要3秒鐘走完 for (int i = 0; i < lineStarts.size(); i++) { QPointF& pos = lineStarts[i]; pos += startsV.at(i); if (pos.x() < 0) startsV[i].setX(qrand() % w / vBase); else if (pos.x() > w) startsV[i].setX(- qrand() % w / vBase); if (pos.y() < 0) startsV[i].setY(qrand() % h / vBase); else if (pos.y() > h) startsV[i].setY(- qrand() % h / vBase); } for (int i = 0; i < lineEnds.size(); i++) { QPointF& pos = lineEnds[i]; pos += endsV.at(i); if (pos.x() < 0) endsV[i].setX(qrand() % w / vBase); else if (pos.x() > w) endsV[i].setX(- qrand() % w / vBase); if (pos.y() < 0) endsV[i].setY(qrand() % h / vBase); else if (pos.y() > h) endsV[i].setY(- qrand() % h / vBase); } update(); } void CaptchaLabel::refresh() { int width = this->width(); int height = this->height(); // 清空全部內(nèi)容 for (int i = 0; i < CAPTCHAR_COUNT; i++) charLabels[i]->hide(); refreshProgress = -1; update(); // 獲取背景底色 QPixmap rend(this->size()); render(&rend); QColor bgColor = rend.toImage().pixelColor(width/2, height/2); int br = bgColor.red(), bg = bgColor.green(), bb = bgColor.blue(); // 開始隨機(jī)生成 const int border = 10; int leftest = width / border; int topest = height / border; int wid = width - leftest * 2; int hei = height - topest * 2; for (int i = 0; i < CAPTCHAR_COUNT; i++) { auto label = charLabels[i]; // 隨機(jī)大小 QFont font; font.setPointSize( qrand() % 8 + 22 ); label->setFont(font); // 隨機(jī)旋轉(zhuǎn) label->setAngle( qrand() % (CAPTCHA_CHAR_ANGLE_MAX*2) - CAPTCHA_CHAR_ANGLE_MAX); // 生成隨機(jī)字符 const QString pool = "QWERTYUIOPASDFGHJKLZXCVBNM"; QChar rc = pool.at(qrand() % pool.size()); // 此時(shí)會調(diào)整大小,setText必須在setFont之后 label->setText(rc); // 生成隨機(jī)位置(排除邊緣) int left = leftest + wid * i / CAPTCHAR_COUNT; int right = leftest + wid * (i+1) / CAPTCHAR_COUNT - label->width(); int x = qrand() % qMax(right-left, 1) + left; int y = qrand() % qMax(hei - label->height(), 1) + topest; label->show(); // 之前是hide狀態(tài) QPropertyAnimation * ani = new QPropertyAnimation(label, "pos"); ani->setStartValue(label->pos()); ani->setEndValue(QPoint(x, y)); ani->setDuration(qrand() % (CAPTCHA_REFRESH_DURATION/2) + CAPTCHA_REFRESH_DURATION/2); ani->setEasingCurve(QEasingCurve::OutQuart); ani->start(); connect(ani, SIGNAL(finished()), ani, SLOT(deleteLater())); // 生成隨機(jī)顏色,且必須和背景顏色有區(qū)分度 QColor color; while (true) { int r = qrand() % 255; int g = qrand() % 255; int b = qrand() % 255; if (abs(r-br) + abs(g-bg) + abs(b-bb) > 383) { color = QColor(r, g, b); break; } } label->setColor(color); label->startRefreshAnimation(); } // 生成噪音點(diǎn) int count = wid * hei / border; // 點(diǎn)的數(shù)量 if (noisePoints.size() == 0) // 第一次 { for (int i = 0; i < count; i++) { int x = qrand() % width; int y = qrand() % height; noisePoints.append(QPoint(x, y / 2)); noisePoints2.append(QPoint(x, y)); pointColors.append(QColor(qrand() % 255, qrand() % 255, qrand() % 255)); } } else { noisePoints = noisePoints2; count = noisePoints.size(); noisePoints2.clear(); for (int i = 0; i < count; i++) { noisePoints2.append(QPoint(qrand() % width, qrand() % height)); } } // 生成噪音線 QPropertyAnimation* ani = new QPropertyAnimation(this, "refreshProgress"); ani->setStartValue(0); ani->setEndValue(100); ani->setDuration(qrand() % (CAPTCHA_REFRESH_DURATION) + CAPTCHA_REFRESH_DURATION); ani->start(); connect(ani, SIGNAL(finished()), ani, SLOT(deleteLater())); } /** * 判斷能否匹配 */ bool CaptchaLabel::match(QString input) { // 根據(jù)label的位置排序 std::sort(charLabels, charLabels+CAPTCHAR_COUNT, [=](QLabel* a, QLabel* b){ if (a->pos().x() == b->pos().x()) return a->pos().y() < b->pos().y(); return a->pos().x() < b->pos().x(); }); // 按順序組合成新的字符串 QString captcha; for (int i = 0; i < CAPTCHAR_COUNT; i++) captcha += charLabels[i]->text(); // 進(jìn)行比較 if (input.toUpper() == captcha) return true; // 記錄失敗 matchFailCount++; if (matchFailCount >= autoRefreshMax // 達(dá)到刷新的次數(shù) || matchFailAndRefreshCount > 2) // 多次錯(cuò)誤導(dǎo)致刷新 { refresh(); matchFailAndRefreshCount++; matchFailCount = 0; } return false; } void CaptchaLabel::paintEvent(QPaintEvent *) { QPainter painter(this); if (refreshProgress == -1) // 不畫,可能需要獲取背景顏色 return ; // 畫噪音點(diǎn) if (isNoAni()) { // 顯示隨機(jī)的點(diǎn) for (int i = 0; i < noisePoints2.size(); i++) { painter.setPen(pointColors.at(i)); painter.drawPoint(noisePoints2.at(i)); } } else { // 動畫過程中的點(diǎn)的移動 double newProp = refreshProgress / 100.0; double oldProp = 1.0 - newProp; int count = qMin(noisePoints.size(), noisePoints2.size()); for (int i = 0; i < count; i++) { QPoint pt1 = noisePoints.at(i); QPoint pt2 = noisePoints2.at(i); QPoint pt( pt1.x() * oldProp + pt2.x() * newProp, pt1.y() * oldProp + pt2.y() * newProp ); painter.setPen(pointColors.at(i)); painter.drawPoint(pt); } } // 畫噪音線 painter.setRenderHint(QPainter::Antialiasing); for (int i = 0; i < lineStarts.size(); i++) { QLinearGradient grad(lineStarts.at(i), lineEnds.at(i)); grad.setColorAt(0, lineColor1s.at(i)); grad.setColorAt(1, lineColor2s.at(i)); painter.setPen(QPen(grad, lineWidths.at(i))); painter.drawLine(lineStarts.at(i), lineEnds.at(i)); } } void CaptchaLabel::mouseReleaseEvent(QMouseEvent *event) { if (QRect(0,0,width(),height()).contains(event->pos())) refresh(); QWidget::mouseReleaseEvent(event); }
3.使用
新建MainWindow,拖動一個(gè)QWidget,提升為CaptchaLabel即可。
到此這篇關(guān)于QT 實(shí)現(xiàn)隨機(jī)驗(yàn)證碼的文章就介紹到這了,更多相關(guān)QT 隨機(jī)驗(yàn)證碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++構(gòu)造函數(shù)初始化列表的實(shí)現(xiàn)詳解
構(gòu)造函數(shù)主要作用在于創(chuàng)建對象時(shí)為對象的成員屬性賦值,構(gòu)造函數(shù)由編譯器自動調(diào)用,無須手動調(diào)用;析構(gòu)函數(shù)主要作用在于對象銷毀前系統(tǒng)自動調(diào)用,執(zhí)行一 些清理工作2022-09-09C語言數(shù)據(jù)結(jié)構(gòu)鏈表隊(duì)列的實(shí)現(xiàn)
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)鏈表隊(duì)列的實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-07-07C++中function包裝器的應(yīng)用實(shí)例詳解
這篇文章主要介紹了C++中function包裝器的相關(guān)資料,std::function是C++11引入的一個(gè)模板類,用于封裝任何可調(diào)用對象,使得函數(shù)能夠像對象一樣傳遞、存儲和調(diào)用,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-12-12C語言實(shí)現(xiàn)修改文本文件中特定行的實(shí)現(xiàn)代碼
最近由于項(xiàng)目需要實(shí)現(xiàn)修改文件的功能,所以,博主認(rèn)真查閱了一些資料,但是,很遺憾,并沒有太多的收獲2013-06-06