본문 바로가기
Development Project/Python tkinter 자판기 Network Project

자판기 네트워크 프로그래밍 프로젝트 2(1) - UI 구현 및 화면 이동 구현(tkinter, python)

by emergensaur 2024. 6. 10.

1. tkinter 화면이동 구현 

현재 프로젝트 구성 상태

프로젝트를 진행할 때 모듈화를 진행하여 구현하기 위해 메인파일 내부에 화면이동을 위한 기능을 몰아넣기로 했다. 

import tkinter as tk 
from MainFrame import MF  
from AdminFrame import AF  
from AdminStock import AS  
from AdminCollect import AC 

# 메인 애플리케이션 클래스를 정의
class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.configure(bg="#008080") 
        self.geometry("800x800") #창 크기를 지정하는 것이 가능하다. 
        self.title("20194035 자판기 앱 ") # 창 제목 

        self.frames = {}  # 각 화면을 저장할 딕셔너리를 초기화
        for F in (MF, AF, AS, AC):  # MF, Admin, Admin1, Admin2 클래스를 순회함
            page_name = F.__name__  # 클래스 이름을 가져옴
            frame = F(parent=self, controller=self)  # 클래스의 인스턴스를 생성함
            self.frames[page_name] = frame  # 인스턴스를 딕셔너리에 저장함
            frame.grid(row=0, column=0, sticky="nsew")  # 각 화면을 그리드에 배치함

        self.show_frame("MF")  # 초기 화면으로 Mainframe 화면을 표시함

    # 화면을 전환하는 메서드를 정의합니다
    def show_frame(self, page_name):
        if self.frames.get(page_name):
            frame = self.frames[page_name]  # 전환할 화면을 딕셔너리에서 가져옴
            frame.tkraise()  # 해당 화면을 맨 앞으로 가져옵니다

if __name__ == "__main__":
    app = App()  # App 클래스의 인스턴스를 생성함
    app.mainloop()  # 애플리케이션의 메인 루프를 시작함

메인 앱클래스를 정의해서 프로그램을 실행하는 파일을 곧 이 파일로 지정했다.  메인 앱 클래스를 정의한 후, 각 화면을 담당하는 파일 4개를 가져왔다. 이후 화면 클래스를 반복시켜서 메인 기본 (빈 800x800) 화면 위에 4개의 슬라이드를 쌓아서 넣는다. 이후 show_frame 메서드를 통해서 화면 간 이동을 진행할 때 원하는 프레임을 딕셔너리에서 선택해서 화면을 맨 앞으로 가져오도록 구현을 진행했다. 마지막은 프로그램 메인루프를 실행시켜 구현이 되도록 하였다. 

2. 메인 프레임 세부구현 코드 설명 

첫 번째로 보이는 메인 프레임에 대한 세부 설명을 진행해 보자. 

화면자체는 바뀐것없이, 전역 변수에 대한 연산의 연계가가 가능해지도록 연산 기능을 추가했다. 혹시 화면 구성을 까먹을 까봐 넣었다.

먼저 메인프레임 코드이다. 관리자 화면 로그인 기능, 음료 버튼을 눌렀을 때의 결제 처리, 금액 버튼을 눌렀을 때 현재 입력된 금액의 변경, 반환하기 시의 금액의 반환 기능을 인앱상에서 전역변수에 대한 연산이 가능하게 구현했다. 

MainFrame.py

import tkinter as tk 
import tkinter.font
from PIL import Image, ImageTk
from tkinter import messagebox
# 파일 모듈화 
import drink_box_maker
import money_inout
import global_var


