Python Qt GUI設計:5種事件處理機制(提升篇—3)

之前在Python Qt GUI設計:QTimer計時器類、QThread多線程類和事件處理類(基礎篇—8)中,我們已經簡單講到,PyQt為事件處理提供了兩種機制:高級的信號與槽機制以及低級的事件處理程序,本篇博文將系統講解Qt的事件處理機類和制。

事件處理機制本身很復雜,是PyQt底層的知識點,當采用信號與槽機制處理不了時,才會考慮使用事件處理機制。

信號與槽可以說是對事件處理機制的高級封裝,如果說事件是用來創建窗口控件的,那么信號與槽就是用來對這個窗口控件進行使用的。比如一個按鈕,當我們使用這個按鈕時,只關心clicked信號,至于這個按鈕如何接收并處理鼠標點擊事件,然后再發射這信號,則不用關心。但是如果要重載一個按鈕,這時就要關心這個問題了。比如可以改變它的行為:在鼠標按鍵按下時觸發clicked信號,而不是在釋放時。

1、常見事件類型

Qt事件的類型有很多,常見的Qt事件如下所示:

鍵盤事件:按鍵按下和松開。

鼠標事件:鼠標指針移動、鼠標按鍵按下和松開。

拖放事件:用鼠標進行拖放。

滾輪事件:鼠標滾輪滾動。

繪屏事件:重繪屏幕的某些部分。

定時事件:定時器到時。

焦點事件:鍵盤焦點移動。

進入和離開事件:鼠標指針移入Widget內,或者移出。

移動事件::Widget的位置改變。

大小改變事件:Widget的大小改變。

顯示和隱藏事件:Widget顯示和隱藏。

窗口事件:窗口是否為當前窗口。

還有一些常見的Qt事件,比如Socket事件、剪貼板事件、字體改變事件、布局改變事件等。

具體事件類型和說明可參見說明文檔:

2、事件處理方法

PyQt提供了如下5種事件處理和過濾方法(由弱到強),其中只有前兩種方法使用最頻繁。

2.1、重新實現事件函數

比如mousePressEvent()、keyPressEvent()、paintEvent()。這是最常規的事件處理方法。

通過示例了解重新實現事件函數的使用方法,效果如下所示:

這個示例中包含了多種事件類型,所以比較復雜。

首先是類的建立,建立text和message兩個變量,使用paintEvent函數把它們輸出到窗口中。

update函數的作用是更新窗口,由于在窗口更新過程中會觸發一次 paintEvent函數(paintEvent是窗口基類QWidget的內部函數),因此在本例中update函數的作用等同于paintEvent函數。

然后是重新實現窗口關閉事件與上下文菜單事件,對于上下文菜單事件,主要影響message變量的結果,paintEvent負責把這個變量在窗口底部輸出。

繪制事件是代碼的核心事件,它的主要作用是時刻跟蹤text與message這兩個變量的信息,并把 text的內容繪制到窗口的中部,把message的內容繪制到窗口的底部(保持5秒后就會被清空)。

以及最后一些鼠標、鍵盤的點擊操作等。

實現代碼如下所示:

import sys

from PyQt5.QtCore import (QEvent, QTimer, Qt)

from PyQt5.QtWidgets import (QApplication, QMenu, QWidget)

from PyQt5.QtGui import QPainter

class Widget(QWidget):

? ? def __init__(self, parent=None):

? ? ? ? super(Widget, self).__init__(parent)

? ? ? ? self.justDoubleClicked = False

? ? ? ? self.key = ""

? ? ? ? self.text = ""

? ? ? ? self.message = ""

? ? ? ? self.resize(400, 300)

? ? ? ? self.move(100, 100)

? ? ? ? self.setWindowTitle("Events")

? ? ? ? QTimer.singleShot(0, self.giveHelp)? # 避免窗口大小重繪事件的影響,可以把參數0改變成3000(3秒),然后在運行,就可以明白這行代碼的意思。

? ? def giveHelp(self):

? ? ? ? self.text = "請點擊這里觸發追蹤鼠標功能"

? ? ? ? self.update() # 重繪事件,也就是觸發paintEvent函數。

? ? '''重新實現關閉事件'''

? ? def closeEvent(self, event):

? ? ? ? print("Closed")

? ? '''重新實現上下文菜單事件'''

