Kalender Backend Plugin Beispiel

QCalendar Beispiel zur Veranschaulichung von benutzerdefinierten Kalendern, die vom Benutzer bereitgestellt werden.

Einführung

Es gibt zahlreiche verschiedene Kalendersysteme, die rund um den Globus verwendet werden. Qt verfügt über eingebaute Unterstützung für einige von ihnen (siehe System), kann aber aufgrund ihrer großen Anzahl keine allgemeine Unterstützung bieten. Zusätzliche Kalendersysteme können durch die Implementierung eines benutzerdefinierten QCalendarBackends bereitgestellt werden, das eine private API ist.

Dieses Beispiel zeigt, wie man ein benutzerdefiniertes Kalender-Backend schreibt und wie man die Low-Level-Plugin-API verwendet, um eine Anwendung um vom Benutzer auswählbare Kalender zu erweitern. Viele Länder sind irgendwann in ihrer Geschichte vom julianischen zum gregorianischen Kalender übergegangen, und dieses benutzerdefinierte Kalender-Backend wird den entsprechenden Kalender als Beispiel implementieren. Das benutzerdefinierte Backend wird in ein Plugin kompiliert und zur Laufzeit von der Hauptanwendung geladen. Das genaue Umstellungsdatum, das für verschiedene Regionen unterschiedlich ist, wird dem Plugin als String übergeben und kann vom Benutzer bestimmt werden.

Kalender-Backend

Die Kalender-Backend-Klasse muss von QCalendarBackend erben und ihre rein virtuellen Funktionen auf thread-safe implementieren. Sie kann auch einige andere virtuelle Funktionen nach Bedarf überschreiben.

Beispiel-Implementierung

Dieses Beispiel erbt von der bereits existierenden QRomanCalendar, die wiederum von der QCalendarBackend erbt und einige ihrer virtuellen Funktionen implementiert. Dies ist konstruktiv, weil der Übergangskalender sowohl mit dem Julianischen als auch mit dem Gregorianischen Kalender Teile des Römischen Kalenders teilt.

Hier ist die Klassendeklaration von JulianGregorianCalendar:

class JulianGregorianCalendar : public QRomanCalendar
{
public:
    JulianGregorianCalendar(QDate endJulian, QAnyStringView name);
    QString name() const override;
    int daysInMonth(int month, int year = QCalendar::Unspecified) const override;
    bool isLeapYear(int year) const override;
    bool dateToJulianDay(int year, int month, int day, qint64 *jd) const override;
    QCalendar::YearMonthDay julianDayToDate(qint64 jd) const override;
private:
    static inline const QCalendar julian = QCalendar(QCalendar::System::Julian);
    static inline const QCalendar gregorian = QCalendar(QCalendar::System::Gregorian);
    QCalendar::YearMonthDay m_julianUntil;
    QCalendar::YearMonthDay m_gregorianSince;
    QString m_name;
};

Die an den Konstruktor übergebene QDate - endJulian - ist das Datum des letzten Tages des Julianischen Kalenders. Der Kalender berechnet automatisch die Verschiebung für ein bestimmtes Jahr, z.B. wurden im Jahr 1582 10 Tage weggelassen, aber im Jahr 1700 mussten 12 Tage weggelassen werden. Das Kalender-Backend ist unter name registriert und eine Kalenderinstanz kann unter diesem Namen erstellt werden. Die Klasse überschreibt nur Funktionen, bei denen die beiden von ihr kombinierten Kalender von der römischen Basis abweichen. Sie hat Instanzen des Julianischen und Gregorianischen Kalenders, an die diese Funktionen delegieren können.

Julianischer Tag - Konvertierungen

dateToJulianDay(int year, int month, int day, qint64 *jd) berechnet die julianische Tageszahl, die den angegebenen year, month und day entspricht. Gibt true zurück und setzt jd, wenn es ein solches Datum in diesem Kalender gibt; andernfalls gibt sie false zurück.

bool JulianGregorianCalendar::dateToJulianDay(int year, int month, int day, qint64 *jd) const
{
    if (year == m_julianUntil.year && month == m_julianUntil.month) {
        if (m_julianUntil.day < day && day < m_gregorianSince.day) {
            // Requested date is in the gap skipped over by the transition.
            *jd = 0;
            return false;
        }
    }
    QDate givenDate = gregorian.dateFromParts(year, month, day);
    QDate julianUntil = julian.dateFromParts(m_julianUntil);
    if (givenDate > julianUntil) {
        *jd = givenDate.toJulianDay();
        return true;
    }
    *jd = julian.dateFromParts(year, month, day).toJulianDay();
    return true;
}