# 홈 화면을 정의하는 클래스입니다
class MF(tk.Frame):
    def __init__(self, parent, controller):
         
        super().__init__(parent)
        self.controller = controller 
        self.configure(bg="#008080")
        #이미지 객체를 변수에 저장 
        # 2x3 그리드 형태로 음료 상자를 배치
        font = tkinter.font.Font(family="Consolas", size=12) # 폰트정의
        #######################################################
        # 음료 상자 프레임 생성
        drink_frame = tk.Frame(self, bg="#008080")
        drink_frame.grid(row=1, column=0, columnspan=3, padx=60, pady=10)

        #######################################
        # 관리자 버튼 생성
        image_path = 'VM/img/admin.jpg'  # 관리자 버튼 이미지 경로
        image = Image.open(image_path)
        resized_image = image.resize((35, 35), Image.LANCZOS)
        photo = ImageTk.PhotoImage(resized_image)
        # "Admin" 버튼을 생성하고 클릭 시 Admin 화면으로 전환함
        # admin_button = tk.Button(self, image=photo, command=lambda: controller.show_frame("AF"))
        admin_button = tk.Button(self, image=photo, command=lambda: show_password_window())
        
        def show_password_window():
            # 비밀번호 입력 창 생성
            password_window = tk.Toplevel(self)
            password_window.geometry("300x150")
            password_window.title("관리자 로그인")
            # 비밀번호 입력 필드 및 버튼 생성
            password_label = tk.Label(password_window, text="로그인을 위해 비밀번호를 입력해 주세요")
            password_label.pack(pady=10)
            password_entry = tk.Entry(password_window, show="*")
            password_entry.pack(pady=5)
        
            def check_password():
                # 사용자가 입력한 비밀번호를 가져옴
                entered_password = password_entry.get()
                # 비밀번호가 맞는지 확인
                if entered_password == global_var.password:
                    messagebox.showinfo("로그인 성공", "환영합니다. 관리자님")
                    controller.show_frame("AF")
                    password_window.destroy()  # 비밀번호 창을 닫음
                else:
                    messagebox.showerror("Error", "비밀번호 잘못되었습니다. 다시 시도해주세요.")
            submit_button = tk.Button(password_window, text="확인", command=check_password)
            submit_button.pack(pady=20)
        
        admin_button.image = photo
        admin_button.grid(row=0, column=0, padx=10, pady=10, columnspan=3,sticky='nw')

        ####################################################
        # 하단에 금액 입력 버튼을 배치할 프레임 생성
        money_frame = tk.Frame(self, bg="#008080")
        money_frame.grid(row=2, column=0, columnspan=5, padx=30, pady=10)

        #################################################
        #하단 프레임 구성 (반환하기, 금액 출력 버튼)
        # 금액 입력 창 및 반환하기 버튼을 배치할 프레임 생성
        bottom_frame = tk.Frame(self, bg="#008080")
        bottom_frame.grid(row=4, column=0, columnspan=5, padx=10, pady=10, sticky="we")

        money_label = tk.Label(bottom_frame, text=("현재 금액: "+str(money_inout.get_money_sum())+ " 원"), font=font, width = 40)
        money_label.grid(row=0, column=0, padx=50, pady=20, sticky="w")
        
        ###################################################
        # money frame 에 금액 버튼 생성하기 
        for i in range (1, 6):
            money_inout.create_money_button(key=i, row=4, column=i-1, in_root=money_frame, change_target=money_label)  # 4번째 행에 금액 버튼 배치
        
        ###################################################
        # 음료버튼 생성하기
        drk_img=drink_box_maker.drink_box(key=1,image_path='VM/img/'+global_var.drink_data[1]["음료"]+'.jpg', drink_name=global_var.drink_data[1]["음료"], drink_quantity=global_var.drink_data[1]["수량"], 
                                            drink_price=global_var.drink_data[1]["가격"], in_root=drink_frame, row = 1, column = 0, change_target=money_label)
        drk_img=drink_box_maker.drink_box(key=2,image_path='VM/img/'+global_var.drink_data[2]["음료"]+'.jpg', drink_name=global_var.drink_data[2]["음료"], drink_quantity=global_var.drink_data[2]["수량"], 
                                          drink_price=global_var.drink_data[2]["가격"], in_root=drink_frame, row = 1, column = 1, change_target=money_label)
        drk_img=drink_box_maker.drink_box(key=3,image_path='VM/img/'+global_var.drink_data[3]["음료"]+'.jpg', drink_name=global_var.drink_data[3]["음료"], drink_quantity=global_var.drink_data[3]["수량"], 
                                          drink_price=global_var.drink_data[3]["가격"], in_root=drink_frame, row = 1, column = 2, change_target=money_label)
        drk_img=drink_box_maker.drink_box(key=4,image_path='VM/img/'+global_var.drink_data[4]["음료"]+'.jpg', drink_name=global_var.drink_data[4]["음료"], drink_quantity=global_var.drink_data[4]["수량"], 
                                          drink_price=global_var.drink_data[4]["가격"], in_root=drink_frame, row = 2, column = 0, change_target=money_label)
        drk_img=drink_box_maker.drink_box(key=5,image_path='VM/img/'+global_var.drink_data[5]["음료"]+'.jpg', drink_name=global_var.drink_data[5]["음료"], drink_quantity=global_var.drink_data[5]["수량"], 
                                          drink_price=global_var.drink_data[5]["가격"], in_root=drink_frame, row = 2, column = 1, change_target=money_label)
        drk_img=drink_box_maker.drink_box(key=6,image_path='VM/img/'+global_var.drink_data[6]["음료"]+'.jpg', drink_name=global_var.drink_data[6]["음료"], drink_quantity=global_var.drink_data[6]["수량"], 
                                          drink_price=global_var.drink_data[6]["가격"], in_root=drink_frame, row = 2, column = 2, change_target=money_label)
        
        # 반환함수 선언하기 
        def return_money():
            money_label.config(text="현재 금액: 0원")
            print(str(money_inout.get_money_sum())+"원이 반환되었습니다.")
            # 딕셔너리의 모든 값을 0으로 초기화
            for key in global_var.current_amount:
                global_var.current_amount[key] = 0
                
        #반환하기 버튼 눌렀을 때 이벤트 처리 진행하기 
        return_button = tk.Button(bottom_frame, text="반환하기", command=return_money, font=font)
        return_button.grid(row=0, column=1, padx=50, pady=20, sticky="w")

        # 하단 영역의 각 열 크기를 조정하여 균형 있게 배치
        bottom_frame.grid_columnconfigure(0, weight=1)

