1. #!trpst#trp-gettext data-...
  2. #!trpst#trp-gettext data-...
  3. QT
  4. Qt Quick ve QML
  5. QML ListView ve C++ Modelleri (QAbstractListModel) ile Akıcı Listeler

QML ListView ve C++ Modelleri (QAbstractListModel) ile Akıcı Listeler

Print

Mobil cihazlarda, gömülü dokunmatik panellerde ve modern masaüstü uygulamalarında binlerce satırlık listeleri (rehber, şarkı listesi, telemetri logları, ağ cihazları) parmak hareketleriyle kaydırırken en ufak bir takılma (jank/stutter) kullanıcı deneyimini doğrudan bozar. Bu tür akıcı arayüzlerde QML ListView C++ modeli entegrasyonu; QML’in esnek delegasyon tasarımını C++’ın sıfır ek yüklü bellek performansı ile harmanlayarak kusursuz 60 FPS kaydırma performansı sunar.

Bu rehberde; QAbstractListModel sınıfını türetmeyi, QML’deki değişken adlarını belirleyen roleNames() hash tablosunu kurmayı, ListView ve delegate yapısını ve bellek tüketimini kontrol altında tutan cacheBuffer optimizasyonlarını adım adım işleyeceğiz.


QML ListView Mimarisi

QML’deki bir liste görünümü üç bileşenden meydana gelir:

  1. Model (QAbstractListModel): Verilerin C++ tarafında tutulduğu dinamik koleksiyon.
  2. View (ListView): Kaydırma, fırlatma (flickable) ve ekran içi görünürlük hesaplarını yöneten QML ögesi.
  3. Delegate: Listedeki tek bir satırın ekranda nasıl görüneceğini (kart, buton, ikonlar) belirleyen görsel şablon.

Adım 1: C++ Veri Modeli (DeviceListModel)

Örnek Senaryo: Bir IoT ağında bulunan bağlı akıllı cihazları (Device) listeleyen bir model kuralım.

Veri Yapısı (Device)

#include <QString>

struct Device {
    QString name;
    QString ipAddress;
    bool isOnline;
    int batteryLevel;
};

devicelistmodel.h

#ifndef DEVICELISTMODEL_H
#define DEVICELISTMODEL_H

#include <QAbstractListModel>
#include <vector>

class DeviceListModel : public QAbstractListModel {
    Q_OBJECT

public:
    // QML tarafında doğrudan 'name', 'ip', 'online', 'battery' olarak kullanılacak roller
    enum DeviceRoles {
        NameRole = Qt::UserRole + 1,
        IpRole,
        OnlineRole,
        BatteryRole
    };

    explicit DeviceListModel(QObject *parent = nullptr);

    // QAbstractListModel Zorunlu Metotları
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    QHash<int, QByteArray> roleNames() const override;

    // Veri Manipülasyon Metotları
    Q_INVOKABLE void addDevice(const QString &name, const QString &ip, bool online, int battery);
    Q_INVOKABLE void removeDevice(int index);

private:
    std::vector<Device> m_devices;
};

#endif // DEVICELISTMODEL_H

devicelistmodel.cpp

#include "devicelistmodel.h"

DeviceListModel::DeviceListModel(QObject *parent) : QAbstractListModel(parent) {
    // Örnek başlangıç verileri
    m_devices.push_back({"Gateway Hub", "192.168.1.1", true, 100});
    m_devices.push_back({"Sıcaklık Sensörü", "192.168.1.45", true, 84});
    m_devices.push_back({"Akıllı Röle", "192.168.1.80", false, 0});
}

int DeviceListModel::rowCount(const QModelIndex &parent) const {
    if (parent.isValid()) return 0;
    return static_cast<int>(m_devices.size());
}

QVariant DeviceListModel::data(const QModelIndex &index, int role) const {
    if (!index.isValid() || index.row() >= static_cast<int>(m_devices.size()))
        return QVariant();

    const Device &device = m_devices[index.row()];

    switch (role) {
        case NameRole:    return device.name;
        case IpRole:      return device.ipAddress;
        case OnlineRole:  return device.isOnline;
        case BatteryRole: return device.batteryLevel;
        default: break;
    }

    return QVariant();
}

// QML'e rollerin hangi string isimle bağlanacağını bildirir (Kritik!)
QHash<int, QByteArray> DeviceListModel::roleNames() const {
    QHash<int, QByteArray> roles;
    roles[NameRole] = "name";
    roles[IpRole] = "ipAddress";
    roles[OnlineRole] = "isOnline";
    roles[BatteryRole] = "batteryLevel";
    return roles;
}

