6강. Model/View 과제 구현

0. 학습 목표
→ 5강 Model/View 과제를 실제 PySide6 코드로 구현하고, 하나의 Model을 여러 View가 공유하는 흐름을 확인합니다.
이번 강의에서 구현할 내용
이번 강의에서는 5강에서 과제로 제시한 Model/View 과제를 실제 코드로 구현합니다.
프로그램은 QTabWidget을 사용해서 로그인 화면, 메인화면, 프로필 화면을 탭으로 나누어 보여줍니다.
중요한 점은 각 화면이 회원 정보를 따로 저장하지 않고, 하나의 MemberModel회원 모델을 함께 사용한다는 것입니다.
| 구분 | 내용 |
| 핵심 개념 | 하나의 MemberModel을 LoginView, MainView, ProfileView가 함께 사용합니다. |
| 실습 준비 | PySide6, QTabWidget, QWidget, QLabel, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout |
| 최종 목표 | 로그인 후 회원 정보를 출력하고, 프로필 수정 내용이 메인화면에도 반영되도록 구현합니다. |

이번 단계의 핵심: View는 화면을 담당하고, Model은 데이터를 담당합니다. 여러 View가 같은 데이터를 보여줘야 할 때는 데이터를 Model 한 곳에서 관리해야 합니다.
1. 5강 과제 요구사항 다시 확인하기
→ 구현하기 전에 과제에서 요구한 화면 구성과 핵심 조건을 다시 확인합니다.
1.1 과제에서 만들어야 하는 화면
5강 과제에서는 하나의 프로그램 안에 3개의 탭 화면을 만들어야 했습니다.
첫 번째 탭은 로그인 화면입니다.
두 번째 탭은 로그인한 회원 정보를 보여주는 메인화면입니다.
세 번째 탭은 회원 정보를 확인하고 수정하는 프로필 화면입니다.
# 탭 화면 구성
QTabWidget
├── 로그인 View
├── 메인화면 View
└── 프로필 View
| 탭 이름 | 역할 |
| 로그인 View | ID와 PW를 입력하고 로그인 성공 여부를 확인합니다. |
| 메인화면 View | 로그인한 회원 정보를 출력합니다. |
| 프로필 View | 회원 정보를 출력하고 이름, 이메일, 전화번호를 수정합니다. |
1.2 이번 구현에서 가장 중요한 조건
이번 구현에서 가장 중요한 조건은 회원 정보를 View마다 따로 저장하지 않는 것입니다.
회원 정보는 MemberModel 하나에만 저장합니다.
그리고 LoginView, MainView, ProfileView가 같은 MemberModel을 사용해야 합니다.
# 구현해야 하는 구조
MemberModel
└── 회원 정보 저장
LoginView
└── MemberModel 사용
MainView
└── MemberModel 사용
ProfileView
└── MemberModel 사용
문제의 핵심: 프로필 View에서 회원 정보를 수정했을 때, 메인화면 View에도 수정된 값이 보여야 합니다.
2. 프로젝트 구조 만들기
→ Model과 View 역할을 구분하기 위해 파일을 나누어 준비합니다.
2.1 파일 구조
이번 구현에서는 파일을 3개로 나눕니다.
처음에는 한 파일에 모두 작성할 수도 있지만, Model과 View 역할을 구분하기 위해 파일을 나누는 것이 좋습니다.
# 프로젝트 구조
tab_model_view_login/
├── main.py
├── member_model.py
└── tab_window.py
| 파일 | 역할 |
| main.py | QApplication을 만들고 프로그램을 실행합니다. |
| member_model.py | 회원 정보 저장, 로그인 검사, 프로필 수정을 담당합니다. |
| tab_window.py | QTabWidget과 LoginView, MainView, ProfileView를 구성합니다. |
2.2 파일을 나누는 이유
member_model.py에는 회원 데이터와 관련된 코드만 넣습니다.
tab_window.py에는 화면과 버튼 동작 코드를 넣습니다.
main.py에는 프로그램 실행 코드만 넣습니다.
이번 단계의 핵심: 파일을 나누는 이유는 코드 양을 줄이기 위해서가 아니라, 역할을 분리해서 구조를 이해하기 쉽게 만들기 위해서입니다.
3. MemberModel 구현하기
→ 회원 정보를 저장하고 로그인 검사와 프로필 수정 기능을 담당하는 Model을 만듭니다.
3.1 member_model.py 코드
먼저 회원 정보를 저장하는 MemberModel을 구현합니다.
이 파일에는 화면을 만드는 코드가 들어가지 않습니다.
회원 정보와 관련된 기능만 작성합니다.
class MemberModel:
def __init__(self):
self.member = {
"id": "test",
"pw": "1234",
"name": "홍길동",
"email": "test@test.com",
"phone": "010-1234-5678",
}
def check_login(self, user_id, user_pw):
return user_id == self.member["id"] and user_pw == self.member["pw"]
def get_member_info(self):
return {
"id": self.member["id"],
"name": self.member["name"],
"email": self.member["email"],
"phone": self.member["phone"],
}
def update_profile(self, name, email, phone):
self.member["name"] = name
self.member["email"] = email
self.member["phone"] = phone
3.2 MemberModel 코드 역할
MemberModel은 회원 정보를 하나의 딕셔너리로 저장합니다.
이번 예제에서는 미리 만들어진 회원 정보를 사용합니다.
| 메서드 | 역할 |
| check_login() | 입력한 ID와 PW가 저장된 회원 정보와 같은지 확인합니다. |
| get_member_info() | 화면에 출력할 회원 정보를 반환합니다. |
| update_profile() | 프로필 화면에서 입력한 이름, 이메일, 전화번호를 Model에 반영합니다. |
여기서 중요한 점은 MainView와 ProfileView가 회원 정보를 직접 만들지 않는다는 것입니다.
두 View는 필요할 때마다 MemberModel의 값을 가져와 화면에 보여줍니다.
핵심 확인: 회원 정보는 View 안에 저장하지 않고 MemberModel 안에만 저장합니다.
4. QTabWidget과 View 구현하기
→ 로그인 View, 메인화면 View, 프로필 View를 만들고 하나의 MemberModel에 연결합니다.
4.1 tab_window.py 전체 코드
이제 화면을 구성하는 tab_window.py를 작성합니다.
이 파일에는 LoginView, MainView, ProfileView, TabWindow가 들어갑니다.
from PySide6.QtWidgets import (
QWidget,
QTabWidget,
QLabel,
QLineEdit,
QPushButton,
QVBoxLayout,
QHBoxLayout,
)
class LoginView(QWidget):
def __init__(self, member_model, tab_widget, main_view, profile_view):
super().__init__()
self.member_model = member_model
self.tab_widget = tab_widget
self.main_view = main_view
self.profile_view = profile_view
self.id_input = QLineEdit()
self.id_input.setPlaceholderText("아이디를 입력하세요")
self.pw_input = QLineEdit()
self.pw_input.setPlaceholderText("비밀번호를 입력하세요")
self.pw_input.setEchoMode(QLineEdit.EchoMode.Password)
self.message_label = QLabel("로그인 정보를 입력하세요.")
self.login_button = QPushButton("로그인")
self.login_button.clicked.connect(self.login)
layout = QVBoxLayout()
layout.addWidget(QLabel("[로그인]"))
layout.addWidget(QLabel("아이디"))
layout.addWidget(self.id_input)
layout.addWidget(QLabel("비밀번호"))
layout.addWidget(self.pw_input)
layout.addWidget(self.login_button)
layout.addWidget(self.message_label)
self.setLayout(layout)
def login(self):
user_id = self.id_input.text()
user_pw = self.pw_input.text()
if self.member_model.check_login(user_id, user_pw):
self.message_label.setText("로그인 성공!")
self.main_view.refresh()
self.profile_view.refresh()
self.tab_widget.setCurrentIndex(1)
else:
self.message_label.setText("로그인 실패! 아이디 또는 비밀번호를 확인하세요.")
class MainView(QWidget):
def __init__(self, member_model):
super().__init__()
self.member_model = member_model
self.title_label = QLabel("[메인화면] 로그인한 회원 정보")
self.id_label = QLabel("아이디:")
self.name_label = QLabel("이름:")
self.email_label = QLabel("이메일:")
self.phone_label = QLabel("전화번호:")
layout = QVBoxLayout()
layout.addWidget(self.title_label)
layout.addWidget(self.id_label)
layout.addWidget(self.name_label)
layout.addWidget(self.email_label)
layout.addWidget(self.phone_label)
self.setLayout(layout)
def refresh(self):
member = self.member_model.get_member_info()
self.id_label.setText(f"아이디: {member['id']}")
self.name_label.setText(f"이름: {member['name']}")
self.email_label.setText(f"이메일: {member['email']}")
self.phone_label.setText(f"전화번호: {member['phone']}")
class ProfileView(QWidget):
def __init__(self, member_model, main_view):
super().__init__()
self.member_model = member_model
self.main_view = main_view
self.id_label = QLabel("아이디:")
self.name_input = QLineEdit()
self.email_input = QLineEdit()
self.phone_input = QLineEdit()
self.update_button = QPushButton("수정하기")
self.update_button.clicked.connect(self.update_profile)
self.message_label = QLabel("프로필 정보를 확인하거나 수정하세요.")
layout = QVBoxLayout()
layout.addWidget(QLabel("[프로필]"))
layout.addWidget(self.id_label)
name_layout = QHBoxLayout()
name_layout.addWidget(QLabel("이름"))
name_layout.addWidget(self.name_input)
email_layout = QHBoxLayout()
email_layout.addWidget(QLabel("이메일"))
email_layout.addWidget(self.email_input)
phone_layout = QHBoxLayout()
phone_layout.addWidget(QLabel("전화번호"))
phone_layout.addWidget(self.phone_input)
layout.addLayout(name_layout)
layout.addLayout(email_layout)
layout.addLayout(phone_layout)
layout.addWidget(self.update_button)
layout.addWidget(self.message_label)
self.setLayout(layout)
def refresh(self):
member = self.member_model.get_member_info()
self.id_label.setText(f"아이디: {member['id']}")
self.name_input.setText(member["name"])
self.email_input.setText(member["email"])
self.phone_input.setText(member["phone"])
def update_profile(self):
name = self.name_input.text()
email = self.email_input.text()
phone = self.phone_input.text()
self.member_model.update_profile(name, email, phone)
self.refresh()
self.main_view.refresh()
self.message_label.setText("프로필 정보가 수정되었습니다.")
class TabWindow(QWidget):
def __init__(self, member_model):
super().__init__()
self.setWindowTitle("Model/View 과제 구현")
self.resize(400, 300)
self.member_model = member_model
self.tab_widget = QTabWidget()
self.main_view = MainView(self.member_model)
self.profile_view = ProfileView(self.member_model, self.main_view)
self.login_view = LoginView(
self.member_model,
self.tab_widget,
self.main_view,
self.profile_view,
)
self.tab_widget.addTab(self.login_view, "로그인")
self.tab_widget.addTab(self.main_view, "메인화면")
self.tab_widget.addTab(self.profile_view, "프로필")
layout = QVBoxLayout()
layout.addWidget(self.tab_widget)
self.setLayout(layout)
4.2 View들이 같은 Model을 받는 부분
TabWindow 안에서 MainView, ProfileView, LoginView를 만듭니다.
이때 세 View에 모두 같은 self.member_model을 전달합니다.
self.main_view = MainView(self.member_model)
self.profile_view = ProfileView(self.member_model, self.main_view)
self.login_view = LoginView(
self.member_model,
self.tab_widget,
self.main_view,
self.profile_view,
)
이 코드가 이번 과제의 핵심입니다.
MainView와 ProfileView가 서로 다른 회원 정보를 가지는 것이 아니라, 같은 MemberModel을 바라보게 됩니다.
[이미지 삽입 위치] TabWindow가 하나의 MemberModel을 각 View에 전달하는 구조
[이미지 생성 제안] TabWindow 안에서 MemberModel 하나가 생성되고 LoginView, MainView, ProfileView 생성자에 전달되는 과정을 화살표로 보여주는 구조 다이어그램을 만듭니다.
5. main.py에서 프로그램 실행하기
→ QApplication을 만들고 MemberModel과 TabWindow를 연결해 프로그램을 실행합니다.
5.1 main.py 코드
마지막으로 main.py를 작성합니다.
main.py에서는 QApplication을 만들고, MemberModel과 TabWindow를 생성합니다.
import sys
from PySide6.QtWidgets import QApplication
from member_model import MemberModel
from tab_window import TabWindow
app = QApplication(sys.argv)
member_model = MemberModel()
window = TabWindow(member_model)
window.show()
sys.exit(app.exec())
5.2 실행 명령어
터미널에서 프로젝트 폴더로 이동한 뒤 아래 명령어를 실행합니다.
python main.py
프로그램이 실행되면 로그인, 메인화면, 프로필 탭이 있는 창이 열립니다.
5.3 실행 전 파일 위치 확인
파일 이름과 위치가 아래처럼 되어 있는지 확인합니다.
# 실행 전 확인
tab_model_view_login/
├── main.py
├── member_model.py
└── tab_window.py
주의할 점: 파일 이름이 다르면 import 부분에서 오류가 날 수 있습니다. 파일 이름을 main.py, member_model.py, tab_window.py로 맞춰 주세요.
6. 로그인과 프로필 수정 흐름 확인하기
→ 로그인 성공, 로그인 실패, 프로필 수정 후 메인화면 반영 흐름을 확인합니다.
6.1 로그인 성공 흐름
먼저 로그인 성공 흐름을 확인합니다.
아이디는 test, 비밀번호는 1234를 입력합니다.
| 항목 | 입력값 |
| 아이디 | test |
| 비밀번호 | 1234 |
# 로그인 성공 흐름
로그인 View에서 ID, PW 입력
↓
로그인 버튼 클릭
↓
MemberModel.check_login() 호출
↓
로그인 성공
↓
MainView.refresh() 호출
↓
ProfileView.refresh() 호출
↓
메인화면 탭으로 이동
로그인에 성공하면 아래 코드가 실행됩니다.
self.main_view.refresh()
self.profile_view.refresh()
self.tab_widget.setCurrentIndex(1)
이 코드는 메인화면과 프로필 화면을 최신 회원 정보로 갱신한 뒤, 메인화면 탭으로 이동하는 코드입니다.
6.2 로그인 실패 흐름
아이디 또는 비밀번호가 틀리면 탭이 이동하지 않습니다.
로그인 실패 메시지만 출력하고 로그인 화면에 그대로 머무릅니다.
# 로그인 실패 흐름
로그인 View에서 ID, PW 입력
↓
로그인 버튼 클릭
↓
MemberModel.check_login() 호출
↓
ID 또는 PW 불일치
↓
로그인 실패 메시지 출력
↓
로그인 View에 그대로 머무름
6.3 프로필 수정 후 메인화면 반영 흐름
프로필 탭에서 이름, 이메일, 전화번호를 수정합니다.
수정하기 버튼을 누르면 ProfileView 안의 update_profile() 함수가 실행됩니다.
self.member_model.update_profile(name, email, phone)
self.refresh()
self.main_view.refresh()
첫 번째 줄은 MemberModel의 회원 정보를 수정합니다.
두 번째 줄은 프로필 화면을 다시 갱신합니다.
세 번째 줄은 메인화면을 다시 갱신합니다.
# 프로필 수정 흐름
프로필 View에서 이름, 이메일, 전화번호 수정
↓
수정하기 버튼 클릭
↓
MemberModel.update_profile() 호출
↓
MemberModel의 회원 정보 변경
↓
ProfileView.refresh() 호출
↓
MainView.refresh() 호출
↓
메인화면에도 수정된 정보 표시
[이미지 삽입 위치] 프로필 수정 후 메인화면이 갱신되는 흐름
[이미지 생성 제안] ProfileView에서 수정하기 버튼을 누르면 MemberModel 값이 변경되고 ProfileView와 MainView가 refresh되는 과정을 단계형 흐름도로 표현합니다.
핵심 확인: 프로필에서 수정한 값이 메인화면에도 보이면, 두 View가 같은 MemberModel을 공유하고 있다는 뜻입니다.
7. 실행 결과 확인하기
→ 로그인 성공 결과와 프로필 수정 결과를 직접 확인합니다.
7.1 로그인 성공 후 메인화면 결과
로그인에 성공하면 메인화면 탭으로 이동합니다.
메인화면에는 MemberModel에 저장된 회원 정보가 출력됩니다.
# 메인화면 출력 예시
[메인화면] 로그인한 회원 정보
아이디: test
이름: 홍길동
이메일: test@test.com
전화번호: 010-1234-5678
7.2 프로필 수정 입력 예시
프로필 탭에서 아래처럼 정보를 수정해 봅니다.
# 수정 입력 예시
이름: 김철수
이메일: kim@test.com
전화번호: 010-9999-8888
수정하기 버튼을 누른 뒤 메인화면 탭으로 돌아가면 아래처럼 변경된 정보가 보여야 합니다.
# 수정 후 메인화면 결과
[메인화면] 로그인한 회원 정보
아이디: test
이름: 김철수
이메일: kim@test.com
전화번호: 010-9999-8888
7.3 제출 전 체크리스트
아래 항목을 직접 확인합니다.
| 확인 항목 | 완료 |
| 로그인 View에 ID, PW 입력칸이 있는가? | □ |
| 로그인 버튼을 누르면 ID/PW 검사가 되는가? | □ |
| 로그인 성공 시 메인화면 View 탭으로 이동하는가? | □ |
| 메인화면 View에 회원 정보가 출력되는가? | □ |
| 프로필 View에도 같은 회원 정보가 출력되는가? | □ |
| 프로필 View에서 이름, 이메일, 전화번호를 수정할 수 있는가? | □ |
| 수정 후 메인화면 View에도 변경된 정보가 보이는가? | □ |
| 회원 정보가 View마다 따로 저장되지 않고 MemberModel을 통해 공유되는가? | □ |
8. 기존 방식과 Model 공유 방식 비교하며 정리하기
→ View마다 데이터를 따로 저장하는 방식과 하나의 Model을 공유하는 방식을 비교합니다.
8.1 기존 방식
기존 방식은 각 View가 자기 데이터를 따로 가지는 구조입니다.
# 기존 구조
LoginView
└── 로그인 정보 따로 저장
MainView
└── 회원 정보 따로 저장
ProfileView
└── 회원 정보 따로 저장
이 구조에서는 ProfileView에서 정보를 수정해도 MainView의 정보가 바뀌지 않을 수 있습니다.
왜냐하면 두 View가 서로 다른 데이터를 가지고 있기 때문입니다.
8.2 Model 공유 방식
이번 강의에서 사용한 방식은 하나의 MemberModel을 여러 View가 함께 사용하는 구조입니다.
# 새 구조
MemberModel
└── 회원 정보 저장
LoginView
└── MemberModel 사용
MainView
└── MemberModel 사용
ProfileView
└── MemberModel 사용
이 구조에서는 ProfileView가 MemberModel의 값을 수정합니다.
그리고 MainView가 같은 MemberModel을 다시 읽으면 수정된 값이 화면에 표시됩니다.
[이미지 삽입 위치] View마다 데이터를 따로 저장하는 방식과 Model을 공유하는 방식 비교
[이미지 생성 제안] 왼쪽에는 LoginView, MainView, ProfileView가 각각 데이터를 따로 가진 구조를 그리고, 오른쪽에는 하나의 MemberModel을 세 View가 공유하는 구조를 좌우 비교 인포그래픽으로 표현합니다.
| 구분 | 기존 방식 | Model 공유 방식 |
| 데이터 위치 | 각 View 안에 흩어져 있음 | MemberModel 한 곳에 저장됨 |
| 수정 반영 | 다른 View에 바로 반영하기 어려움 | Model을 다시 읽으면 반영 가능 |
| 구조 이해 | 처음에는 쉬워 보이지만 점점 복잡해짐 | 역할이 분리되어 유지보수하기 쉬움 |
| 이번 과제 목표 | 피해야 하는 방식 | 구현해야 하는 방식 |
8.3 최종 정리
이번 강의에서 구현한 내용을 정리하면 다음과 같습니다.
| 핵심 내용 | 정리 |
| MemberModel | 회원 정보를 저장하고 로그인 검사, 프로필 수정을 담당합니다. |
| LoginView | ID와 PW를 입력받고 MemberModel에 로그인 검사를 요청합니다. |
| MainView | MemberModel에서 회원 정보를 읽어 화면에 출력합니다. |
| ProfileView | MemberModel의 회원 정보를 수정하고 MainView를 다시 갱신합니다. |
| refresh() | Model의 최신 값을 다시 읽어 View에 보여주는 역할을 합니다. |
기억할 문장: View는 데이터를 직접 소유하지 않고, Model의 데이터를 읽고 보여주는 역할에 집중해야 합니다.
참고. 공식 문서로 확인하기
→ QTabWidget과 Qt Model/View 개념은 공식 문서에서 추가로 확인할 수 있습니다.
참고 문서
이번 강의에서 사용한 주요 개념은 아래 공식 문서에서 더 자세히 확인할 수 있습니다.
참고: 공식 Qt Model/View 구조는 QAbstractItemModel 같은 클래스를 사용하는 더 확장된 구조입니다. 이번 강의에서는 초보자용으로 하나의 Python 클래스인 MemberModel을 여러 View가 공유하는 방식에 집중했습니다.