#PYTHON3 #QT #PYSISE2 #TUTORIAL #QTIME #QTIMEEDIT #QLCDNUMBER

PySide #17: 時間的重要性(下), QTimeEdit, QLCDNumber, AnalogClock

每分每秒每日每夜 每一個你都是幸福的起點

DekBan
Bucketing
Published in
14 min readSep 14, 2020

--

Photo by Kaylah Matthews on Unsplash

上一篇,我們提到了日期以及日曆的使用方式,而這一篇將繼續向更細節的時間 — 時分秒來做介紹,本章會提到的東西都非常簡單,如QTimeEdit、QLCDNumber都是非常直覺且簡單的物件,但是呢,Qt本身卻沒有時鐘的物件,所以我們需要自己製作一個客製化的UI物件。

官方其實已經有相關的範例了,但是呢卻沒有Python的版本,並且非常陽春,因此我們要來製作一個更完整的物件以供未來使用。

QTimeEdit

上一章我們提過QDateEdit,而QTimeEdit使用上跟它是一模一樣的,只是格式改成了時間的樣子。
事實上,他們都是繼承於QDateTimeEdit,就如同QTime、QDate都是繼承於QDateTime一樣,所以在使用的時候會發現也包含QDatetimeEdit的函式,不過因為是時間的關係,日期、日曆相關的函式我們都是用不到的。

Declare

通常會設定上下限的值,這邊表示正常上下班時間的上下限時間,另外還有時間的格式,預設的格式通常會是 h:mm AP

def set_time_edit(self):
time_edit = self._window.time_edit
time_edit.setMaximumTime(QTime(18, 0, 0))
time_edit.setMinimumTime(QTime(8, 0, 0))
time_edit.setDisplayFormat('hh:mm:ss.zzz')

這邊提供QTime格式的表格:

https://doc.qt.io/qt-5/qtime.html#toString

QLCDNumber

我們當然可以用QLabel來顯示所有的東西,但是這樣就Low掉啦!有時候顯示一些Hex的格式或是像時間這種Digital的東西就會想要使用七段顯示器的顯示方式,原因無他,就一個字了得!

Mode

首先,我們要知道QLCDNumber有幾種模式,表拿不同進位方式:

https://doc.qt.io/qt-5/qlcdnumber.html#Mode-enum

模式的設定非常簡單,可以直接透過相對應的函式直接設定,像是 setHexMode()setDecMode()等等。

也可以透過上表的Flag來做設定: setMode(QLCDNumber.Hex)

Display

至於顯示的部分,除了大家應該已經想到的數字顯示方式,還有另一個就是純文字的String顯示方式,這個方式就是我們要在電子時鐘中使用的格式。

def set_lcd(self):
lcd = self._window.lcd
lcd.setDecMode()
s_time = QTime.currentTime().toString('hh:mm')
lcd.display(s_time)

注意!

使用String的顯示方式 “”只能使用一次,也就是說沒辦法顯示秒數唷!

Runtime clock

如果要讓時鐘動態一直跑,不斷刷新,只要使用上一章教的QTimer每秒刷新就能輕鬆完成啦!

self._clock_timer.timeout.connect(self.update_clock)def set_lcd(self):
lcd = self._window.lcd
lcd.setDecMode()

self._clock_timer.start(1000)
@QtCore.Slot()
def update_clock(self):
time = QTime.currentTime()
s_time = time.toString('hh:mm')

if time.second() % 2 == 0:
s_time = s_time.replace(':', '.')

self._window.lcd.display(s_time)

Analog Clock

接下來!我們要來製作時鐘的UI物件了!
實作的方式主要是用QWidget當做基底,利用QWidget中內建的PaintEvent定期刷新畫面並用QPaint作畫,可以想像成小畫家在QWidget這個畫布上畫上2D的圖形。

Outlook Define

首先先定義一下,指針的長度以及顏色等等,這邊需要用到一些特殊的物件,包括QPolygon、QPoint、QColor等等繪圖常用的物件。

這些主要用來定義等等要畫的物件的座標,我們會由Widget的正中間為中心座標。

class AnalogClock(QWidget):
basic_size = 100

hour_pin = QPolygon([
QPoint(2, 5),
QPoint(-2, 5),
QPoint(0, -45)
])
minute_pin = QPolygon([
QPoint(2, 5),
QPoint(-2, 5),
QPoint(0, -75),
])
# 定義各個部位的顏色
hour_pin_color = QColor('#da8602')
min_pin_color = QColor('#2196f3')
background_color = QColor(255, 255, 255, 32)
edge_color = QColor(255, 255, 255, 96)
text_color = QColor(120, 255, 255, 128)
def __init__(self, parent=None, showFrame=False, size=None):
super(AnalogClock, self).__init__(parent)

timer = QTimer(self)
timer.timeout.connect(self.checkUpdate)
timer.start(1000)

self.setAttribute(Qt.WA_TranslucentBackground)

if size is None:
size = self.basic_size
self.resize(size, size)
else:
if size < self.basic_size:
size = self.basic_size
self.resize(size, size)

PaintEvent

畫圖的核心物件,每個QWidget都有這個Event主要掌管了update, repaint等等繪製畫面的函式,當我們改了一些UI物件的Attribute這個event就會被觸發,並且呼叫repaint做畫面刷新,但也可能會遇到沒刷新的問題,這時候我們就要手動呼叫repaint來做更新了。