julianDayToDate(qint64 jd) berechnet Jahr, Monat und Tag in diesem Kalender für die angegebene Nummer des julianischen Tages, jd. Wenn der angegebene Tag außerhalb des Geltungsbereichs dieses Kalenders liegt, lautet der Rückgabewert für isValid() false . Wenn in diesem Beispiel das angegebene Datum in die Lücke fällt, die durch den Übergang vom Julianischen zum Gregorianischen Kalender übersprungen wird, liegt es außerhalb des Geltungsbereichs.

QCalendar::YearMonthDay JulianGregorianCalendar::julianDayToDate(qint64 jd) const
{
    const qint64 jdForChange = julian.dateFromParts(m_julianUntil).toJulianDay();
    if (jdForChange < jd) {
        QCalendar gregorian(QCalendar::System::Gregorian);
        QDate date = QDate::fromJulianDay(jd);
        return gregorian.partsFromDate(date);
    } else if (jd <= jdForChange) {
        QCalendar julian(QCalendar::System::Julian);
        QDate date = QDate::fromJulianDay(jd);
        return julian.partsFromDate(date);
    }
    return QCalendar::YearMonthDay(QCalendar::Unspecified, QCalendar::Unspecified,
                                   QCalendar::Unspecified);
}
Unterstützung von Gebietsschemata

Ein Kalender kann im Allgemeinen seine eigene Benennung von Monaten und Wochentagen haben. Diese müssen in geeigneter Weise lokalisiert werden, um für alle Benutzer verständlich zu sein. Standardmäßig kümmert sich die Backend-Basisklasse um die Namen der Wochentage, was für diese julianisch/regorianischen Übergangskalender völlig ausreichend ist.

Obwohl ein Backend die Methoden zur Monatsbenennung direkt außer Kraft setzen kann, kann die Basisklassenversion dieser Methoden durch die Implementierung von localeMonthData() und localeMonthIndexData() angepasst werden, um Tabellen mit lokalisierten Monatsnamen bereitzustellen. Da der julianische und der gregorianische Kalender die gleiche Monatsbezeichnung verwenden, erben sie diese Anpassung von einer gemeinsamen Basis, QRomanCalendar. Das bedeutet auch, dass der benutzerdefinierte Kalender die gleichen Namen verwenden kann, indem er wiederum von dieser Basis erbt. Dies sorgt für die Lokalisierung.

Plugin

Qt-Anwendungen können durch Plugins erweitert werden. Dazu muss die Anwendung Plugins mit QPluginLoader erkennen und laden.

Ein Plugin schreiben

Um ein Plugin zu schreiben, muss als erstes eine rein virtuelle Klasse erstellt werden, die die Schnittstelle zwischen Plugin und Anwendung definiert.

In diesem Beispiel wurde die folgende Schnittstelle verwendet:

class RequestedCalendarInterface
{
public:
    RequestedCalendarInterface() = default;
    virtual QCalendar::SystemId loadCalendar(QAnyStringView requested) = 0;
    virtual ~RequestedCalendarInterface() = default;
};

und registrieren es im Qt-Meta-Objektsystem:

#define RequestedCalendarInterface_iid \
"org.qt-project.Qt.Examples.CalendarBackend.RequestedCalendarInterface/1.0"
Q_DECLARE_INTERFACE(RequestedCalendarInterface, RequestedCalendarInterface_iid)

Q_DECLARE_INTERFACEDas Makro () wird verwendet, um das ClassName (hier: RequestedCalendarInterface) mit dem definierten Identifier (hier: RequestedCalendarInterface_iid) zu verknüpfen. Die Identifier muss eindeutig sein. Diese Schnittstelle kann von Plugins implementiert werden, die andere Kalender laden und den String-Parameter von loadCalendar() auf verschiedene Weise interpretieren. Sie ist nicht auf dieses spezielle Plugin beschränkt, das damit implementiert wird, daher hat sie einen generischen Namen, der nicht spezifisch für dieses spezielle Backend ist.

Dann wird eine Plugin-Klasse erstellt, die von QObject und von der Schnittstelle erbt.

class JulianGregorianPlugin : public QObject, public RequestedCalendarInterface
{
    Q_OBJECT
    Q_INTERFACES(RequestedCalendarInterface)
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples."
                          "CalendarBackend."
                          "RequestedCalendarInterface/1.0")
public:
    JulianGregorianPlugin();
    QCalendar::SystemId loadCalendar(QAnyStringView request) override;
    ~JulianGregorianPlugin();
};

