ตอนที่ 12: Workshop 1 - สร้างโปรแกรม Text Editor (Part 2: การอ่าน/เขียนไฟล์)

1. 🎯 ตอนที่ 12: Workshop 1 - สร้างโปรแกรม Text Editor (Part 2: การอ่าน/เขียนไฟล์)
2. 📖 เปิดฉาก (The Hook)
สวัสดีครับน้องๆ สถาปนิกซอฟต์แวร์ทุกคน! กลับมาลุยกันต่อในซีรีส์ ลุยโปรเจกต์ Cross-Platform GUI ด้วย C++ และ Qt ครับ
ในตอนที่แล้ว เราได้ขึ้นโครงสถาปัตยกรรมหน้าจอ (UI Design) ของโปรแกรม Text Editor กันไปแล้ว แต่โปรแกรมพิมพ์ข้อความที่บันทึกไฟล์ไม่ได้ มันก็ไม่ต่างอะไรกับกระดาษทดที่เขียนเสร็จแล้วก็ต้องทิ้งไปใช่ไหมครับ?
ถ้าย้อนกลับไปในยุค C/C++ ดั้งเดิม เวลาที่เราต้องจัดการกับไฟล์ (File I/O) เราต้องปวดหัวกับฟังก์ชันอย่าง fopen() หรือ std::fstream ที่ต้องมานั่งจัดการเรื่องการเข้ารหัสตัวอักษร (Encoding) หรือแม้แต่ปัญหาโลกแตกว่าการขึ้นบรรทัดใหม่ของ Windows ต้องใช้ \r\n แต่ฝั่ง Linux/Mac ใช้แค่ \n ซึ่งถ้าจัดการไม่ดี ไฟล์ที่เซฟออกมาก็จะกลายเป็นภาษาต่างดาว หรือบรรทัดติดกันเป็นพรืด!
แต่โชคดีที่โลกนี้มี Qt ครับ! สถาปนิกของ Qt ได้เตรียมชุดเครื่องมือที่ทรงพลังอย่าง QFile และ QTextStream เอาไว้ให้เราแล้ว มันสามารถจัดการความซับซ้อนเหล่านี้ให้เราโดยอัตโนมัติ วันนี้เราจะมาเจาะลึกการใช้ 2 คลาสนี้เพื่อเขียนลอจิกการเปิด (Open), บันทึก (Save), และบันทึกเป็น (Save As) ให้กับ Text Editor ของเรากันครับ!
3. 🧠 แก่นวิชา (Core Concepts)
ในโลกของ Qt การอ่านหรือเขียนไฟล์ข้อความ (Plain Text) มีกระบวนการทำงานที่แบ่งหน้าที่กันอย่างชัดเจนตามหลักการของ Object-Oriented Design ครับ:
QFile(ท่อส่งข้อมูล): ทำหน้าที่เป็นตัวแทนของ “ไฟล์ทางกายภาพ” บนฮาร์ดดิสก์ รับผิดชอบเรื่องการเชื่อมต่อ (IO Device) โดยตรง เช่น การสั่งopen()และclose()ไฟล์ รวมถึงกำหนดโหมดการเปิด (Open Mode) เช่นQIODevice::ReadOnly(อ่านอย่างเดียว) หรือQIODevice::WriteOnly(เขียนอย่างเดียว).QTextStream(ล่ามแปลภาษา): เนื่องจากQFileมองทุกอย่างเป็นแค่ข้อมูลไบนารี (ก้อน Byte) ดิบๆ เราจึงต้องนำQTextStreamมาครอบQFileเอาไว้อีกชั้นหนึ่ง. เปรียบเสมือน “ล่าม” ที่คอยรับข้อความ (String) จากโปรแกรมเรา แล้วแปลงเป็นการเข้ารหัสที่ถูกต้องก่อนส่งลงท่อQFileการใช้ Text Stream ทำให้เราสามารถใช้เครื่องหมาย<<หรือ>>ได้เหมือนกับstd::coutของ C++ เลยครับ.
ความแตกต่างระหว่าง Save และ Save As ในการทำโปรแกรม Text Editor ลอจิกของการบันทึกไฟล์จะมี 2 กรณีครับ:
- Save As (บันทึกเป็น): เราจะต้องเรียกหน้าต่าง
QFileDialogเพื่อให้ผู้ใช้เลือกพาธ (Path) และตั้งชื่อไฟล์ใหม่ จากนั้นจึงทำการเขียนข้อมูลลงไป. - Save (บันทึก): เราต้องเช็กก่อนว่า “ไฟล์นี้เคยถูกบันทึกไปหรือยัง?” (อาจใช้ตัวแปรแบบ
bool isUntitledมาคอยดัก) ถ้ายังไม่เคยบันทึก ให้โยนการทำงานไปที่ฟังก์ชัน “Save As” แต่ถ้าเคยบันทึกแล้ว (มีชื่อไฟล์อยู่ในระบบ) ก็ให้เขียนทับไฟล์เดิมได้เลยทันที.