2. 1관리자 버튼 이벤트 처리 

관리자 버튼 이미지 경로는 화면을 연결해 놓은 그대로이다. 다만 이때 show_password_window 함수를 이용해서 안내창을 나타내고, 이때의 비밀번호 라벨과 엔트리를 띄운 후, check_password 메서드를 선언해 로그인 확인 버튼의 이벤트 처리를 할 때 엔트리에서의 정보를 가져와 전역 변수로 선언 password에 대해 비교를 진행하여 로그인 성공 여부를 정해서 화면 이동 시켜준다. 

2.2 음료구매 이벤트 처리

음료를 구매할 때 무슨 절차가 발생해야 할까? 음료를 누르면 drink_data에 접근해서 음료 가격을 알아내서 저장하고, 이를 계산하기 위해 자판기에 입력된 금액 current_amount 접근한 후, 음료의 가격이 자판기에 입력된 금액 보다 적다면 구매가 가능한 금액이 들어온 상태이므로 전체 잔돈데이터에 자판기에 입력된 금액을 추가시켜서 필요한 만큼의 잔돈만을 계산 음료의 수량을 1만큼 줄이고, 현재 주어진 잔돈만큼을 라벨에 출력시킨다. 이와 동시에 config를 통해서 음료자체의 ui도 실시간으로 함께 변경될 수 있도록 했다.

drink_box_maker.py

import tkinter as tk
import tkinter.font
from PIL import Image, ImageTk
import money_inout 
from tkinter import messagebox
import global_var