? ? def contextMenuEvent(self, event):

? ? ? ? menu = QMenu(self)

? ? ? ? oneAction = menu.addAction("&One")

? ? ? ? twoAction = menu.addAction("&Two")

? ? ? ? oneAction.triggered.connect(self.one)

? ? ? ? twoAction.triggered.connect(self.two)

? ? ? ? if not self.message:

? ? ? ? ? ? menu.addSeparator()

? ? ? ? ? ? threeAction = menu.addAction("Thre&e")

? ? ? ? ? ? threeAction.triggered.connect(self.three)

? ? ? ? menu.exec_(event.globalPos())

? ? '''上下文菜單槽函數'''

? ? def one(self):

? ? ? ? self.message = "Menu option One"

? ? ? ? self.update()

? ? def two(self):

? ? ? ? self.message = "Menu option Two"

? ? ? ? self.update()

? ? def three(self):

? ? ? ? self.message = "Menu option Three"

? ? ? ? self.update()

? ? '''重新實現繪制事件'''

? ? def paintEvent(self, event):

? ? ? ? text = self.text

? ? ? ? i = text.find("\n\n")

? ? ? ? if i >= 0:

? ? ? ? ? ? text = text[0:i]

? ? ? ? if self.key: # 若觸發了鍵盤按鈕,則在文本信息中記錄這個按鈕信息。

? ? ? ? ? ? text += "\n\n你按下了: {0}".format(self.key)

? ? ? ? painter = QPainter(self)

? ? ? ? painter.setRenderHint(QPainter.TextAntialiasing)

? ? ? ? painter.drawText(self.rect(), Qt.AlignCenter, text) # 繪制信息文本的內容

? ? ? ? if self.message: # 若消息文本存在則在底部居中繪制消息,5秒鐘后清空消息文本并重繪。

? ? ? ? ? ? painter.drawText(self.rect(), Qt.AlignBottom | Qt.AlignHCenter,

? ? ? ? ? ? ? ? ? ? ? ? ? ? self.message)

? ? ? ? ? ? QTimer.singleShot(5000, self.clearMessage)

? ? ? ? ? ? QTimer.singleShot(5000, self.update)

? ? '''清空消息文本的槽函數'''

? ? def clearMessage(self):

? ? ? ? self.message = ""

? ? '''重新實現調整窗口大小事件'''

? ? def resizeEvent(self, event):

? ? ? ? self.text = "調整窗口大小為: QSize({0}, {1})".format(

? ? ? ? ? ? event.size().width(), event.size().height())

? ? ? ? self.update()

? ? '''重新實現鼠標釋放事件'''

? ? def mouseReleaseEvent(self, event):

? ? ? ? # 若鼠標釋放為雙擊釋放,則不跟蹤鼠標移動

? ? ? ? # 若鼠標釋放為單擊釋放,則需要改變跟蹤功能的狀態,如果開啟跟蹤功能的話就跟蹤,不開啟跟蹤功能就不跟蹤

? ? ? ? if self.justDoubleClicked:

? ? ? ? ? ? self.justDoubleClicked = False

? ? ? ? else:

? ? ? ? ? ? self.setMouseTracking(not self.hasMouseTracking()) # 單擊鼠標

? ? ? ? ? ? if self.hasMouseTracking():

? ? ? ? ? ? ? ? self.text = "開啟鼠標跟蹤功能.\n" + \

? ? ? ? ? ? ? ? ? ? ? ? ? ? "請移動一下鼠標!\n" + \

? ? ? ? ? ? ? ? ? ? ? ? ? ? "單擊鼠標可以關閉這個功能"

? ? ? ? ? ? else:

? ? ? ? ? ? ? ? self.text = "關閉鼠標跟蹤功能.\n" + \

? ? ? ? ? ? ? ? ? ? ? ? ? ? "單擊鼠標可以開啟這個功能"

? ? ? ? ? ? self.update()

? ? '''重新實現鼠標移動事件'''

? ? def mouseMoveEvent(self, event):

? ? ? ? if not self.justDoubleClicked:

? ? ? ? ? ? globalPos = self.mapToGlobal(event.pos()) # 窗口坐標轉換為屏幕坐標

