본문 바로가기

PyQt GUI

PyQt GUI (10) : StyleSheet 설정을 통해 계산기 꾸미기 - setStyleSheet

728x90

지난 포스팅에서 만든 계산기 입니다. 간단한 계산기의 기능을 문제 없이 소화하긴 하지만... 생김새가 그리 마음에 드는 편은 아닙니다. UI를 꾸미는 설정을 하나도 하지 않았기 때문에 Dialog, 레이아웃, 위젯들은 기본으로 설정 돼 있는 색이나 모양을 갖게 됩니다. 글씨체(Font) 역시 마찬가지로 가장 무난하지만 밋밋한 글꼴입니다. 

 

이번 포스팅에서는 UI를 꾸미는 것을 알아 보도록 하겠습니다. 우선, 최종적으로 어떻게 꾸밀 것인지를 먼저 보여주는 것이 좋을 것 같네요. 

넵. 최종 GUI 계산기 어플리에케이션의 모습니다. 꾸미기를 하나도 하지 않은 계산기에 비해서 완전히 달라진 모습입니다. 저의 미적감각으로는 상당히 깔끔하고 이쁜데 여러분은 어떠신가요? 더 이쁘게 꾸밀 수 있으신 분은 이번 포스팅의 내용을 바탕으로 더 이쁘게 꾸밀 수 있을 겁니다. 

 

레이아웃이나 위젯의 모양이나 색을 변화시킬 수 있는 방법은 크게 두 가지 인데요, 

 

(1) 각 위젯의 크기등에 대한 정책을 설정한다

(2) 스타일시트(Stylesheet)를 통해서 외관을 꾸민다

 

입니다. (1)의 내용은 이전 포스팅에서 알아본 바 있는데, 사이즈 정책을 바꾸어서 전체 창의 크기에 따라서 위젯의 크기를 따라 변하게 하는 것 등 보통은 크기에 대한 꾸미기 입니다. 이걸갖고 꾸미기 라고 하는게 맞을지는 모르겠는데, 어쨌든 기본적으로 설정 돼 있는 것들 건드리는 것이니 꾸미기라고 하겠습니다. 

 

(2)의 내용이 이번 포스팅에서 다룰 주제입니다. 스타일시트는 말 그대로 위젯이나 레이아웃에 대한 스타일을 정해주는 것인데, 색, 폰트, 모양, 반응에 대한 외적인 설정을 해주는 것 입니다. 

 

이전 계산기에 비해서 최종적으로 꾸며진 계산기의 차이는 크게 봐서 두 가지가 있는데요,

 

(1) Equation, Solution 부분을 버튼 부분과 구분 짓기 위해서 Equation, Solution 부분을 GroupBox를 이용해서 구분지었다

(2) 각 위젯, 특히 버튼 위젯, 의 색상과 모양(꼭지점 부분)을 변경하였다.

 

입니다. (1)은 꾸미기라기 보다는 GroupBox 위젯을 도입하여 도드라지게하기 라고 해야할 것 같습니다. 이 부분의 코딩은 GroupBox를 다룬 지난 포스팅을 보신 분이라면 쉽게 하실 수 있을 것 입니다. 이 부분의 코딩을 보면, 

group_equation_solution = QGroupBox() # QGroupBox를 생성
group_equation_solution.setLayout(layout_equation_solution)
        # 기존에 있던 layout_equation_solution 레이아웃을 group_equation_solution에 설정

......

main_layout.addWidget(group_equation_solution, stretch=2)
        # addLayout(layout_equation_solution) 대신 , addWidget(group_equation_solution)

위와 같이 레이아웃을 그룹박스로 한 번 감싸고, 그룹박스를 메인 레이아웃에 추가하였습니다. 이제 기본 코딩을 통해서 할 수 있는 부분은 완료하였습니다. 기능적으로도 변하는 것이 없으니, 앞으로 다룰 모든 부분은 스타일시트에 대한 것 것입니다. 

 

우선 가장 간단한 스타일시트를 만들어 보겠습니다. 회색의 배경색을 흰색으로 바꾸어 보겠습니다. update_style 이라는 이름의 파일을 추가로 하나 만들고, 이 파일안에 텍스트로 아래와 같이 입력하고 저장합니다.

QDialog{
background:white;
}