# 현재금액 라벨은 money_label
#이미지경로, 음료이름, 음료 개수, 음료 가격, 그리드 뷰에서의 위치, 이벤트 처리 대상 객체(현재금액) 를 입력받아 시각화 하는 함수를 선언한다.
def drink_box(key, drink_name, drink_quantity, drink_price, in_root, image_path, row, column, change_target): # 프레임을 생성해 그 안에 데이터를 넣고 리턴시킨다.  
    # 이미지 로드
    image = Image.open(image_path)
    resized_image = image.resize((170, 170), Image.ANTIALIAS)
    photo = ImageTk.PhotoImage(resized_image)  
    font = tkinter.font.Font(family="Consolas", size=12) # 폰트 지정하기 
    #음료 설명 추가 (음료이름, 음료 수량)
    # 텍스트와 이미지를 포함하는 프레임 생성
    frame = tk.Frame(in_root, width=250, height=250) # 루트에 대한 프레임을 생성한다. 
    frame.grid(row=row, column=column, padx=10, pady=10)  # grid 메소드를 사용하여 그리드 형태로 배치
    frame.pack_propagate(False)  # 프레임 크기 고정

    # 텍스트 라벨 생성
    label1 = tk.Label(frame, text=drink_name, font = font)
    label2 = tk.Label(frame, text="수량: "+str(drink_quantity), font = font)
    # 라벨을 버튼 위에 배치
    label1.grid(row=0, column=0, sticky="w")  # 라벨을 왼쪽으로 정렬하여 그리드에 배치
    label2.grid(row=0, column=1, sticky="w")

    # 버튼 클릭 이벤트 핸들러 함수 
    # 음료 구매 이벤트 음료를 누르면 drink_data로 음료의 가격에 접근하여 이 가격에 대해서 
    # 현재 입력된금액 current_amount 에 접근한 후, 적절한 금액울 분배해 current_amount에서 필요한 
    # 금액만큼 제외한다.이후 change target에 최종 금액에 대한 업데이트를 진행한다.
    # label1에서 슈량에 대한 데이터도 변경을 해야한다.  
    def on_button_click(change_target):
        if(global_var.drink_data[key]["수량"] <= 0):
            messagebox.showinfo(title = "음료가 없습니다.", message=f" 음료가 없습니다..ㅠㅠ")
            return False  
        
        # 잔돈 금액과 동전 구성들리턴 
        change, updated_current_amount =calculate_change(current_amount=global_var.current_amount, drink_price=drink_price) 
        if change: #올바르게 코드가 처리 가능한 경우
            messagebox.showinfo(title = "결제가 이루어집니다.", 
                                  message=f"금액을 {drink_price}원 현재 잔액{change}\
                                  남은 지폐와 동전: {updated_current_amount}") 
            # 현재가 금액의 변경 자체는 이루어짐 
            # 음료 재고에 대한 업데이트와 음료 재고 라벨을 변경해야함 
            global_var.drink_data[key]["수량"] -= 1 # 음료수 수량을 1 줄인다. 
            label2.config(text= "수량: "+str(global_var.drink_data[key]["수량"]), font = font)
            # 현재 금액 라벨 표시 변경 
            change_target.config(text="현재 금액: {} 원".format(money_inout.get_money_sum()))
        else:
            messagebox.showinfo(title = "금액을 만들 수 없습니다.", message=f" 현재 잔돈은 다음과 같습니다. {updated_current_amount}")

    # 텍스트와 이미지를 포함하는 버튼 생성 
    #프레임을 대상으로 버튼 추가 
    button = tk.Button(frame, text=str(drink_price) + " 원", image=photo, 
                    command=lambda:on_button_click(change_target=change_target), width = 200, height = 200, font = font,
                    compound = tk.TOP # 텍스트 위치를 바닥으로
                    )
    #이미지 객체에 대한 참조를 유지 
    button.image = photo 
    button.grid(row=2, column=0, columnspan=2)  # 버튼을 2행 0열에 배치하고, 2열에 걸쳐 표시

