1. Home
  2. Docs
  3. QT
  4. Genel
  5. Custom Widget Çizimi: QPainter ile Özel Gösterge ve Grafik Yapımı

Custom Widget Çizimi: QPainter ile Özel Gösterge ve Grafik Yapımı

Print

Qt Framework’ün sunduğu standart butonlar, etiketler ve kaydırma çubukları genel masaüstü yazılımları için fazlasıyla yeterlidir. Ancak endüstriyel otomasyon arayüzleri, telemetri ekranları veya otomotiv panelleri geliştirirken dairesel hız göstergeleri (gauge), dalga formu monitörleri veya özel durum ışıkları tasarlamanız gerekir. Bu tür ileri düzey grafiksel gereksinimlerde QPainter ile custom widget oluşturmak, piksel düzeyinde tam kontrol ve yüksek donanımsal çizim performansı sunar.

Bu rehberde; bir QWidget sınıfından türeyerek paintEvent metodunu ezmeyi (override), kenar yumuşatmayı (Antialiasing), QPainter koordinat sistemi dönüşümlerini ve dinamik bir dairesel analog gösterge (Gauge Widget) yapımını adım adım inşa edeceğiz.


Özel Widget Çiziminin Yaşam Döngüsü

Qt’de bir widget ekrana her çizildiğinde veya pencere yeniden boyutlandırıldığında (resize), Qt olay döngüsü ilgili nesneye bir QPaintEvent iletir.

Kendi görselimizi çizmek için:

  1. QWidget sınıfından yeni bir C++ sınıfı türetiriz.
  2. paintEvent(QPaintEvent *event) sanal fonksiyonunu ezerek (override) içine QPainter nesnesi bağlarız.
  3. Çizimi güncellemek istediğimizde (örneğin yeni bir sensör verisi geldiğinde) widget üzerinde update() fonksiyonunu çağırırız.

Altın Kural: paintEvent içinde asla ağır hesaplamalar, dosya okuma veya ağ istekleri yapmayın! Sadece mevcut durumu ekrana çizin. Yeniden çizim talebi için doğrudan paintEvent çağırmayın, daima this->update() metodunu kullanın.


Örnek Proje: Dairesel Analog Hız Göstergesi (Speedometer Gauge)

Gelin sıfırdan değer alan, iğnesi dönen ve renkli kadranı olan bir AnalogGauge sınıfı tasarlayalım.

Başlık Dosyası: analoggauge.h

#ifndef ANALOGGAUGE_H
#define ANALOGGAUGE_H

#include <QWidget>
#include <QPainter>

class AnalogGauge : public QWidget {
    Q_OBJECT
    Q_PROPERTY(double value READ value WRITE setValue NOTIFY valueChanged)

public:
    explicit AnalogGauge(QWidget *parent = nullptr);

    double value() const { return m_value; }
    void setValue(double val);
    void setRange(double min, double max);

signals:
    void valueChanged(double newValue);

protected:
    void paintEvent(QPaintEvent *event) override;
    QSize sizeHint() const override { return QSize(200, 200); }

private:
    double m_value = 0.0;
    double m_minValue = 0.0;
    double m_maxValue = 100.0;
};

#endif // ANALOGGAUGE_H

Kaynak Dosyası: analoggauge.cpp

#include "analoggauge.h"
#include <QtMath>

AnalogGauge::AnalogGauge(QWidget *parent) : QWidget(parent) {
    // Arka planın şeffaf olmasına veya otomatik silinmesine izin ver
    setAttribute(Qt::WA_OpaquePaintEvent, false);
}

void AnalogGauge::setValue(double val) {
    if (qFuzzyCompare(m_value, val)) return;
    
    // Değeri sınırlar içine hapsedelim
    m_value = qBound(m_minValue, val, m_maxValue);
    emit valueChanged(m_value);
    
    // Qt'ye widget'ın yeniden çizilmesi gerektiğini bildir
    update();
}

void AnalogGauge::setRange(double min, double max) {
    m_minValue = min;
    m_maxValue = max;
    update();
}

