9.3 파일 저장하기
728x90

1. 목표
더보기
- 메뉴/툴바의 [Save] 메뉴(actionSave) 를 클릭했을 때
- QFileDialog 로 경로를 선택하고,
- QFile + QIODevice + QTextStream 을 사용해서
QTextEdit 의 내용을 텍스트 파일로 저장한다. - Qt 파일 I/O 흐름을 익히고
QTextStream 의 기본 인코딩이 UTF-8 이라는 점도 같이 이해한다. - 성공 / 실패 메시지를 보여준다.
아직은 “현재 파일 이름 기억하기”, “수정됨 표시” 같은 복잡한 부분은 생략하고,
“일단 저장이 된다” 에만 집중하는 단계라고 생각하시면 좋습니다.
2. 전체 로직
더보기
# mainwindow.py
from PySide6.QtWidgets import QMainWindow, QFileDialog, QMessageBox
from PySide6.QtCore import QFile, QIODevice, QDir, QTextStream
from ui_form import Ui_MainWindow
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.actionSave.triggered.connect(self.save_file)
def save_file(self) -> bool:
filename, _ = QFileDialog.getSaveFileName(
self,
"파일 저장",
QDir.homePath(),
"Text Files (*.txt);;All Files (*)"
)
if not filename:
return False
file = QFile(filename)
if not file.open(QIODevice.WriteOnly | QIODevice.Text):
QMessageBox.critical(self, "오류", f"파일을 열 수 없습니다:\n{file.errorString()}")
return False
stream = QTextStream(file)
stream << self.ui.textEdit.toPlainText()
file.close()
QMessageBox.information(self, "저장 성공", f"파일이 저장되었습니다.\n\n{filename}")
return True
# main.py
import sys
from PySide6.QtWidgets import QApplication
from mainwindow import MainWindow
if __name__ == "__main__":
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
3. 필요한 import + 주석 설명
더보기


# QApplication : PySide6 애플리케이션과 메인 윈도우의 기본 클래스
# QFileDialog : 파일 저장/열기 표준 대화상자
# QMessageBox : 정보/경고/오류 메시지 창
from PySide6.QtWidgets import QMainWindow, QFileDialog, QMessageBox
# QFile : Qt 의 파일 클래스, open()/close() 를 통해 QIODevice 로 동작
# QIODevice : 읽기/쓰기 모드를 지정하기 위한 플래그(ReadOnly, WriteOnly 등)
# QDir : 디렉터리 유틸리티, homePath(), currentPath() 등 제공
# QTextStream : QFile 과 연결해서 텍스트를 읽고 쓰는 스트림 클래스
from PySide6.QtCore import QFile, QIODevice, QDir, QTextStream
# Qt Designer 로 만든 form.ui 를 pyside6-uic 로 변환한 코드
# 이 안에 textEdit, actionSave 등 위젯과 액션이 정의되어 있다
from ui_form import Ui_MainWindow
Ui_MainWindow 는 이미 9.1, 9.2 단계에서 만들어 둔 메뉴와 아이콘들을 포함한 완성된 GUI 클래스입니다.
파일 I/O 를 Qt 쪽 세트(QFile, QIODevice, QTextStream)로 통일한다 는 점입니다.
그래야 나중에 Qt 네트워크, 소켓, 리소스 등과 연결할 때도 흐름이 동일해집니다.
4. 메뉴 액션과 슬롯 연결하기 (actionSave ↔ save_file)
더보기


class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# 9.3 파일 저장하기 기능 연결
# actionSave 가 클릭되거나 단축키(Ctrl+S)가 눌리면 save_file 메서드를 호출한다
self.ui.actionSave.triggered.connect(self.save_file)
- self.ui.actionSave
- .ui 에서 변환 ui_form.py 안에서 생성된 QAction 객체입니다.
- [File] → [Save] 메뉴, 툴바의 디스크 아이콘, 단축키 Ctrl+S 에 모두 연결되어 있습니다.
- .triggered
- QAction 이 실행될 때(클릭/단축키) 발생하는 시그널입니다.
- .connect(self.save_file)
- 이 시그널이 발생하면 MainWindow.save_file() 메서드를 호출해 달라는 의미입니다.
- 이렇게 <디자이너에서 만든 UI 액션>과 <파이썬 로직>을 연결하는 것이 GUI 프로그래밍의 핵심 패턴입니다.
5. 실제 파일 저장하기 기능 구현 함수
더보기