#거스름돈 계산 함수 거스름돈과 거스름돈 구성 리턴     
def calculate_change(current_amount, drink_price):
        #음료 수량이 0일때 예외처리 
    
    #애초에 돈이 부족한 경우 계산 
    if(money_inout.get_money_sum() < drink_price):
        messagebox.showinfo(title="안내", message="금액이 부족합니다. ")
        return None, current_amount
    #####################################################################
    #입력 금액이 음료를 사기 위한 금액은 넘었다. 
    # 기계내에 존재하는 잔돈을 합친 변수를 생성한다. 
    updated_current = global_var.money_data
    
     # 이제 거스름돈을 계산해보자. 
    change_required = money_inout.get_money_sum() - drink_price
    cge = change_required # 리턴할 거스름돈 저장 change_required는 차감시키면서 거스름돈 계산 
    
    tmp_current_amount = current_amount # 잘 안됐을 때 되돌리기 위해 존재 
    
    # 현재 입력된 금액을 업데이트를 진행해야 한다.  
    i = 1
    # 결제된 금액(current amount)를 실제 잔돈의 수량인 updated_current에 적용함
    for a, amount in global_var.current_amount.items():
        updated_current[i]["수량"] += amount
        i += 1
    for key in current_amount:
        current_amount[key] = 0
    # 입력금액을 모두 통합 금액인 updated_current로 옮겼기 때문에 0으로 변경한다. 
    
    # current_amount = {10: 0, 50: 3, 100:0, 500:1, 1000:1}
    # 적용한 updated_current = {1: {"금액": 10, "수량": 20}, 2: {"금액": 50, "수량": 23},
    # 3: {"금액": 100, "수량": 20},4: {"금액": 500, "수량": 21}, 5: {"금액": 1000, "수량": 21},
    # 와 같은 형식이 된다. 
   
    # 변경을 저장하는 변수 
    change_denominations = []
    
    
    # 거스름돈 계산 부분 사용 가능 금액 선택 및 차감 
    # updated keys를 금액이 높은 순으로 정렬한다. 
    for key in sorted(updated_current.keys(), reverse=True):
        denomination = updated_current[key]["금액"] # 현재 키에 해당하는 금액을 변수에 저장함
        while change_required >= denomination and updated_current[key]["수량"] > 0: 
            # changed_required가 현재금액 보다 크거나 같고, 해당금액의 수량이 0보다 큰동안 루프실행
            change_required -= denomination # 이조건이 참인동안 계속 현재 화폐를 거스름돈으로 씀
            updated_current[key]["수량"] -= 1 # change_required에서 금액 차감하고 돈의 수량을 감소
            change_denominations.append(denomination) # 리스트에 현재 금액 값을 추가한다. 
            # [100, 100, 50, 10] 과 같은 식으로 거스름돈리스트가 생기는것 

    if (change_required!= 0): # 돈이 딱 떨어지지 않은 경우 되돌린다. 
        # money_data에서 차감하기 
        current_amount = tmp_current_amount
        # 잔돈 화폐가 부족합니다. 출력 
        messagebox.showinfo(title="안내", message="잔돈 갯수가 부족합니다. ")
        return None, current_amount
    else: 
        for denomination in change_denominations:
            current_amount[denomination] += 1 
            # 0으로 변해 있는 current amount를 업데이트 시킨다. 
        global_var.money_data = updated_current # 변화를 적용한다. 
        print(global_var.money_data)   
        return cge, current_amount # 거스름돈과 거스름돈 구성 동전들 리턴

잔돈을 계산할 때 현재 금액을 변수에 저장하고, 내림차순 정렬된 잔돈 데이터가 금액 보다 적은 상태라면 최대한 넣어서 잔돈을 어떤 상황에서 표현이 가능하도록 구현하였다.  돈이 딱 떨어지지 않는 잔돈이 부족한 상황에서의 예외처리 역시 진행했다.  change와 updated_current_amount 모두 참값(false가 아닌)이 들어와야 올바르게 계산이 된 것이라는 것을 알 수 있게 하여 클릭이 된다고 해서 무조건적으로 음료수가 감소된다거나 전역변수에 영향을 주는 상황을 분리했다.

2.3 반환 함수 

반환의 구현은 현재 입력된 금액을 0으로 초기화 시키는 과정만 거치게 하면 된다. 

3. 기능 작동 확인하기

초기 화면은 가장 위에 있다.  이제 이온음료 버튼을 눌러보자. 

결제 확인 화면이 나타난다. 남은 돈(current amount)가 어떻게 저장되었는지도 나타난다.
이온음료의 수량이 줄고, 현재 금액이 줄어들어 있는것을 확인 가능하다.

{1: {'금액': 10, '수량': 20}, 2: {'금액': 50, '수량': 19}, 3: {'금액': 100, '수량': 16}, 4: {'금액': 500, '수량': 20}, 5: {'금액': 1000, '수량': 21}} 다음과 같이 인앱 내 print를 확인하면 전체 거스름돈 데이터가 어떻게 구성되어 있는지 확인할 수 있다. 초기값을 20으로 지정해 놓은 상태여서 1000원이 한 장 늘고 나머지는 잔돈을 맞추기 위해 줄어들었다.

 

