영어단어 데스크탑 위젯 개발기 #1

Ashred·2025년 5월 26일

화면 구성


엔터를 치면 정답을 확인 -> 다음 문제로 넘어감


주요 코드

1. 프레임 없는 위젯 창 + 마우스 드래그 이동

self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)

→ 위젯 느낌 내려면 꼭 해야 하는 설정.
창 테두리 싹 없애고 항상 위에 떠 있게 만들 수 있음.

그리고 드래그 이동도 구현함.
진짜 위젯처럼 마우스로 꾹 누르고 이동 가능:

def mousePressEvent(self, event):
    if event.button() == Qt.LeftButton:
        self.offset = event.pos()

def mouseMoveEvent(self, event):
    if event.buttons() == Qt.LeftButton:
        self.move(self.pos() + event.pos() - self.offset)

2. 다크모드 전체 적용 (QPalette 활용)

def apply_dark_theme(self):
    dark_palette = QPalette()
    dark_palette.setColor(QPalette.Window, QColor(30, 30, 30))
    ...
    QApplication.instance().setPalette(dark_palette)

3. 엑셀 기반 콘텐츠 불러오기 + 무작위 퀴즈

df = pd.read_excel(filepath)
self.current_quiz = random.choice(self.quiz_data)

이 프로젝트 핵심은 결국 엑셀로 문제 등록해서 내가 외우고 싶은 표현 넣는 거니까
표현/뜻/뉘앙스/예문 등을 엑셀에서 불러오고, 매번 랜덤 출제하도록 했다.

뭔가 반복 학습 자동화 느낌으로 편한듯?

4. Enter로 제출 + 커스텀 팝업으로 정답 확인

self.answer_input.returnPressed.connect(self.check_answer)

→ 키보드만으로 퀴즈 진행 가능.


전체 코드

🗊 main.py

from PySide2.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QLineEdit, QVBoxLayout, QDialog
from PySide2.QtGui import QPalette, QColor, QFont
from PySide2.QtCore import Qt, QPoint
import sys, random
from expression_loader import load_expressions


class QuizWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Daily English Quiz")
        self.setFixedSize(350, 200)

        self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) # 창 프레임 제거 + 항상 위에
        self.offset = QPoint() # 드래그 관련 속성
        self.apply_dark_theme()

        self.quiz_data = load_expressions("assets/english_sentenses.xlsx")
        self.current_quiz = {}

        # 문제 칸
        self.meaning_label = QLabel()
        self.meaning_label.setWordWrap(True)
        self.meaning_label.setAlignment(Qt.AlignTop)
        self.meaning_label.setFont(QFont("나눔 고딕", 12))
        self.nuance_label = QLabel()
        self.nuance_label.setWordWrap(True)
        self.nuance_label.setAlignment(Qt.AlignTop)
        self.nuance_label.setFont(QFont("D2Coding", 9))
        self.nuance_label.setStyleSheet("color: #cc99ff;")
        self.example_label = QLabel()
        self.example_label.setFont(QFont("Courier New", 10))
        self.example_label.setVisible(False)  # 퀴즈 유형 따라 표시 유무


        self.answer_input = QLineEdit()
        self.answer_input.returnPressed.connect(self.check_answer)
        self.answer_input.setStyleSheet("""
            color: #ffffff;           /* 입력 글자 색 */
            background-color: #2e2e2e; /* 배경색 */
            border: 1px solid #555;
        """)
        self.feedback_label = QLabel()

        layout = QVBoxLayout()
        layout.addWidget(self.meaning_label)
        layout.addWidget(self.nuance_label)
        layout.addWidget(self.example_label)
        layout.addWidget(self.answer_input)

        self.setLayout(layout)

        self.next_question()

    def apply_dark_theme(self):
        dark_palette = QPalette()
        dark_palette.setColor(QPalette.Window, QColor(30, 30, 30))
        dark_palette.setColor(QPalette.WindowText, QColor(220, 220, 220))
        dark_palette.setColor(QPalette.Base, QColor(45, 45, 45))
        dark_palette.setColor(QPalette.Text, QColor(220, 220, 220))
        dark_palette.setColor(QPalette.Button, QColor(50, 50, 50))
        dark_palette.setColor(QPalette.ButtonText, QColor(200, 200, 200))
        QApplication.instance().setPalette(dark_palette)

    def check_answer(self):
        user_input = self.answer_input.text().strip().lower()
        correct = self.current_quiz['expression'].lower()

        if user_input == correct:
            message = ("✅ 정답입니다!")
        else:
            message = (f"❌ 틀렸습니다.\n정답은: {correct}")

        dialog = ResultDialog(message, self)
        dialog.exec_()
        self.next_question()

    def next_question(self):
        self.current_quiz = random.choice(self.quiz_data)
        self.meaning_label.setText(f"{self.current_quiz['meaning']}")
        self.nuance_label.setText(f"{self.current_quiz['nuance']}")
        self.example_label.setVisible(False)  # 예문은 이 퀴즈에서는 비활성화
        self.answer_input.clear()
        self.feedback_label.setText("")

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.offset = event.pos()

    def mouseMoveEvent(self, event):
        if event.buttons() == Qt.LeftButton:
            self.move(self.pos() + event.pos() - self.offset)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.close()


class ResultDialog(QDialog):
    def __init__(self, message, parent=None):
        super().__init__(parent)
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog)
        self.setStyleSheet("""
                    background-color: #2e2e2e;
                    color: #ffffff;
                """)
        self.setFixedSize(280, 150)

        label = QLabel(message)
        label.setFont(QFont("D2Coding", 9))
        label.setAlignment(Qt.AlignCenter)

        ok_button = QPushButton("OK")
        ok_button.setStyleSheet("""
                    QPushButton {
                        background-color: #444;
                        color: white;
                        border: 1px solid #666;
                        padding: 5px;
                    }
                    QPushButton:hover {
                        background-color: #555;
                    }
                """)
        ok_button.clicked.connect(self.accept)

        layout = QVBoxLayout()
        layout.addWidget(label)
        layout.addWidget(ok_button)
        self.setLayout(layout)



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

🗊 expression_loader.py

import pandas as pd

def load_expressions(filepath):
    df = pd.read_excel(filepath)

    expressions = []
    for _, row in df.iterrows():
        entry = {
            "expression": str(row.get("표현", "")).strip(),
            "meaning": str(row.get("의미", "")).strip(),
            "nuance": str(row.get("뉘앙스", "")).strip()
        }
        expressions.append(entry)

    return expressions

📎 english_sentenses.xlsx

숙어 | 뜻 | 뉘앙스 | 예문1 | 예문2 의 형식으로 데이터 정리

데이터를 어디서 받아오면 좋을듯? 수작업으로 하기엔 손이 많이 간다.


앞으로 계획

  • 메인 페이지 만들어서 퀴즈 분기 (단어퀴즈/문장퀴즈 등)
  • 데이터 추가 UI

0개의 댓글