Gaebal/Python

PyQT-1(code)

ha3kkkkk 2020. 5. 2. 22:07

1. PyQT

어떤 프로그램을 CLI로 개발하는 것, GUI로 개발하는 것은 엄청난 차이다. 상대방에게.

사실 GUI로 만드는것은 개발자의 입장에서는 굉장히 번거로운데,

잘모르는사람의 입장에서는 GUI로 만들어져있으면 버튼의 나열이라도, 우와-하게된다.

그래서 기왕주는거 더 어깨 으쓱할 수 있게 허접한 형태로라도 GUI로 주자.

 

PyQT는 클래스가 1000여개가 넘는 범용 프레임워크다. 그래서 모두 설명할 수는 없고, 이런거구나 하는 정도만 확인한뒤 직접 찾아보면서 하며된다. C나 C++로 이루어진 라이브러리를 임포트 해서 사용할 수 있기 때문에 한번 알아놓으면 굉장히 좋다.

 

 

 

2. 준비

(a)개별적인 개발환경을 위해 anaconda 설치

https://www.anaconda.com/products/individual

 

Individual Edition | Anaconda

🐍 Open Source Anaconda Individual Edition is the world’s most popular Python distribution platform with over 20 million users worldwide. You can trust in our long-term commitment to supporting the Anaconda open-source ecosystem, the platform of choice for

www.anaconda.com

(별로 어렵지 않으니 아나콘다를 잘모르는 사람은 다른 블로그를 잠깐보고 오면 된다. 그리고 설치가 귀찮다면 로컬에 해도 상관없다.)

 

(b)anaconda 프로젝트 만들기

[그림1] create conda project
[그림2] activate conda project
[그림3] pip isnatll pyqt5 AND pyqt5-tools

 python 3.5로는 에러가 있는것 같습니다. 3.6 설치 추천 드립니다.

 

(c). 프로젝트관련 폴더 준비

[그림4] prepare project resource folders

 

(d). Text Editor 실행. 

[그림5] Anaconda navigator

모든 설명은 나의 환경 중심이다.. Anaconda와 visual studio code연동이 가능하니 실행해준다.

(설치안된사람은 설치를 추천한다. VS Code는 다른 코드 작성에도 유용하다.)

VS Code가 싫은사람은 atom을 이용할 수 도 있다. atom의 경우 [그림3]의 상황에서 atom이라고 입력하면 연동된다.

[그림6] prepare VS Code

VSCode에서 Open Folder'4'에서 만든 폴더를 불러오고,

터미널을 열어서 앞서 만든 conda project와 연동해주면 개발 준비 끝

 

 

 

3. 기본

import sys
from PyQt5.QtWidgets import *

if __name__ == "__main__":
    #1. 캔버스를 만들자
    app = QApplication(sys.argv) 
    #2. 캔버스에 붙일 스티커 하나를 만들자
    label = QLabel("Hello World !")
    #3. 스티커를 캔버스에 붙이자
    label.show()
    #4. 캔버스를 눈에 보이게 하자
    app.exec_()
    print("END!")

코드의 주석에서 말하는 캔버스는 Main GUI로 App 자체라 생각하면 된다.

QApplication은 PyQt5.Qwidgets.QApplication()으로, sys.argv(파일실행 경로)를 초기화 변수로 넘겨주고 있다.

 

여기서 #3의 .show()까지하면 캔버스가 안보이기때문에 label이 안나타난다.

 

print("END!")는 언제출력될까.

exec_()가 실행되면 exec_()안에서 무한루프로 사용자의 입력을 계속 기다리고 그에 대한 이벤트 처리를 하기 때문에

닫기 버튼을 눌러야만 print("END!")가 실행될 수 있다.

 

[그림7] pyqt-1 example

 

 

다음 예제를 살펴보자

 

if __name__ == "__main__":
    app = QApplication(sys.argv) 
    window = exampleForm()
    window.show()
    app.exec_()

