Post

Pyqt5를 공부해보자!

파이썬으로 shimejiee 같은 나를 힐링시켜줄 프로그램을 만들어보고 싶다는 생각에 시작한 기록

코드

tray icon

나는 처음 프로그램 gui만들기로 파이썬 내에 있는 tkinter라는 라이브러리를 사용했었다. gui프로그래밍을 처음하다보니 아무래도 0부터 시작하는거라 하고 싶은 것은 많은데 실력이 안되 여기저기 찾아봤었다. 그러다 gif, 움직임까지 성공하고 tray icon을 만들어서 제어하려 했으나 tkinter에서는 따로 pystray라는 라이브러리를 사용했었다.

tkinter로 마우스 이벤트를 만들다 실패 후 한참이 지나 다시 이것저것 찾아보다 Pyqt를 찾게되었고 tray icon 이 훨씬 쉬운 방법으로 만들 수 있다는 것을 알게되었다.

전체 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *

app = QApplication([])
app.setQuitOnLastWindowClosed(False)

icon = QIcon("icon.png")

tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)

menu = QMenu()
option1 = QAction("hi")
option2 = QAction("HI2")
menu.addAction(option1)
menu.addAction(option2)

quit = QAction("Quit")
quit.triggered.connect(app.quit)
menu.addAction(quit)

tray.setContextMenu(menu)

app.exec_()


코드 설명

처음 Pyqt5의 라이브러리를 사용하기 위해 QtGui(QIcon)와 QtWidgets(QApplication, QSystemTrayIcon, QMenu, QAction)을 import 하였다.

1
2
from PyQt5.QtGui import * #QIcon
from PyQt5.QtWidgets import * #QApplication, QSystemTrayIcon, QMenu, QAction

위젯을 초기화, 마무리, 설정등을 위해 가장 기본 단계인 QApplication을 사용하고 setQuitOnLastWindowClosed를 False로 설정하여 켜져있는 창이 없더라도 프로그램이 종료되지 않도록 해준다.

1
2
app = QApplication([])
app.setQuitOnLastWindowClosed(False)

내가 넣고 싶은 아이콘을 QIcon 안에 넣어주면 된다. 그럼 아래와 같이 뜰 것이다. pyqt5trayicon

1
icon = QIcon("아이콘 위치")

이제 진짜로 트레이 아이콘을 만들 차례이다. PyQt자체에 QSystemTrayIcon이라는 함수가 있기에 이것을 이용해 나는 트레이 아이콘을 쓸거고 tray라는 이름으로 정해주겠다, 선언한다.
아까 정해준 아이콘을 setIcon을 이용해 넣어준다.
트레이 아이콘이 보일 수 있게 setVisible을 True로 해준다.( 나중에 앱이 켜진 상황에서는 트레이 아이콘을 끄고 싶다면 False로 바꾸면 되겠다.)

1
2
3
tray = QSystemTrayIcon()
tray.setIcon(icon)
tray.setVisible(True)

이제 메뉴판을 만들어줄 차례다
QMenu를 menu로 선언해 주고 QAction안에 이름을 넣어주면 되겠다. 그리고 실행되면 위에서부터 하나씩 뜨는 듯 하니 순서를 정하면 된다.(menu안에 menu 넣는 법을 따로 찾아봐야겠다)
addAction을 이용해 menu에 option설정한 것을 넣어주자. (마치 피자에 넣는 치즈?)

1
2
3
4
5
menu = QMenu()
option1 = QAction("hi")
option2 = QAction("HI2")
menu.addAction(option1)
menu.addAction(option2)

위와 과정은 같지만 지금은 다른 명령어 없이 트레이 아이콘 만으로 종료를 해주기 위해 triggered.connect(app.quit)을 만들어주었다.

1
2
3
quit = QAction("Quit")
quit.triggered.connect(app.quit)
menu.addAction(quit)

이제 만들어진 트레이 아이콘에 memu를 넣어주자. (피자를 굽자!)

1
tray.setContextMenu(menu)

파이썬은 위에서 아래로 다 실행하고 나면 종료하지만 원래는 for문을 사용하겠으나 tkinter에서 main.loop()를 사용한 것 처럼 무한루프를 걸어주기 위해 app.exec_()를 사용한다. 앱이 종료되면 0을 return한다고 한다.

1
app.exec_()

고양이 코드

tkinter에서 만든 고양이 코드를 pyqt에서 구현하기

version 1 처음 써보는 gui이니 일단 고양이 gif 실행만
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import sys
from PyQt5.QtGui import QMovie
from PyQt5 import QtCore, QtWidgets, QtGui
import random

