본문 바로가기
GUI 튜토리얼/PySide6 # Qt의 원조가 돌아왔다!

0. PySide6의 시그널-슬롯 활용예제: 다른 위젯을 슬롯으로 연결하기

by 일코 2021. 3. 28.

PySide 시그널-슬롯 활용예제2

QLineEdit의 textChanged 시그널을 QLabel.setText 슬롯과 연결하기

안녕하세요? 이번 포스팅을 시작하기 전에 지난 포스팅에서 작성했던 코드를 최대한 간단히 요약해서 다른 사람에게 설명하려면 뭐라고 하시겠어요? 참고하시라고 지난 번 작성한 코드를 여기 적어볼게요.

from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton
 
 
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.button = QPushButton(parent=self)
        self.setCentralWidget(self.button)
        self.button.setCheckable(True)
        self.button.clicked.connect(self.button_clicked)
        self.click_count = 0
 
    def button_clicked(self):
        self.click_count += 1
        self.button.setText(f"{self.click_count}번 클릭함")
 
 
app = QApplication()
window = MainWindow()
window.show()
app.exec_()
"MainWindow 창을 하나 만들고 버튼을 하나 생성한 후, 버튼을 클릭한 횟수를 버튼 텍스트에 표시하는 코드"

이 정도로 표현할 수 있겠죠? 근데 Qt를 사용하는 사람끼리는 아래처럼 표현할 수도 있어요. 다소 장황하게 느껴질 수 있지만 훨씬 명시적인 설명으로요.

"버튼의 clicked 시그널을 직접 작성한 button_clicked라는 메서드와 연결, button_clicked 안에다 클릭횟수를 카운트해서 button.setText를 통해 버튼 텍스트에 클릭횟수를 표시하게 했다."

(흠, 만족스럽지는 않네요;;;) 어떤 표현이 더 좋다고는 할 수 없지만, 아래쪽이 좀 더 명시적인 설명이라고 생각해요. 이렇게 명시적으로 소통하기 위해서 꼭 필요한 개념이 바로 "용어의 약속"이거든요. 프로그래밍 뿐만 아니라 어느 분야든 그렇죠. 상호간에 약속된 용어를 사용하지 않는다면 훨씬 비효율적이고, 의사소통이 힘들어지겠죠. 다행히 프로그래밍 분야에서는 이런 용어나 개념들이 상당히 오래 전(그래봤자 20여년?)에 확립됐고, 다행히 엄청나게 많거나 복잡하지도 않아요. 그런 맥락에서 시그널과 슬롯, 클래스와 인스턴스, 메서드와 속성, 상속이나 부모클래스, self와 super 같은 생소한 단어들이 나올 때, 인터넷 검색이나 관련서적을 통해서 정확히 어떤 의미인지 읽어보고 알아둘 필요는 있다고 생각해요. 인터넷엔 정말 많은 포스팅이 있어요. 예를 들어 self의 용도나 의미를 잘 모르겠으면 "파이썬 self" 라는 식으로 구글링하시면 돼요.

그럼 본론으로 돌아와서

이번에 제작해볼 프로그램은 바로 이겁니다.

라인에디트+레이블 위젯

 

 

TMI처럼 느껴지시겠지만,

Qt를 "큐티"라고 읽으시는 분들이 많죠.

"큐트"라고 읽으셔야 한다고 합니다.

"큐트"라고 읽는다. 출처는 나무위키.

하여튼, 저런 위젯을 만들어보겠습니다. 텍스트입력란에 타이핑을 하면 결과가 하단의 레이블에 동시에 출력되는 윈도우입니다.  코드는 아래와 같습니다.

from PySide6.QtWidgets import QApplication, QMainWindow, \
    QLabel, QLineEdit, QWidget, QVBoxLayout


class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.label = QLabel()
        self.input = QLineEdit()
        self.input.textChanged.connect(self.label.setText)
        layout = QVBoxLayout()
        layout.addWidget(self.input)
        layout.addWidget(self.label)
        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


app = QApplication()
window = MainWindow()
window.show()
app.exec_()

임포트문을 보시면, QWidget은 예전에 사용해보신 적이 있고요, QLabel, QLineEdit 및 QVBoxLayout 등 세 개의 위젯은 이번에 처음 사용해보시는 위젯입니다. 이 중에 QVBoxLayout은 이후 레이아웃에 관해 설명드릴 때 한 번 더 다루겠지만, 간략히 설명을 드리고자 합니다.

QVBoxLayout의 사용법

QVBoxLayout에서 "V"는 "Vertical"의 "V"입니다. 세로방향으로 위젯을 위에서부터 차례대로 붙일 수 있습니다. addWidget 메서드를 사용해서 다른 위젯을 붙일 수 있고, 심지어는 다른 레이아웃 위젯을 붙일 수도 있습니다. 중요한 점은 나중에 추가한 위젯이 밑으로 붙는다는 겁니다. (그래서 "그릇"이나 "담는다"는 비유는 적절하지 않은 것 같네요.)

