10 คะแนน โดย GN⁺ 2025-05-12 | 3 ความคิดเห็น | แชร์ทาง WhatsApp
  • โปรเจกต์โอเพนซอร์สนี้เป็นแอป Todo แบบเนทีฟบน Windows ที่สร้างด้วย C และ Win32 API เท่านั้น
  • ทำงานได้ด้วยขนาดที่เล็กมาก (สูงสุด 26.5KB) โดยไม่พึ่งพาเฟรมเวิร์ก และลงมือทำทั้ง Windows GUI ระดับสูงรวมถึงการผสานกับระบบด้วยตนเอง
  • นอกจากฟังก์ชันพื้นฐานอย่าง เพิ่ม แก้ไข ลบ และทำเครื่องหมายว่างานเสร็จแล้ว สำหรับรายการ Todo ยังมีฟังก์ชันเพื่อ การใช้งานจริงด้านประสิทธิภาพ เช่น การผสานกับ system tray และตัวเลือกเริ่มทำงานอัตโนมัติ
  • ที่เก็บข้อมูลเป็น ไฟล์ไบนารี แบบถาวร และบันทึกรายการสิ่งที่ต้องทำได้สูงสุด 100 รายการในโฟลเดอร์ AppData
  • จุดเด่นคือการเขียนโปรแกรมแบบคลาสสิกที่แนบแน่นกับ OS มาก โดยไม่ใช้เฟรมเวิร์กขนาดใหญ่ และมีสภาพแวดล้อมการรันที่เบามาก

🌟 Simple Todo (C / WinAPI)

ภาพรวมโปรเจกต์

  • โปรเจกต์นี้สร้าง แอป Todo แบบเนทีฟสมัยใหม่บน Windows โดยใช้เพียง C และ Win32 API
  • แสดงให้เห็นความสามารถด้าน การเขียนโปรแกรม Windows GUI ระดับสูงและการผสานเข้ากับระบบ
  • ตัวโปรเจกต์มีขนาดเล็กมาก (สูงสุด 26.5KB) และคงรูปลักษณ์เฉพาะของ Windows ไว้อย่างครบถ้วน

✨ ฟีเจอร์หลัก

  • สามารถ สร้าง แก้ไข และลบรายการสิ่งที่ต้องทำ ได้
  • สามารถ ทำเครื่องหมายว่างานเสร็จแล้ว ได้
  • บันทึกถาวรใน AppData ทำให้ข้อมูลคงอยู่เสมอ
  • ผสานกับ system tray และเมื่อย่อหน้าต่างจะย้ายไปอยู่ใน tray
  • มีรูปลักษณ์แบบ Windows style เนทีฟ
  • มีตัวเลือก ให้รันอัตโนมัติเมื่อเริ่ม Windows

🛠️ รายละเอียดทางเทคนิค

  • เขียนทั้งหมดด้วย C ล้วน
  • ใช้เพียง Win32 API สำหรับการทำ GUI
  • ขนาดไฟล์รันได้เล็กมาก (26.5KB เมื่อบีบอัดด้วย UPX)
  • มีฟังก์ชันผสานกับ system tray
  • ใช้ modern visual style ผ่าน manifest

💾 การจัดเก็บข้อมูล

  • งานทั้งหมดจะถูกเก็บไว้ใน ไฟล์ไบนารี เพียงไฟล์เดียว
  • ตำแหน่งจัดเก็บ: %APPDATA%\TodoApp\todos.dat
  • เป็นฟอร์แมตไบนารีและรองรับการเก็บได้ สูงสุด 100 รายการ

📋 ข้อกำหนดที่จำเป็น

  • ต้องใช้สภาพแวดล้อม ระบบปฏิบัติการ Windows
  • ต้องมี MinGW-w64 (คอมไพเลอร์ GCC) และ Windows SDK