? ? ? ? ? ? self.text = """鼠標位置:

? ? ? ? ? ? 窗口坐標為:QPoint({0}, {1})

? ? ? ? ? ? 屏幕坐標為:QPoint({2}, {3}) """.format(event.pos().x(), event.pos().y(), globalPos.x(), globalPos.y())

? ? ? ? ? ? self.update()

? ? '''重新實現鼠標雙擊事件'''

? ? def mouseDoubleClickEvent(self, event):

? ? ? ? self.justDoubleClicked = True

? ? ? ? self.text = "你雙擊了鼠標"

? ? ? ? self.update()

? ? '''重新實現鍵盤按下事件'''

? ? def keyPressEvent(self, event):

? ? ? ? self.key = ""

? ? ? ? if event.key() == Qt.Key_Home:

? ? ? ? ? ? self.key = "Home"

? ? ? ? elif event.key() == Qt.Key_End:

? ? ? ? ? ? self.key = "End"

? ? ? ? elif event.key() == Qt.Key_PageUp:

? ? ? ? ? ? if event.modifiers() & Qt.ControlModifier:

? ? ? ? ? ? ? ? self.key = "Ctrl+PageUp"

? ? ? ? ? ? else:

? ? ? ? ? ? ? ? self.key = "PageUp"

? ? ? ? elif event.key() == Qt.Key_PageDown:

? ? ? ? ? ? if event.modifiers() & Qt.ControlModifier:

? ? ? ? ? ? ? ? self.key = "Ctrl+PageDown"

? ? ? ? ? ? else:

? ? ? ? ? ? ? ? self.key = "PageDown"

? ? ? ? elif Qt.Key_A <= event.key() <= Qt.Key_Z:

? ? ? ? ? ? if event.modifiers() & Qt.ShiftModifier:

? ? ? ? ? ? ? ? self.key = "Shift+"

? ? ? ? ? ? self.key += event.text()

? ? ? ? if self.key:

? ? ? ? ? ? self.key = self.key

? ? ? ? ? ? self.update()

? ? ? ? else:

? ? ? ? ? ? QWidget.keyPressEvent(self, event)

if __name__ == "__main__":

? ? app = QApplication(sys.argv)

? ? form = Widget()

? ? form.show()

? ? app.exec_()

2.2、重新實現QObject.event()

一般用在PyQt沒有提供該事件的處理函數的情況下,即增加新事件時。

對于窗口所有的事件都會傳遞給event函數,event函數會根據事件的類型,把事件分配給不同的函數進行處理。例如,對于繪圖事件,event會交給paintEvent函數處理;對于鼠標移動事件,event會交給mouseMoveEvent函數處理;對于鍵盤按下事件,event會交給keyPressEvent函數處理。

有一種特殊情況是對Tab鍵的觸發行為,event函數對Tab鍵的處理機制是把焦點從當前窗口控件的位置切換到Tab鍵次序中下一個窗口控件的位置,并返回True,而不是交給keyPressEvent函數處理。

因此這里需要在event函數中對按下Tab鍵的處理邏輯重新改寫,使它與鍵盤上普通的鍵沒什么不同。

2.1、重新實現事件函數例子中補充以下代碼,實現重新定義:

'''重新實現其他事件,適用于PyQt沒有提供該事件的處理函數的情況,Tab鍵由于涉及焦點切換,不會傳遞給keyPressEvent,因此,需要在這里重新定義。'''

? ? def event(self, event):

? ? ? ? if (event.type() == QEvent.KeyPress and

? ? ? ? ? ? ? ? ? ? event.key() == Qt.Key_Tab):

? ? ? ? ? ? self.key = "在event()中捕獲Tab鍵"

? ? ? ? ? ? self.update()

? ? ? ? ? ? return True

效果如下所示:

2.3、安裝事件過濾器

如果對QObject調用installEventFilter,則相當于為這個QObject安裝了一個事件過濾器,對于QObject的全部事件來說,它們都會先傳遞到事件過濾函數eventFilter中,在這個函數中我們可以拋棄或者修改這些事件,比如可以對自己感興趣的事件使用自定義的事件處理機制,對其他事件使用默認的事件處理機制。

由于這種方法會對調用installEventFilter的所有QObject的事件進行過濾,因此如果要過濾的事件比較多,則會降低程序的性能。

通過示例,了解事件過濾器的使用方法,效果如下所示:

對于使用事件過濾器,關鍵是要做好兩步。

