Jeder Entwickler ist schon auf die Situation gestoßen, in der Standard-Qt-Widgets nicht mehr ausreichen: Das Interface ist fast fertig, aber eine „Kleinigkeit” — ungewöhnliches Verhalten, spezielle Eingabe oder Custom-Visualisierung — wird zur Quelle von Kompromissen und Workarounds. Kennen Sie das?
Dieses Kapitel zerstört sorgfältig den Mythos, dass eigene Steuerelemente zeitaufwendig, komplex und riskant sind. Hier wird offenbart, wie Entwickler Widgets so designen, dass sie sich natürlich in das Layout-System einfügen, korrekt skalieren und wiederverwendbar bleiben. Sie werden entdecken, dass die richtige Wahl der Basisklasse Arbeitsstunden spart.
Es werden Praktiken der Vererbung von QLineEdit und QFrame gezeigt, Arbeit mit paintEvent(), sizeHint() und QSizePolicy, sowie die Signal-Slot-Architektur für wiederverwendbare Komponenten. Ein reales Beispiel — und es wird klar, warum dieser Ansatz vielfach stabiler funktioniert.
Dieses Kapitel zu überspringen bedeutet, weiterhin mit den Einschränkungen von Standardlösungen zu kämpfen, anstatt die Kontrolle über das Interface selbst zu übernehmen.
Das Kapitel enthält Code-Beispiele, die sofort einsatzbereit sind.
Selbsttest zum Kapitel
Warum ist die Wahl der richtigen Basisklasse bei der Erstellung eines eigenen Widgets kritisch wichtig?Antwort
Richtige Antwort: Eine gut gewählte Basisklasse enthält bereits die meisten notwendigen Eigenschaften und Methoden, was erheblich Entwicklungszeit spart und von der Notwendigkeit befreit, Funktionalität von Grund auf zu implementieren.
Was gibt die Methode sizeHint() zurück und wie wird dies vom Layout-System verwendet?Antwort
Richtige Antwort: Sie gibt ein QSize-Objekt mit den empfohlenen Widget-Dimensionen zurück. Layout-Klassen verwenden diesen Wert zur Berechnung optimaler Widget-Größen bei der Platzierung und interpretieren ihn gemäß der QSizePolicy.
Was ist der Unterschied zwischen den Konstanten QSizePolicy::Expanding und QSizePolicy::Preferred?Antwort
Richtige Antwort: Beide erlauben dem Widget, sich zu strecken und zu schrumpfen, aber Expanding teilt dem Layout mit, dass das Widget vorzugsweise gestreckt werden sollte und es wünschenswert ist, ihm maximalen Platz zu geben, während Preferred keine solche Priorität hat.
Warum verwendet das Beispiel CustomWidget die Parameter QSizePolicy::Minimum für die Breite und Fixed für die Höhe?Antwort
Richtige Antwort: Minimum für die Breite ermöglicht dem Widget horizontale Streckung, aber kein Schrumpfen unter sizeHint(), während Fixed für die Höhe die Höhe strikt auf den sizeHint()-Wert fixiert, was für einen Fortschrittsindikator geeignet ist.
Warum sendet CustomWidget das Signal progressChanged(), wenn im Beispiel niemand damit verbunden ist?Antwort
Richtige Antwort: Dies ist ein Designprinzip für wiederverwendbare Komponenten — das Widget sollte Signale für alle wichtigen Ereignisse bereitstellen, damit es in anderen Anwendungen verwendet werden kann, wo Zustandsänderungen verfolgt werden müssen.
Warum wird in Qt6 empfohlen, update() statt repaint() für Widget-Neuzeichnung zu verwenden?Antwort
Richtige Antwort: Die Methode update() ist effizienter, da sie die Neuzeichnungsanforderung in die Ereigniswarteschlange stellt und mehrere Anfragen optimieren kann, während repaint() sofortige Neuzeichnung durchführt.
Warum erfolgt in der Methode paintEvent() des CustomWidget-Beispiels der Aufruf von drawFrame() ganz am Ende?Antwort
Richtige Antwort: Damit der Rahmen über dem gesamten Widget-Inhalt gezeichnet wird und nicht von Farbverlauf oder Text überdeckt wird, was die korrekte Zeichenreihenfolge der Ebenen gewährleistet.
Welche Funktion erfüllt std::clamp(n, 0, 100) im Slot slotSetProgress()?Antwort
Richtige Antwort: Es begrenzt den Wert n auf den Bereich von 0 bis 100 und garantiert, dass der Wert des Fortschrittsindikators nicht über die zulässigen Grenzen hinausgeht, wodurch die Grenzwertüberprüfung vereinfacht wird.
Warum verwendet QScrollArea die Policy QSizePolicy::Expanding in beide Richtungen?Antwort
Richtige Antwort: Dank Scrollbalken kann QScrollArea mit jeder Größe arbeiten, aber je größer die Größe, desto bequemer die Nutzung, daher teilt Expanding dem Layout mit, maximalen verfügbaren Platz bereitzustellen.
Wie stellt man Dark-Theme-Unterstützung im eigenen Widget sicher, anstatt hart kodierte Farben zu verwenden?Antwort
Richtige Antwort: QPalette verwenden und Farben aus der aktuellen Widget-Palette über palette().color() abrufen, z.B. QPalette::Text, QPalette::Base, QPalette::Highlight, was sich automatisch an das Theme anpasst.
In welchen Fällen sollten Widget-Klassenattribute als protected statt private deklariert werden?Antwort
Richtige Antwort: Wenn weitere Vererbung der erstellten Widget-Klasse geplant ist und abgeleitete Klassen Zugriff auf interne Daten der Basisklasse benötigen.
Welche Ereignismethoden müssen für korrekte Widget-Arbeit mit Tastaturfokus überschrieben werden?Antwort
Richtige Antwort: Die Methoden focusInEvent() zur Verarbeitung des Fokusempfangs und focusOutEvent() zur Verarbeitung des Fokusverlusts, was dem Widget ermöglicht, auf Fokuszustandsänderungen zu reagieren.
Wenn ein Widget benötigt wird, das sich strecken und schrumpfen kann, aber das Layout ihm maximalen Platz geben soll — welche Policy wählen?Antwort
Richtige Antwort: QSizePolicy::MinimumExpanding (wenn es eine Mindestgröße gibt) oder QSizePolicy::Expanding (wenn es keine Mindestgröße gibt), da diese Policies dem Layout mitteilen, so viel Platz wie möglich bereitzustellen.
Praktische Aufgaben
Einfaches Level
Farbiges Label-Widget
Erstellen Sie ein eigenes Widget, das von QLabel erbt und Text auf farbigem Hintergrund darstellt, mit der Möglichkeit, die Hintergrundfarbe über einen öffentlichen Slot setBackgroundColor(QColor) zu ändern. Das Widget sollte korrekt mit Layouts arbeiten und vernünftige Größen haben.
Hinweise: Erben Sie von QLabel, um die fertige Textfunktionalität zu nutzen. Überschreiben Sie paintEvent() zum Zeichnen des farbigen Hintergrunds. Verwenden Sie palette().color(QPalette::Text) für die Textfarbe. Überschreiben Sie sizeHint() zur Rückgabe einer passenden Größe (z.B. 150×50).
Mittleres Level
Interaktiver Zähler-Indikator
Entwickeln Sie ein Zähler-Widget, das einen numerischen Wert als sich füllenden Kreis darstellt (Fortschritt von 0 bis 100). Das Widget sollte Slots zum Erhöhen/Verringern des Werts haben, ein Signal bei Änderung senden, korrekt mit Themes arbeiten und Mausklicks zur Wertänderung unterstützen.
Hinweise: Erben Sie von QWidget. Überschreiben Sie paintEvent() mit Verwendung von QPainter::drawPie() für den Kreis. Überschreiben Sie mousePressEvent() für Interaktivität. Verwenden Sie QPalette für Farben. Definieren Sie das Signal valueChanged(int). Setzen Sie QSizePolicy::Fixed für beide Richtungen, damit das Widget quadratisch bleibt. Verwenden Sie std::clamp() zur Wertbegrenzung.
Schwieriges Level
Multifunktionales Diagramm-Widget
Erstellen Sie ein Widget zur Darstellung eines Liniendiagramms mit Zoom und Scrolling. Das Widget sollte ein Array von Datenpunkten akzeptieren, automatisch an die Fenstergröße skalieren, Dark und Light Themes unterstützen, anpassbare Linienfarben über Slots haben, ein Signal beim Klick auf einen Datenpunkt senden und korrekt in allen Layouts mit Expanding-Policy funktionieren.
Hinweise: Erben Sie von QWidget. Speichern Sie Daten in QVector<QPointF>. Überschreiben Sie paintEvent() mit Koordinatenskalierung. Verwenden Sie QPainterPath zum Zeichnen von Linien. Überschreiben Sie mousePressEvent() und wheelEvent() für Interaktivität. Implementieren Sie resizeEvent() zur Neuberechnung des Maßstabs. Verwenden Sie palette() für alle Farben. Definieren Sie Signale pointClicked(int) und dataChanged(). Setzen Sie setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding). Fügen Sie Antialiasing über setRenderHint() hinzu.
💬 Beteiligen Sie sich an der Diskussion!
Eigene Widgets verstanden? Fragen zum Überschreiben von Ereignismethoden oder zur Arbeit mit QSizePolicy aufgetaucht?
Teilen Sie Ihre Widgets, berichten Sie von Schwierigkeiten bei der paintEvent()-Implementierung oder diskutieren Sie Best Practices für Dark-Theme-Unterstützung!