이제 1000원 버튼을 마구 눌러서 돈을 추가해주자. 

입력금액이 늘어난 것을 확인 가능하고, 7000원이 넘었을 때 안내창을 나타나게 하고 더이상 금액 입력이 불가능하게 하는 기능이 있다.

관리자 화면으로 이동할 때 비밀번호를 입력하기 위한 기능이다. 비밀번호가 마스킹 처리된 상태이다. 

올바르게 비밀번호를 입력하면 관리자화면으로 이동된다.

관리자 화면으로 무사히 이동이 된 상태이다.

관리자 화면 - 비밀 번호 변경 기능

관리자 화면의 전체 코드이다. 

import tkinter as tk  # tkinter 모듈을 가져옵니다
import tkinter.font
import global_var

import admin_changePW
from tkinter import messagebox
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.ticker as ticker
#그래프를 그리기 위한 데이터인 판매량 데이터를 초기화 시켜둠 
sales_data = global_var.sales_data 

# 관리자 화면을 정의하는 클래스입니다
class AF(tk.Frame):
    def __init__(self, parent, controller):
        super().__init__(parent)
        self.controller = controller
        self.configure(bg="#008080")
        
        font = tkinter.font.Font(family="Consolas", size=12) # 폰트정의
        #######################상단프레임###############################
        top_frame = tk.Frame(self, bg="#008080")
        top_frame.grid(row=0, column=0, columnspan=3, padx=10, pady=10, sticky = 'ew')
        # "Back to Home" 버튼을 생성하고 클릭 시 MF 화면으로 전환
        back_button = tk.Button(top_frame, text="돌아가기", font = font,
                                command=lambda: controller.show_frame("MF"))
        back_button.grid(row=0, column=0, padx=(5,75), pady=10,  sticky='nw')
        # 관리자 화면 나타내기 
        label = tk.Label(top_frame, text="관리자 화면에 오신 것을 환영합니다.", font = font)  # "Admin Screen"이라는 텍스트 레이블을 만듭니다
        label.grid(row = 0, column= 1, pady= 10, padx = 100, sticky='nw') 
        
        ##################중단 프레임 1 기능 선택및 화면 이동 버튼연결###########
        mid_frame1 = tk.Frame(self, bg="#008080")
        mid_frame1.grid(row=1, column=0, columnspan=2, padx=100, pady=10, sticky = 'ew')
        # "Admin Stock " 버튼을 생성하고 클릭 시 Admin Stock 화면으로 전환
        as_button = tk.Button(mid_frame1, text="재고 보충", font = font,
                                  command=lambda: controller.show_frame("AS"))
        as_button.grid(row=1, column=0, padx=120, pady=10, sticky='nw')
        # "Admin Collect" 버튼을 생성하고 클릭 시 Admin Collect화면으로 전환
        ac_button = tk.Button(mid_frame1, text="수금", font = font,
                                  command=lambda: controller.show_frame("AC"))
        ac_button.grid(row=1, column=1, padx=120, pady=10, sticky='nw')
        
        ##################중단 프레임 2 비밀번호 변경하기 ###########
        mid_frame2 = tk.Frame(self, bg="#008080")
        # 전체 프레임 기준(self) row 2 -> 3번쨰 프레임을 구성한다.
        mid_frame2.grid(row=2, column=0, columnspan=3, padx=10, pady=5)
        # 비밀번호 변경창 구성하기 
        label = tk.Label(mid_frame2, text="- - - - - - - - - - - 비밀번호 변경 - - - - - - - - - - -", font = font) 
        label.grid(row = 0, column= 0, pady= 10, columnspan=3, padx = 10) 
        # 현재 비밀번호 입력
        mid_frame2.current_password_label = tk.Label(mid_frame2, text="현재 비밀번호", font= font)
        mid_frame2.current_password_label.grid(row=1, column=0, padx=50, pady=10, sticky='nw')
        mid_frame2.current_password_entry = tk.Entry(mid_frame2, show="*")
        mid_frame2.current_password_entry.grid(row=1, column= 1, padx=50, pady=10, sticky='nw')
        # 새로운 비밀번호 입력하기 
        mid_frame2.new_password_label = tk.Label(mid_frame2, text="새 비밀번호", font= font)
        mid_frame2.new_password_label.grid(row=2, column=0, padx=50, pady=10, sticky='nw')
        mid_frame2.new_password_entry = tk.Entry(mid_frame2, show="*")
        mid_frame2.new_password_entry.grid(row=2, column= 1, padx=50, pady=10, sticky='nw')
        # 새로운 비밀번호 확인하기 
        mid_frame2.confirm_password_label = tk.Label(mid_frame2, text="비밀번호 확인", font= font)
        mid_frame2.confirm_password_label.grid(row=3, column=0, padx=50, pady=10, sticky='nw')
        mid_frame2.confirm_password_entry = tk.Entry(mid_frame2, show="*")
        mid_frame2.confirm_password_entry.grid(row=3, column= 1, padx=50, pady=10, sticky='nw')
        # 확인 버튼
        mid_frame2.change_button = tk.Button(mid_frame2, text="비밀번호 변경하기", font = font, command=lambda:admin_changePW.change_password(mid_frame2.current_password_entry, mid_frame2.new_password_entry, mid_frame2.confirm_password_entry))
        mid_frame2.change_button.grid(row = 4, column= 0, pady= 10, columnspan=3, padx = 10)
        
        ##################하단프레임 그래프 선택하고 출력하기 ###########
        bottom_frame = tk.Frame(self, bg="#008080")
        # 전체 프레임 기준(self) row 3 4번째이자 하단 프레임을 구성한다.
        # 그래프 출력창 구성하기 
        bottom_frame.grid(row=3, column=0, columnspan= 6, padx=10, pady=10)
        label = tk.Label(bottom_frame, text="- - - - - - - - - - - - 음료, 날짜단위 별 매출 시각화 - - - - - - - - - - - -", font = font) 
        label.grid(row = 0, column= 0, pady= 10, columnspan=6) 
        ###############하단프레임 그래프 생성함수 ###############
        def plot_graph():
            selected_drink = drink_selection.get()  # 선택된 음료
            days = day_selection.get()
            if not selected_drink:
                messagebox.showwarning("경고", "음료를 선택해주세요.")
                return
            # 선택된 음료가 일단위 => days 가 7이면 
            if(days == 7): # 최근 7일 만 출력하면 됨
                len_data = 7
                recent_data = sales_data[selected_drink][-days:]
            else: # 월단위의 합을 저장한다. 
                recent_data = []
                len_data = len(sales_data[selected_drink]) 
                # 몇 달만큼의 데이터가 있는지를 인식하기 
                if (len_data < 30) : #데이터가 한달이 안되면 
                    len_data = 1
                    recent_data = sum(sales_data[selected_drink])
                else:    
                    len_data = int(len_data/30)
                    for i in range(1, len_data+1): 
                        recent_data.append(sum(sales_data[selected_drink][-(30*i):]))
                    #월별로 총합이 월 만큼 합해진다. 
                
            # Matplotlib 그래프 생성
            fig = Figure(figsize=(5, 4), dpi=100)
            ax = fig.add_subplot(111)
            ax.bar(range(1, len_data+1), recent_data)  # 최근 7일 데이터를 x축에 날짜로 표시
            
            # 그래프 타이틀 및 축 라벨 설정
            ax.set_title(f'Resent {days}')
            ax.set_xlabel('days')
            ax.set_ylabel('sales')
            ax.xaxis.set_major_locator(ticker.MaxNLocator(integer=True))
            # 새로운 Tkinter 윈도우 생성
            graph_window = tk.Toplevel(bottom_frame)
            graph_window.title(( selected_drink +" 판매량"))
            
            # Matplotlib 그래프를 표시하는 캔버스 생성
            canvas = FigureCanvasTkAgg(fig, master=graph_window)
            canvas.draw()
            canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
        
        ###################하단 프레임 구현 #############################################
        # 음료 선택 라디오 버튼 생성
        drink_selection = tk.StringVar()
        col_num = 0
        for drink in sales_data.keys():
            rb = tk.Radiobutton(bottom_frame, text=drink, variable=drink_selection, value=drink, font = font)
            rb.select()
            rb.grid(row=1, column=col_num, padx=10, pady=10,sticky='w')
            col_num += 1
        # 날짜 단위 선택 라디오 버튼 생성하기 
        day_selection = tk.IntVar()
        day_rb = tk.Radiobutton(bottom_frame, text="월 별", variable=day_selection, value=30,font=font)
        day_rb.select()
        day_rb.grid(row=2, column=0, padx=20, pady=30, sticky='w')
        # 한달 단위 
        day_rb = tk.Radiobutton(bottom_frame, text="일주일", variable=day_selection, font = font,value=7)
        day_rb.select()
        day_rb.grid(row=2, column=1,padx=10, pady=10, sticky='w')
        # 그래프 출력 버튼 생성
        plot_button = tk.Button(bottom_frame, text="그래프 출력", font = font, command=plot_graph)
        plot_button.grid(row=2, column=5,padx=10, pady=10, sticky='w')