class MainWindow(QMainWindow):
...
def save_file(self) -> bool:
"""
[9.3] 파일 저장하기 기능
- 파일 저장 대화상자를 띄워서 파일 이름을 선택하고
- Qt 의 QFile + QTextStream 을 사용해 textEdit 내용을 저장한다.
- 성공 여부를 bool 로 반환한다.
"""
# 1) QFileDialog 를 이용해 저장할 파일 이름을 선택한다
# QDir.homePath() 를 초기 폴더로 사용하면 사용자의 홈 디렉터리가 기본으로 열린다
filename, _ = QFileDialog.getSaveFileName(
self,
"파일 저장",
QDir.homePath(),
"Text Files (*.txt);;All Files (*)"
)
# 2) 사용자가 '취소'를 눌렀다면 filename 이 빈 문자열("") 이므로 저장을 중단한다
if not filename:
return False
# 3) QFile 객체를 생성하고, 지정한 경로에 쓰기 모드로 연다
file = QFile(filename)
# WriteOnly | Text 플래그로 열기
# - WriteOnly : 쓰기 전용 모드
# - Text : 텍스트 모드(윈도우의 \r\n 을 \n 으로 변환하는 등 텍스트 처리)
if not file.open(QIODevice.WriteOnly | QIODevice.Text):
# open 이 실패하면 errorString() 에 원인이 들어 있다
QMessageBox.critical(
self,
"오류",
f"파일을 열 수 없습니다.\n\n{file.errorString()}"
)
return False
# 4) QTextStream 을 QFile 과 연결해서 텍스트를 기록한다
# QFile 을 직접 write() 해도 되지만, QTextStream 을 쓰면
# 유니코드 처리와 줄 단위, 숫자 포맷 등 다양한 기능을 편하게 쓸 수 있다
stream = QTextStream(file)
# Qt6 기준으로 QTextStream 은 기본적으로 UTF-8 인코딩을 사용하므로
# 별도 인코딩 설정 없이 한글을 포함한 텍스트를 안전하게 저장할 수 있다
# 필요하다면 stream.setEncoding(...) 으로 변경할 수 있다
# 5) QTextEdit 의 전체 텍스트를 가져와 스트림에 쓴다
text = self.ui.textEdit.toPlainText()
stream << text
# 6) 작업이 끝나면 QFile 을 닫아준다
file.close()
# 7) 사용자에게 저장이 완료되었음을 알린다
QMessageBox.information(
self,
"저장 성공",
f"파일이 저장되었습니다.\n\n{filename}"
)
return True
이 함수는 이후 단계(예: 9.4 새 파일, 9.5 기존 파일 열기)에서도 재사용될 수 있습니다.
그때는 self.current_file 같은 멤버 변수를 추가해서 “이미 한 번 저장한 문서는 같은 파일로 저장”하는 로직을 확장할 수 있습니다.
6. 실행 테스트
7. 학습 주요 포인트
더보기
파이썬 open() 대신 Qt 파일 I/O 사용
- 단순 예제에서는 open() 이 더 간단하지만,
Qt 애플리케이션에서는 QFile + QIODevice + QTextStream 을 사용하는 쪽이
다른 Qt 기능들과 연동할 때 훨씬 자연스럽다 - 네트워크, 소켓, 메모리 버퍼 등도 모두 QIODevice 기반이라,
“장치(QIODevice) + 스트림(QTextStream)” 패턴을 익혀두면 재사용성이 올라간다.
QFileDialog.getSaveFileName() 의 반환값 처리
- (filename, filter) 튜플을 반환한다.
- filename 이 빈 문자열이면 사용자가 대화상자를 취소한 것이므로,
저장을 진행하면 안 된다.
QFile.open(QIODevice.WriteOnly | QIODevice.Text)
- WriteOnly : 쓰기 전용
- Text : 텍스트 줄바꿈 처리 등 텍스트 모드로 사용
- 실패하면 False 를 반환하고, file.errorString() 으로 원인 문자열을 확인 가능.
QTextStream 의 역할
- QFile 에 연결된 텍스트 스트림으로,
stream << "문자열"; 형식으로 쉽게 쓰기 가능. - 내부적으로 유니코드/인코딩을 처리해 주기 때문에
한글 포함 텍스트도 안전하게 저장할 수 있다.
UI 액션과 로직 연결 패턴
- self.ui.actionSave.triggered.connect(self.save_file)
- Qt Designer 에서 만든 메뉴/툴바 액션과 Python 메서드를 연결하는
기본 패턴을 이 단계에서 확실히 익히는 것이 중요하다.
9.3 단계의 역할
- 이후 9.4(새 파일), 9.5(기존 파일 열기)에서
self.current_file 를 도입하고,
“이미 저장된 파일은 바로 덮어쓰기” 같은 로직을 추가할 수 있는 기반이 된다. - 나중에 “종료 전 변경 내용 저장 여부 확인(maybe_save)” 로 확장할 때도
이 save_file() 함수가 그대로 재사용된다.
단계별 완성 파일