class Pet(QtWidgets.QMainWindow):
    def __init__(self, img_path, xy, on_top, size=1.0):
        super(Pet, self).__init__()
        self.timer = QtCore.QTimer(self)
        self.img_path = img_path
        self.xy = xy
        self.from_xy = xy
        self.from_xy_dif = [0, 0]
        self.to_xy = xy
        self.to_xy_dif = [0, 0]
        self.speed = 60#나중에 설정
        self.direction = [0, 0]
        self.size = size
        self.on_top = on_top
        self.localPos =None

        self.setupUi()
        self.show()

    def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None:
        if self.to_xy_dif == [0, 0] and self.from_xy_dif == [0, 0]:
            pass
        else:
            self.walk_dif(self.from_xy_dif, self.to_xy_dif, self.speed, restart=True)

    # 마우스 눌렀을 때
    def mousePressEvent(self, a0: QtGui.QMouseEvent):
        self.localPos = a0.localPos()

    # 드래그 할 때
    def mouseMoveEvent(self, a0: QtGui.QMouseEvent):
        self.timer.stop()
        self.xy = [int(a0.globalX() - self.localPos.x()), int(a0.globalY() - self.localPos.y())]
        print(self.xy)
        self.move(*self.xy)

    def walk(self, from_xy, to_xy, speed=60):
        self.from_xy = from_xy
        self.to_xy = to_xy
        self.speed = speed

        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.__walkHandler)
        self.timer.start(1000 / self.speed)

    # 초기 위치로부터의 상대적 거리를 이용한 walk
    def walk_dif(self, from_xy_dif, to_xy_dif, speed=60, restart=False):
        self.from_xy_dif = from_xy_dif
        self.to_xy_dif = to_xy_dif
        self.from_xy = [self.xy[0] + self.from_xy_dif[0], self.xy[1] + self.from_xy_dif[1]]
        self.to_xy = [self.xy[0] + self.to_xy_dif[0], self.xy[1] + self.to_xy_dif[1]]
        self.speed = speed
        if restart:
            self.timer.start()
        else:
            self.timer.timeout.connect(self.__walkHandler)
            self.timer.start(1000 / self.speed)

    def __walkHandler(self):
        if self.xy[0] >= self.to_xy[0]:
            self.direction[0] = 0
        elif self.xy[0] < self.from_xy[0]:
            self.direction[0] = 1

        if self.direction[0] == 0:
            self.xy[0] -= 1
        else:
            self.xy[0] += 1

        if self.xy[1] >= self.to_xy[1]:
            self.direction[1] = 0
        elif self.xy[1] < self.from_xy[1]:
            self.direction[1] = 1

        if self.direction[1] == 0:
            self.xy[1] -= 1
        else:
            self.xy[1] += 1

        self.move(*self.xy)


    def setupUi(self):
        Centralwidget = QtWidgets.QWidget(self)#https://wikidocs.net/35746
        self.setCentralWidget(Centralwidget)

        flags = QtCore.Qt.WindowFlags(QtCore.Qt.WindowType.FramelessWindowHint | QtCore.Qt.WindowType.WindowStaysOnTopHint if self.on_top else QtCore.Qt.WindowType.FramelessWindowHint)
        self.setWindowFlags(flags)
        self.setAttribute(QtCore.Qt.WidgetAttribute.WA_NoSystemBackground, True)
        self.setAttribute(QtCore.Qt.WidgetAttribute.WA_TranslucentBackground, True)

        label = QtWidgets.QLabel(Centralwidget)
        movie = QMovie(self.img_path)
        label.setMovie(movie)
        movie.start()
        movie.stop()

        width = int(movie.frameRect().size().width() * self.size)
        height = int(movie.frameRect().size().height() * self.size)
        movie.setScaledSize(QtCore.QSize(width, height))
        movie.start()

        self.setGeometry(self.xy[0], self.xy[1], width, height)

    def mouseDoubleClickEvent(self, e):
        QtWidgets.qApp.quit()

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)

    s = Pet('idle.gif', xy = [200 , 200], on_top=True)

    sys.exit(app.exec_())
version 2 tray icon 추가

아직 하는중

문제사항

처음에 https://github.com/kairess/animated-wallpaper-sticker/blob/master/Sticker.py 사이트를 참조해서 코딩을 하려 했으나

1
flags = QtCore.Qt.WindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint if self.on_top else QtCore.Qt.FramelessWindowHint)

이 부분이 실행 안되는 것을 발견 찾아보니 WA_TranslucentBackground으로 실행 요망 혹은 WindowType FramelessWindowHint로 바꿔야함 시간이 지나면서 바뀐 듯 하다.

1
flags = QtCore.Qt.WindowFlags(QtCore.Qt.WindowType.FramelessWindowHint | QtCore.Qt.WindowType.WindowStaysOnTopHint if self.on_top else QtCore.Qt.WindowType.FramelessWindowHint)

참조

This post is licensed under CC BY 4.0 by the author.