Q_PLUGIN_METADATA() und Q_INTERFACES() werden verwendet, um Metadaten zu deklarieren, die auch in der Schnittstellenklasse deklariert wurden, und um Qt mitzuteilen, welche Schnittstelle die Klasse implementiert.

Dieses Plugin instanziiert und registriert ein benutzerdefiniertes Kalender-Backend, das wiederum von der Anwendung verwendet werden kann, um QCalendar zu einem beliebigen Zeitpunkt zu instanziieren.

Qt-Plugins werden in einer einzigen gemeinsam genutzten Bibliothek (einer DLL) gespeichert, und QPluginLoader wird zur Erkennung und zum dynamischen Laden der Plugin-Datei verwendet (weitere Informationen finden Sie unter Wie man Qt-Plugins erstellt).

Laden des Plugins

QPluginLoader prüft, ob die Qt-Version des Plugins mit der der Anwendung übereinstimmt und bietet direkten Zugriff auf ein Qt-Plugin.

Hier ist die Verwendung von QPluginLoader in diesem Beispiel:

    QPluginLoader loader;
    loader.setFileName("../plugin/calendarPlugin");
    loader.load();
    if (!loader.isLoaded())
        return 1;
    auto *myplugin = qobject_cast<RequestedCalendarInterface*>(loader.instance());

Zunächst muss eine Instanz eines QPluginLoader -Objekts initialisiert werden. Als nächstes muss angegeben werden, welches Plugin geladen werden soll, indem ein DLL-Dateiname an setFileName() übergeben wird. Dann wird mit load() die Plugin-Datei dynamisch geladen. Am Ende wird durch einen Aufruf von qobject_cast() getestet, ob ein Plugin eine bestimmte Schnittstelle implementiert. qobject_cast() verwendet instance(), um auf die Wurzelkomponente im Plugin zuzugreifen. Wenn das Plugin korrekt geladen wurde, sollten seine Funktionen verfügbar sein.

Instanziierung des Backends

In diesem Beispiel gibt es nur eine Funktion im Plugin. loadCalendar() ist für die Registrierung des benutzerdefinierten Kalender-Backends in QCalendarRegistry mit gegebenem Datum des Übergangs und Namen verantwortlich.

QCalendar::SystemId JulianGregorianPlugin::loadCalendar(QAnyStringView request)
{
    Q_ASSERT(!request.isEmpty());
    QStringList names = request.toString().split(u';');
    if (names.size() < 1)
        return {};
    QString dateString = names.takeFirst();
    auto date = QDate::fromString(dateString, u"yyyy-MM-dd",
                                  QCalendar(QCalendar::System::Julian));
    if (!date.isValid())
        return {};
    QString primary = names.isEmpty() ?
            QString::fromStdU16String(u"Julian until ") + dateString : names[0];
    auto backend = new JulianGregorianCalendar(date, primary);
    names.emplaceFront(backend->name());
    auto cid = backend->registerCustomBackend(names);
    return cid;
}

JulianGregorianPlugin::~JulianGregorianPlugin()
{
}

Das String-Argument für loadCalendar() wird vom Benutzer über Kommandozeilenargumente angegeben. Dann wird das Datum des Übergangs vom julianischen zum gregorianischen Kalender extrahiert, indem die angegebene Zeichenkette zerlegt wird. Nach der Validierung wird ein benutzerdefiniertes Backend-Objekt erstellt. Das Backend muss registriert werden, bevor es in QCalendar verwendet werden kann. Dazu wird die Methode registerCustomBackend() verwendet. Sobald ein Backend registriert ist, kann ein QCalendar mit dem entsprechenden SystemId oder name instanziiert werden.

Hier ist die Verwendung von loadCalendar in der main:

   const auto cid =  myplugin->loadCalendar(args.at(0)); if (!cid.isValid()) {        qWarning() << "Invalid ID";
        parser.showHelp(1); } const QCalendar Kalender(cid);
Erweitern von QCalendarWidget

Durch das Erstellen einer QCalendar Instanz mit einem bestimmten Kalender als Backend ist es möglich, QCalendarWidget mit diesem Backend zu versehen und zu visualisieren.

    QCalendarWidget widget;
    widget.setCalendar(calendar);
    widget.show();
    QCalendar::YearMonthDay when = { 1582, 10, 4 };
    QCalendar julian = QCalendar(QCalendar::System::Julian);
    auto got = QDate::fromString(args.at(0).left(10), u"yyyy-MM-dd", julian);
    if (got.isValid())
        when = julian.partsFromDate(got);
    widget.setCurrentPage(when.year, when.month);

Beispielprojekt @ code.qt.io

Siehe auch QCalendarWidget, QCalendar, QDate, QLocale, QtPlugin, und QPluginLoader.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.