29/06/2009
I hjertet af Qt's grafiske brugerflader ligger et kraftfuldt og fleksibelt malesystem, der gør det muligt for udviklere at skabe alt fra simple knapper til komplekse, interaktive diagrammer. Centralt i dette system er QPainter-klassen, et alsidigt værktøj, der fungerer som en digital kunstner, klar til at male på forskellige overflader. At forstå QPainter er essentielt for enhver Qt-udvikler, der ønsker at have fuld kontrol over udseendet og fornemmelsen af deres applikationer. Denne artikel vil guide dig gennem QPainter's funktioner, fra de grundlæggende principper til avancerede teknikker, og give dig den viden, du har brug for til at bringe dine grafiske visioner til live.

Kernen i Qt's Malesystem: Trioen QPainter, QPaintDevice og QPaintEngine
For at forstå, hvordan QPainter fungerer, er det vigtigt at kende de to andre klasser, det arbejder tæt sammen med. Tilsammen udgør de grundlaget for al tegning i Qt.
- QPaintDevice: Dette er lærredet. Et QPaintDevice er en abstraktion af ethvert objekt, der kan tegnes på. De mest almindelige eksempler er en
QWidget(et vindueselement), enQPixmap(et off-screen billede optimeret til skærmvisning) eller enQImage(en hardware-uafhængig billedrepræsentation). - QPainter: Dette er kunstneren. QPainter indeholder alle tegnefunktionerne, såsom
drawLine(),drawRect()ogdrawText(). Du giver QPainter kommandoer om, hvad der skal tegnes, og hvor det skal tegnes. - QPaintEngine: Dette er oversætteren. QPaintEngine er en intern klasse, der fungerer som broen mellem QPainter's abstrakte kommandoer og den specifikke QPaintDevice. For eksempel vil en QPaintEngine for en
QWidgetoversætte tegnekommandoer til native kald til operativsystemets grafik-API, mens en QPaintEngine for enQImagevil manipulere pixeldata direkte i hukommelsen.
Denne adskillelse gør QPainter utrolig fleksibel. Den samme tegnekode kan bruges til at tegne på skærmen, generere en billedfil eller sende output til en printer, blot ved at initialisere QPainter med en anden QPaintDevice.
Sådan Kommer Du i Gang: paintEvent og QPainter's Livscyklus
Den mest almindelige måde at bruge QPainter på er inden i en paintEvent()-metode for en custom widget. Denne metode kaldes automatisk af Qt, hver gang widget'en skal genoptegnes, f.eks. når den først vises, ændrer størrelse eller bliver afdækket.
En typisk livscyklus ser således ud:
- En
QPaintEventudløses. - Inden i din overskrevne
paintEvent(QPaintEvent *event)-metode opretter du et QPainter-objekt. - Du initialiserer maleren, oftest ved at give den en reference til den aktuelle widget:
QPainter painter(this);. - Du bruger malerens funktioner til at tegne.
- Når
paintEvent-metoden afsluttes, destrueres QPainter-objektet automatisk, hvilket frigør alle ressourcer.
Konstruktøren QPainter(QPaintDevice *device) kalder automatisk begin() for at aktivere maleren. Når objektet går ud af scope, kalder destruktøren automatisk end(). Dette er den foretrukne metode for kortlivede malere, som dem i et paintEvent. For langvarige operationer eller tegning på enheder, hvor initialisering kan fejle (som en printer), er det sikrere at bruge den tomme konstruktør og manuelt kalde begin() og end() for at kunne håndtere eventuelle fejl.
Grundlæggende Værktøjer: Pen og Pensel
Før du kan tegne noget, skal du definere, hvordan det skal se ud. QPainter bruger to primære værktøjer til dette: pennen (QPen) og penslen (QBrush).
QPen: Definition af Konturer
En QPen bruges til at tegne linjer og omrids af former. Du kan kontrollere tre hovedegenskaber:
- Farve: Definerer farven på linjen.
- Bredde: Angiver tykkelsen af linjen i pixels.
- Stil: Bestemmer, om linjen skal være solid, stiplet, prikket osv. (f.eks.
Qt::SolidLine,Qt::DashLine).
Du indstiller pennen på maleren med painter.setPen(myPen);.

QBrush: Udfyldning af Former
En QBrush bruges til at udfylde det indre af lukkede former som rektangler, ellipser og polygoner. En pensel har en farve og en stil.
- Farve: Definerer udfyldningsfarven.
- Stil: Kan være en solid farve (
Qt::SolidPattern), et mønster (f.eks.Qt::Dense1Pattern,Qt::CrossPattern) eller endda en gradient eller en tekstur.
Du indstiller penslen med painter.setBrush(myBrush);.
Tegning af Primitive Former
QPainter tilbyder en rig samling af funktioner til at tegne forskellige geometriske former. Alle disse funktioner findes i versioner, der accepterer både heltal (int) og flydende tal (qreal) for højere præcision.
Simple Former
drawPoint(x, y): Tegner et enkelt punkt.drawLine(x1, y1, x2, y2): Tegner en linje mellem to punkter.drawRect(x, y, width, height): Tegner et rektangel.drawRoundedRect(...): Tegner et rektangel med afrundede hjørner.drawEllipse(x, y, width, height): Tegner en ellipse inden for det angivne rektangel.
Komplekse Former og Stier
For mere komplekse former er QPainterPath et utroligt kraftfuldt værktøj. En Painter Path er en container for tegneoperationer, der kan genbruges. Du kan bygge en form ved at flytte til et startpunkt (moveTo()) og derefter tilføje linjer (lineTo()), kurver (cubicTo()) og andre former.
Når din sti er defineret, kan du tegne den med et enkelt kald:
drawPath(path): Tegner omridset af stien med den aktuelle pen.fillPath(path, brush): Udfylder det indre af stien med den angivne pensel.
Avancerede Funktioner
Ud over grundlæggende former tilbyder QPainter avancerede muligheder for at manipulere tegneoperationer.
Koordinattransformationer
Normalt tegner QPainter i enhedens eget koordinatsystem (typisk pixels med (0,0) i øverste venstre hjørne). Men du kan transformere koordinatsystemet for at opnå komplekse effekter uden at skulle genberegne alle dine punkter manuelt.

