问题
I have a Qt application and want to show a little tutorial when the user access the application for the first time. Something like this:
How can I do something like this on Qt?
I'm using Qt 5.4 on Windows.
回答1:
To perform this task we must make the translucent background, we do it by activating the attribute Qt::WA_TranslucentBackground
, then we use QPainterPath to draw the rectangle minus the transparent circle.
Then use eventFilter to know some events like when they are displayed, if you change the size or position.
Then a structure is created to store the data, in this case the center of the circle, the radius, the position of the text and the text itself.
Then the next and return buttons are added, and the logic of the page change is handled in the slot.
tutowidget.h
#ifndef TUTOWIDGET_H
#define TUTOWIDGET_H
#include <QWidget>
class QButtonGroup;
class TutoWidget : public QWidget
{
Q_OBJECT
struct Pages{
QPoint center;
int radius;
QPoint pText;
QString text;
};
public:
TutoWidget(QWidget *parent);
void addPage(const QPoint ¢er, int radius, const QPoint &pText, const QString & text);
bool eventFilter(QObject *watched, QEvent *event);
protected:
void paintEvent(QPaintEvent *);
private slots:
void onClicked(int id);
private:
QWidget *mParent;
QButtonGroup *group;
QVector<Pages> pages;
int currentIndex = -1;
};
#endif // TUTOWIDGET_H
tutowidget.cpp
#include "tutowidget.h"
#include <QVBoxLayout>
#include <QButtonGroup>
#include <QPushButton>
#include <QEvent>
#include <QTimer>
#include <QPainter>
TutoWidget::TutoWidget(QWidget *parent):QWidget(0)
{
setWindowFlags(Qt::FramelessWindowHint|Qt::Popup);
setAttribute(Qt::WA_TranslucentBackground, true);
mParent = parent;
mParent->installEventFilter(this);
QVBoxLayout *vlay = new QVBoxLayout(this);
vlay->addItem(new QSpacerItem(20, 243, QSizePolicy::Minimum, QSizePolicy::Expanding));
QHBoxLayout *hlay = new QHBoxLayout;
vlay->addLayout(hlay);
group = new QButtonGroup(this);
const QStringList nameBtns{"Return", "Next"};
for(int i=0; i < nameBtns.length(); i++){
QPushButton* btn = new QPushButton(nameBtns[i]);
btn->setFlat(true);
group->addButton(btn, i);
hlay->addWidget(btn);
}
connect(group, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked),
this, static_cast<void (TutoWidget::*)(int)>(&TutoWidget::onClicked));
hlay->addItem(new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum));
group->button(0)->hide();
}
void TutoWidget::addPage(const QPoint ¢er, int radius, const QPoint &pText, const QString &text)
{
pages << Pages{center, radius, pText, text};
if(currentIndex == -1){
currentIndex = 0;
update();
}
}
bool TutoWidget::eventFilter(QObject *watched, QEvent *event){
if(watched == mParent){
switch (event->type()) {
case QEvent::Show:
QTimer::singleShot(10, this, &QWidget::show);
break;
case QEvent::Close:
close();
break;
case QEvent::Move:
move(mParent->mapToGlobal(QPoint(0, 0)));
break;
case QEvent::Resize:
resize(mParent->size());
break;
default:
break;
}
}
return QWidget::eventFilter(watched, event);
}
void TutoWidget::paintEvent(QPaintEvent *){
QPainter painter(this);
painter.setPen(Qt::NoPen);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(QColor(100, 100, 100, 200));
QPainterPath path;
if(currentIndex != -1){
QPoint center = pages[currentIndex].center;
int radius = pages[currentIndex].radius;
QString text = pages[currentIndex].text;
QPoint pText = pages[currentIndex].pText;
path.moveTo(center + radius/2*QPoint(1, 0));
path.arcTo(QRect(center-radius/2*QPoint(1, 1),radius*QSize(1, 1)), 0, 360);
path.addText(pText, font(), text);
}
path.addRect(rect());
painter.drawPath(path);
}
void TutoWidget::onClicked(int id)
{
if(id == 0){
if(currentIndex > 0)
currentIndex--;
}
else if(id == 1){
if(currentIndex < pages.count()-1)
currentIndex++;
}
update();
group->button(0)->setVisible(currentIndex!=0);
group->button(1)->setVisible(currentIndex!=(pages.count()-1));
}
Example:
tuto = new TutoWidget(this); // this is the widget
tuto->addPage(QPoint(200, 200), 40, QPoint(100, 100), "some text1");
tuto->addPage(QPoint(300, 300), 60, QPoint(200, 100), "some text2");
tuto->addPage(QPoint(100, 200), 100, QPoint(200, 50), "some text3");
tuto->addPage(QPoint(200, 100), 80, QPoint(100, 200), "some text4");
The complete example is in the following link
回答2:
There is nothing that will be of help to do this out of the box.
You can have an overlay widget that is normally hidden and only shows up during "tutorial" mode. You can easily punch a hole by using a QPainterPath
to which you first add the rectangle of the entire widget and then add a circle where you want the hole, and then fill the resulting path.
You can get the absolute position of gui elements by mapping to the tutorial overlay widget.
I suggest you implement an abstract Tutorial
class or at least a basic list of pointer/text pairs you implement for each widget you want to have a tutorial, then register each element of that widget in the tutorial - pointer to the gui element and the tutorial text. Then, when the tutorial for that widget is invoked, the overlay widget becomes visible and allows to iterate the particular elements of a widget that have information associated with them and redraw on iteration to show the circle and text.
来源:https://stackoverflow.com/questions/47660289/create-tutorial-mode