Setting
https://github.com/KhamisiKibet/QT-PyQt-PySide-Custom-Widgets 참조
1) pip install QT-PyQt-PySide-Custom-Widgets
2) https://feathericons.com/ <- Download All
3) 리소스 탐색기 - 편집 - resources.qrc로 파일 생성 - icons 파일 생성 - feathericons 전부 추가
interface.ui 디자인
bustop.py(모든 로직 및 디자인 변경 포함)
자세한 내용 주석 및 Github ReadMe / Notion 참조
https://github.com/PKNU-IOT3/bustop_pyqt_practice
https://www.notion.so/BuStop-90f6c1fb42b54b90bfa0d6d0a69ea701import sys import os from PyQt5 import uic from PyQt5.QtWidgets import * from PyQt5.QtGui import * from PyQt5 import QtCore from PyQt5.QtCore import * import mysql.connector import pymysql import resources_rc class qtApp(QMainWindow): isClicked=False # 행 선택 확인 bool 변수 saveBattery=False # 좌측 상단 Device ON/OFF 상태 변수 timer=QTimer() # DB 내용 변경 시 실시간 반영을 위해 사용할 timer def __init__(self): super().__init__() uic.loadUi('interface.ui',self) # ui 로드 self.setWindowIcon(QIcon('images/bustopimage.png')) self.setWindowTitle('BuSTOP v2') self.initUI() #initUI 메소드 호출하여 실행 직후의 statusBar 출력 및 DB 연결 self.InitSignal() #버튼 시그널 연결 <- 해당 소스 분리 하지 않고 버튼 시그널을 __init__ 내부에 작성 시 시그널이 중복 호출되어 연결된 슬롯 함수가 2번씩 실행 self.BtnHideClicked() # 사용자가 정보 출력 버튼 누를 때 까지 버스 정보 화면을 숨겨놓기 위함 ## 초기 LblInfor Font 설정 font=QFont('Rockwell',12) font.setBold(True) self.LblInfor.setFont(font) self.LblInfor.setStyleSheet("color: DarkSeaGreen;") # 상태바에 시간 출력 및 db 연결, QTableWidget인 BusInfor에 DB 정보 연결 def initUI(self): self.date = QDate.currentDate() self.datetime = QDateTime.currentDateTime() self.statusBar().showMessage(self.datetime.toString(Qt.DefaultLocaleLongDate)) mydb = mysql.connector.connect( host="localhost", user="root", password="12345", database="bus" ) mycursor = mydb.cursor() mycursor.execute("SELECT * FROM bus_table") myresult=mycursor.fetchall() self.BusInfor.setRowCount(len(myresult)) header_style="QHeaderView::section {background-color: %s; text-align: center;}" %QColor(0,0,0).name() self.BusInfor.horizontalHeader().setStyleSheet(header_style) item_style="QTableWidget::item {text-align: center;}" self.BusInfor.setStyleSheet(item_style) for row,data in enumerate(myresult): for column,item in enumerate(data): # 데이터를 QTableWidgetItem으로 변환하여 테이블 위젯에 추가 cell=QTableWidgetItem(str(item)) # 가독성을 위해 ~ 번 / ~ 명 / ~ 분의 형태로 정보를 출력하기 위함 if column==1: cell.setText(str(item)+"번") cell.setFont(QFont('Rockwell',14)) if column==2: cell.setText(str(item)+"명") cell.setFont(QFont('Rockwell',14)) if column==3: cell.setText(str(item)+"분") cell.setFont(QFont('Rockwell',14)) self.BusInfor.setItem(row,column,cell) cell.setTextAlignment(QtCore.Qt.AlignCenter) cell.setFont(QFont('Rockwell',14)) # 버튼 시그널을 따로 함수로 구현하지 않고 UI 초기화에 넣으면 버튼에 연결된 슬롯함수가 두번씩 호출 def InitSignal(self): # 버튼 시그널 self.BtnAddCnt.clicked.connect(self.BtnAddCntClicked) self.BtnMinusCnt.clicked.connect(self.BtnMinusCntClicked) self.BusInfor.cellClicked.connect(self.CellPosition) self.BtnSearch.clicked.connect(self.BtnSearchClicked) self.BtnHide.clicked.connect(self.BtnHideClicked) self.BtnInfo.clicked.connect(self.BtnInfoClicked) self.BtnHelp.clicked.connect(self.BtnHelpClicked) self.BtnClearNote.clicked.connect(self.BtnClearNoteClicked) self.BtnDeviceOnOff.clicked.connect(self.BtnDeviceOnOffClicked) # 셀 클릭 위치 확인 def CellPosition(self): qtApp.isClicked=True # 셀이 클릭 된경우 선언해둔 bool형 변수 isClicked를 True로 변경 row=self.BusInfor.currentRow() mybus_num=self.BusInfor.item(row,1).text() # ex) 100-1 self.LblNotification.setText(f'{mybus_num} 버스 선택') self.LblNotification.setFont(QFont('Rockwell',14)) self.LblNotification.setStyleSheet("color: green;") # 탑승 대기 버튼 클릭 def BtnAddCntClicked(self): #if self.BusInfor.currentRow()<0: if(qtApp.isClicked==False): # CellPosition 함수가 호출되지 않은 경우 즉 셀 선택이 안된 경우 self.LblNotification.setText("버튼 사용은 버스 선택 이후 가능합니다!") font=QFont('Rockwell',14) font.setBold(True) self.LblNotification.setFont(font) self.LblNotification.setStyleSheet("color: orange;") return else: row=self.BusInfor.currentRow() mybus_num=self.BusInfor.item(row,1).text() mybus_cnt=self.BusInfor.item(row,2) if(mybus_cnt.text()=='50명'): # 버스 최대 탑승 인원 50명으로 제한 self.LblNotification.setText("탑승 대기 인원 초과이기에 대기 불가능합니다.") font=QFont('Rockwell',14) font.setBold(True) self.LblNotification.setFont(font) self.LblNotification.setStyleSheet("color: orange;") return self.mydb=mysql.connector.connect( host="localhost", user="root", password="12345", database="bus" ) try: cursor=self.mydb.cursor() cursor.execute(f"UPDATE bus_table SET bus_cnt = bus_cnt+1 WHERE bus_num = '{mybus_num.replace('번', '')}'") # 탑승 대기 버튼 클릭 시 DB의 bus_cnt 를 1 증가 self.mydb.commit() self.updateTable(row) self.LblNotification.setText(f"{mybus_num} 버스 탑승 대기 완료!") font=QFont('Rockwell',14) font.setBold(True) self.LblNotification.setFont(font) self.LblNotification.setStyleSheet("color: green;") qtApp.isClicked=False # 선택된 버스 탑승 대기 / 탑승 취소 후 셀 선택 해제를 의미함 self.BusInfor.clearSelection() # 선택된 셀 해제 return except mysql.connector.Error as error: print("MySQL 서버 접속 에러 : {}".format(error)) finally: self.mydb.close() # 탑승 취소 버튼 클릭 def BtnMinusCntClicked(self): if qtApp.isClicked==False: # CellPosition 함수가 호출되지 않은 경우 즉 셀 선택이 안된 경우 self.LblNotification.setText("버튼 사용은 버스 선택 이후 가능합니다!") font=QFont('Rockwell',14) font.setBold(True) self.LblNotification.setFont(font) self.LblNotification.setStyleSheet("color: orange;") return else: row=self.BusInfor.currentRow() mybus_num=self.BusInfor.item(row,1).text() mybus_cnt=self.BusInfor.item(row,2) if mybus_cnt.text()=='0명': self.LblNotification.setText("탑승 대기 인원이 0명이기에 취소 불가능합니다.") font=QFont('Rockwell',14) font.setBold(True) self.LblNotification.setFont(font) self.LblNotification.setStyleSheet("color: orange;") self.BusInfor.clearSelection() return else: self.mydb=mysql.connector.connect( host="localhost", user="root", password="12345", database="bus") # 탑승 취소 버튼 클릭 시 cnt-1 try: cursor=self.mydb.cursor() cursor.execute(f"UPDATE bus_table SET bus_cnt = bus_cnt-1 WHERE bus_num = '{mybus_num.replace('번', '')}'") self.mydb.commit() self.updateTable(row) self.LblNotification.setText(f"{mybus_num} 버스 탑승 취소 완료!") font=QFont('Rockwell',14) font.setBold(True) self.LblNotification.setFont(font) self.LblNotification.setStyleSheet("color: green;") qtApp.isClicked=False # 선택된 버스 탑승 대기 / 탑승 취소 후 셀 선택 해제를 의미함 self.BusInfor.clearSelection() # 선택된 셀 해제 return except mysql.connector.Error as error: print("MySQL 서버 접속 에러 : {}".format(error)) finally: self.mydb.close() #변경된 DB 내용을 QTableWidget인 BusInfor에 뿌려줌 def updateTable(self,row): mydb=mysql.connector.connect( host="localhost", user="root", password="12345", database="bus" ) try: # SQL 쿼리 실행 cursor=mydb.cursor() # QtableWidget의 행은 0번부터 시작인데 우리 DB 는 bus_idx가 1부터 시작하기에 row+1 필요함 cursor.execute(f"SELECT * FROM bus_table WHERE bus_idx = {row+1}") result = cursor.fetchone() #셀에 데이터를 추가 for i,value in enumerate(result): cell = QTableWidgetItem(str(value)) # 사용자의 가독성을 위함 if i==1: cell.setText(str(value)+"번") cell.setFont(QFont('Rockwell',14)) if i==2: cell.setText(str(value)+"명") cell.setFont(QFont('Rockwell',14)) if i==3: cell.setText(str(value)+"분") cell.setFont(QFont('Rockwell',14)) self.BusInfor.setItem(row,i,cell) cell.setTextAlignment(QtCore.Qt.AlignCenter) cell.setFont(QFont('Rockwell',14)) except mysql.connector.Error as error: print("MySQL 서버 접속 에러 : {}".format(error)) finally: mydb.close() ### 좌측 버튼 함수 / 우측 최소,최대화 ### # 정보 출력 버튼 클릭 시 해당 함수 실행 def BtnSearchClicked(self): self.initUI() self.LblInfor.setText('우측 패널에\n 버스 정보가\n 출력 되었습니다!') self.timer.timeout.connect(self.initUI) #self.timer.timeout 즉 timer 객체가 종료되면 initUI와 연결시킴 self.timer.start(1)#정보 출력 시 계속해서 timer가 돌아야하기 때문에 timer.start(1) # 정보 숨기기 버튼 클릭 시 해당 함수 실행 def BtnHideClicked(self): self.BusInfor.setRowCount(0) # BusInfor을 비움 self.LblInfor.setText('버스 도착 정보를\n 확인하시려면 \n좌측 정보 출력 버튼을 \n클릭해주세요!') self.timer.stop() #timer 중지 -> 정보 출력 버튼 클릭 시 timer 다시 돌아가도록 구성 # LblInfor 라벨에 프로그램 설명을 출력해주는 함수 def BtnInfoClicked(self): self.LblInfor.setText("<BuSTOP!>은\n실시간으로\n 버스 정보를\n제공함으로써\n 승객들은 탑승 예약을\n"+ "버스 기사님들은\n 승객이 탑승하는\n정류장에만 정차해\n"+ "효율적인 운행을\n할 수 있습니다.\n\n"+ "<BuSTOP!>은\n 버스 정류장에\n터치패드를 설치하여\n 앱 사용이 "+ "불편하거나\n 휴대폰이 없는\n사람들 모두\n이용할 수 있습니다.\n\n"+ "<BuSTOP!> 시스템은\n누구나 쉽게\n간단한 UI 조작을 통해\n탑승 정보를\n"+ "기사님에게 알려\n정류장에서의\n불필요한 정차를 줄여\n보다 효율적인\n대중교통 시스템을\n"+ "구축하는데\n도움이 됩니다.") font=QFont('Rockwell',12) font.setBold(True) self.LblInfor.setFont(font) self.LblInfor.setStyleSheet("color: DarkSeaGreen;") # 도움말 버튼 def BtnHelpClicked(self): self.LblInfor.setText('관리자\n전화번호\n\n010-8515-0728') # 우측 하단 (x) 버튼 def BtnClearNoteClicked(self): self.LblNotification.setText("") self.LblInfor.setText("") # 장치 ON/OFF def BtnDeviceOnOffClicked(self): if qtApp.saveBattery: # 장치가 켜진 상태일때 self.LblLeftPanel.setStyleSheet("color: white;") self.LblRightPanel.setStyleSheet("color: white;") self.LblBottomPanel.setStyleSheet("color: white;") self.LblStatusBar.setStyleSheet("color: white;") self.LblTopPanel.setStyleSheet("color: white;") self.BtnSearch.setStyleSheet("color: white;") self.BtnHide.setStyleSheet("color: white;") self.BtnInfo.setStyleSheet("color: white;") self.BtnHelp.setStyleSheet("color: white;") self.BtnAddCnt.setStyleSheet("color: white;") self.BtnMinusCnt.setStyleSheet("color: white;") header_style="QHeaderView::section {background-color: %s; text-align: center;}" %QColor(0,0,0).name() self.BusInfor.horizontalHeader().setStyleSheet(header_style) qtApp.saveBattery = False #self.timer.start(1) # 장치가 켜지면서 timer 실행 else: # 장치가 꺼진 상태일 때 self.BusInfor.setRowCount(0) self.LblNotification.setText("") self.LblInfor.setText("") self.LblLeftPanel.setStyleSheet("color: #16191d;") self.LblRightPanel.setStyleSheet("color: #16191d;") self.LblBottomPanel.setStyleSheet("color: #16191d;") self.LblStatusBar.setStyleSheet("color: #2c313c;") self.LblTopPanel.setStyleSheet("color: #2c313c;") self.BtnSearch.setStyleSheet("color: #16191d;") self.BtnHide.setStyleSheet("color: #16191d;") self.BtnInfo.setStyleSheet("color: #16191d;") self.BtnHelp.setStyleSheet("color: #16191d;") self.BtnAddCnt.setStyleSheet("color: #2c313c;") self.BtnMinusCnt.setStyleSheet("color: #2c313c;") header_style="QHeaderView::section {background-color: %s; text-align: center;}" %QColor(255,255,255).name() self.BusInfor.horizontalHeader().setStyleSheet(header_style) qtApp.saveBattery = True self.timer.stop() # 장치가 꺼지면 timer 중지 if __name__ == '__main__': app=QApplication(sys.argv) ex=qtApp() ex.show() sys.exit(app.exec_())