위 스타일시트는 QDialog의 배경을 흰색으로 바꾸겠다는 설정을 하고 있습니다. 추가적으로 설정이 필요하다면 background:white; 그 다음줄에 계속 추가를 해 주면 됩니다. 이제 update_style 파일에 설정한 스타일을 메인 프로그램에 반영을 해야하는데,

    def __init__(self):
        super().__init__()
        self.set_style()
        self.init_ui()

    def set_style(self):
        with open("update_style", 'r') as f:
            self.setStyleSheet(f.read())

메인 프로그램의 init_ui() 메서드를 부르기 전에 set_style() 메서드를 호출 합니다. set_style() 메서드에서는 방금 우리가 반든 update_style의 내용을 f.read()를 통해 모두 텍스트 형태로 읽고, 이를 setStyleSheet()를 이용하여 스타일시트로 설정을 하게 됩니다. 스티일시트에 적용할 내용이 짧을 경우에는 굳이 스타일시트를 위한 파일을 따로 만들 필요는 없고 바로 텍스트 형태로 쓴 다음 위와 같이 setStyleSheet()를 통해서 스타일시트를 적용해도 되지만, 설정이 많은 경우 혹은 코딩을 좀 더 깔끔하게 하고 싶은 경우에는 우리의 방법과 같이 새로운 파일을 이용하는 편이 더 좋습니다. 

배경색이 회색에서 흰색으로 변경 되었습니다.

 

이제, 사칙연산 버튼을 하늘색으로 변경해 보도록 하겠습니다. 역시나 update_style 파일에 버튼에 대한 옵션을 추가하면 되는데요, 사칙연산 버튼은 이전 포스팅에서

class QPushButtonOperation(QPushButton):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        font = QFont("Helvetia", 13)
        font.setBold(True)
        self.setFont(font)

와 같이 QPushButtonOperation 이라는 클래스를 새롭게 만들어서 사용했습니다. 따라서 스타일시트를 설정할 때도, QPushButton에 대한 설정이 아니라 QPushButtonOperation에 대한 설정을 해 주어야 합니다.

QPushButtonOperation{
background-color:rgb(80, 188, 223, 0.5);
color:black;
border-radius:5px;
}

QPushButtonOperation에 대한 스타일 설정입니다. QPushButton을 그대로 상속 받았기 때문에, QPushButton의 스타일 설정과 같은 방식으로 하면 됩니다. background-color, color, border-radius는 각각 버튼의 배경색, 글짜색, 버튼의 테두리 꼭지점에서 둥글게 만드는 반지름을 뜻 합니다. 색상은 white, black, yellow와 같이 자주 사용하는 색상에 대해서는 영문으로 입력을 해도 되고, 그렇지 않은 경우에는 rbg로 입력할 수 있습니다. 이번 계산기에서 사용할 하늘색의 rgb 코드는 (80, 188, 223) 입니다. 마지막에 0.5는 투명한 정도 입니다. 위와 같이 설정을 추가하고 update_style파일을 저장하고 다시 전체 프로그램을 실행하면, 

와 같이 사칙연산 버튼의 색깔이 하늘색으로 바뀐것을 볼 수 있습니다. 

 

Clear, BackSpace, = 버튼과 숫자 버튼 역시 같은 방식으로 색깔을 변경할 수 있는데요,

QPushButtonNumber{
background-color:rgb(247, 230, 0, 0.5);
color:black;
border-radius:5px;
}

와 같이 추가하면 됩니다. 위 QPushButtonOperation 의 스타일과 달라진 것은 단지 rgb코드 뿐 입니다. 실행을 하면, 

네, 설정이 제대로 반영 되었습니다. 

 

Equation, Solution 부분의 설정도 해주어야 하는데요, 사실 기본값이 원하는 값이기 때문에 굳이 따로 설정을 해 주어야 하는 것은 아니지만,

QLineEditCustom{
background-color:white;
color:black;
}

QLabelCustom{
color:black;
}

와 같이 LineEdit과 Label의 스타일 설정도 해 줄 수 있습니다. LineEdit과 Label을 각각 LineEditCustom, LabelCustom으로 새로운 위젯 클래스를 생성했으니, 위 코드에서 빨간색으로 표시된 이름 역시 이와 같아야 합니다. 

 

