Qt bietet dem Programmierer sehr umfangreiche Funktionen, aber die Anzahl der Widgets ist begrenzt. Wenn keines der verfügbaren Elemente geeignet ist, müssen Sie etwas Eigenes zeichnen. Der einfachste Weg - vorgefertigte Bilder zu verwenden - weist schwerwiegende Nachteile auf: die Notwendigkeit, Bilder in einer Datei oder in Ressourcen zu speichern, Probleme mit der Skalierbarkeit und Portabilität von Bildformaten. Im Folgenden wird die Verwendung der Prinzipien von Vektorgrafiken ohne Verwendung der tatsächlichen Vektorgrafiken beschrieben.
Präambel
Alles begann mit der Tatsache, dass einmal ein Hinweis auf Ein-Bit-Zeichen benötigt wurde. Einige Anwendungen empfangen Daten an einem Port. Das Paket muss zerlegt und auf dem Bildschirm angezeigt werden. Es wäre gleichzeitig schön, das vertraute Dashboard irgendwie nachzuahmen. Um digitale Daten anzuzeigen, bietet Qt die QLCDNumber-Klasse "out of the box" an, ähnlich den bekannten Sieben-Segment-Anzeigen, aber bei einzelnen Lampen ist etwas nicht sichtbar.
Die Verwendung von Flags (sie sind Kontrollkästchen) und Schaltern (sie sind Optionsfelder) für diese Zwecke ist schlecht, und hier ist eine Liste von Gründen:
- Das ist semantisch falsch. Schaltflächen - Sie sind Schaltflächen und dienen zur Benutzereingabe und nicht dazu, ihm etwas anzuzeigen.
- Dies impliziert das zweite: Der Benutzer bemüht sich, auf solche Tasten zu stoßen. Wenn gleichzeitig die Informationsaktualisierung nicht besonders schnell ist, liegt die Anzeige und der Benutzer meldet ein fehlerhaftes Programm, das heftig kichert.
- Wenn Sie die Taste zum Drücken sperren (setEnabled (false)), wird sie hässlich grau. Ich erinnere mich, dass es in Delphi in der Region von Version 6 eine solche Finte mit Ohren gab: Man konnte eine Flagge auf das Panel setzen und die Verfügbarkeit des Panels deaktivieren, nicht die Flagge, dann war die Flagge weder grau noch aktiv. Dieser Trick funktioniert hier nicht.
- Die Tasten haben einen Eingabefokus. Wenn sich also Eingabeelemente im Fenster befinden und der Benutzer mit der Tabulatortaste daran entlang geht, muss er entlang der Ausgabeelemente gehen, was unpraktisch und hässlich ist.
- Am Ende sehen diese Tasten nur unästhetisch aus, besonders neben dem Siebensegment.
Fazit: Sie müssen selbst eine Glühbirne zeichnen.
Mehl der Wahl
Zuerst suchte ich nach vorgefertigten Lösungen. In dieser Zeit, als ich Delphi verwendete, konnte man nur eine gigantische Menge fertiger Komponenten finden, sowohl von seriösen Unternehmen als auch von Amateurherstellern. Qt hat große Probleme damit. QWT hat einige Elemente, aber das nicht. Amateurismus habe ich überhaupt nicht gesehen. Wenn Sie auf Github richtig graben, können Sie wahrscheinlich etwas finden, aber ich werde es wahrscheinlich selbst schneller machen.
Das erste, was sich aus dem hausgemachten herausstellte, war die Verwendung von zwei Bilddateien mit Bildern des Lichts ein und aus. Schlecht:
- Es ist notwendig, gute Bilder zu finden (oder zu zeichnen, aber ich bin kein Künstler).
- Die Frage des Prinzips lautet: Binden ist nicht gut, auch Bilder, sogar unter den Füßen liegen;
- Sie müssen irgendwo aufbewahrt werden. Die Dateien sind sehr schlecht: versehentlich gelöscht - und es gibt keine Schaltflächen. Die Ressourcen sind besser, aber ich habe auch keine Lust, durchzukommen.
- Keine Skalierbarkeit;
- Die Anpassbarkeit (z. B. Farben) wird nur durch Hinzufügen von Dateien erreicht. Das heißt, ressourcenintensiv und unflexibel.
Das zweite, was sich aus dem ersten ergibt, ist die Verwendung von Vektorbildern anstelle von Bildern. Darüber hinaus kann Qt SVG rendern. Hier ist die Suche nach dem Bild selbst schon etwas einfacher: Es gibt viele Lektionen zu den Vektorgrafiken im Netzwerk, Sie können etwas mehr oder weniger geeignetes finden und es an Ihre Bedürfnisse anpassen. Es bleibt jedoch die Frage nach Speicherung und Anpassung, und das Rendern ist nicht ressourcenfrei. Pennies natürlich, aber immer noch ...
Und der dritte folgt aus dem zweiten: Sie können die Prinzipien der Vektorgrafik zum Selbstzeichnen von Bildern verwenden! Eine Vektorbilddatei in Textform gibt an, was und wie gezeichnet werden soll. Ich kann den gleichen Code mithilfe von Vektor-Tutorials angeben. Glücklicherweise verfügt das QPainter-Objekt über die erforderlichen Werkzeuge: einen Stift, einen Pinsel, einen Verlauf und Zeichenprimitive sowie eine Texturfüllung. Ja, die Werkzeuge sind bei weitem nicht alles: Es gibt keine Masken, Mischmodi, aber es ist absolut kein Fotorealismus erforderlich.
Ich habe im Internet nach einigen Beispielen gesucht. Er nahm die erste Lektion, die auftauchte: "Wir zeichnen eine Schaltfläche im Inkscape-Grafikeditor" von der Website "Es ist einfach zu zeichnen." Der Knopf aus dieser Lektion ähnelt viel eher einer Glühbirne als einem Knopf, was perfekt zu mir passt. Ich mache einen Entwurf: Anstelle von Inkscape, einem Projekt in Qt.
Federtest
Ich erstelle ein neues Projekt. Ich wähle den Namen des Projekts rgbled (weil ich so etwas wie eine RGB-LED machen möchte) und den Pfad dazu. Ich wähle die Basisklasse QWidget und den Namen RgbLed und lehne es ab, eine Formulardatei zu erstellen. Das Projekt macht nach dem Start standardmäßig ein leeres Fenster, es ist immer noch uninteressant.
Vorbereitung zum Zeichnen
Es ist ein Leerzeichen. Jetzt müssen Sie die privaten Mitglieder der Klasse abrufen, die die Geometrie des Bildes bestimmen. Ein wesentlicher Vorteil von Vektorgrafiken ist ihre Skalierbarkeit, daher sollte es ein Minimum an konstanten Zahlen geben, und sie legen nur die Proportionen fest. Die Dimensionen werden im resizeEvent () -Ereignis neu berechnet, das neu definiert werden muss.
Im verwendeten Zeichentutorial werden die Abmessungen im Laufe der Zeit in Pixel angegeben. Ich muss im Voraus bestimmen, was ich verwenden werde und wie ich nachzählen soll.
Ein gezeichnetes Bild besteht aus folgenden Elementen:
- Außenring (nach außen geneigt, Teil des konvexen Randes)
- Innenring (nach innen gekippt)
- LED-Lampengehäuse, „Glas“
- Schatten am Rand des Glases
- Top-Highlight
- Bodenfackel
Konzentrische Kreise, dh alles außer der Blendung, werden durch die Position des Zentrums und den Radius bestimmt. Die Blendung wird durch die Mitte, Breite und Höhe bestimmt, und die Position der X-Blendungszentren stimmt mit der Position der X-Mitte des gesamten Bildes überein.
Um die Elemente der Geometrie zu berechnen, müssen Sie bestimmen, welche größer ist - die Breite oder Höhe, da die Glühbirne rund ist und in ein Quadrat mit einer Seite passen muss, die der kleineren der beiden Dimensionen entspricht. Also füge ich die entsprechenden privaten Mitglieder zur Header-Datei hinzu.
Codeprivate: int height; int width; int minDim; int half; int centerX; int centerY; QRect drawingRect; int outerBorderWidth; int innerBorderWidth; int outerBorderRadius; int innerBorderRadius; int topReflexY; int bottomReflexY; int topReflexWidth; int topReflexHeight; int bottomReflexWidth; int bottomReflexHeight;
Dann definiere ich die geschützte Funktion neu, die aufgerufen wird, wenn die Größe des Widgets geändert wird.
Code protected: void resizeEvent(QResizeEvent *event); void RgbLed::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); this->height = this->size().height(); this->width = this->size().width(); this->minDim = (height > width) ? width : height; this->half = minDim / 2; this->centerX = width / 2; this->centerY = height / 2; this->outerBorderWidth = minDim / 10; this->innerBorderWidth = minDim / 14; this->outerBorderRadius = half - outerBorderWidth; this->innerBorderRadius = half - (outerBorderWidth + innerBorderWidth); this->topReflexY = centerY - (half - outerBorderWidth - innerBorderWidth) / 2; this->bottomReflexY = centerY + (half - outerBorderWidth - innerBorderWidth) / 2; this->topReflexHeight = half / 5; this->topReflexWidth = half / 3; this->bottomReflexHeight = half / 5; this->bottomReflexWidth = half / 3; drawingRect.setTop((height - minDim) / 2); drawingRect.setLeft((width - minDim) / 2); drawingRect.setHeight(minDim); drawingRect.setWidth(minDim); }
Hier wird die Seite des Quadrats berechnet, in die die Glühbirne eingeschrieben ist, die Mitte dieses Quadrats, der Radius der Felge, die die maximal mögliche Fläche einnimmt, die Breite der Felge, deren äußerer Teil 1/10 des Durchmessers betragen sollte, und der innere Teil 1/14. Dann wird die Position der Blendung berechnet, die sich in der Mitte des oberen und unteren Radius befindet, die Breite und Höhe werden mit dem Auge ausgewählt.
Außerdem werde ich in den geschützten Feldern sofort eine Reihe von Farben hinzufügen, die verwendet werden sollen.
Code QColor ledColor; QColor lightColor; QColor shadowColor; QColor ringShadowDarkColor; QColor ringShadowMedColor; QColor ringShadowLightColor; QColor topReflexUpColor; QColor topReflexDownColor; QColor bottomReflexCenterColor; QColor bottomReflexSideColor;
An den Namen ist ungefähr klar, dass dies die Farben der Glühbirne, der helle Teil des Schattens, der dunkle Teil des Schattens, die drei Farben des ringförmigen Schattens um die Glühbirne und die Farben der Blendungsverläufe sind.
Die Farben sollten initialisiert werden, damit ich das Werkstück des Designers ergänzen kann.
Code RgbLed::RgbLed(QWidget *parent) : QWidget(parent), ledColor(Qt::green), lightColor(QColor(0xE0, 0xE0, 0xE0)), shadowColor(QColor(0x70, 0x70, 0x70)), ringShadowDarkColor(QColor(0x50, 0x50, 0x50, 0xFF)), ringShadowMedColor(QColor(0x50, 0x50, 0x50, 0x20)), ringShadowLightColor(QColor(0xEE, 0xEE, 0xEE, 0x00)), topReflexUpColor(QColor(0xFF, 0xFF, 0xFF, 0xA0)), topReflexDownColor(QColor(0xFF, 0xFF, 0xFF, 0x00)), bottomReflexCenterColor(QColor(0xFF, 0xFF, 0xFF, 0x00)), bottomReflexSideColor(QColor(0xFF, 0xFF, 0xFF, 0x70)) { }
Vergessen Sie auch nicht, die Einschlüsse von Klassen, die beim Zeichnen benötigt werden, in die Header-Datei einzufügen.
Code #include <QPainter> #include <QPen> #include <QBrush> #include <QColor> #include <QGradient>
Dieser Code wird erfolgreich kompiliert, aber im Widget-Fenster hat sich nichts geändert. Es ist Zeit zu zeichnen.
Zeichnen
Ich gebe eine geschlossene Funktion ein
void drawLed(const QColor &color);
und definieren Sie die geschützte Funktion neu
void paintEvent(QPaintEvent *event);
Das Neuzeichnungsereignis bewirkt die eigentliche Zeichnung, an die die Farbe des "Glases" als Parameter übergeben wird.
Code void RgbLed::paintEvent(QPaintEvent *event) { QWidget::paintEvent(event); this->drawLed(ledColor); }
So weit so. Und wir beginnen allmählich, die Zeichenfunktion auszufüllen.
Code void RgbLed::drawLed(const QColor &color) { QPainter p(this); QPen pen; pen.setStyle(Qt::NoPen); p.setPen(pen); }
Zunächst wird ein Künstlerobjekt erstellt, das mit dem Zeichnen beschäftigt ist. Dann wird ein Bleistift erstellt, der benötigt wird, damit kein Bleistift vorhanden ist: In diesem Bild wird der Konturstrich nicht nur nicht, sondern überhaupt nicht benötigt.
Dann wird der erste Kreis ungefähr gemäß der Lektion über Vektorgrafiken gezeichnet: ein großer Kreis, gefüllt mit einem radialen Gradienten. Der Farbverlauf hat oben einen hellen Ankerpunkt, aber nicht ganz am Rand, und unten einen dunklen, aber auch nicht ganz am Rand. Basierend auf dem Farbverlauf wird ein Pinsel erstellt. Dieser Pinselmaler malt einen Kreis (dh eine Ellipse, die in ein Quadrat eingeschrieben ist). Es stellt sich ein solcher Code heraus
Code QRadialGradient outerRingGradient(QPoint(centerX, centerY - outerBorderRadius - (outerBorderWidth / 2)), minDim - (outerBorderWidth / 2))
Die Umgebung betont den Farbparameter der Funktion drawLed, da er nicht verwendet wird. Lass ihn tolerieren, er wird noch nicht gebraucht, aber er wird es bald brauchen. Das gestartete Projekt führt zu folgendem Ergebnis:
Fügen Sie einen weiteren Code-Stapel hinzu.
Code QRadialGradient innerRingGradient(QPoint(centerX, centerY + innerBorderRadius + (innerBorderWidth / 2)), minDim - (innerBorderWidth / 2))
Fast der gleiche Kreis, nur kleiner und verkehrt herum. Wir bekommen folgendes Bild:
Dann brauchen Sie endlich die Farbe des Glases:
Code QColor dark(color.darker(120)); QRadialGradient glassGradient(QPoint(centerX, centerY), innerBorderRadius); glassGradient.setColorAt(0, color); glassGradient.setColorAt(1, dark); QBrush glassBrush(glassGradient); p.setBrush(glassBrush); p.drawEllipse(QPoint(centerX, centerY), innerBorderRadius, innerBorderRadius);
Hier wird unter Verwendung der dunkleren Funktion aus der übertragenen Farbe dieselbe Farbe erhalten, jedoch dunkler, um den Gradienten zu organisieren. Koeffizient 120 mit dem Auge ausgewählt. Hier ist das Ergebnis:
Fügen Sie einen ringförmigen Schatten um das Glas hinzu. Dies geschieht in der Lektion über Vektorgrafiken und sollte Volumen und Realismus hinzufügen:
Code QRadialGradient shadowGradient(QPoint(centerX, centerY), innerBorderRadius); shadowGradient.setColorAt(0, ringShadowLightColor); shadowGradient.setColorAt(0.85, ringShadowMedColor); shadowGradient.setColorAt(1, ringShadowDarkColor); QBrush shadowBrush(shadowGradient); p.setBrush(shadowBrush); p.drawEllipse(QPoint(centerX, centerY), innerBorderRadius, innerBorderRadius);
Es gibt einen dreistufigen Farbverlauf, so dass der Schatten bis zum Rand dicker wird und zur Mitte hin blass wird. Es stellt sich so heraus:
Fügen Sie beide Highlights gleichzeitig hinzu. Das obere Highlight wird im Gegensatz zum unteren (und allen anderen Elementen) linear dargestellt. Der Künstler von mir ist so lala, ich nehme mein Wort für den Autor der Lektion. Vielleicht stimmt das, ich werde nicht mit verschiedenen Arten von Verläufen experimentieren.
Code QLinearGradient topTeflexGradient(QPoint(centerX, (innerBorderWidth + outerBorderWidth)), QPoint(centerX, centerY))
Das ist in der Tat alles, eine fertige Glühbirne, wie bei KDPV.
Die Sichtbarkeit von Blendung und Ausbuchtung des Glases wird durch die Farbe oder vielmehr durch die Dunkelheit beeinflusst. Es mag sinnvoll sein, je nach Dunkelheit eine Anpassung für die Helligkeit der Blendung und den Dimmkoeffizienten in der dunkleren Funktion hinzuzufügen, aber das ist Perfektionismus, denke ich.
Unten finden Sie ein Beispiel für die Verwendung in einem Programmfenster.
Verwöhnen
Zum Spaß können Sie mit Blumen herumspielen. Beispiel: Überschreiben eines geschützten Mausklickereignisses
void mousePressEvent(QMouseEvent *event);
auf diese Weise:
Code void RgbLed::mousePressEvent(QMouseEvent *event) { static int count = 0; if (event->button() == Qt::LeftButton) { switch (count) { case 0: ledColor = Qt::red; count++; break; case 1: ledColor = Qt::green; count++; break; case 2: ledColor = Qt::blue; count++; break; case 3: ledColor = Qt::gray; count++; break; default: ledColor = QColor(220, 30, 200); count = 0; break; } this->repaint(); } QWidget::mousePressEvent(event); }
Nicht zu vergessen, Mausereignisse zum Header hinzuzufügen:
#include <QMouseEvent>
Jetzt ändert ein Mausklick auf die Komponente die Farbe der Glühbirne: Rot, Grün, Blau, Grau und etwas zufälliges Licht von der Lampe.
Nachwort
Das Zeichnen ist alles. Und das Widget sollte Funktionen hinzufügen. In meinem Fall wurde ein boolesches Feld "use state" hinzugefügt, ein weiteres boolesches Feld, das den Status "On" oder "Off" und die Standardfarben für diese Zustände definiert, sowie offene Getter und Setter für all dies. Diese Felder werden in verwendet Mit der Funktion paintEvent () können Sie die Farbe auswählen, die als Parameter an drawLed () übergeben wird. Infolgedessen können Sie die Verwendung von Zuständen deaktivieren und die Glühbirne auf eine beliebige Farbe einstellen oder die Zustände aktivieren und die Glühbirne je nach Ereignis ein- oder ausschalten verbunden sei es mit dem Signal, das überwacht werden muß.
Die Verwendung von mousePressEvent zeigt, dass ein Widget nicht nur als Indikator, sondern auch als Schaltfläche erstellt werden kann. Dadurch wird es gedrückt, freigegeben, gebogen, gedreht, farbig und was auch immer Sie möchten, für die Ereignisse Zeigen, Klicken und Freigeben.
Dies ist jedoch nicht mehr grundlegend. Ziel war es zu zeigen, wo Sie beim Zeichnen Ihrer eigenen Widgets Vorbilder verwenden können und wie diese Zeichnung ohne Verwendung von Raster- oder Vektorbildern in Ressourcen oder Dateien einfach implementiert werden kann.