create tutorial mode

空扰寡人 提交于 2020-01-30 11:47:30

问题


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 &center, 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 &center, 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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!