translate(dx, dy): Flytter origo (0,0) til en ny position.scale(sx, sy): Skalerer alt, hvad der tegnes efterfølgende.rotate(angle): Roterer koordinatsystemet med uret om origo.shear(sh, sv): Forvrænger koordinatsystemet.
Disse transformationer er kumulative. Det er god praksis at gemme malerens tilstand med painter.save() før du anvender transformationer, og gendanne den bagefter med painter.restore() for at undgå uventede sideeffekter.
Klipning (Clipping)
Du kan begrænse tegneområdet til en bestemt form. Alt, hvad der tegnes uden for denne form, vil blive ignoreret. Dette er kendt som klipning og er nyttigt til at skabe effekter som f.eks. et rundt profilbillede. Du kan indstille en klipningsregion med setClipRect(), setClipRegion() eller endda en kompleks setClipPath().
Rendering Hints for Bedre Kvalitet
For at opnå glattere og mere æstetisk tiltalende grafik kan du aktivere anti-aliasing. Dette udglatter de takkede kanter på former og tekst.
painter.setRenderHint(QPainter::Antialiasing, true);
Andre hints inkluderer TextAntialiasing for tekst og SmoothPixmapTransform for billeder af højere kvalitet under skalering.
Ydeevne: Hurtige og Langsomme Operationer
Ikke alle tegneoperationer er lige hurtige. I applikationer, hvor ydeevne er kritisk, er det vigtigt at vide, hvilke operationer der er optimeret. Qt fokuserer på at gøre visse operationer ekstremt hurtige på specifikke backends (som OpenGL eller raster-motoren).
| Hurtige Operationer | Potentielt Langsommere Operationer |
|---|---|
| Tegning af simple primitive former (linjer, rektangler) | Komplekse gradienter og penselmønstre |
| Udfyldning af rektangler med solide farver | Tegning af komplekse QPainterPath-objekter |
Tegning af pixmaps og billeder (drawPixmap, drawImage) | Transformationer, der ikke er simple (f.eks. komplekse shear) |
Tegning af forud-renderet tekst (QStaticText) | Rendering med anti-aliasing (kræver flere beregninger) |
Generelt er drawPixmap() ofte hurtigere til skærmvisning, da det kan optimeres af hardwaren, mens drawImage() kan være hurtigere på andre enheder som printere. For at opnå den bedste ydeevne, især ved gentagne tegninger af den samme komplekse form, kan det betale sig at cache resultatet i en QPixmap og derefter blot tegne denne pixmap.

Ofte Stillede Spørgsmål (FAQ)
Hvad er forskellen mellem `QPainter`, `QPaintDevice` og `QPaintEngine`?
Tænk på det som en analogi: QPaintDevice er lærredet, QPainter er kunstneren med pensler og maling, og QPaintEngine er den teknik, kunstneren bruger til at påføre maling på den specifikke type lærred.
Skal jeg bruge `begin()`/`end()` eller konstruktøren?
For tegning inde i en paintEvent er det nemmest og sikrest at bruge konstruktøren: QPainter painter(this);. Destruktøren vil automatisk rydde op. Brug kun begin() og end() manuelt, hvis du har brug for at håndtere potentielle fejl under initialiseringen, eller hvis maleren skal leve længere end et enkelt scope.
Hvordan gør jeg min tegning glattere?
Brug rendering hints. Kald painter.setRenderHint(QPainter::Antialiasing, true); i starten af din paintEvent for at aktivere kantudglatning på dine former. Dette vil give et meget mere professionelt udseende.
Hvorfor virker min `QPainter` kun i `paintEvent()` på en widget?
Qt's GUI-system er event-drevet. En widget kan kun garantere, at dens visuelle repræsentation er i en stabil tilstand og klar til at blive tegnet på, når den udsender et paintEvent. At forsøge at tegne uden for denne metode kan føre til uforudsigelig adfærd, visuelle artefakter eller endda programnedbrud.
Hvad er forskellen på `drawPixmap` og `drawImage`?
QPixmap er optimeret til den specifikke skærm, den vises på, hvilket gør drawPixmap() meget hurtig til visning på skærmen. QImage er en hardware-uafhængig repræsentation, der er bedre egnet til pixel-manipulation og tegning på tværs af forskellige enheder, f.eks. når du gemmer til en fil eller printer.
Hvis du vil læse andre artikler, der ligner Mestring af 2D-grafik med QPainter i Qt, kan du besøge kategorien Sundhed.