離題了,讓我們來繪製我們的時鐘吧!

Declare

  1. 我們要在paintEvent中,宣告QPainter這個物件為我們完成畫圖的動作,首先要執行 begin(QPaintDevice)函式,並帶入QWidget本體表示我們要畫出自己,也就是時鐘。
  2. 接下來要定義繪圖的繪圖引擎針對繪圖時的抗鋸齒模式,如果不了解可以使用預設的值,詳細的模式內容可以看下表。
  3. 再來我們要定義我們的時鐘要出現在哪裡,當然不會是Widget的左上角的,所以我們將其移動掉正中間。
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.translate(self.width() / 2, self.height() / 2)
https://doc.qt.io/qt-5/qpainter.html#RenderHint-enum

Draw Clock Frame

宣告完後我們來繪製時鐘本體,呼叫 drawEllipse()函式來繪製圓形後,我們要用繪製刻度以及數字,這邊會用到一點圓周率的概念。

# 利用side確保時鐘一直處於max size又不會超出邊界的狀態
side = min(self.width(), self.height())
painter.drawEllipse(QPoint(0, 0), side/2, side/2)

繪製刻度,我們在這邊實作一個旋轉座標的函式 rotate_point()

def rotated_point(self, x, y, degree):
theta = degree * math.pi / 180
s = math.sin(theta)
c = math.cos(theta)
return x * c - y * s, x * s + y * c
def paintEvent(self, event):
# ....(略)
for i in range(0, 12):
x, y = self.rotated_point(0, -r * 0.95, i * 360/12)
painter.drawEllipse(x - 3, y - 3, 6, 6)
painter.setPen(self.text_color)
for i in range(0, 12):
x, y = self.rotated_point(0, -r * 0.85, i * 360/12)
painter.drawText(QRect(x - 10, y - 10, 20, 20),
Qt.AlignCenter, "%d" % i)

painter.setPen(self.background_color)
painter.setBrush(QBrush(self.min_pin_color))
for min in range(0, 60):
if min % 5 != 0:
x, y = self.rotated_point(0, -r * 0.95, min * 360/60)
painter.drawEllipse(x - 1, y - 1, 2, 2)
clock frame demo

Draw Clock Hand

最後我們要畫上指針,利用先前定義好的polygon座標,利用QPainter將其畫出來。

這邊要讓大家了解, save()函式跟 restore()函式一個是將目前的Painter的狀態存入queue中與釋放。

curr_time = QTime.currentTime()# 時針
painter.setBrush(QBrush(self.hour_pin_color))

painter.save()
# 依據時鐘大小更改指針長度
self.hour_pin[2] = QPoint(0, int(-r/3))
painter.rotate(30.0 * (curr_time.hour() + curr_time.minute() /
60.0))
painter.drawConvexPolygon(self.hour_pin)
painter.restore()
# 分針
painter.setBrush(QBrush(self.min_pin_color))

painter.save()
self.minute_pin[2] = QPoint(0, int(-r * 0.9))
painter.rotate(6.0 * (curr_time.minute() + curr_time.second() /
60.0))
painter.drawConvexPolygon(self.minute_pin)
painter.restore()

結束繪圖:

painter.end()

Update Frame

最後,我們要利用QTimer來定期觸發QWidget中的 update函式或是 repaint函式,這邊我使用update來刷新畫面,刷新畫面時會同時觸發paintEvent以達到重新繪製指針的效果。

@Qt.Slot()
def update_frame(self):
time = QTime.currentTime()
if time.second() % 10 == 0:
self.update()
# in __init__()timer = QTimer(self)
timer.timeout.connect(self.update_frame)
timer.start(1000)
Analog clock demo

Set Time

最後,我們不只是要讓時鐘一直處於現在時間,可能也想要設定他的時間,因此我們要把時間的參數拉出來到外部,並透過自訂的 setTime()函式來設定時鐘。

@property
def time(self):
return self._time

@time.setter
def time(self, value: QTime):
self._time = value
self.update_frame()
def paintEvent(self, event):
# 修改現在時間
curr_time = self._time

再mainWindow中,更新digital的時間時,同時設定Analog的時間,這樣就完成了!

# In MainWindow classdef set_clock(self):
self._analog_clock = AnalogClock(self._window, 500)
self._window.v_layout.addWidget(self._analog_clock)
@QtCore.Slot()
def update_clock(self):
time = QTime.currentTime()
s_time = time.toString('hh:mm')

if time.second() % 2 == 0:
s_time = s_time.replace(':', '.')

self._window.lcd.display(s_time)
# 同步analog時間
self._analog_clock.time = time

Source Code

完整代碼請看 :clock_time

結論

QTimeEdit以及QLCDNumber的使用方式非常的簡單,相信大家上手的非常快速,而我們自製的AnalogClock結合了2D繪圖的元素,因此相對不好理解一些,但UI的東西只需要做一次之後套用即可,大家可以花點時間把它好好了看過唷!

我是夜海中的宅男DekBan,我們下次見,see ya next night.

Next Step : PySide #18: 秀出你的生活點滴, Logging to Everywhere!

本文範例參考:

Qt Official

tos-kamiya / analogclock.py

--

--

DekBan
Bucketing

เด็กบ้าน | 🌃夜裡溜搭的宅男,漂泊於月色鋪成的海