void DeviceListModel::addDevice(const QString &name, const QString &ip, bool online, int battery) {
    int newIndex = static_cast<int>(m_devices.size());
    beginInsertRows(QModelIndex(), newIndex, newIndex);
    m_devices.push_back({name, ip, online, battery});
    endInsertRows();
}

void DeviceListModel::removeDevice(int index) {
    if (index < 0 || index >= static_cast<int>(m_devices.size())) return;
    beginRemoveRows(QModelIndex(), index, index);
    m_devices.erase(m_devices.begin() + index);
    endRemoveRows();
}

Adım 2: Modeli QML’e Kaydetmek (main.cpp)

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "devicelistmodel.h"

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    DeviceListModel deviceModel;

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("deviceModel", &deviceModel);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

Adım 3: QML Tarafında ListView ve Delegate Tasarımı

import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 450
    height: 600
    title: "Ağ Cihazları Listesi"
    color: "#18191a"

    ListView {
        id: deviceListView
        anchors.fill: parent
        anchors.margins: 10
        spacing: 8
        clip: true // Liste sınırları dışına taşan ögeleri gizle

        // C++ Tarafındaki Modeli Bağla
        model: deviceModel

        // Performans Optimizasyonu: Ekranda olmayan 100 piksellik tamponu hazır tut
        cacheBuffer: 200

        // Liste Boşken Gösterilecek Mesaj
        // (Empty state placeholder)

        // TEK BİR SATIRIN GÖRSEL DELEGESİ (DELEGATE)
        delegate: Rectangle {
            id: card
            width: deviceListView.width
            height: 70
            radius: 10
            color: mouseArea.pressed ? "#2d3436" : "#242526"

            Behavior on color { ColorAnimation { duration: 150 } }

            Row {
                anchors.fill: parent
                anchors.margins: 15
                spacing: 15

                // Çevrimiçi/Çevrimdışı Durum Göstergesi
                Rectangle {
                    width: 12
                    height: 12
                    radius: 6
                    anchors.verticalCenter: parent.verticalCenter
                    color: isOnline ? "#2ecc71" : "#e74c3c"
                }

                // Cihaz Adı ve IP Adresi
                Column {
                    anchors.verticalCenter: parent.verticalCenter
                    spacing: 4

                    Text {
                        text: name // C++ NameRole doğrudan burada!
                        font.pixelSize: 16
                        font.bold: true
                        color: "#ffffff"
                    }

                    Text {
                        text: ipAddress // C++ IpRole
                        font.pixelSize: 12
                        color: "#b0b3b8"
                    }
                }

                Item { Layout.fillWidth: true; width: 1 } // Araya esnek boşluk

                // Pil Seviyesi
                Text {
                    anchors.verticalCenter: parent.verticalCenter
                    text: isOnline ? (batteryLevel + "% 🔋") : "Kapalı"
                    color: (batteryLevel < 20) ? "#e74c3c" : "#b0b3b8"
                    font.bold: true
                }
            }

            // Satıra Tıklama Olayı
            MouseArea {
                id: mouseArea
                anchors.fill: parent
                onClicked: {
                    console.log("Seçilen Cihaz:", name, "Index:", index);
                }
            }
        }
    }
}

ListView Performans Kuralları

  1. clip: true Kullanımı: ListView sınırları dışındaki çizimleri kırpmak ekran kartı (GPU) aşırı çizimini (overdraw) önler.
  2. cacheBuffer Ayarı: Kaydırma sırasında yeni satırların gecikmeden ekrana gelmesi için cacheBuffer: 200 gibi makul bir tampon bellek bırakın. Değeri çok yüksek tutmayın; gereksiz RAM tüketir.
  3. Delegateleri Basit Tutun: Bir delege içinde onlarca iç içe Item veya ağır görsel efektler (DropShadow, FastBlur) kullanmaktan kaçının.

Özet

QML ListView ve QAbstractListModel kombinasyonu; veri güvenliği, düşük bellek tüketimi ve yüksek animasyon akıcılığını bir araya getirir. roleNames eşleşmesi ile C++ verilerini QML delege elemanlarına bağlayarak modern ve güçlü liste uygulamaları inşa edebilirsiniz.

Tags , , , ,

How can we help?