전반적으로 상단 프레임 , 중단프레임 1, 2, 하단 프레임으로 나누고, 각각에 대한 기능 구현을 나누어서 진행했다. 

비밀번호의 변경은 현재 비밀번호 입력창, 새 비밀번호 입력하기, 새 비밀번호 확인하기 입력 창 3개로 구성한 후, 비밀번호 변경하기 버튼이 눌렸을 때의 이벤트 처리를 함수를 통해서 하는 방식으로 구현했다.

 admin_changePW.py

from tkinter import messagebox
import global_var
# 비밀번호 변경 이벤트 처리 함수 구성하기
import re

def is_valid_password(password):
    # 비밀번호가 8자리 이상인지 확인
    if len(password) < 8:
        return False
    
    # 특수문자 및 숫자가 각각 하나 이상 포함되어 있는지 확인하는 정규 표현식
    has_special_char = re.search(r'[!@#$%^&*(),.?":{}|<>]', password)
    has_digit = re.search(r'\d', password)
    
    if has_special_char and has_digit:
        return True
    else:
        return False 

def change_password(current_password_entry, new_password_entry, confirm_password_entry):
        current_password = current_password_entry.get()
        new_password = new_password_entry.get()
        confirm_password = confirm_password_entry.get()

        # 여기에 현재 비밀번호와의 일치 여부를 확인하는 로직을 추가할 수 있습니다.
        # 예를 들어, 현재 비밀번호가 "1234"라고 가정
        if current_password != global_var.password:
            messagebox.showerror("Error", "현재 비밀번호가 잘못됨 ")
            return

        if new_password != confirm_password:
            messagebox.showerror("Error", "새 비밀번호가 일치하지 않음 ")
            return

        if not new_password:
            messagebox.showerror("Error", "비밀번호가 비어서는 안됨")
            return

        if not is_valid_password(new_password): #비밀번호 유효성검사
            messagebox.showerror("Error", "비밀번호는 8자리 이상이 되어야 하고, 특수문자가 포함되어야 합니다.")
            return
        
        # 비밀번호 변경 로직을 여기에 추가
        # 예를 들어, 파일에 저장하거나 데이터베이스에 저장
        messagebox.showinfo("비밀번호 변경성공", "비밀번호 변경이 완료되었습니다.")
        global_var.password = new_password

관리자 비밀번호 이벤트 처리를 담당하기 위한 파일을 구성해 보았다. is_valid_password 메서드를 통해서 비밀번호의 길이 확인과 특수문자 포함 여부를 확인해 비밀번호의 유효성을 확인해 True, False를 리턴했다. 또한 비밀번호에 대해서 현재 비밀번호가 잘못된 상황, 새 비밀번호가 일치하지 않는 상황, 비밀번호가 빈상황에 대한 이벤트 처리를 진행했다. 비밀번호가 모두 유효하다고 할 때 최종적으로 전역변수로 선언된 비밀번호가 변경된다. 

비밀번호 변경 기능확인하기 

현재 비밀번호가 잘못되었다고 안내해준다.
새 비밀번호가 일치하지 않는다는 것을 보여준다.
비밀번호가 올바르게 입력되면 비밀번호 변경확인 창이 나타난다.

글이 넘 길어서 좀 나눠서 진행하도록 해보겠습니다.,,,