QQuickPaintedItem鼠标精准拾取(pick/select)研究

QT C++在2D图形方面已经做的很完善了,在PC端(Windows、Linux和MaC)上都有很好的表现。

QT中的QML特别适合于移动端应用的开发,QML中的一些基本形状类型并不是一一地与Qt C++相对应,但是通过C++可以来扩展QML。

QQuickPaintedItem继承自QQuickItem,提供了使用QPainter API的方法来扩展QML中的2D图形项。

QQuickPaintedItem没有像QGraphicsItem那样提供shape()方法来获取图形项的具体实际形状,但是其包含contains()方法,我们可以间接地结合鼠标操作点来判断是否包含在实际形状范围内。

废话不多说,直接上测试代码:

CustomItem.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 
#ifndef CUSTOMITEM_H
#define CUSTOMITEM_H

#include <QQuickPaintedItem>
#include <QQuickItem>
#include <QMouseEvent>
#include <QPainter>

class CustomItem : public QQuickPaintedItem
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName)
    Q_PROPERTY(QColor color READ color WRITE setColor)

public:
    CustomItem(QQuickItem *parent = nullptr);

    QString name() 
const;
    
void setName(const QString &name);

    QColor color() 
const;
    
void setColor(const QColor &color);

    
// override paint() for actual painting.
    virtual void paint(QPainter *painter);
    
// override contans() for shape mask, Q_INVOKABLE for QML use.
    Q_INVOKABLE virtual bool contains(const QPointF &point) const;

    
virtual void mousePressEvent(QMouseEvent *event);
    
virtual void mouseMoveEvent(QMouseEvent *event);
    
virtual void mouseReleaseEvent(QMouseEvent *event);

private:
    QString m_name;
    QColor m_color;
};

#endif // CUSTOMITEM_H
 CustomItem.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
 
#include "CustomItem.h"
#include <QPainter>
#include <QPainterPathStroker>
#include <QDebug>

CustomItem::CustomItem(QQuickItem *parent)
    : QQuickPaintedItem(parent)
{
    setAcceptedMouseButtons(Qt::LeftButton
                            | Qt::RightButton
                            | Qt::MiddleButton);
    setFlag(QQuickItem::ItemHasContents);
}

QString CustomItem::name() 
const
{
    
return m_name;
}

void CustomItem::setName(const QString &name)
{
    m_name = name;
}

QColor CustomItem::color() 
const
{
    
return m_color;
}

void CustomItem::setColor(const QColor &color)
{
    m_color = color;
}

void CustomItem::paint(QPainter *painter)
{
    
// pen & brush
    QPen pen(m_color, 10);
    QLinearGradient gradient;
    gradient.setStart(
050);
    gradient.setFinalStop(
20050);
    gradient.setColorAt(
0, Qt::blue);
    gradient.setColorAt(
0.2, Qt::green);
    gradient.setColorAt(
0.4, Qt::red);
    gradient.setColorAt(
0.6, Qt::yellow);
    gradient.setColorAt(
1, Qt::cyan);
    painter->setPen(pen);
    painter->setBrush(gradient);
    painter->setRenderHints(QPainter::Antialiasing, 
true);

    
// Unclosed shape
    static const QPointF points[3] =
    {
        QPointF(
10.0100.0),
        QPointF(
20.010.0),
        QPointF(
200.050.0),
    };

    painter->save();
    pen.setJoinStyle(Qt::MiterJoin);    
// MiterJoin, BevelJoin, RoundJoin
    pen.setCapStyle(Qt::RoundCap);      // FlatCap, SquareCap, RoundCap
    pen.setStyle(Qt::DashLine);
    painter->drawPolyline(points, 
3);
    painter->restore();

    
// Closed shape
    QPainterPath path;
    path.addEllipse(QRectF(
10.010.0, width() - 10.0, height() - 10.0));
    painter->drawPath(path);

    
/* 三角形
    QPainterPath path;
    path.moveTo(width() / 2, 0);
    path.lineTo(width(), height());
    path.lineTo(0, height());
    path.lineTo(width() / 2, 0);
    painter->fillPath(path, m_color);
    */

}

bool CustomItem::contains(const QPointF &point) const
{
    
// Unclosed shape
    static const QPointF points[3] =
    {
        QPointF(
10.0100.0),
        QPointF(
20.010.0),
        QPointF(
200.050.0),
    };
    QPainterPath path;
    path.moveTo(points[
0]);
    path.lineTo(points[
1]);
    path.lineTo(points[
2]);
    QPainterPathStroker stroker;
    stroker.setWidth(
10);
    stroker.setJoinStyle(Qt::MiterJoin);
    stroker.setCapStyle(Qt::RoundCap);
    stroker.setDashPattern(Qt::SolidLine);
    
return stroker.createStroke(path).contains(point);

    
// Close shape
    QPainterPath path;
    path.addEllipse(QRectF(
10.010.0, width() - 10.0, height() - 10.0));
    QPainterPathStroker stroker;
    stroker.setWidth(
10);
    
return path.contains(point) || stroker.createStroke(path).contains(point);

    
/* 三角形
    QPainterPath path;
    path.moveTo(width() / 2, 0);
    path.lineTo(width(), height());
    path.lineTo(0, height());
    path.lineTo(width() / 2, 0);
    QPainterPathStroker stroker;
    stroker.setWidth(10);
    return return path.contains(point) || stroker.createStroke(path).contains(point);
    */

}

