日历后台插件示例
QCalendar 示例说明了用户提供的自定义日历。
简介
全球有许多不同的日历系统。Qt 已内置了对其中一些系统的支持(请参阅System ),但由于其数量众多,因此无法提供通用支持。其他日历系统可通过实现自定义 QCalendarBackend(这是一个私有 API)来提供。
本示例演示了如何编写自定义日历后端,以及如何使用底层插件 API 将应用程序扩展为用户可选日历。许多国家在历史上都曾从儒略历过渡到格里高利历,本自定义日历后端将以这些国家的日历为例进行说明。自定义后端编译成插件,并在主程序运行时加载。不同地区的确切过渡日期会以字符串的形式提供给插件,用户可自行决定。
日历后台
日历后台类必须继承自QCalendarBackend
,并以thread-safe
的方式实现其纯虚拟函数。它还可以根据需要覆盖其他一些虚拟函数。
实现示例
本示例继承自已有的QRomanCalendar
,而 又继承自QCalendarBackend
,并实现了其部分虚拟函数。这样做很有建设性,因为过渡历法与儒略历和格里高利历共享罗马历法提供的部分功能。
下面是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; };
传递给构造函数的QDate
-endJulian - 是儒略历最后一天的日期。日历会自动计算特定年份的转换,例如 1582 年省略了 10 天,但 1700 年必须省略 12 天。日历后台注册在name 下,可以使用该名称创建日历实例。该类只在其结合的两种历法与罗马基础历法不同的情况下覆盖函数。它有朱利安历和格里高利历的实例,这些函数可以委托给它们。
儒略日转换
dateToJulianDay(int year, int month, int day, qint64 *jd)
计算与指定的 、 和 相对应的儒略日数字。如果该日历中有这样的日期,则返回 并设置 ;否则,返回 。year month day true
jd false
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)
计算给定的儒略日号 在本日历中的年、月、日。如果给定的日期不在本日历的范围内,则 的返回值为 。在本例中,如果给定的日期位于从儒略历过渡到公历的间隙中,则不在范围内。jd isValid()
false
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); }
地区支持
一般来说,日历可能有自己的年月和星期命名。这些命名必须适当本地化,以便所有用户都能理解。默认情况下,后端基类会为我们处理星期名称,这对于这些儒略/格雷戈里过渡日历来说完全足够。
虽然后端可以直接覆盖月份命名方法,但可以通过实现localeMonthData()
和localeMonthIndexData()
来定制基类版本,以提供本地化的月份名称表。由于儒略历和格里高利历使用相同的月份命名方法,因此它们都从共同的基类QRomanCalendar
继承了自定义功能。这也意味着自定义日历可以使用相同的名称,同样也是继承自该基础。这就解决了本地化问题。
插件
Qt 应用程序可通过插件进行扩展。这就要求应用程序使用QPluginLoader.NET 来检测和加载插件。
编写插件
要编写插件,首先要创建一个纯虚类,定义插件与应用程序之间的接口。
本例中使用了以下接口:
class RequestedCalendarInterface { public: RequestedCalendarInterface() = default; virtual QCalendar::SystemId loadCalendar(QAnyStringView requested) = 0; virtual ~RequestedCalendarInterface() = default; };
并在 Qt 元对象系统中注册:
#define RequestedCalendarInterface_iid \ "org.qt-project.Qt.Examples.CalendarBackend.RequestedCalendarInterface/1.0" Q_DECLARE_INTERFACE(RequestedCalendarInterface, RequestedCalendarInterface_iid)
Q_DECLARE_INTERFACE() 宏用于将ClassName
(此处为:RequestedCalendarInterface
)与定义的Identifier
(此处为:RequestedCalendarInterface_iid
)关联起来。Identifier
必须是唯一的。该接口可由加载其他日历的插件实现,以各种方式解释loadCalendar()
的字符串参数。该接口并不局限于使用它的特定插件,因此它有一个通用名称,而不是特定于该后台的名称。
然后创建一个继承自QObject 和接口的插件类。
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() 和Q_INTERFACES() 被用来声明在接口类中也声明了的元数据,并告诉 Qt 该类实现了哪个接口。
该插件实例化并注册了一个自定义日历后台,应用程序可在任何时候使用该后台实例化QCalendar 。
Qt 插件存储在一个共享库(DLL)中,QPluginLoader 用于检测和动态加载插件文件(更多信息请参阅如何创建 Qt 插件)。
加载插件
QPluginLoader 检查插件的 Qt 版本是否与应用程序的版本相同,并提供对 Qt 插件的直接访问。
下面是QPluginLoader 在示例中的使用:
QPluginLoader loader; loader.setFileName("../plugin/calendarPlugin"); loader.load(); if (!loader.isLoaded()) return 1; auto *myplugin = qobject_cast<RequestedCalendarInterface*>(loader.instance());
首先,需要初始化QPluginLoader 对象的实例。然后,必须通过向setFileName() 传递 DLL 文件名来指定要加载的插件。然后,使用load() 动态加载插件文件。最后,调用qobject_cast() 测试插件是否实现了给定的接口。qobject_cast() 使用instance() 访问插件中的根组件。如果插件已正确加载,则其功能应该可用。
实例化后台
在本示例中,插件中只有一个函数。loadCalendar()
负责根据给定的过渡日期和名称在QCalendarRegistry
中注册自定义日历后台。
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() { }
loadCalendar()
的字符串参数由用户通过命令行参数提供。然后,通过分割给定的字符串,提取从儒略历过渡到公历的日期。验证完成后,将创建一个自定义后端对象。在QCalendar 中使用前,必须使用registerCustomBackend()
方法注册后端。一旦注册了后端,就可以使用相应的SystemId 或name
对QCalendar 进行实例化。
下面是loadCalendar
在main
中的使用情况:
const autocid= myplugin->loadCalendar(args.at(0));if(!cid.isValid()) { qWarning() << "Invalid ID"; 解析器.showHelp(1); }constQCalendarcalendar(cid);
扩展 QCalendarWidget
通过创建一个以特定日历为后台的QCalendar 实例,可以为QCalendarWidget 提供该后台并将其可视化。
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);
另请参见 QCalendarWidget,QCalendar,QDate,QLocale,QtPlugin 和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.