python:: GUI app framework _flet _CustomTkinter

 

python으로 무언가를 만들어 보려면

_web으로는 flask, django가 있고

_local app으로는 PyQT (or PySide), kivy 이런 툴이 자주 언급된다.

 

 

어느 정도 코딩에 재미가 붙어서 지속하려면 본인만 쓰는 것이 아니라 누군가에게 도움을 주거나 공유할 만한 성과?도 있으면 좋을 것이다. 그래서 조금 더 편한 방법으로는 local App을 만들어 보는 것을 추천한다. pyinstaller를 통한 간단하지만 '앱'이나 exe파일을 배포하면서 간단한 툴이라도 만들면 다른 사람에게 전달해도 좋고 간단히 실행할 수 있어서 바탕화면에 놓고 정기적으로 실행을 해도 좋겠다.

 

다른 방법인 웹web에서 구동하자니 py파일을 실행하려면 이를 실행해 줄 Server가 필요하다. 내 컴퓨터와는 환경이 다르다. 지금은 그나마 파이썬의 기본 문법을 알기에  flask로 만들어도 좋겠군! 하는 생각이 들지만, 처음 파이썬을 접했던 시기를 되돌아보면 이 방법을 처음하는 사람에게 추천하지 않는다. 나의 초기시절엔 구름ide, HEROKU, azure 등의 무료server 찾아다니면서 많은 시간을 썼던 것 같다. (일단 oracle 같은 무료서버를 이용하니 port forwarding부터 설정해야 하는데....)

 

 

그런데!! (알면서도, 실력이 없기도 하지만)

선뜻 파이썬으로 local app을 만들기 싫던 이유가 있는데, 그것은

너무 디자인이 구리다!는 점이다

잘 만든 앱인데, VBA 창 같다_ 엄청 시간 들었는데 20년 전 프로그램 같다

 

 

 

그리고 용량이 무지막지하게 크다. 아무 것도 포함하지 않고 기본이 25MB 용량이다

그래서 이메일이나 어디 공간에 올리려고 해도 만만치가 않다.

 

 

 

 

많은 책과 인터넷 자료에서 언급하는 PyQt는 무료 사용은 문제 없으나, 유료에서는 라이선스를 구입해야 하고 PySide6를 사용하면 괜찮다는데 딱히 명확히 이걸 쓰세요! 라고 해주는 글이 잘 없다.

 

그래서 여기저기를 검색하다보니 python.org에 목록으로 잘 정리된 것을 발견하였다.

 

 

 

https://wiki.python.org/moin/GuiProgramming

 

GuiProgramming - Python Wiki

