【问题标题】:QML Calendar and Google Calendar API in Python events integrationPython 事件集成中的 QML Calendar 和 Google Calendar API
【发布时间】:2020-07-16 04:33:09
【问题描述】:

我目前正在创建一个 QML 日历,它可以理想地显示来自 Google 日历的事件。

这是我想在 python 中模拟的示例: https://doc.qt.io/qt-5.9/qtquickcontrols-calendar-example.html

现在,一个 python 文件正在从 Google Calendar API 获取日期和事件摘要,并将它们作为 {date: event summary} 的字典(python 代码中的 tenevents 变量)返回。我有一个非常简单的 QML 窗口,显示一个日历和一个矩形。我想单击日历上的某个日期,并让该日期的事件显示在矩形中,并标记有事件的日期。我想我有我需要的大部分数据,我只是不知道如何使用它。我不确定如何在点击时提取日期信息,也不确定如何传递 python 字典并显示我想要的内容——我感谢任何指向有用文档的方向和/或指针!

我在下面包含了我的代码!

getevents() 返回如下内容:

{'2020-07-30T13:00:00-05:00': 'Buy a Single Olive', '2020-07-31T10:00:00-05:00': 'Jarvis', '2020-08-02': 'Peel an Apple', '2020-08-04T02:30:00-05:00': 'Eat Two Crisp Grapes'}

python.py:

from __future__ import print_function
import datetime
import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import os.path
import os
from PySide2 import QtCore
from PySide2.QtCore import Property, Signal, Slot, QObject, QUrl, QUrlQuery
from PySide2 import QtGui
from PySide2 import QtQml

class CalendarModule(QtCore.QObject):
    
    def __init__(self):
        super(CalendarModule, self).__init__()
        self.scopes = ['https://www.googleapis.com/auth/calendar']
        self.service = self.use_token_pickle_to_get_service()
    def use_token_pickle_to_get_service(self):
        creds = None
        if os.path.exists('token.pickle'):
            with open('token.pickle', 'rb') as token:
                creds = pickle.load(token)
        # If there are no (valid) credentials available, let the user log in.
        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                flow = InstalledAppFlow.from_client_secrets_file(
                    'credentials.json', SCOPES)
                creds = flow.run_local_server(port=0)
            # Save the credentials for the next run
            with open('token.pickle', 'wb') as token:
                pickle.dump(creds, token)
        service = build('calendar', 'v3', credentials=creds)
        return service
        
    @Slot(result='QVariant')
    def getevents(self):
        # Call the Calendar API
        now = datetime.datetime.utcnow().isoformat() + 'Z'  # 'Z' indicates UTC time
        events_result = self.service.events().list(calendarId='primary', timeMin=now,
                                              maxResults=10, singleEvents=True,
                                              orderBy='startTime').execute()
        events = events_result.get('items', [])
        tenevents = {}
        if not events:
            return 'No upcoming events found.'

        for event in events:
            start = event['start'].get('dateTime', event['start'].get('date'))
            tenevents[start] = event['summary']
        return tenevents



def main():
    import os
    import sys

    CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    app = QtGui.QGuiApplication(sys.argv)
    engine = QtQml.QQmlApplicationEngine()
    filename = os.path.join(CURRENT_DIR, "calendar2.qml")
    cal = CalendarModule()
    engine.rootContext().setContextProperty("Cal", cal)
    engine.load(QUrl.fromLocalFile(filename))

    if not engine.rootObjects():
        sys.exit(-1)

    sys.exit(app.exec_())

if __name__ == '__main__':
    print(CalendarModule().getevents())
    main() 

calendar2.qml:

import QtQuick 2.3
import QtQuick.Controls.Styles 1.2
import QtQuick.Layouts 1.11