끝! 이라고 생각하실 수 있는데, 사실 끝이 아닙니다. 사진에서는 확인하기가 어려운데, 실제 제가 최종적으로 만든 계산기에서는 버튼 위에 마우스포인터를 올려 놓으면 버튼의 색이 바뀌게 돼 있습니다. 

위와 같이 만일 1번 버튼위에 마우스 포인터를 올리면, 실제로 1번이 선택되었다는 것을 강조하기 위해서 기존의 색깔 보다는 조금 더 진한색으로 바뀌도록 만들었습니다. 사칙연산 버튼 역시, 버튼 위에 마우스 포인터를 올리면 조금 더 진한 하늘색으로 색이 변하게 됩니다. 

 

이 같이 위젯 위에 마우스 포인터를 올는 것을 hover라고 합니다. hover의 뜻는 "맴돌다"인데요, 마우스 포인터가 위젯위를 맴도는것을 뜻한다고 볼 수 있습니다. 따라서 해당 위젯에서 hover를 할 때, 위젯의 스타일이 어떻게 바뀔지를 설정할 수 있습니다. 물론 설정을 하지 않으면 해당 위젯에서 hover를 한다고하더라도 아무런 변화가 없습니다. 

QPushButtonNumber:hover{
background-color:rgb(247, 230, 0, 1.0);
color:black;
border-radius:5px;
}

QPushButtonOperation:hover{
background-color:rgb(80, 188, 223, 1.0);
color:black;
border-radius:5px;
}

위와 같이 위젯의 이름에 :hover를 추가하여 스타일을 추가적으로 설정할 수 있습니다. 기본 위젯의 설정에 비해서 변화시킬 것은 없고, 단지 색상만 진하게 만들어주면 되는것이라서 background-color 부분의 투명도를 나타내는 마지막 숫자만 0.5 -> 1.0 으로 변경하였습니다. 0.0이면 완전히 투명한것, 1.0이면 완전히 불투명하여 원래의 색을 뜻합니다. 

 

자~ 이제 원하는 스타일시트 적용은 모두 끝났습니다. 그러나 아직 하지 않은 것 2개가 있는데요. 

위에 있는 이미지는 최종적으로 목표로하는 GUI의 제목 표시줄, 아래는 현재 GUI의 제목 표시줄입니다. 현재까지의 GUI프로그램에서는 제목 설정과 아이콘 설정을 하지 않아서 기본적인 아이콘과 기본적인 이름 python 이 설정 돼 있습니다. 이걸 수정해 보도록 하죠.

 

        self.setWindowTitle("CALCULATOR")
        self.setWindowIcon(QIcon("./icon.png"))

이 두가지는 위와 같이 코드 두 줄을 추가하면 간단히 완수 할 수 있습니다. 뭐 아마 추가적으로 설명을 하지 않아도 이해할 수 있을 것 같은데요. 이미지 파일을 준비하고, 이 이미지 파일을 두 번째 줄과 같이 하면 프로그램의 아이콘으로 사용할 수 있습니다. 

네. 최종적으로 완료된 모습입니다! 제법 쓸만하죠?

 

프로그램의 전체 코드와 전체 스타일시트 파일의 내용은 아래와 같습니다. 

import sys
import PyQt5
from PyQt5 import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *

class QPushButtonOperation(QPushButton):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        font = QFont("Helvetia", 13)
        font.setBold(True)
        self.setFont(font)

class QPushButtonNumber(QPushButton):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        font = QFont("Helvetia", 13)
        font.setBold(True)
        self.setFont(font)

class QLineEditCustom(QLineEdit):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        font = QFont("Helvetia", 13)
        self.setFont(font)

class QLabelCustom(QLabel):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        font = QFont("Helvetia", 13)
        font.setBold(True)
        self.setFont(font)