GUI Programming in Python Python has a huge number of GUI frameworks (or toolkits) available for it, from TkInter (traditionally bundled with Python, using Tk) to a number of other cross-platform solutions, as well as bindings to platform-specific (also kn

wiki.python.org

 

 

하나를 만들면, 윈도우/맥/인터넷/안드로이드 등 가리지 않고 사용하면 좋을 것 같아서 Cross-Platform 을 쭈욱 살펴보면

최근에는 업데이트 안되는 것들도 많고 각 사이트 들을 살펴보면 결국 PyQt를 제일 많이 사용하게 되는 이유를 알게 된다.

 

요즘 핫HOT 하다는 빙챗도 동일한 대답을 준다.

 

 

유투브 자료도 찾아보자. 비슷하다

(21) 7 Top Python GUI Libraries (2023) [Pricing, Pros, Cons, & 5 factors to help you choose] - YouTube

 

 

 

 

 

 

 

 

 

새로운 것도 접해보자

뭔가 새로운 것도 있을 것 같아서 하나하나 찾아보니 많이들 언급하는 것 외에 괜찮은 것들이 있어서 살펴본다

 

 

CustomTkinter

 

https://github.com/TomSchimansky/CustomTkinter

기존에 VBA스러운 화면을 나름 현대화 해서 보여준다. 언뜻 보면 나름 이 정도면 나쁘지 않다라는 생각이 들어서 간단한 툴을 만들기에는 나쁘지 않아 보인다.

 

 

pip 명령어로 모듈을 설치하고

pip3 install customtkinter

 

 

app.py 파일을 만들어서 

#app.py

import tkinter
import customtkinter

customtkinter.set_appearance_mode("System")  # Modes: system (default), light, dark
customtkinter.set_default_color_theme("blue")  # Themes: blue (default), dark-blue, green

app = customtkinter.CTk()  # create CTk window like you do with the Tk window
app.geometry("400x240")

def button_function():
    print("button pressed")

# Use CTkButton instead of tkinter Button
button = customtkinter.CTkButton(master=app, text="CTkButton", command=button_function)
button.place(relx=0.5, rely=0.5, anchor=tkinter.CENTER)

app.mainloop()

 

간단한 버튼 1개 있는 창을 만들어 보자.

 

 

 

** 이 친구를 exe파일로 만들려면 pyinstaller로 만들면 에러가 난다.

그래서 아래 링크에 걸어둔 "Auto-py-to-exe"를 사용해서 만들어야 문제 없이 작동한다.

 

 

https://github.com/TomSchimansky/CustomTkinter/wiki/Packaging

 

Packaging

A modern and customizable python UI-library based on Tkinter - TomSchimansky/CustomTkinter

github.com

 

 

https://seong6496.tistory.com/214

 

[Python] py파일 exe로 만들기(Auto-py-to-exe)

파이썬에서 pyinstaller로 exe 파일을 만들었습니다만 각종 옵션을 넣는 코드를 직접 알아야해서 불편함이 많았는데 이번에 auto-py-to-exe라는 모듈이 나왔습니다. 제 생각엔 pyinstaller의 옵션을 gui로

seong6496.tistory.com

 

 

 

 

 

 

 

 

Flet.dev  (using Flutter)

 

 

Flutter를 사용해서, Python 파일을 app로 만들어 주는 것이다.

https://flet.dev/

 

The fastest way to build Flutter apps in Python | Flet

Build internal web apps quickly in the language you already know.

flet.dev

 

 

 

일단, 앱이 이 쁘 다 

 

 

 

요즘에 출시되는 어떠한 것이라도 대부분 Documentation이 잘 되어 있지만, 

나처럼 초보자가 따라하기에 문서가 길지 않고 차근차근 따라하면 뚝딱 창하나를 만들 수 있게 되어 있어서 좋다. 

 

 

 

 

 

 

https://flet.dev/docs/tutorials/python-calculator

 

제일 만만한 계산기 앱을 하나 만들어 보자.

pip install flet

 

 

이렇게만 넣으면 창이 하나 뚝딱 나오고

import flet as ft

def main(page: ft.Page):
    page.add(ft.Text(value="Hello, world!"))

ft.app(target=main)

 

 

아래 코드를 붙여 넣으면 계산기가 하나 뚝딱 만들어 진다.

#calc.py

import flet
from flet import (
    Column,
    Container,
    ElevatedButton,
    Page,
    Row,
    Text,
    UserControl,
    border_radius,
    colors,
)


class CalculatorApp(UserControl):
    def build(self):
        self.reset()
        self.result = Text(value="0", color=colors.WHITE, size=20)

        # application's root control (i.e. "view") containing all other controls
        return Container(
            width=300,
            bgcolor=colors.BLACK,
            border_radius=border_radius.all(20),
            padding=20,
            content=Column(
                controls=[
                    Row(controls=[self.result], alignment="end"),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="AC",
                                bgcolor=colors.BLUE_GREY_100,
                                color=colors.BLACK,
                                expand=1,
                                on_click=self.button_clicked,
                                data="AC",
                            ),
                            ElevatedButton(
                                text="+/-",
                                bgcolor=colors.BLUE_GREY_100,
                                color=colors.BLACK,
                                expand=1,
                                on_click=self.button_clicked,
                                data="+/-",
                            ),
                            ElevatedButton(
                                text="%",
                                bgcolor=colors.BLUE_GREY_100,
                                color=colors.BLACK,
                                expand=1,
                                on_click=self.button_clicked,
                                data="%",
                            ),
                            ElevatedButton(
                                text="/",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="/",
                            ),
                        ],
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="7",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="7",
                            ),
                            ElevatedButton(
                                text="8",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="8",
                            ),
                            ElevatedButton(
                                text="9",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="9",
                            ),
                            ElevatedButton(
                                text="*",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="*",
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="4",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="4",
                            ),
                            ElevatedButton(
                                text="5",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="5",
                            ),
                            ElevatedButton(
                                text="6",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="6",
                            ),
                            ElevatedButton(
                                text="-",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="-",
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="1",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="1",
                            ),
                            ElevatedButton(
                                text="2",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="2",
                            ),
                            ElevatedButton(
                                text="3",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="3",
                            ),
                            ElevatedButton(
                                text="+",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="+",
                            ),
                        ]
                    ),
                    Row(
                        controls=[
                            ElevatedButton(
                                text="0",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=2,
                                on_click=self.button_clicked,
                                data="0",
                            ),
                            ElevatedButton(
                                text=".",
                                bgcolor=colors.WHITE24,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data=".",
                            ),
                            ElevatedButton(
                                text="=",
                                bgcolor=colors.ORANGE,
                                color=colors.WHITE,
                                expand=1,
                                on_click=self.button_clicked,
                                data="=",
                            ),
                        ]
                    ),
                ],
            ),
        )

    def button_clicked(self, e):
        data = e.control.data
        if self.result.value == "Error" or data == "AC":
            self.result.value = "0"
            self.reset()

        elif data in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "."):
            if self.result.value == "0" or self.new_operand == True:
                self.result.value = data
                self.new_operand = False
            else:
                self.result.value = self.result.value + data

        elif data in ("+", "-", "*", "/"):
            self.result.value = self.calculate(
                self.operand1, float(self.result.value), self.operator
            )
            self.operator = data
            if self.result.value == "Error":
                self.operand1 = "0"
            else:
                self.operand1 = float(self.result.value)
            self.new_operand = True

        elif data in ("="):
            self.result.value = self.calculate(
                self.operand1, float(self.result.value), self.operator
            )
            self.reset()

        elif data in ("%"):
            self.result.value = float(self.result.value) / 100
            self.reset()

        elif data in ("+/-"):
            if float(self.result.value) > 0:
                self.result.value = "-" + str(self.result.value)

            elif float(self.result.value) < 0:
                self.result.value = str(
                    self.format_number(abs(float(self.result.value)))
                )

        self.update()

    def format_number(self, num):
        if num % 1 == 0:
            return int(num)
        else:
            return num

    def calculate(self, operand1, operand2, operator):

        if operator == "+":
            return self.format_number(operand1 + operand2)

        elif operator == "-":
            return self.format_number(operand1 - operand2)

        elif operator == "*":
            return self.format_number(operand1 * operand2)

        elif operator == "/":
            if operand2 == 0:
                return "Error"
            else:
                return self.format_number(operand1 / operand2)

    def reset(self):
        self.operator = "+"
        self.operand1 = 0
        self.new_operand = True


def main(page: Page):
    page.title = "Calc App"

    # create application instance
    calc = CalculatorApp()

    # add application's root control to the page
    page.add(calc)


flet.app(target=main)

 

 

 

 

 

이제 원했던 exe 파일을 만들어 보자

https://flet.dev/docs/guides/python/packaging-desktop-app

 

 

명령어 창에서 pyinstaller를 설치하고 , "flet pack" 명령어로 exe파일 만들 .py 파일을 지정하면 완성이다

# exe파일 모듈 설치
pip install pyinstaller


# flet 명령어로 exe 파일 만들기
flet pack calc.py

 

 

그럼 calc.py 있던 폴더에 dist 폴더가 생기면서 exe파일이 추가되어 있을 것이다.

초간단!

 

 

 

** 추가로 web publish가 된다!, flask / django 처럼 인터넷에 올리면 쓸 수 있다는 것이다.

(아직 import module에서 문제가 좀 있는 듯 하지만...) py 파일 안에 모든 코드를 입력하면 잘 작동한다.

1~2년 더 기다리면 어디에서든 사용하기 좋은 앱을 빨리 만들 수 있을지도 모른다. flutter를 base하는 것의 장점 일 것이다.

 

 

 

 

 

 

_end

 

반응형