ApplicationWindow {
    title: qsTr("Calendar")
    width: 700
    height: 400
    visible: true


    RowLayout {
    anchors.fill: parent

        Rectangle {
        id: tasks
        Layout.fillWidth: true
        Layout.fillHeight: true
        height: 400
        width: 150
        color: "white"
        Text {
            anchors.horizontalCenter: parent.horizontalCenter
            font.bold: true
            font.pointSize:14
            text: 'To-Do'
        }
        }
        Calendar {
        Layout.fillWidth: true
        Layout.fillHeight: true
        frameVisible: true
        style: CalendarStyle {
            gridVisible: false
            dayDelegate: Rectangle {
                gradient: Gradient {
                    GradientStop {
                        position: 0.00
                        color: styleData.selected ? "#111" : (styleData.visibleMonth && styleData.valid ? "#444" : "#666");
                    }
                    GradientStop {
                        position: 1.00
                        color: styleData.selected ? "#444" : (styleData.visibleMonth && styleData.valid ? "#111" : "#666");
                    }
                    GradientStop {
                        position: 1.00
                        color: styleData.selected ? "#444" : (styleData.visibleMonth && styleData.valid ? "#111" : "#666");
                    }
                }

                Label {
                    text: styleData.date.getDate()
                    anchors.centerIn: parent
                    color: styleData.valid ? "white" : "white"
                }

                Rectangle {
                    width: parent.width
                    height: 1
                    color: "#555"
                    anchors.bottom: parent.bottom
                }

                Rectangle {
                    width: 1
                    height: parent.height
                    color: "#555"
                    anchors.right: parent.right
                }
            }
        }
        }
    }
}