class Main(QDialog):
    def __init__(self):
        super().__init__()
        self.set_style()
        self.init_ui()

    def set_style(self):
        with open("update_style", 'r') as f:
            self.setStyleSheet(f.read())

    def init_ui(self):
        main_layout = QVBoxLayout()

        layout_operation = QHBoxLayout()
        layout_clear_equal = QHBoxLayout()
        layout_number = QGridLayout()
        layout_equation_solution = QFormLayout()

        label_equation = QLabelCustom("Equation: ")
        label_solution = QLabelCustom("Solution: ")
        self.equation = QLineEditCustom("")
        self.solution = QLineEditCustom("")

        layout_equation_solution.addRow(label_equation, self.equation)
        layout_equation_solution.addRow(label_solution, self.solution)
        group_equation_solution = QGroupBox()
        group_equation_solution.setLayout(layout_equation_solution)

        button_plus = QPushButtonOperation("+")
        button_minus = QPushButtonOperation("-")
        button_product = QPushButtonOperation("x")
        button_division = QPushButtonOperation("/")

        button_plus.clicked.connect(lambda state, operation = "+": self.button_operation_clicked(operation))
        button_minus.clicked.connect(lambda state, operation = "-": self.button_operation_clicked(operation))
        button_product.clicked.connect(lambda state, operation = "*": self.button_operation_clicked(operation))
        button_division.clicked.connect(lambda state, operation = "/": self.button_operation_clicked(operation))

        layout_operation.addWidget(button_plus)
        layout_operation.addWidget(button_minus)
        layout_operation.addWidget(button_product)
        layout_operation.addWidget(button_division)

        button_equal = QPushButtonNumber("=")
        button_clear = QPushButtonNumber("Clear")
        button_backspace = QPushButtonNumber("Backspace")

        button_equal.clicked.connect(self.button_equal_clicked)
        button_clear.clicked.connect(self.button_clear_clicked)
        button_backspace.clicked.connect(self.button_backspace_clicked)

        layout_clear_equal.addWidget(button_clear)
        layout_clear_equal.addWidget(button_backspace)
        layout_clear_equal.addWidget(button_equal)

        number_button_dict = {}
        for number in range(0, 10):
            number_button_dict[number] = QPushButtonNumber(str(number))
            number_button_dict[number].clicked.connect(lambda state, num = number:
                                                       self.number_button_clicked(num))
            if number >0:
                x,y = divmod(number-1, 3)
                layout_number.addWidget(number_button_dict[number], x, y)
            elif number==0:
                layout_number.addWidget(number_button_dict[number], 3, 1)

        button_dot = QPushButtonNumber(".")
        button_dot.clicked.connect(lambda state, num = ".": self.number_button_clicked(num))
        layout_number.addWidget(button_dot, 3, 2)

        button_double_zero = QPushButtonNumber("00")
        button_double_zero.clicked.connect(lambda state, num = "00": self.number_button_clicked(num))
        layout_number.addWidget(button_double_zero, 3, 0)

        main_layout.addWidget(group_equation_solution, stretch=2)
        main_layout.addLayout(layout_operation, stretch=1)
        main_layout.addLayout(layout_clear_equal, stretch=1)
        main_layout.addLayout(layout_number, stretch=3)

        self.setLayout(main_layout)
        self.setWindowTitle("CALCULATOR")
        self.setWindowIcon(QIcon("./icon.png"))
        self.resize(500, 500)
        self.show()

    #################
    ### functions ###
    #################
    def number_button_clicked(self, num):
        equation = self.equation.text()
        equation += str(num)
        self.equation.setText(equation)

    def button_operation_clicked(self, operation):
        equation = self.equation.text()
        equation += operation
        self.equation.setText(equation)

    def button_equal_clicked(self):
        equation = self.equation.text()
        solution = eval(equation)
        self.solution.setText(str(solution))

    def button_clear_clicked(self):
        self.equation.setText("")
        self.solution.setText("")

    def button_backspace_clicked(self):
        equation = self.equation.text()
        equation = equation[:-1]
        self.equation.setText(equation)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = Main()
    sys.exit(app.exec_())
QDialog{
background:white;
}

QPushButtonOperation{
background-color:rgb(80, 188, 223, 0.5);
color:black;
border-radius:5px;
}

QPushButtonNumber{
background-color:rgb(247, 230, 0, 0.5);
color:black;
border-radius:5px;
}

QLineEditCustom{
background-color:white;
color:black;
}

QLabelCustom{
color:black;
}

QPushButtonNumber:hover{
background-color:rgb(247, 230, 0, 1.0);
color:black;
border-radius:5px;
}

QPushButtonOperation:hover{
background-color:rgb(80, 188, 223, 1.0);
color:black;
border-radius:5px;
}

 

728x90