對要過濾的控件設置installEventFilter,這些控件的所有事件都會被eventFilter函數接收并處理。

示例中,這個過濾器只對label1的事件進行處理,并且只處理它的鼠標按下事件(MouseButtonPress)和鼠標釋放事件(MouseButtonRelease) 。

如果按下鼠標鍵,就會對label1裝載的圖片進行縮放(長和寬各縮放一半)。

實現代碼如下所示:

# -*- coding: utf-8 -*-

from PyQt5.QtGui import *

from PyQt5.QtCore import *

from PyQt5.QtWidgets import *

import sys

class EventFilter(QDialog):

? ? def __init__(self, parent=None):

? ? ? ? super(EventFilter, self).__init__(parent)

? ? ? ? self.setWindowTitle("事件過濾器")

? ? ? ? self.label1 = QLabel("請點擊")

? ? ? ? self.label2 = QLabel("請點擊")

? ? ? ? self.label3 = QLabel("請點擊")

? ? ? ? self.LabelState = QLabel("test")

? ? ? ? self.image1 = QImage("images/cartoon1.ico")

? ? ? ? self.image2 = QImage("images/cartoon1.ico")

? ? ? ? self.image3 = QImage("images/cartoon1.ico")

? ? ? ? self.width = 600

? ? ? ? self.height = 300

? ? ? ? self.resize(self.width, self.height)

? ? ? ? self.label1.installEventFilter(self)

? ? ? ? self.label2.installEventFilter(self)

? ? ? ? self.label3.installEventFilter(self)

? ? ? ? mainLayout = QGridLayout(self)

? ? ? ? mainLayout.addWidget(self.label1, 500, 0)

? ? ? ? mainLayout.addWidget(self.label2, 500, 1)

? ? ? ? mainLayout.addWidget(self.label3, 500, 2)

? ? ? ? mainLayout.addWidget(self.LabelState, 600, 1)

? ? ? ? self.setLayout(mainLayout)

? ? def eventFilter(self, watched, event):

? ? ? ? if watched == self.label1: # 只對label1的點擊事件進行過濾,重寫其行為,其他的事件會被忽略

? ? ? ? ? ? if event.type() == QEvent.MouseButtonPress: # 這里對鼠標按下事件進行過濾,重寫其行為

? ? ? ? ? ? ? ? mouseEvent = QMouseEvent(event)

? ? ? ? ? ? ? ? if mouseEvent.buttons() == Qt.LeftButton:

? ? ? ? ? ? ? ? ? ? self.LabelState.setText("按下鼠標左鍵")

? ? ? ? ? ? ? ? elif mouseEvent.buttons() == Qt.MidButton:

? ? ? ? ? ? ? ? ? ? self.LabelState.setText("按下鼠標中間鍵")

? ? ? ? ? ? ? ? elif mouseEvent.buttons() == Qt.RightButton:

? ? ? ? ? ? ? ? ? ? self.LabelState.setText("按下鼠標右鍵")

? ? ? ? ? ? ? ? '''轉換圖片大小'''

? ? ? ? ? ? ? ? transform = QTransform()

? ? ? ? ? ? ? ? transform.scale(0.5, 0.5)

? ? ? ? ? ? ? ? tmp = self.image1.transformed(transform)

? ? ? ? ? ? ? ? self.label1.setPixmap(QPixmap.fromImage(tmp))

? ? ? ? ? ? if event.type() == QEvent.MouseButtonRelease: # 這里對鼠標釋放事件進行過濾,重寫其行為

? ? ? ? ? ? ? ? self.LabelState.setText("釋放鼠標按鈕")

? ? ? ? ? ? ? ? self.label1.setPixmap(QPixmap.fromImage(self.image1))

? ? ? ? return QDialog.eventFilter(self, watched, event) # 其他情況會返回系統默認的事件處理方法。

if __name__ == '__main__':

? ? app = QApplication(sys.argv)

? ? dialog = EventFilter()

? ? dialog.show()

? ? sys.exit(app.exec_())

2.4、在QApplication中安裝事件過濾器

這種方法比2.3、安裝事件過濾器更強大,QApplication的事件過濾器將捕獲所有QObject的所有事件,而且第一個獲得該事件。也就是說,在將事件發送給其他任何一個事件過濾器之前(就是在第三種方法之前),都會先發送給QApplication的事件過濾器。