🎮 วิธีใช้งาน

  • รัน bin/todo.exe แล้วใช้อินเทอร์เฟซเพื่อทำงานต่อไปนี้ได้
  • เพิ่มงานใหม่ด้วยปุ่ม "Add"
  • เลือกรายการแล้วคลิก "Edit" เพื่อแก้ไข
  • ลบรายการด้วย "Delete"
  • ทำเครื่องหมายว่า เสร็จแล้ว ด้วย "Complete"
  • สามารถกำหนดลำดับความสำคัญให้แต่ละรายการได้

🏗️ โครงสร้างโปรเจกต์

  • ในโฟลเดอร์ src/ มี จุดเริ่มต้นหลัก (main.c), ลอจิกจัดการงาน (todo.c), การประกาศโครงสร้าง (todo.h), การทำ GUI (gui.c)
  • ไฟล์รันที่คอมไพล์แล้วจะอยู่ใน bin/
  • มีสคริปต์ build (build.bat) และเอกสารของโปรเจกต์รวมอยู่ด้วย

🔧 องค์ประกอบการพัฒนา

  • Win32 API: ใช้จัดการหน้าต่างและ GUI ทั้งหมด
  • Common Controls: ใช้องค์ประกอบ UI แบบสมัยใหม่
  • UXTheme: รองรับการใช้ Windows visual style
  • File I/O: ทำให้สามารถบันทึกข้อมูลแบบถาวรได้

📝 ใบอนุญาต

  • ใช้งานและแก้ไขได้อย่างอิสระภายใต้ MIT License

🤝 การมีส่วนร่วม

  • ยินดีรับ Pull Request
  • ทุกคนสามารถมีส่วนร่วมกับโปรเจกต์ได้

📫 ติดต่อและลิงก์

3 ความคิดเห็น

 
aer0700 2025-05-13

