使用QT实现滑块验证程序,原理是画个图片,然后在图片上画个空白区域,再画个滑块图片。
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
void initForm();
void drawPicture();
private slots:
void onUpdateWidget();
void onSliderValueChanged(int value);
void onSliderReleased();
void onUpdatePixmap();
protected:
bool eventFilter(QObject *watched, QEvent *event);
void paintEvent(QPaintEvent *event);
private:
Ui::Widget *ui;
QString m_pixmap;
QPoint m_offsetPoint;
int m_value;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPaintEvent>
#include <QPainter>
#include <QPainterPath>
#include <QDebug>
#include <QMessageBox>
#include <QTimer>
#include <QSlider>
#include <QRandomGenerator>
const int squarewidth = 46;
const int squareradius = 20;
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setFixedHeight(600);
this->setFixedWidth(800);
m_value = 0;
m_offsetPoint = QPoint(0, 0);
ui->widget->installEventFilter(this);
this->initForm();
}
Widget::~Widget()
{
delete ui;
}
void Widget::initForm()
{
QTimer::singleShot(10, this, SLOT(onUpdateWidget()));
connect(ui->horizontalSlider, &QSlider::valueChanged, this, &Widget::onSliderValueChanged);
connect(ui->horizontalSlider, &QSlider::sliderReleased, this, &Widget::onSliderReleased);
m_pixmap = QString("H:\\picture\\TorchLight\\TorchLight0.bmp");
QTimer::singleShot(100, this, SLOT(onUpdatePixmap()));
}
void Widget::onUpdateWidget()
{
ui->horizontalSlider->setRange(0, ui->widget->width() - squarewidth);
}
void Widget::onUpdatePixmap()
{
m_offsetPoint.rx() = qBound(0, QRandomGenerator::global()->bounded(1024*10) % this->width() + squarewidth + squareradius,
this->width() - squarewidth);
m_offsetPoint.ry() = qBound(0, QRandomGenerator::global()->bounded(1024*10) % ui->widget->height() + squarewidth + squareradius,
ui->widget->height() - squarewidth - squareradius);
qDebug()<<m_offsetPoint.rx()<<m_offsetPoint.ry();
this->update();
}
void Widget::onSliderValueChanged(int value)
{
//ui->widget->setValue(value);
m_value = qBound(0, value, ui->widget->width() - squarewidth);
update();
}
void Widget::onSliderReleased()
{
bool isOverlap = qAbs(-m_offsetPoint.x() + m_value) < 5;
QString content = isOverlap ? "验证成功!" : "验证失败!";
QMessageBox msgBox;
msgBox.setWindowTitle("滑块图片验证");
msgBox.setText(content);
msgBox.exec();
}
void Widget::drawPicture()
{
QPainter painter(ui->widget);
painter.setRenderHint(QPainter::Antialiasing);
QPainterPath clippath;
clippath.addRoundedRect(ui->widget->rect(), 4, 4);
painter.setClipPath(clippath);
//画背景图
const QPixmap & pixmap = QPixmap(m_pixmap).scaled(ui->widget->width(), ui->widget->height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
painter.drawPixmap(0, 0, ui->widget->width(), ui->widget->height(), pixmap);
QPainterPath cutoutpath;
cutoutpath.setFillRule(Qt::WindingFill);
QRect rect(m_offsetPoint, QSize(squarewidth, squarewidth));
cutoutpath.addEllipse(rect);
//画被扣除的空白区域
QPainterPath subellipsepath;
subellipsepath.addEllipse(rect.x(), rect.y() - rect.height() / 2, rect.width(), rect.height());
cutoutpath = cutoutpath.united(subellipsepath);
painter.setPen(QPen(QColor(80, 80, 80), 1));
painter.setBrush(QColor(200, 200, 200, 220));
painter.drawPath(cutoutpath);
//画滑块图片
QPixmap puzzlePixmap(ui->widget->size());
puzzlePixmap.fill(Qt::transparent);
QPainter puzzlePainter(&puzzlePixmap);
puzzlePainter.setRenderHints(QPainter::Antialiasing);
puzzlePainter.setClipPath(cutoutpath);
puzzlePainter.setPen(QPen(QColor(229, 228, 228), 2));
puzzlePainter.drawPixmap(0, 0, ui->widget->width(), ui->widget->height(), pixmap);
puzzlePainter.drawPath(cutoutpath);
painter.drawPixmap(-m_offsetPoint.x() + m_value, 0, ui->widget->width(), ui->widget->height(), puzzlePixmap);
}
bool Widget::eventFilter(QObject *watched, QEvent *event)
{
if (watched == ui->widget && event->type() == QEvent::Paint)
{
drawPicture();
return true;
}
return QWidget::eventFilter(watched, event);
}
void Widget::paintEvent(QPaintEvent *)
{
}
QSlider的样式表
QSlider::groove:horizontal {
border: 1px solid #999999;
height: 10px; /* the groove expands to the size of the slider by default. by giving it a height, it has a fixed size */
background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #B1B1B1, stop:1 #c4c004);
margin: 2px 0;
}
QSlider::handle:horizontal {
background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #b4b4b4, stop:1 #8f0f8f);
border: 1px solid #5c0c5c;
width: 18px;
margin: -12px 0; /* handle is placed by default on the contents rect of the groove. Expand outside the groove */
border-radius: 3px;
}
空白区域画法
在painter中绘制一个封闭的painterpath。path中的路径通过添加形状的方式自己设置。背景色通过setBrush设置。
QPainterPath cutoutpath;
cutoutpath.setFillRule(Qt::WindingFill);
QRect rect(m_offsetPoint, QSize(squarewidth, squarewidth));
cutoutpath.addEllipse(rect);
//画被扣除的空白区域
QPainterPath subellipsepath;
subellipsepath.addEllipse(rect.x(), rect.y() - rect.height() / 2, rect.width(), rect.height());
cutoutpath = cutoutpath.united(subellipsepath);
painter.setPen(QPen(QColor(80, 80, 80), 1));
painter.setBrush(QColor(200, 200, 200, 220));
painter.drawPath(cutoutpath);
滑块图片画法
注意puzzlePainter指向puzzlePixmap,puzzlePixmap是个透明图,在里面绘制同上方空白区域的painterpath可见,然后合并图片和空白区域的painter。
QPixmap puzzlePixmap(ui->widget->size());
puzzlePixmap.fill(Qt::transparent);
QPainter puzzlePainter(&puzzlePixmap);
puzzlePainter.setRenderHints(QPainter::Antialiasing);
puzzlePainter.setClipPath(cutoutpath);
puzzlePainter.setPen(QPen(QColor(229, 228, 228), 2));
puzzlePainter.drawPixmap(0, 0, ui->widget->width(), ui->widget->height(), pixmap);
puzzlePainter.drawPath(cutoutpath);
painter.drawPixmap(-m_offsetPoint.x() + m_value, 0, ui->widget->width(), ui->widget->height(), puzzlePixmap);