2.3、安裝事件過濾器示例基礎上修改,屏蔽三個label標簽控件的installEventFilter代碼,這種事件處理方法確實過濾了所有事件,而不像第三種方法那樣只過濾三個標簽控件的事件。效果如下所示:

實現代碼如下所示:

# -*- coding: utf-8 -*-

from PyQt5.QtGui import *

from PyQt5.QtCore import *

from PyQt5.QtWidgets import *

import sys

class EventFilter(QDialog):

? ? def __init__(self, parent=None):

? ? ? ? super(EventFilter, self).__init__(parent)

? ? ? ? self.setWindowTitle("事件過濾器")

? ? ? ? self.label1 = QLabel("請點擊")

? ? ? ? self.label2 = QLabel("請點擊")

? ? ? ? self.label3 = QLabel("請點擊")

? ? ? ? self.LabelState = QLabel("test")

? ? ? ? self.image1 = QImage("images/cartoon1.ico")

? ? ? ? self.image2 = QImage("images/cartoon1.ico")

? ? ? ? self.image3 = QImage("images/cartoon1.ico")

? ? ? ? self.width = 600

? ? ? ? self.height = 300

? ? ? ? self.resize(self.width, self.height)

? ? ? ? # self.label1.installEventFilter(self)

? ? ? ? # self.label2.installEventFilter(self)

? ? ? ? # self.label3.installEventFilter(self)

? ? ? ? mainLayout = QGridLayout(self)

? ? ? ? mainLayout.addWidget(self.label1, 500, 0)

? ? ? ? mainLayout.addWidget(self.label2, 500, 1)

? ? ? ? mainLayout.addWidget(self.label3, 500, 2)

? ? ? ? mainLayout.addWidget(self.LabelState, 600, 1)

? ? ? ? self.setLayout(mainLayout)

? ? def eventFilter(self, watched, event):

? ? ? ? print(type(watched))

? ? ? ? if watched == self.label1: # 只對label1的點擊事件進行過濾,重寫其行為,其他的事件會被忽略

? ? ? ? ? ? if event.type() == QEvent.MouseButtonPress: # 這里對鼠標按下事件進行過濾,重寫其行為

? ? ? ? ? ? ? ? mouseEvent = QMouseEvent(event)

? ? ? ? ? ? ? ? if mouseEvent.buttons() == Qt.LeftButton:

? ? ? ? ? ? ? ? ? ? self.LabelState.setText("按下鼠標左鍵")

? ? ? ? ? ? ? ? elif mouseEvent.buttons() == Qt.MidButton:

? ? ? ? ? ? ? ? ? ? self.LabelState.setText("按下鼠標中間鍵")

? ? ? ? ? ? ? ? elif mouseEvent.buttons() == Qt.RightButton:

? ? ? ? ? ? ? ? ? ? self.LabelState.setText("按下鼠標右鍵")

? ? ? ? ? ? ? ? '''轉換圖片大小'''

? ? ? ? ? ? ? ? transform = QTransform()

? ? ? ? ? ? ? ? transform.scale(0.5, 0.5)

? ? ? ? ? ? ? ? tmp = self.image1.transformed(transform)

? ? ? ? ? ? ? ? self.label1.setPixmap(QPixmap.fromImage(tmp))

? ? ? ? ? ? if event.type() == QEvent.MouseButtonRelease: # 這里對鼠標釋放事件進行過濾,重寫其行為

? ? ? ? ? ? ? ? self.LabelState.setText("釋放鼠標按鈕")

? ? ? ? ? ? ? ? self.label1.setPixmap(QPixmap.fromImage(self.image1))

? ? ? ? return QDialog.eventFilter(self, watched, event) # 其他情況會返回系統默認的事件處理方法。

if __name__ == '__main__':

? ? app = QApplication(sys.argv)

? ? dialog = EventFilter()

? ? app.installEventFilter(dialog)

? ? dialog.show()

? ? sys.exit(app.exec_())

2.5、重新實現QApplication的notify()方法

PyQt使用notify()來分發事件,要想在任何事件處理器之前捕獲事件,唯一的方法就是重新實現QApplication的notify(),在實踐中,在調試時才會使用這種方法,實際中基本用不多,這里不再贅述了。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容