4. 💻 ร่ายมนต์โค้ด (Show me the Code)
เรามาดูวิธีการนำทฤษฎีมาเขียนเป็น C++ กันครับ (โค้ดนี้จะอยู่ใน mainwindow.cpp ต่อจากตอนที่แล้ว) เราจะประกาศตัวแปร QString currentFile; เอาไว้เก็บชื่อไฟล์ปัจจุบันด้วยนะครับ
1. การเปิดไฟล์ (Open File)
#include <QFile>
#include <QTextStream>
#include <QFileDialog>
#include <QMessageBox>
void MainWindow::on_actionOpen_triggered()
{
// 1. เรียก Dialog เลือกไฟล์
QString fileName = QFileDialog::getOpenFileName(this, "เปิดไฟล์", "", "Text Files (*.txt);;All Files (*.*)");
if (fileName.isEmpty()) return; // ถ้าผู้ใช้กด Cancel ให้จบการทำงาน
// 2. ใช้ QFile เปิดไฟล์แบบอ่านอย่างเดียว และโหมดข้อความ
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::warning(this, "Error", "ไม่สามารถเปิดไฟล์ได้!");
return;
}
// 3. ใช้ QTextStream เป็นล่ามอ่านข้อมูล
QTextStream in(&file);
in.setAutoDetectUnicode(true); // ป้องกันปัญหาภาษาไทยกลายเป็นต่างดาว (乱码)
// 4. อ่านเนื้อหาทั้งหมดมาใส่ใน QPlainTextEdit (สมมติชื่อ textEdit)
ui->textEdit->setPlainText(in.readAll());
// 5. ปิดไฟล์เสมอเมื่อทำงานเสร็จ!
file.close();
// เก็บชื่อไฟล์ไว้ใช้ตอนกด Save ปกติ
currentFile = fileName;
setWindowTitle(currentFile + " - WP Text Editor");
}2. การบันทึกเป็น (Save As)
void MainWindow::on_actionSaveAs_triggered()
{
// 1. เลือกที่เก็บและตั้งชื่อไฟล์
QString fileName = QFileDialog::getSaveFileName(this, "บันทึกเป็น", "", "Text Files (*.txt);;All Files (*.*)");
if (fileName.isEmpty()) return;
// 2. โยนชื่อไฟล์ไปให้ฟังก์ชัน Save ทำงานต่อ
saveFile(fileName);
}3. การบันทึก (Save) แบบชาญฉลาด
void MainWindow::on_actionSave_triggered()
{
// ถ้ายังไม่มีชื่อไฟล์ (เป็นไฟล์ใหม่) ให้ไปเรียก Save As แทน
if (currentFile.isEmpty()) {
on_actionSaveAs_triggered();
} else {
// ถ้ามีชื่อไฟล์อยู่แล้ว ก็บันทึกทับไฟล์เดิมได้เลย
saveFile(currentFile);
}
}
// ฟังก์ชันแกนหลักสำหรับเขียนไฟล์ลงฮาร์ดดิสก์
void MainWindow::saveFile(const QString &fileName)
{
QFile file(fileName);
// เปิดแบบเขียนอย่างเดียว (ถ้ามีไฟล์อยู่แล้วจะถูกเคลียร์เนื้อหาเดิมทิ้งอัตโนมัติจาก WriteOnly)
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, "Error", "ไม่สามารถบันทึกไฟล์ได้!");
return;
}
QTextStream out(&file);
out.setAutoDetectUnicode(true); // เซตให้รองรับ Unicode
// ดึงข้อความจากหน้าจอ ส่งเข้าไปใน Stream ผ่าน Operator <<
QString text = ui->textEdit->toPlainText();
out << text;
// ปิดไฟล์และอัปเดตสถานะ
file.close();
currentFile = fileName;
setWindowTitle(currentFile + " - WP Text Editor");
}5. 🛡️ เคล็ดลับจากคัมภีร์ลับ (Under the Hood / Pro-Tips)
ในฐานะ Senior พี่ขอหยิบหลุมพรางที่โปรแกรมเมอร์ C++ มือใหม่มักจะพลาดเวลาทำ File I/O มาเตือนกันครับ:
- จงใช้
QIODevice::Textเสมอเวลาอ่าน/เขียนไฟล์ข้อความ: ถ้าน้องๆ สังเกต พี่จะใส่ Flag ตัวนี้ไว้เสมอตอนเรียกfile.open(). เวทมนตร์ของมันคือ หากเรารันโปรแกรมนี้บน Windows เวลามันเซฟไฟล์ มันจะแอบเปลี่ยนเครื่องหมายการขึ้นบรรทัดใหม่\nให้กลายเป็น\r\nโดยอัตโนมัติ! และเวลาอ่านกลับมา มันก็จะยุบ\r\nกลับเป็น\nให้เราจัดการต่อใน C++ ได้อย่างง่ายดาย ทำให้ไฟล์ของเราเปิดอ่านข้าม OS (Mac/Linux/Windows) ได้อย่างเนียนกริบครับ. - อย่าลืม
file.close()เด็ดขาด!: การเขียนข้อมูลลงQFileไม่ได้หมายความว่าข้อมูลจะถูกเขียนลงฮาร์ดดิสก์ทันทีนะครับ มันอาจจะยังค้างอยู่ใน Buffer (Memory) การเรียกคำสั่งclose()นอกจากจะเป็นการคืนทรัพยากร (File Handle) ให้กับระบบปฏิบัติการแล้ว มันยังเป็นการบังคับให้ข้อมูลใน Buffer ถูกเขียน (Flush) ลงไฟล์จนเสร็จสมบูรณ์ด้วย หากลืมปิด ข้อมูลของน้องๆ อาจจะหายวับไปกับตานะครับ!. - เกราะป้องกันภาษาต่างดาวด้วย
setAutoDetectUnicode: หากไฟล์ของน้องๆ มีภาษาไทย จีน ญี่ปุ่น หรือข้อความอีโมจิ การใช้คำสั่งsetAutoDetectUnicode(true)ให้กับQTextStreamจะช่วยให้ Qt ตรวจจับและใช้การเข้ารหัสแบบ Unicode ได้อย่างถูกต้อง. หมดปัญหากับคำว่า “อ่านไฟล์มาแล้วกลายเป็นตัวสี่เหลี่ยม หรือเครื่องหมายคำถาม” อย่างสิ้นเชิงครับ.
6. 🏁 บทสรุป (To be continued…)
จบกันไปแล้วครับสำหรับ Workshop การสร้าง Text Editor ที่สมบูรณ์แบบ ตอนนี้โปรแกรมของเราไม่เพียงแค่มีหน้าตาที่สวยงามจากตอนที่แล้ว แต่ยังมี “สมอง” ที่สามารถโต้ตอบ สื่อสาร และจัดเก็บข้อมูลลงฮาร์ดดิสก์ (File I/O) ได้อย่างแข็งแกร่งด้วยพลังของ QFile และ QTextStream ครับ!
น้องๆ ลองเอาโค้ดชุดนี้ไปประกอบร่างเข้ากับโปรเจกต์เดิม แล้วทดลองคอมไพล์รันดูนะครับ ลองพิมพ์ภาษาไทยแล้วเซฟ จากนั้นลองเปิดกลับมาดู ความรู้สึกตอนที่โปรแกรมของเราสามารถทำงานได้จริงบนโลกใบนี้ มันเป็นความภาคภูมิใจที่สุดของคนเป็นโปรแกรมเมอร์เลยล่ะครับ! ในตอนต่อไป เราจะเข้าสู่ระบบที่ซับซ้อนขึ้นอีกนิด นั่นคือโลกของการจัดการข้อมูลด้วย Model/View Architecture ครับ รอติดตามกันได้เลย!
ต้องการที่ปรึกษาด้านการออกแบบสถาปัตยกรรมซอฟต์แวร์ C++ หรือระบบ Cross-Platform GUI ให้กับองค์กรของคุณ? ทีมงาน WP Solution พร้อมให้บริการออกแบบและพัฒนาซอฟต์แวร์แบบครบวงจร ดูรายละเอียดบริการของเราได้ที่: www.wpsolution2017.com หรือพูดคุยปรึกษาเบื้องต้นได้ที่ Line: wisit.p