void CustomItem::mousePressEvent(QMouseEvent *event)
{
    qDebug() << 
"CustomItem::mousePressEvent";
    QQuickPaintedItem::mousePressEvent(event);
}

void CustomItem::mouseMoveEvent(QMouseEvent *event)
{
    qDebug() << 
"CustomItem::mouseMoveEvent";
    QQuickPaintedItem::mouseMoveEvent(event);
}

void CustomItem::mouseReleaseEvent(QMouseEvent *event)
{
    qDebug() << 
"CustomItem::mouseReleaseEvent";
    QQuickPaintedItem::mouseReleaseEvent(event);
}

在main.cpp中注册CustomItem类型:qmlRegisterType<CustomItem>("CustomItem", 1, 0, "CustomItem");

 main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "CustomItem.h"

int main(int argc, char *argv[])
{
    qputenv(
"QT_IM_MODULE", QByteArray("qtvirtualkeyboard"));

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    qmlRegisterType<CustomItem>(
"CustomItem"10"CustomItem");

    QQmlApplicationEngine engine;
    
const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject * obj, 
const QUrl & objUrl)
    {
        
if (!obj && url == objUrl)
            QCoreApplication::exit(-
1);
    }, Qt::QueuedConnection);
    engine.load(url);

    
return app.exec();
}

在qml文件中使用:

 main.qml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
 
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
import CustomItem 1.0


Window {
    id: window
    visible: 
true
     
640
    height: 
480
    title: qsTr(
"qml-2d-viewer")

    Button {
        id: btn
        anchors.right: parent.right
        anchors.bottom: parent.bottom
        text: 
"Populate"
        onClicked: {
            
var object = Qt.createQmlObject(
                        
'import QtQuick 2.12;
                         import CustomItem 1.0;
                         CustomItem {
                             
100
                            height: 
50
                            x: window.width * Math.random()
                            y: window.height * Math.random()
                            name: 
"A simple Rectangle Item"
                            color: Qt.rgba(Math.random(), Math.random(), Math.random(), 
1)

                            MouseArea {
                                anchors.fill: parent
                                onPressed: {
                                    
if (parent.contains(Qt.point(mouse.x, mouse.y))){
                                        drag.target = parent
                                        parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 
1);
                                        parent.update();
                                    }
                                    
else{
                                        drag.target = 
null
                                    }
                                }
                                drag.axis: Drag.XAndYAxis
                                drag.minimumX: 
0
                                drag.maximumX: window.width - parent.width
                                drag.minimumY: 
0
                                drag.maximumY: window.height - parent.width
                                }
                            }
',
                        parent,
                        
"dynamicSnippet");
        }
    }

    
// QML Rectangle Type
    Rectangle {
        id: blueSquare
         
120; height: 120
        x: window.width - width - 
10; y: 10    // making this item draggable, so don't use anchors
        color: "blue"
        visible: 
false

        Text { text: 
"Drag"; font.pixelSize: 16; color: "white"; anchors.centerIn: parent }

        MouseArea {
            anchors.fill: parent
            
//! [drag]
            drag.target: blueSquare
            drag.axis: Drag.XAndYAxis
            drag.minimumX: 
0
            drag.maximumX: window.width - parent.width
            drag.minimumY: 
0
            drag.maximumY: window.height - parent.width
            
//! [drag]
        }
    }

    
// CustomItem Type Inherited from QQuickPaintedItem
    CustomItem {
        id: aRectangle
         
200
        height: 
100
        name: 
"A simple Rectangle Item"
        color: 
"red"

        MouseArea {
            anchors.fill: parent
            onPressed: {
                
if (parent.contains(Qt.point(mouse.x, mouse.y))){
                    drag.target = parent
                    parent.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 
1);
                    parent.update();
                }
                
else{
                    drag.target = 
null
                }
            }
            drag.axis: Drag.XAndYAxis
            drag.minimumX: 
0
            drag.maximumX: window.width - parent.width
            drag.minimumY: 
0
            drag.maximumY: window.height - parent.width
        }
    }
}

Q_INVOKABLE宏将contains()方法注册到元对象系统中,这样QML就可以调用该方法来判断鼠标指针点是否在图形项形状区域,从而实现精准拾取。

在QML中MouseArea为简单的鼠标交互提供了方便,比较容易使用,如果有了MouseArea,则C++中的鼠标响应事件就不再响应。

原文地址:https://www.cnblogs.com/MakeView660/p/11238422.html