void AnalogGauge::paintEvent(QPaintEvent *event) {
    Q_UNUSED(event);

    QPainter painter(this);
    
    // 1. Kenar yumuşatmayı (Antialiasing) aç: Pürüzsüz çizgiler ve yaylar için şarttır!
    painter.setRenderHint(QPainter::Antialiasing);

    // 2. Widget boyutuna göre merkezleme ve ölçekleme
    int side = qMin(width(), height());
    painter.setViewport((width() - side) / 2, (height() - side) / 2, side, side);
    painter.setWindow(-100, -100, 200, 200); // Koordinat merkezini (0,0) tam ortaya alır

    // 3. Kadran Arka Plan Çemberi
    QRadialGradient bgGradient(0, 0, 100);
    bgGradient.setColorAt(0.0, QColor(45, 52, 54));
    bgGradient.setColorAt(1.0, QColor(29, 31, 33));
    painter.setBrush(bgGradient);
    painter.setPen(QPen(QColor(99, 110, 114), 4));
    painter.drawEllipse(-90, -90, 180, 180);

    // 4. Renkli Skala Yayı Çizimi (Yeşilden Kırmızıya)
    QPen arcPen;
    arcPen.setWidth(8);
    arcPen.setCapStyle(Qt::FlatCap);
    
    // Güvenli bölge (Yeşil)
    arcPen.setColor(QColor(46, 204, 113));
    painter.setPen(arcPen);
    painter.drawArc(-75, -75, 150, 150, -45 * 16, -150 * 16);

    // Tehlike bölgesi (Kırmızı)
    arcPen.setColor(QColor(231, 76, 60));
    painter.setPen(arcPen);
    painter.drawArc(-75, -75, 150, 150, -195 * 16, -90 * 16);

    // 5. Değer İbresi (İğne - Needle) Çizimi
    // Mevcut değere göre açıyı hesaplayalım (225 derecelik yay alanı)
    double ratio = (m_value - m_minValue) / (m_maxValue - m_minValue);
    double angle = 135.0 + (ratio * 270.0);

    painter.save(); // Koordinat durumunu yedekle
    painter.rotate(angle); // İğne açısı kadar ekseni döndür

    // İğne Geometrisi (İnce Poligon)
    static const QPoint needle[3] = {
        QPoint(-3, 0),
        QPoint(3, 0),
        QPoint(0, 70)
    };
    painter.setPen(Qt::NoPen);
    painter.setBrush(QColor(230, 126, 34)); // Turuncu ibre
    painter.drawPolygon(needle, 3);
    painter.restore(); // Ekseni orijinal haline getir

    // 6. Merkez Göbek Pimi
    painter.setPen(Qt::NoPen);
    painter.setBrush(QColor(236, 240, 241));
    painter.drawEllipse(-8, -8, 16, 16);

    // 7. Sayısal Değer Metni Yazımı
    painter.setPen(QColor(245, 246, 250));
    painter.setFont(QFont("Arial", 12, QFont::Bold));
    painter.drawText(QRect(-50, 35, 100, 30), Qt::AlignCenter, QString::number(m_value, 'f', 1));
}

QPainter Performans İpuçları

  1. setWindow() ve setViewport() Gücü: Çizim kodlarında piksel hesapları yapmak yerine setWindow(-100, -100, 200, 200) tanımlayarak sanal bir koordinat uzayı kurun. Pencere boyutu değişse bile çiziminiz kalitesinden ve oranlarından hiçbir şey kaybetmeden otomatik ölçeklenir.
  2. save() ve restore() Kullanımı: Dönüşüm matrislerini (rotate, translate, scale) kullanırken mutlaka painter.save() ile durumu kaydedip işlem bitince painter.restore() ile sıfırlayın.
  3. Pikselleri Önbelleğe Almak (QPixmap Cache): Her karede değişmeyen statik arka plan kadranlarını veya cetvel çizgilerini bir QPixmap içerisine bir kez çizip paintEvent içinde sadece drawPixmap ile basmak, CPU yükünü dramatik şekilde düşürür.

Signals ve Slots ile Entegrasyon

Hazırladığımız bu widget’ı QSerialPort rehberimizde geliştirdiğimiz seri port istemcisine tek satırla bağlayabiliriz:

// Arduino'dan gelen analog potansiyometre verisini göstergeye bağla
connect(serialWorker, &SerialWorker::temperatureReceived,
        gaugeWidget, &AnalogGauge::setValue);

Özet

QPainter, Qt GUI dünyasının fırçasıdır. paintEvent içinde gradyanlar, yaylar ve koordinat dönüşümleri kullanarak endüstri standartlarında göstergeler, özel butonlar ve etkileyici veri grafikleri inşa edebilirsiniz.

Tags , , , , ,

How can we help?