첫번째 예제 코드와 달라진 부분은 window = exampleForm() 부분이다.

텍스트하나 찍어낼게 아니라 좀더 복잡한 GUI를 만들어야하니 클래스로 빼낸것이다.

 

클래스의 코드를 보면,

 

class exampleForm(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setupUI()
    def setupUI(self):
        self.setWindowTitle("PY PY PY")
        self.setGeometry(800,400, 900,500)

        btn_1 = QPushButton("button1", self)
        btn_2 = QPushButton("button2", self)
        btn_3 = QPushButton("button3", self)

        btn_1.move(20,20)
        btn_2.move(20,60)
        btn_3.move(20,100)

        btn_1.clicked.connect(self.btn_1_clicked)
        btn_2.clicked.connect(self.btn_2_clicked)
        btn_3.clicked.connect(QCoreApplication.instance().quit)
        
    def btn_1_clicked(self):
        QMessageBox.about(self, "message", "clicked")
    def btn_2_clicked(self):
        print("BUTTON CLICKED!")

예제코드인 exampleForm은 QtWidgets 패키지 안에있는 QMainWindow를 상속받는다.

 

QMainWindow도 Qtwidgets처럼 캔버스(레이아웃) 이지만, 

QtWidgets와는 다르게 메뉴바와 상태바 등이 이미 세팅되어 나오는데 이부분은 나중에 살펴 볼 것이다.

 

 

코드를 한 부분씩 살펴보자

 

    def __init__(self):
        super().__init__()
        self.setupUI()

class 생성시 실행되는 생성자이다.

상속받은 부모 클래스의 생성자를 실행시켜주고 같은 클래스 내의 setupUI() 메소드를 실행시킨다.

 

    def setupUI(self):
        self.setWindowTitle("PY PY PY")
        #800,400 지점에 위치하도록 실행되고 900,500 크기를 가지도록 해라
        self.setGeometry(800,400, 900,500)

        btn_1 = QPushButton("button1", self)
        btn_2 = QPushButton("button2", self)
        btn_3 = QPushButton("button3", self)

        btn_1.move(20,20)
        btn_2.move(20,60)
        btn_3.move(20,100)

        btn_1.clicked.connect(self.btn_1_clicked)
        btn_2.clicked.connect(self.btn_2_clicked)
        btn_3.clicked.connect(QCoreApplication.instance().quit)

setWindowsTitle()

 - APP의 이름 설정

setGeometry()

 - APP이 실행될 위치와 창 크기 설정

QPushButton()

 -버튼  생성

btn_#.move()

 - 생성한 버튼 위치 설정 (지정하지않으면 0,0 부분에 모든 버튼들이 겹쳐질 것이다)

btn_#.clicked.connect()

 - 버튼이 클릭되었을때 어떤 동작을 할지 설정

 

 

버튼이 클릭되는걸 APP의 입장에서는 이벤트(시그널) 발생이라고 하는데,

connect() 메소드로 시그널과 이벤트처리함수를 연결시킬 수 있다.

여기서 이벤트를 처리하는 함수를 슬롯이라 부른다.

 

즉 예제 코드처럼 btn_1.clicked.connect(self.btn_1_clicked)이면,

btn_1 버튼이 클릭되었을때,

클래스 내의 btn_1_clicked라는 슬롯이 호출되어 지정한 동작을 수행할 것이다. (이 경우 메시지박스 생성)

 

QCoreApplication.instance().quit은 이미 정의된 슬롯으로서 앱을 종료시킨다. (닫기 클릭과 동일)

 

[그림8] pyqt-2 example

 

다음 예제를 살펴보자

 

이번에는 간단하게 Text Box를 만들어서 입력, 출력 부분을 해볼거다.

이전 예제에서 setupUI() 메소드 코드만 바꾸면된다.

 

    def setupUI(self):
        self.setWindowTitle("PY PY PY")
        self.setGeometry(800,400, 900,500)

        self.statusBar = QStatusBar(self)
        self.setStatusBar(self.statusBar)

        label_in = QLabel("input", self)
        label_in.move(100,100)
        label_out = QLabel("output", self)
        label_out.move(100,150)

	#EditBox
        self.editBox = QLineEdit("Please enter anything...", self)
        self.editBox.move(150,100)
        #TextBox
        self.textBox = QtWidgets.QPlainTextEdit(self)#from pyQt import QtWidgets
        self.textBox.move(150,150)

label 변수는 self를 안썼는데

editBox과 textBox 앞에 self를 쓴 이유는 다른 메소드에서 참조하기 위해서이다.

뒤에 사용할때 다시 한번 알아보자

 

[그림9] pyqt-3 example-1

결과화면이 뭔가 마음에 안든다.

editBox의 경우 넓이가 더 넓었으면좋겠고

textBox의 경우 더 컸으면 좋겠다.

 

코드를 아래와같이 수정해보자.

 

self.editBox = QLineEdit("Please enter anything...", self)
self.editBox.setGeometry(QtCore.QRect(150,100,350,30))#from pyQt import QtCore

self.textBox = QtWidgets.QPlainTextEdit(self)#from pyQt import QtWidgets
self.textBox.setGeometry(QtCore.QRect(100,180,400,200))#x, y, w, h
self.textBox.setReadOnly(True)

이제 훨씬 볼만하다.

self.textBox.stReadOnly(True)로 수정하지 못하고 출력만 할 수있도록 지정할 수 있다.

 

이제 이벤트를 몇개 만들어 슬롯과 연결해 보자.

 

    def setupUI(self):
        self.setWindowTitle("PY PY PY")
        self.setGeometry(800,400, 900,500)

        self.statusBar = QStatusBar(self)
        self.setStatusBar(self.statusBar)

        label_in = QLabel("input", self)
        label_in.move(100,100)
        label_out = QLabel("output", self)
        label_out.move(100,150)

        self.editBox = QLineEdit("Please enter anything...", self)
        self.editBox.setGeometry(QtCore.QRect(150,100,350,30))
        
        self.textBox = QtWidgets.QPlainTextEdit(self)
        self.textBox.setGeometry(QtCore.QRect(100,180,400,200))
        self.textBox.setReadOnly(True)
        
        self.editBox.textChanged.connect(self.ChangedEditBox)
        self.editBox.returnPressed.connect(self.InputEditBox)
        
    
    def ChangedEditBox(self):
    	#이렇게 다른 메소드에서 불러오기위해서 self를 입력한 것입니다.
        self.statusBar.showMessage(self.editBox.text())

    def InputEditBox(self):
        self.textBox.appendPlainText(self.editBox.text())
        self.editBox.clear()

5-6번째 라인을 보면 statusBar가 추가되었다.

statusBar는 레이아웃 아래쪽에 자리해서 '입력중...'같은 상태를 나타내어줄 수 있다. 

 

여기서는 self.editBox.textChanged.connect(self.ChangedEditBox) 을 통해

사용자가 입력한 값을 그대로 출력 시켜줄 것이다.

 

또한 self.editBox.returnPressed.connect(self.InputEditBox)을 통해

EditBox에서 사용자가 엔터를 입력하면 textBox에 출력 시켜줄 것이다.

 

직접실행해보면 아래와같다.

 

 

 

이제 코드부분은 간단하게 끝났다.

레이아웃 디자인을 코드로 하게되면 크기같은것을 지정할때 코드 수정 > 실행을 반족하는데 굉장히 비효율적이다.

 

때문에 C#이 그러하듯 PYQT 코드는 잘 안쓰고 대부분(거의 모두) 디자이너를 이용하는데

 

다음 PyQT-2에서 디자이너 사용법을 알아보자.