멋진 Qt 레이아웃 예(출처:https://build-system.fman.io/pyqt5-tutorial). 가장 크게 네 개의 VBox레이아웃을 사용한 것으로 보입니다. 각각의 VBox는 한 개 이상의 HBox를 포함하고 있고요.

그런데 레이아웃 위젯은 사용시 유의할 점이 하나 있습니다. 첫 번째는 레이아웃위젯 단독으로는 메인윈도우 안에 들어갈 수 없다는 것입니다. QPushButton이나 QLabel 위젯과는 달리 레이아웃 위젯은 일종의 컨테이너가 추가로 필요합니다. 컨테이너로는 거의 대부분의 경우 QWidget을 사용합니다. 최종 레이아웃을 QWidget 안에 세팅하고 나서(라인15), QWidget을 메인윈도우에 얹는 방식(라인16)으로 코딩하는 것이 일반적입니다. (혹은 QMainWindow 말고 QWidget 자체를 윈도우로 사용하는 경우도 많습니다.)

왜냐면, QVBoxLayout은 QWidget가 아니라 QLayout에서 상속된 클래스이기 때문입니다. QMainWindow 안에는 QWidget을 상속받아 만든 위젯만 들어갈 수 있기 때문에, QVBoxLayout을 바로 집어넣을 수 없는 것입니다. 그래서 container라는 QWidget 인스턴스를 하나 추가로 만드는 것입니다. 레이아웃 자체가 뭔가를 넣을 수 있는 "컨테이너"인데, 이 컨테이너를 QWidget이라는 "컨테이너" 안에 다시 담는다는 것이 생소하게 느껴지실 수 있지만, 똑똑한 프로그래머들이 고안해놓은 방식이니 우선은 이대로 받아들이고 넘어갑시다.

그럼, 모든 코드를 다 설명드리지 않아도 될 것 같습니다. 중요하거나 쉽게 이해하기 어려운 라인만 찝어서 설명드리겠습니다.

라인8~9: self.label = QLabel(); self.input = QLineEdit()

윈도우 안에서 사용할 위젯을 미리 정의했습니다. QLabel은 레이블(혹은 라벨) 이름대로 이름표 같은 느낌의 위젯입니다. QLineEdit 위젯은 한 줄짜리 입력창입니다. 두 줄 이상의 텍스트를 입력할 수 없으며, 기본적으로는 서식이 적용되지 않습니다.

라인10: self.input.textChanged.connect(self.label.setText)

미안합니다. 이거 보여드리려고 어그로 끌었습니다. 이번 포스팅의 핵심인데 별로 부각되지 않는 것 같아 부끄럽습니다. 라인에디트 위젯에서 텍스트가 바뀌는 시그널(textChanged)을 어느 슬롯과 연결하냐면 바로 self.label.setText입니다. 조금만 곰곰이 생각해보면 이게 어떻게 가능할까 싶기도 한데, 이런 궁금증은 doc.qt.io로 직접 가서 검색해보시거나 구글링해보시면 의외로 간단하게 궁금증을 해결하실 수 있습니다.

textChanged 시그널 안에 "새로운 텍스트"를 인수로 넣어 보냅니다.

위 설명을 참고해보면, 문자열이 변경될 때마다 빠르게 문자열 전체가 새로고침되는 방식으로 레이블이 변경되는군요.

라인11: layout = QVBoxLayout()

QVBoxLayout 클래스의 인스턴스 layout을 생성했습니다. 현재는 완전히 비어있는 보드이고, 위젯을 붙일 때에는 위에서부터 차곡차곡 추가됩니다.

라인12~13: layout.addWidget(self.input); layout.addWidget(self.label)

layout에다 미리 생성한 위젯들을 추가합니다. self.input을 먼저 붙였으니 input이 위에, label이 아래에 위치해요.

나중에 붙인 위젯이 아래로 붙습니다.

라인14~15: container = QWidget(); container.setLayout(layout)

호기심이 많은 분들이 의아해하실 수도 있는 부분입니다. ('그냥 레이아웃을 MainWindow 안에 바로 붙이면 안되나?')

위에서 설명드렸듯이 layout은 QWidget이 아닌 QLayout을 상속받은 인스턴스이므로 QMainWindow 객체 안에 바로 붙일 수가 없습니다. 그래서 QWidget 객체인 container 안에 layout을 적용하고, container를 QMainWindow 안에다 붙이면 됩니다.

라인16: self.setCentralWidget(container)

지난 포스팅에도 빠지지 않고 사용해서 이제 익숙해질 법한 코드입니다. container를 window 안에 꽉 차게 담았습니다.


코드를 실행해봅시다.

Ctrl-Shift-F10을 눌러 코드를 실행해봅시다.

실행결과, 텍스트가 길어지니까 창도 알아서 넓어진다?

텍스트가 길어지니까 창도 따라서 넓어져버리는 문제가 발생했네요? 창의 크기는 고정해야되겠다는 생각이 드는데 여러분은 어떠세요? 해결방법도 제가 알려드린 적이 있었어요. 바로 self.setFixedSize를 사용하는 겁니다. 생성자(__init__)를 정의하는 부분 제일 아래에다

self.setFixedSize(200, 80)

를 추가한 후에 다시 실행해봅시다.

setFixedSize 메서드를 사용해서 창의 크기를 고정했습니다. 훨씬 낫군요.

이번 포스팅은 여기서 마쳐야겠습니다.


 

혹시 이거 아시나요?

많이들 사용하시는 프로그램 중에 Qt로 제작된 프로그램 혹시 어떤 게 있는지 아시나요?

 

Category:Software that uses Qt - Wikipedia

 

en.wikipedia.org

참고로 밀리의서재 PC뷰어도 Qt로 제작되었던 걸로 기억이 나네요.

그럼 PySide로 제작된 프로그램은 어떤 게 있을까요?

 

PySide Based Apps - Qt Wiki

Non-comprehensive list of PySide based applications This page aggregates the applications that were developed using PySide. Feel free to include your own application, helping this list to grow and be as complete as possible! Description Available for: Linu

wiki.qt.io

대략 이런 것들이 있다고 합니다. (이외에도 많이 있을 거예요.)

Qt(PySide)의 기본 문법을 익히고 나면 직접 간단한 응용프로그램을 만들어보면 좋을 것 같습니다.

다소 간단한 수준에서요.

 

그럼 이번 포스팅은 여기서 마치겠습니다.

 

 


donaricano-btn

댓글