มีความโรแมนติกดีนะ

 
GN⁺ 2025-05-12
ความเห็นจาก Hacker News
  • มีบางอย่างในงานเขียนโปรแกรม GUI แบบ win32 ที่ฉันชอบ แม้มันจะค่อนข้างแปลกอยู่บ้าง แต่ถ้าอ่านบล็อกของ Raymond Chen ก็จะเข้าใจว่าทำไม win32 API เริ่มมาตั้งแต่ยุคโปรเซสเซอร์ 8088 และมีรายละเอียดแบบว่าถ้าทำบางอย่างด้วยวิธีเฉพาะจะประหยัดโค้ดได้ 40 ไบต์ หรือใช้รีจิสเตอร์น้อยลงหนึ่งตัว สมัยก่อนฉันเคยอ่านหนังสือของ mingw กับ Petzold แล้วเขียนแอป GUI ง่าย ๆ เองหลายตัว สนุกมากที่ได้ทำทุกอย่างเองทั้งหมด ทั้ง custom control, การวาดกราฟิก/ข้อความ, การจัดการสกอลล์, hit testing และอื่น ๆ ฉันเห็นว่าแอปของคุณใช้ strcpy, sprintf ถ้าจะเขียนโปรแกรมแบบจริงจังควรใช้ตัวแปรที่ตรวจสอบความยาวได้เสมอ น่าแปลกที่คอมไพเลอร์ไม่เตือนทันที ใน win32 API มีฟังก์ชันจำนวนมากที่ใช้แทนฟังก์ชันมาตรฐานของไลบรารี C ได้ ถ้าอยากลดขนาดไฟล์ executable ลงอีก แนะนำให้ลองเขียนโดยใช้แค่ <Windows.h> โดยไม่พึ่ง cstdlib คุณใช้ ZeroMemory แทน memset และ CopyMemory แทน memcpy ได้ แน่นอนว่าการเขียน C แบบล้วน ๆ จะเริ่มทรมานมากในจุดหนึ่ง แต่ช่วงแรก ๆ การทำด้วย pure C เองมีประโยชน์ต่อการเรียนรู้ที่สุด มันช่วยสร้างเซนส์ด้านโครงสร้างในการจัดการรายละเอียดเล็ก ๆ พวกนี้ ถ้าอยากทำ GUI ด้วย win32 ต่ออีกหน่อยก็อยากแนะนำ WTL (Windows Template Library) ด้วย เพราะมันห่อ win32 API ด้วย C++ ทำให้เข้าใจวิธีการทำงานได้ง่ายขึ้นมาก
    • อย่างน้อยทุกวันนี้ก็ควรใช้ strncpy แทน strcpy ไม่อย่างนั้นทุกคนจะคอยทักเรื่องนี้ต่อไป หนึ่งในเหตุผลใหญ่ที่ใช้ zig ก็คือมันช่วยลดความผิดพลาดพื้นฐานแบบนี้ได้ แน่นอนว่า C ก็ยังโอเค
    • เรื่อง ZeroMemory แทน memset และ CopyMemory แทน memcpy นั้น intrinsic ของ MSVC ใช้คำสั่ง rep stos/movs ทำให้โค้ดเล็กกว่าการเรียกฟังก์ชัน และยังลดขนาด import table ได้ด้วย
    • ฉันก็เคยทำอะไรแบบนี้เยอะเมื่อก่อน และพูดตรง ๆ ว่าคิดถึงความสามารถในการพัฒนา native UI ด้วย native code
    • มีคำถามเกี่ยวกับเหตุผลที่มี ZeroMemory, CopyMemory มาแทน memset และ memcpy ว่าทำไมถึงต้องสร้างสิ่งเหล่านี้ขึ้นมาแทนที่จะใช้ไลบรารีมาตรฐาน C เดิม
  • เมื่อก่อนแทนที่จะเรียก CreateWindow แบบลำบากทุกครั้ง คนมักเขียน dialog resource เป็นไฟล์ .rc แทน (ใน Visual Studio ก็มี dialog editor ให้ด้วย) แล้วใช้ CreateDialog แบบนี้จะสร้าง control ทั้งหมดได้ในครั้งเดียว แค่เพิ่ม application manifest ก็รองรับ UI style แบบสมัยใหม่และ DPI ความละเอียดสูงได้
    • ถ้าใช้วิธีนี้ ฟีเจอร์คีย์ลัดอย่างการย้ายโฟกัสด้วย Tab ระหว่าง control ก็ใช้ได้อัตโนมัติ เพียงแต่เรื่องการปรับขนาดยังต้องทำเองอยู่ แต่ก็ขยายโค้ดเพิ่มได้เร็วและไม่ยาก
    • วิธีนี้มีอยู่ในหนังสือของ Petzold ด้วย แนะนำให้ลองไปหาอ่าน
  • ฉันเองก็เคยทำอะไรคล้าย ๆ กันบน Linux โดยเขียนเป็นแอสเซมบลีให้เล็กกว่า 2KiB มาก่อน ถ้าเขียนด้วย C แล้วลิงก์แบบไดนามิก บน Linux ก็ทำให้ต่ำกว่า 20KiB ได้ง่าย ๆ คิดว่า Windows น่าจะยิ่งง่ายกว่าเพราะมีของในตัวมาเยอะอยู่แล้ว เลยอยากเชียร์ความพยายามแบบนี้ ถ้าอ้างอิงตัวเลือกการลิงก์ท้ายบทความก็น่าจะช่วยลดขนาดลงได้อีกง่ายขึ้น
  • ถ้าทำโดยไม่ใช้เฟรมเวิร์ก ก็จะมีปัญหาอย่างฟอนต์เบลอเวลาสเกล DPI, ไม่มี Tab support, ไม่มีฟีเจอร์พื้นฐานยุคหลังอย่างเลือกทั้งหมดด้วย Ctrl-A ใน text field, มีบั๊กตอนเพิ่มแถว และอื่น ๆ เลยสงสัยว่าในแง่ไหนถึงเรียกว่า "สมัยใหม่"
    • มีตัวอย่างการตั้งค่า DPI awareness แนบมา โดยโค้ดจะพยายามตั้งค่า DPI awareness ผ่านฟังก์ชันของ Windows หลายตัวตามเวอร์ชัน เช่น user32:SetProcessDpiAwarenessContext, shcore:SetProcessDpiAwareness, user32:SetProcessDPIAware ถ้าเก่ามากจริง ๆ ก็จะไม่เรียกอะไรเลย
    • คำว่าโมเดิร์นดูไม่ค่อยเข้ากัน เพราะขนาดใหญ่เกินไปแต่ฟีเจอร์กลับน้อย (ส่วนการย้ายโฟกัสด้วย Tab ระหว่าง control นั้นเขียนเองได้ง่ายอยู่แล้ว)
  • ในฐานะคนที่เคยเขียนโปรแกรมบน 6502 ความจริงที่ว่าตอนนี้ 278KB ยังถูกนับว่าเบา ทำให้รู้สึกเจ็บปวด
    • ฉันลองวิเคราะห์ขนาดไบนารีดู แล้วเจออุปสรรคแรกคือ build.bat ทำงานไม่ถูกต้องเมื่อใช้การตั้งค่า core.autocrlf=false พอเปลี่ยนเป็น core.autocrlf=true แล้วโคลนใหม่ก็ build สำเร็จ toolchain บางตัวของ mingw สร้าง .exe ขนาด 102KB ได้ ซึ่งมีประสิทธิภาพกว่ามากเมื่อเทียบกับ 278KB ถ้าอยากลดลงอีกก็สามารถใส่แฟลกเพิ่มให้ GCC ได้ เช่น gcc -s -Oz -flto ก็ลงไปถึง 47KB ได้เลย ถ้าสนใจแค่ขนาดไบนารี ยังมีพื้นที่ให้ปรับปรุงอีกมาก
    • ที่ขนาดออกมาเท่านี้เป็นเพราะแพลตฟอร์มและรูปแบบไฟล์ executable เอง ทั้งข้อมูล stack trace, โครงสร้างพื้นฐานของ dynamic linking, ตารางจัดการ exception และอีกหลายอย่างที่กินพื้นที่
    • อยากเสนอให้การแข่งขัน demoscene เพิ่มหมวด '64KB TODO app'
    • พูดตรง ๆ ว่าประหลาดใจที่มันใหญ่ขนาดนี้ ฉันจำได้ว่าสมัยก่อนถ้าไม่นับขนาดไอคอนมันจะเล็กกว่านี้ เลยสงสัยว่าเป็นเพราะ MinGw หรือเปล่า
    • 6502 เหรอ? นั่นหรูแล้วนะ สมัยฉันบางทีไม่มี CPU ด้วยซ้ำ
    • มันทำให้นึกถึงช่วงที่การเขียนแอสเซมบลีบน win32 ได้รับความนิยมขึ้นมาแบบฉับพลัน โดยเฉพาะตอนที่ไฟล์ดาวน์โหลดแชร์แวร์เริ่มมีขนาดใหญ่ขึ้น นึกถึงการพัฒนาบน Palm Pilot 68k ยุคแรก ๆ ด้วย ราวกับเป็นเปลวไฟสุดท้ายของแอสเซมบลีแบบไม่ย้อนยุค
    • มีคนเคยทำ quickrun.exe ขนาด 15KB ด้วยเหมือนกัน ใช้แค่ C กับ pure Win32 API ไม่มีเทคนิคพิเศษในการลดขนาดไบนารี ใช้คอมไพเลอร์ Mingw32 เป็นแอป GUI สำหรับเปิดแอปอย่างรวดเร็วด้วย alias
    • คืนนี้ฉันกำลังดีบักอีมูเลเตอร์ของตัวเองที่จำลองระบบ Z80 พร้อม RAM 64KB อยู่ พอเป็นแบบนี้ก็อดรู้สึกไม่ได้ว่ากาลเวลาและสภาพแวดล้อมเปลี่ยนไปมากแค่ไหน แต่ถึงอย่างนั้นก็คิดว่าเราได้พัฒนาก้าวกระโดดมหาศาลสมกับขนาดที่เพิ่มขึ้น
    • จากสถาปัตยกรรม 8 บิตมาถึง 64 บิต แค่ตัวชี้ที่อยู่ก็ใหญ่ขึ้น 8 เท่าแล้ว อย่าบ่นเลย ลองชื่นชมว่าการเปลี่ยนแปลงนี้ก็เป็นศิลปะอย่างหนึ่ง
    • 278KB นี่แทบจะพอดีกับฟลอปปีดิสก์ 5 1/4 นิ้วเท่านั้นเอง
  • แอปนี้ทำขึ้นมาเล่น ๆ แค่อยากลองทำเอง อย่างที่คอมเมนต์บอก ถ้าจะให้สมเหตุสมผลกว่านี้ก็อาจใช้ C++ หรือภาษาอื่นได้ แต่สำหรับฉันความสนุกอยู่ที่การได้ลองทำ
    • เมื่อราว 30 ปีก่อน ฉันก็เคยทำโปรแกรม Windows แรกของตัวเองแทบจะเหมือนแบบนี้ทุกอย่าง ต่างกันแค่ว่าฉันใช้คอมไพเลอร์ C++ ตอนนั้นการเขียนโค้ดสไตล์ C ด้วยคอมไพเลอร์ C++ เป็นแนวทางที่เอกสารทางการแนะนำไว้ เพราะ C++ เป็นซูเปอร์เซ็ตของ C และ Microsoft ก็มีแนวโน้มทำแบบนั้นเหมือนกัน
    • พูดจากใจเลย ฉันอยากใช้แอปของคุณมากกว่าแอป to-do เริ่มต้นของ Windows 11 เยอะ
    • เวลาใช้ win32 API จะใช้ภาษาไหน แก่นแท้ก็ไม่ได้ต่างกันมากนัก บางทีเปลี่ยนภาษาอาจยิ่งทำให้สับสน ถ้าไปยึดติดกับสไตล์ C++ มากเกินไป สำหรับคนที่เพิ่งเริ่มเรียน win32 API มันอาจยิ่งเพิ่มความงง โปรเจกต์ส่วนตัวเล็ก ๆ น่ารัก ๆ แบบนี้ที่ช่วยให้คุ้นเคยกับ win32 API ถือเป็นพื้นฐานสำคัญของนักพัฒนาเลย
    • ฉันมีอีกแอปหนึ่งชื่อ YoutubeGO ถ้าได้ลองดูด้วยก็จะดีใจ
    • โปรเจกต์ native UI ที่สะอาดแบบนี้นี่แหละที่เคยเป็นแรงบันดาลใจให้ฉันเริ่มเรียนเขียนโปรแกรม เลยทั้งอินและอยากชม
  • ในโลกที่เว็บหรือซอฟต์แวร์ยอมโหลด JS หรือ C# ระดับหลายเมกะไบต์เพื่อส่ง telemetry แค่ 278KB ความพยายามแบบนี้จึงสดใหม่ดี
    • แอปคล้ายกันที่ทำด้วย C# + WinForms ใช้พื้นที่ดิสก์ไม่ถึง 10KB ใช้ RAM 6MB ส่วนแอปนี้ใช้ RAM 1.5MB ทั้งคู่เปิดขึ้นมาทันที
  • ตอนนี้ดูเหมือนมีการลิงก์ static library ถ้าลิงก์กับ DLL ขนาดแอปน่าจะลดลงอย่างมาก
    • อันนั้นจริง ๆ ตรงข้ามนิดหน่อย ถ้าคุณต้องแจก DLL ไปพร้อมโปรแกรมด้วยเสมอ (และ DLL นั้นไม่ได้มีอยู่ใน OS อยู่แล้ว) DLL แต่ละตัวก็มักมี C runtime ของตัวเองรวมมาด้วย เลยยิ่งบวมกว่าเดิม ถ้ายัดทุกอย่างแบบ static ลงใน EXE เดียว จะมี C runtime แค่ชุดเดียว และฟังก์ชันที่ไม่ได้ใช้ก็ตัดทิ้งได้ง่าย DLL จะช่วยประหยัดพื้นที่ก็ต่อเมื่อหลายโปรแกรมใช้ DLL ตัวเดียวกันร่วมกันเท่านั้น
    • การลิงก์ CRT (runtime library) แบบ static กลับช่วยลดโค้ดที่ไม่จำเป็นได้ด้วย ถ้าลิงก์ DLL แบบ dynamic บางทีก็ต้องให้ผู้ใช้ไปติดตั้ง VCRUNTIME DLL เพิ่มต่างหาก ใน Visual Studio เอง การลิงก์แบบ dynamic กับ MSVCRT ก็ไม่ได้ง่ายเสมอไป ยกเว้นกรณีที่ต้องปฏิบัติตาม LGPL
  • นึกถึง File Pilot ซึ่งเป็น file explorer ตัวเร็วที่เพิ่งเปิดตัวไปไม่นาน ทำด้วย C และมีขนาดแค่ 1.8MB
  • บอกว่าเป็นแอป Todo สำหรับ Windows แบบ 'สมัยใหม่' และ 'เนทีฟ' แต่ก็ยังสงสัยว่ามันทันสมัยตรงไหน และถ้าเขียนด้วย C++ ก็ป้องกันปัญหาได้หลายอย่าง แถมตัด global variable ออกได้ ใช้ std::string, std::array, std::list, anonymous namespace และเลิกใช้ malloc คุณน่าจะได้โค้ดสั้นลงครึ่งหนึ่งพร้อมบั๊กลดลงด้วย
    • global variable ในแอปราว 500 บรรทัดแบบนี้แทบไม่มีผลอะไร และวัตถุประสงค์ก็ชัดเจน การเปลี่ยนไปใช้ std::string, std::list ไม่ได้แปลว่าผลลัพธ์แอสเซมบลีจริงจะเหมือนเดิม ความเห็นนี้ดูเหมือนไม่เข้าใจการทำงานภายในจริง ๆ
    • แม้แต่หนังสือของ Petzold ฉบับใหม่ก็ยัง build ด้วย Visual C++ ในโหมด C++ และแนะนำไวยากรณ์ที่ใช้ร่วมกันได้ทั้ง C/C++ ตั้งแต่ยุค Windows 95 ก็แทบไม่มีใครเขียนทุกอย่างด้วย C ล้วนแล้ว ภาษาอย่าง VB, Delphi, C++ ได้กลายเป็นกระแสหลักไปแล้ว
    • สำหรับความเห็นที่บอกให้ใช้ standard string หรือ array/list ถ้าจะใช้ winapi โดยตรงอยู่แล้ว การใช้ LPWSTR (wide string) จะเข้ากับ API มากกว่า std::string และน่าแนะนำกว่า แบบเก่าอย่าง char[] ก็ควรเปลี่ยนเป็น LPWSTR ส่วน std::array หรือ list ก็คงไม่ได้ทำให้โค้ดดีขึ้นนัก
    • มันอาจไม่ทันสมัย แต่การเขียนโปรแกรมที่ใกล้กับพื้นฐานแบบนี้ช่วยให้เข้าใจกลไกแท้จริงได้ดี ปัญหาก็เรียบง่ายพอจะตามทัน เลยคิดว่าเป็นโปรเจกต์สำหรับเรียนรู้ที่ดี และก็อยากเห็นเวอร์ชันแอสเซมบลีของแอปแบบนี้เหมือนกัน
 
roxie 2025-05-16

พี่ๆ ลมหายใจอุ่นๆ เหมือนส่งมาถึงตรงนี้เลย...