【问题讨论】:

    标签: python google-api qml pyside2


    【解决方案1】:

    在修改任何代码之前,您必须了解代码并了解它与您想要的不同。而在你的情况下,有一个区别:数据不能快速访问,但你必须提出一个耗时的请求,并且可能会阻塞 GUI。

    所以在上述之前最好先下载事件(也许将它保存在像 sqlite 这样的数据库中并使用原始示例)以将其作为缓存,从而最大限度地减少下载量。

    然后你必须分离逻辑:创建一个将信息导出到 QML 的类,以及另一个获取它的类。

    由于每次有事件都会重新加载信息,所以最好使用每次缓存更新时都会重新绘制GUI的Loader。

    注意:我用的是官方的例子

    ma​​in.py

    import logging
    import os
    import pickle
    import sys
    import threading
    
    from PySide2 import QtCore, QtGui, QtQml
    
    from googleapiclient.discovery import build
    from google_auth_oauthlib.flow import InstalledAppFlow
    from google.auth.transport.requests import Request
    
    
    SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
    CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
    
    
    logging.basicConfig(level=logging.DEBUG)
    
    
    class CalendarBackend(QtCore.QObject):
        eventsChanged = QtCore.Signal(list)
    
        def __init__(self, parent=None):
            super().__init__(parent)
            self._service = None
    
        @property
        def service(self):
            return self._service
    
        def updateListEvents(self, kw):
            threading.Thread(target=self._update_list_events, args=(kw,)).start()
    
        def _update_list_events(self, kw):
            self._update_credentials()
    
            events_result = self.service.events().list(**kw).execute()
            events = events_result.get("items", [])
    
            qt_events = []
            if not events:
                logging.debug("No upcoming events found.")
            for event in events:
                start = event["start"].get("dateTime", event["start"].get("date"))
                end = event["end"].get("dateTime", event["end"].get("date"))
                logging.debug(f"From {start} - To {end}:  {event['summary']}")
    
                start_dt = QtCore.QDateTime.fromString(start, QtCore.Qt.ISODate)
                end_dt = QtCore.QDateTime.fromString(end, QtCore.Qt.ISODate)
                summary = event["summary"]
    
                e = {"start": start_dt, "end": end_dt, "summary": summary}
                qt_events.append(e)
    
            self.eventsChanged.emit(qt_events)
    
        def _update_credentials(self):
            creds = None
            if os.path.exists("token.pickle"):
                with open("token.pickle", "rb") as token:
                    creds = pickle.load(token)
            if not creds or not creds.valid:
                if creds and creds.expired and creds.refresh_token:
                    creds.refresh(Request())
                else:
                    flow = InstalledAppFlow.from_client_secrets_file(
                        "credentials.json", SCOPES
                    )
                    creds = flow.run_local_server(port=0)
                with open("token.pickle", "wb") as token:
                    pickle.dump(creds, token)
    
            self._service = build("calendar", "v3", credentials=creds)
    
    
    class CalendarProvider(QtCore.QObject):
        loaded = QtCore.Signal()
    
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self._cache_events = []
            self._backend = CalendarBackend()
            self._backend.eventsChanged.connect(self._handle_events)
    
        @QtCore.Slot("QVariant")
        def updateListEvents(self, parameters):
            d = dict()
            for k, v in parameters.toVariant().items():
                if isinstance(v, QtCore.QDateTime):
                    v = v.toTimeSpec(QtCore.Qt.OffsetFromUTC).toString(
                        QtCore.Qt.ISODateWithMs
                    )
                d[k] = v
            self._backend.updateListEvents(d)
    
        @QtCore.Slot(QtCore.QDate, result="QVariantList")
        def eventsForDate(self, date):
            events = []
            for event in self._cache_events:
                start = event["start"]
                if start.date() == date:
                    events.append(event)
            return events
    
        @QtCore.Slot(list)
        def _handle_events(self, events):
            self._cache_events = events
            self.loaded.emit()
            logging.debug("Loaded")
    
    
    def main():
        app = QtGui.QGuiApplication(sys.argv)
    
        QtQml.qmlRegisterType(CalendarProvider, "MyCalendar", 1, 0, "CalendarProvider")
    
        engine = QtQml.QQmlApplicationEngine()
        filename = os.path.join(CURRENT_DIR, "main.qml")
        engine.load(QtCore.QUrl.fromLocalFile(filename))
    
        if not engine.rootObjects():
            sys.exit(-1)
    
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()
    

    ma​​in.qml

    import QtQuick 2.2
    import QtQuick.Controls 1.2
    import QtQuick.Controls.Private 1.0
    import QtQuick.Controls.Styles 1.1
    import MyCalendar 1.0
    
    ApplicationWindow {
        visible: true
        width: 640
        height: 400
        minimumWidth: 400
        minimumHeight: 300
        color: "#f4f4f4"
    
        title: "Calendar Example"
    
        SystemPalette {
            id: systemPalette
        }
    
        CalendarProvider {
            id: eventModel
            onLoaded: {
                // reload
                loader.sourceComponent = null
                loader.sourceComponent = page_component
            }
            Component.onCompleted: {
                eventModel.updateListEvents({
                    calendarId: "primary",
                    timeMin: new Date(),
                    maxResults: 10,
                    singleEvents: true,
                    orderBy: "startTime",
                })
            }
        }
    
        Loader{
            id: loader
            anchors.fill: parent
            sourceComponent: page_component
        }
    
        Component{
            id: page_component
            Flow {
                id: row
                anchors.fill: parent
                anchors.margins: 20
                spacing: 10
                layoutDirection: Qt.RightToLeft
    
                Calendar {
                    id: calendar
                    width: (parent.width > parent.height ? parent.width * 0.6 - parent.spacing : parent.width)
                    height: (parent.height > parent.width ? parent.height * 0.6 - parent.spacing : parent.height)
                    frameVisible: true
                    weekNumbersVisible: true
                    selectedDate: new Date()
                    focus: true
                    style: CalendarStyle {
                        dayDelegate: Item {
                            readonly property color sameMonthDateTextColor: "#444"
                            readonly property color selectedDateColor: Qt.platform.os === "osx" ? "#3778d0" : systemPalette.highlight
                            readonly property color selectedDateTextColor: "white"
                            readonly property color differentMonthDateTextColor: "#bbb"
                            readonly property color invalidDatecolor: "#dddddd"
    
                            Rectangle {
                                anchors.fill: parent
                                border.color: "transparent"
                                color: styleData.date !== undefined && styleData.selected ? selectedDateColor : "transparent"
                                anchors.margins: styleData.selected ? -1 : 0
                            }
    
                            Image {
                                visible: eventModel.eventsForDate(styleData.date).length > 0
                                anchors.top: parent.top
                                anchors.left: parent.left
                                anchors.margins: -1
                                width: 12
                                height: width
                                source: "images/eventindicator.png"
                            }
    
                            Label {
                                id: dayDelegateText
                                text: styleData.date.getDate()
                                anchors.centerIn: parent
                                color: {
                                    var color = invalidDatecolor;
                                    if (styleData.valid) {
                                        // Date is within the valid range.
                                        color = styleData.visibleMonth ? sameMonthDateTextColor : differentMonthDateTextColor;
                                        if (styleData.selected) {
                                            color = selectedDateTextColor;
                                        }
                                    }
                                    color;
                                }
                            }
                        }
                    }
                }
    
                Component {
                    id: eventListHeader
    
                    Row {
                        id: eventDateRow
                        width: parent.width
                        height: eventDayLabel.height
                        spacing: 10
    
                        Label {
                            id: eventDayLabel
                            text: calendar.selectedDate.getDate()
                            font.pointSize: 35
                        }
    
                        Column {
                            height: eventDayLabel.height
    
                            Label {
                                readonly property var options: { weekday: "long" }
                                text: Qt.locale().standaloneDayName(calendar.selectedDate.getDay(), Locale.LongFormat)
                                font.pointSize: 18
                            }
                            Label {
                                text: Qt.locale().standaloneMonthName(calendar.selectedDate.getMonth())
                                      + calendar.selectedDate.toLocaleDateString(Qt.locale(), " yyyy")
                                font.pointSize: 12
                            }
                        }
                    }
                }
    
                Rectangle {
                    width: (parent.width > parent.height ? parent.width * 0.4 - parent.spacing : parent.width)
                    height: (parent.height > parent.width ? parent.height * 0.4 - parent.spacing : parent.height)
                    border.color: Qt.darker(color, 1.2)
    
                    ListView {
                        id: eventsListView
                        spacing: 4
                        clip: true
                        header: eventListHeader
                        anchors.fill: parent
                        anchors.margins: 10
                        model: eventModel.eventsForDate(calendar.selectedDate)
    
                        delegate: Rectangle {
                            width: eventsListView.width
                            height: eventItemColumn.height
                            anchors.horizontalCenter: parent.horizontalCenter
    
                            Image {
                                anchors.top: parent.top
                                anchors.topMargin: 4
                                width: 12
                                height: width
                                source: "images/eventindicator.png"
                            }
    
                            Rectangle {
                                width: parent.width
                                height: 1
                                color: "#eee"
                            }
    
                            Column {
                                id: eventItemColumn
                                anchors.left: parent.left
                                anchors.leftMargin: 20
                                anchors.right: parent.right
                                height: timeLabel.height + nameLabel.height + 8
    
                                Label {
                                    id: nameLabel
                                    width: parent.width
                                    wrapMode: Text.Wrap
                                    text: modelData["summary"]
                                }
                                Label {
                                    id: timeLabel
                                    width: parent.width
                                    wrapMode: Text.Wrap
                                    text: modelData.start.toLocaleTimeString(calendar.locale, Locale.ShortFormat) + "-" + modelData.end.toLocaleTimeString(calendar.locale, Locale.ShortFormat)
                                    color: "#aaa"
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    

    完整的例子是here

    【讨论】:

    • 非常感谢!您的回答和解释非常有帮助,并且利用了我不知道存在的东西!我一定会考虑你关于使用 sqlite 数据库的建议!
    猜你喜欢
    • 2021-05-19
    • 1970-01-01
    • 1970-01-01
    • 2013-04-23
    • 2020-09-12
    • 2022-01-09
    • 2018-09-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多