qml
Tworzenie niestandardowych elementów w C ++
Szukaj…
Tworzenie niestandardowych elementów w C ++
QML przyszedł z bogatym zestawem elementów wizualnych. Używając tylko QML, możemy budować złożone aplikacje z tych elementów. Również bardzo łatwo jest zbudować własny element na podstawie zestawu standardowych elementów, takich jak Prostokąt, Przycisk, Obraz itp. Co więcej, możemy użyć przedmiotów takich jak Płótno, aby zbudować element z niestandardowym malowaniem. Wydaje się, że możemy budować różnorodne aplikacje tylko w języku QML, bez naruszania możliwości C ++. I to prawda, ale czasami chcielibyśmy przyspieszyć naszą aplikację lub chcemy rozszerzyć ją o moc Qt lub dodać jakieś możliwości, które nie są dostępne w QML. I z pewnością istnieje taka możliwość w QML . Zasadniczo QtQuick wykorzystuje Scene Graph do malowania zawartości wysoce wydajnym silnikiem renderującym opartym na OpenGL . Aby wdrożyć własny element wizualny, możemy użyć 2 sposobów:
- Tradycyjny sposób dla Qt przy użyciu QPainter ( QQuickPaintedItem ).
- Popularny sposób QML przy użyciu funkcji QQuickItem i OpenGL.
Możliwe, że pierwsza metoda wydaje się łatwiejsza, ale warto wziąć pod uwagę, że jest ona również wolniejsza niż pierwsza, ponieważ QtQuick maluje zawartość elementu na powierzchni, a następnie wstawia ją do wykresu sceny, aby rendering był operacją dwuetapową. Zatem bezpośrednie użycie interfejsu API wykresów scen jest zawsze znacznie szybsze.
Aby bliżej zbadać obie metody, stwórzmy własny element, który zdecydowanie nie istnieje w QML, na przykład trójkąt.
Deklaracja klasy
class QQuickCustomItem : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public:
QQuickCustomItem(QQuickItem *parent = Q_NULLPTR);
protected:
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData);
QColor color() const;
void setColor(const QColor &color);
private:
QColor m_color;
bool m_needUpdate;
signals:
void colorChanged();
};
Dodajemy makro Q_OBJECT do pracy z sygnałami. Dodajemy również niestandardową właściwość, aby określić kolor naszego prostokąta. Aby działało , potrzebujemy ponownie zaimplementować funkcję wirtualną QQuiclItem :: updatePaintNode () .
Implementacja klasy.
Najpierw definiujemy konstruktor.
QQuickCustomItem::QQuickCustomItem(QQuickItem *parent) :
QQuickItem(parent),
m_color(Qt::red),
m_needUpdate(true)
{
setFlag(QQuickItem::ItemHasContents);
}
Należy pamiętać, że wywołanie funkcji setFlag () jest obowiązkowe, w przeciwnym razie obiekt nie zostanie dodany do wykresu sceny. Następnie definiujemy funkcję bólu.
QSGNode *QQuickCustomItem::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *updatePaintNodeData)
{
Q_UNUSED(updatePaintNodeData)
QSGGeometryNode *root = static_cast<QSGGeometryNode *>(oldNode);
if(!root) {
root = new QSGGeometryNode;
QSGGeometry *geometry = new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 3);
geometry->setDrawingMode(GL_TRIANGLE_FAN);
geometry->vertexDataAsPoint2D()[0].set(width() / 2, 0);
geometry->vertexDataAsPoint2D()[1].set(width(), height());
geometry->vertexDataAsPoint2D()[2].set(0, height());
root->setGeometry(geometry);
root->setFlag(QSGNode::OwnsGeometry);
root->setFlag(QSGNode::OwnsMaterial);
}
if(m_needUpdate) {
QSGFlatColorMaterial *material = new QSGFlatColorMaterial;
material->setColor(m_color);
root->setMaterial(material);
m_needUpdate = false;
}
return root;
}
Przy pierwszym wywołaniu funkcji nasz węzeł nie został jeszcze utworzony, więc oldNode będzie miał wartość NULL. Tworzymy więc węzeł i przypisujemy mu geometrię i materiał. Tutaj używamy GL_TRIANGLE_FAN dla naszej geometrii do malowania jednolitego prostokąta. Ten punkt jest taki sam jak w OpenGL. Na przykład, aby narysować ramkę trójkąta, możemy zmienić kod na:
geometry->setDrawingMode(GL_LINE_LOOP);
geometry->setLineWidth(5);
Możesz sprawdzić instrukcję OpenGL, aby sprawdzić inne kształty. Pozostaje tylko zdefiniować setter / getter dla naszej właściwości:
QColor QQuickCustomItem::color() const
{
return m_color;
}
void QQuickCustomItem::setColor(const QColor &color)
{
if(m_color != color) {
m_color = color;
m_needUpdate = true;
update();
colorChanged();
}
}
Teraz jest tylko jeden mały szczegół, aby działał. Musimy powiadomić QtQuick o nowym elemencie. Na przykład możesz dodać ten kod do pliku main.cpp:
qmlRegisterType<QQuickCustomItem>("stackoverflow.qml", 1, 0, "Triangle");
A oto nasz plik testowy QML:
import QtQuick 2.7
import QtQuick.Window 2.0
import stackoverflow.qml 1.0
Window {
width: 800
height: 800
visible: true
Rectangle {
width: 200
height: 200
anchors.centerIn: parent
color: "lightgrey"
Triangle {
id: rect
width: 200
height: 200
transformOrigin: Item.Top
color: "green"
onColorChanged: console.log("color was changed");
PropertyAnimation on rotation {
from: 0
to: 360
duration: 5000
loops: Animation.Infinite
}
}
}
Timer {
interval: 1000
repeat: true
running: true
onTriggered: rect.color = Qt.rgba(Math.random(),Math.random(),Math.random(),1);
}
}
Jak widzisz, nasz produkt zachowuje się jak wszystkie inne elementy QML. Teraz stwórzmy ten sam element za pomocą QPainter :
Wszystko, czego potrzebujemy, to wymienić
QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData);
z
void paint(QPainter *painter);
i, QQuickPaintedItem dziedziczymy naszą klasę po QQuickPaintedItem zamiast QQuickItem . Oto nasza funkcja malowania:
void QQuickCustomItem::paint(QPainter *painter)
{
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);
}
Wszystko inne pozostaje niezmienione.