รับข้อความด้วย textarea และ keyboard
เป้าหมาย
หัวข้อที่มีชื่อว่า “เป้าหมาย”- เชื่อม lv_textarea กับ lv_keyboard และรับข้อความแบบ realtime และแบบยืนยันด้วย OK
- สลับโหมดแป้นพิมพ์ระหว่าง normal กับ number จาก dropdown
- อธิบายว่าเมื่อไรควรอัปเดตค่าทันที และเมื่อไรควรรอให้ผู้ใช้ยืนยัน
ทำไมต้องมี on-screen keyboard
หัวข้อที่มีชื่อว่า “ทำไมต้องมี on-screen keyboard”บอร์ดนี้ไม่มี hardware keyboard ติดมา ทุกครั้งที่ผู้ใช้ต้องพิมพ์ (ชื่อ Wi-Fi, password, ค่า setpoint) ต้องเรียก
lv_keyboard ซึ่งเป็น widget สำเร็จรูปของ LVGL ที่วาด layout แป้นพิมพ์เองไม่ได้ทำงานแบบแยกส่วนกับ textarea แต่ต้อง
ผูกเข้าด้วยกันผ่าน lv_keyboard_set_textarea(kb, ta) ให้ keyboard รู้ว่ากำลังพิมพ์ลง textarea ตัวไหนอยู่
สองโหมดของการรับ input: realtime กับ commit-on-OK
หัวข้อที่มีชื่อว่า “สองโหมดของการรับ input: realtime กับ commit-on-OK”EP03 วาง textarea สองช่องคู่กันเพื่อเทียบพฤติกรรม — Input A (Realtime) ผูก event LV_EVENT_VALUE_CHANGED
เข้ากับ label แสดงผล ทำให้ label เปลี่ยนทุกครั้งที่กดปุ่มบนคีย์บอร์ด (เหมาะกับ search box หรือ preview) ส่วน
Input B (Confirmed on OK) ไม่ sync label ตอนพิมพ์เลย ค่าจะไปอัปเดตก็ต่อเมื่อ event LV_EVENT_READY ยิง (ผู้ใช้
กดปุ่ม OK บนคีย์บอร์ด) ซึ่งเหมาะกับฟอร์มที่ต้อง validate ก่อน apply เช่น WiFi password ในโมดูลถัดไป การเลือกโหมด
ขึ้นกับว่าค่าที่ผิดพลาดระหว่างพิมพ์ (เช่นพิมพ์ยังไม่ครบ) จะสร้างปัญหาให้ระบบทันทีหรือไม่ — ถ้าไม่ ใช้ realtime ได้
ถ้าใช่ ต้องรอ commit
callback ตัวเดียวรับหลาย event: ต้องแยกด้วย event code และ target
หัวข้อที่มีชื่อว่า “callback ตัวเดียวรับหลาย event: ต้องแยกด้วย event code และ target”text_input_logic_textarea_event_cb ถูกผูกกับ ทั้งสอง textarea และรับ สาม event
(LV_EVENT_CLICKED, LV_EVENT_FOCUSED, LV_EVENT_VALUE_CHANGED) ภายในฟังก์ชันเดียว ตัว callback แยกพฤติกรรมด้วย
lv_event_get_code(e) และ lv_event_get_target(e) — ถ้าเป็น CLICKED หรือ FOCUSED จะเปิด keyboard และตั้ง
state->active_textarea = target เสมอไม่ว่าจะเป็น textarea ไหน แต่ถ้าเป็น VALUE_CHANGED จะอัปเดต label เฉพาะ
เมื่อ target == state->realtime_textarea เท่านั้น — Input B ก็ subscribe VALUE_CHANGED เหมือนกัน แต่ callback
เลือกไม่ทำอะไรกับมัน (ปล่อยผ่าน) นี่คือเหตุผลที่ค่า A เปลี่ยนทุกตัวอักษรแต่ค่า B ไม่เปลี่ยนจนกว่าจะกด OK ทั้งที่ event
ที่ถูกยิงเหมือนกันทุกประการ
ตอน OK: อัปเดต label ไหนขึ้นกับ active_textarea ตัวไหนถูกโฟกัสอยู่
หัวข้อที่มีชื่อว่า “ตอน OK: อัปเดต label ไหนขึ้นกับ active_textarea ตัวไหนถูกโฟกัสอยู่”text_input_logic_keyboard_event_cb ผูกกับตัว keyboard เอง (ไม่ใช่ textarea) รับ LV_EVENT_READY และ
LV_EVENT_CANCEL เมื่อ READY ยิง โค้ดเช็คว่า state->active_textarea ตอนนั้นคือ confirmed หรือ realtime
textarea แล้วอัปเดต label ของฝั่งนั้น — พูดอีกแบบคือ keyboard ตัวเดียวรับใช้ทั้งสอง textarea โดยรู้ว่ากำลังพิมพ์
ให้ตัวไหนอยู่จาก state ที่ callback ของ textarea ตั้งไว้ตอน FOCUSED ไม่ใช่จากการผูก callback แยกกันต่อ textarea
dropdown เปลี่ยนโหมด ไม่ได้แค่เปลี่ยนหน้าตาแป้นพิมพ์
หัวข้อที่มีชื่อว่า “dropdown เปลี่ยนโหมด ไม่ได้แค่เปลี่ยนหน้าตาแป้นพิมพ์”text_input_apply_mode_for_target() ถูกเรียกทั้งตอนเปลี่ยน dropdown และตอน textarea ได้ focus เมื่อเลือก
“Number” มันเรียก lv_keyboard_set_mode(kb, LV_KEYBOARD_MODE_NUMBER) และ
lv_textarea_set_accepted_chars(target, "0123456789") พร้อมกัน — แปลว่าโหมด Number ไม่ได้แค่เปลี่ยนปุ่มบนแป้นพิมพ์
แต่ยังบล็อกตัวอักษรอื่นไม่ให้พิมพ์เข้า textarea ได้เลย แม้จะพิมพ์จาก physical keyboard ก็ตาม
text_input_apply_mode_for_all() เรียก apply กับ textarea ทั้งสองช่องพร้อมกันทุกครั้งที่ dropdown เปลี่ยน แม้จะ
ยังไม่ได้โฟกัสช่องไหนอยู่ก็ตาม
ตัวอย่างสมบูรณ์
หัวข้อที่มีชื่อว่า “ตัวอย่างสมบูรณ์”โค้ดของ episode นี้อยู่ใน Developer Hub (อ้างอิงที่ commit 9a8e3ed) อ่าน Why / What / How ฉบับเต็มก่อนใน README ของ episode โค้ดตัวอย่างด้านล่างคัดลอกจาก tesaiot/developer-hub (Apache-2.0) ที่ commit เดียวกัน — ชื่อ callback ตรงนี้อ้างอิงจากซอร์สโค้ดจริง ไม่ใช่จากคำอธิบายในส่วน How ของ README ต้นทาง ซึ่งเรียกชื่อไว้ต่างออกไปเล็กน้อย
text_input_logic.c — callback ของ textarea แยก CLICKED/FOCUSED ออกจาก VALUE_CHANGED:
void text_input_logic_textarea_event_cb(lv_event_t *e){ lv_event_code_t code = lv_event_get_code(e); text_input_state_t *state = (text_input_state_t *)lv_event_get_user_data(e); lv_obj_t *target = (lv_obj_t *)lv_event_get_target(e);
if(state == NULL || target == NULL) { return; }
/* Keyboard opens only from input widgets (not from dropdown). */ if(code == LV_EVENT_CLICKED || code == LV_EVENT_FOCUSED) { state->active_textarea = target; lv_keyboard_set_textarea(state->keyboard, target); text_input_apply_mode_for_target(state, target); text_input_show_keyboard(state); return; }
/* Realtime output is bound only to Input A. */ if(code == LV_EVENT_VALUE_CHANGED && target == state->realtime_textarea) { text_input_update_realtime_label(state); }}callback ของ keyboard เอง แยก READY (commit) กับ CANCEL:
if(code == LV_EVENT_READY) { /* Confirm behavior depends on which input currently owns keyboard focus. */ if(state->active_textarea == state->confirmed_textarea) { text_input_update_confirmed_label(state); } else if(state->active_textarea == state->realtime_textarea) { text_input_update_realtime_label(state); } text_input_hide_keyboard(state); return;}
if(code == LV_EVENT_CANCEL) { text_input_hide_keyboard(state);}ui_text_input_keyboard.c — การผูก event ของ Input A ทั้งสามตัวเข้ากับ callback เดียว:
/* Input A: open keyboard + realtime update while typing. */lv_obj_add_event_cb(realtime_input, text_input_logic_textarea_event_cb, LV_EVENT_CLICKED, &s_text_input_state);lv_obj_add_event_cb(realtime_input, text_input_logic_textarea_event_cb, LV_EVENT_FOCUSED, &s_text_input_state);lv_obj_add_event_cb(realtime_input, text_input_logic_textarea_event_cb, LV_EVENT_VALUE_CHANGED, &s_text_input_state);Input B ลงทะเบียนสาม event เดียวกันทุกประการ (ดูไฟล์เต็ม) เพียงแต่ callback เลือกไม่ทำอะไรกับ VALUE_CHANGED ของมัน
main_example.c,text_input_logic.h,ui_text_input_keyboard.hและui_text_input_layout.h(layout constant ทั้งหมดเพื่อเลี่ยง magic number)
จุดที่มักพลาด
หัวข้อที่มีชื่อว่า “จุดที่มักพลาด”- คิดว่า Cancel จะคืนค่าเดิมให้ textarea — โค้ดจริงของ
LV_EVENT_CANCELแค่ซ่อน keyboard เท่านั้น ไม่ได้เรียก API ใดเพื่อคืนค่าตัวอักษรที่พิมพ์ไปแล้ว เพราะ textarea แก้ไขตัวอักษรจริงตั้งแต่ตอนพิมพ์ (ไม่ใช่ buffer ชั่วคราว) ถ้าต้องการพฤติกรรม “cancel แล้วคืนค่าเดิม” ต้องเก็บ backup ข้อความเองตอน FOCUSED แล้ว restore เองใน callback - ลืมว่าโหมด Number บล็อกตัวอักษรที่ textarea ด้วย ไม่ใช่แค่เปลี่ยนปุ่ม —
lv_textarea_set_accepted_chars()ถูกเรียกคู่กับlv_keyboard_set_mode()เสมอ ถ้าอยากให้พิมพ์ตัวอักษรอื่นได้อีกครั้งต้องเรียกlv_textarea_set_accepted_chars(ta, NULL)เพื่อล้าง filter - ผูก callback ของ Input A ให้ Input B โดยไม่เช็ค
target— เพราะ callback ตัวเดียวรับทั้งสอง textarea การ ลืมเช็คtarget == state->realtime_textareaก่อนอัปเดต label จะทำให้ label ผิดตัวอัปเดตข้าม input - สับสนชื่อ callback ระหว่าง README ส่วน How กับโค้ดจริง — README ต้นทางอธิบายว่ามี callback แยกเป็น
text_input_logic_focus_cb,text_input_logic_realtime_change_cbฯลฯ แต่โค้ดจริงที่ commit9a8e3edรวม ทุก event ของ textarea ไว้ในtext_input_logic_textarea_event_cbฟังก์ชันเดียว — เวลาอ่านโค้ดให้ยึดโค้ดจริงเป็นหลัก
build และ flash
หัวข้อที่มีชื่อว่า “build และ flash”# ในโฟลเดอร์ master template (ดูบทเรียน 1.1)# 1) ลบไฟล์ของ episode เก่าใน proj_cm55/apps/# 2) คัดลอกไฟล์ทั้งหมดของ episode นี้ลงใน proj_cm55/apps/make buildmake program # flash ผ่าน KitProg3หรือเปิด ตัวอย่างนี้บน Developer Hub แล้ว flash เฟิร์มแวร์สำเร็จรูป
ดูของจริงก่อน
หัวข้อที่มีชื่อว่า “ดูของจริงก่อน”
ก่อนอ่านโค้ด ให้ทายว่าหน้าจอนี้มี object อะไรบ้าง และอะไรเปลี่ยนเมื่อผู้ใช้แตะหรือเมื่อค่าเซนเซอร์เปลี่ยน
- ทาย ก่อนแก้: เลือกค่าหนึ่งค่าที่ README ของตัวอย่างอธิบายไว้ในส่วน How แล้วเขียนว่าจะเห็นอะไรเปลี่ยนบนจอหรือใน log
- แก้และรัน build + flash แล้วเทียบกับที่ทายไว้ ถ้าไม่ตรง ให้หาว่าเข้าใจส่วนไหนผิด
- ทำเพิ่ม ต่อยอดหนึ่งอย่างที่ตัวอย่างยังไม่มี แล้วเก็บภาพหรือวิดีโอไว้ใน portfolio
เช็กความเข้าใจ
หัวข้อที่มีชื่อว่า “เช็กความเข้าใจ”- event ใดบอกว่าผู้ใช้กด OK บนแป้นพิมพ์
- ช่องกรอกรหัส Wi-Fi ควรใช้แบบ realtime หรือ commit-on-OK เพราะอะไร
- โหมด number ป้องกันความผิดพลาดแบบไหน
คำตอบอยู่ใน README ของตัวอย่างและในโค้ด ถ้าตอบข้อใดไม่ได้ ให้กลับไปอ่านส่วน Why / What / How อีกครั้ง
แหล่งอ้างอิง
หัวข้อที่มีชื่อว่า “แหล่งอ้างอิง”- README ของ episode · โฟลเดอร์โค้ด · commit
9a8e3ed - เปิดตัวอย่างนี้บน Developer Hub
- โค้ดเป็นของ Developer Hub และอ้างอิงด้วยลิงก์ ไม่ได้คัดลอกเข้าคลังนี้
คำถามทบทวน
ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย
-
ผู้ใช้พิมพ์ “abc” ลงช่อง Input B (commit on OK) แต่ยังไม่กด OK ข้อความ Confirmed Label จะแสดงอะไร (เป้าหมายข้อ 1)
- ค่าที่ยืนยันครั้งล่าสุด (ตอนเริ่มคือ “-”) เพราะ label B อัปเดตเฉพาะเมื่อแป้นพิมพ์ส่ง LV_EVENT_READY
- “abc” ทันทีที่พิมพ์แต่ละตัว
- “abc” เมื่อผู้ใช้แตะช่องอื่น
- ข้อความว่างเสมอจนกว่าจะรีเซ็ตบอร์ด
ดูเฉลย
คำตอบ: A. ค่าที่ยืนยันครั้งล่าสุด (ตอนเริ่มคือ “-”) เพราะ label B อัปเดตเฉพาะเมื่อแป้นพิมพ์ส่ง LV_EVENT_READY
ใน text_input_logic.c มีแค่ realtime textarea ที่ผูก LV_EVENT_VALUE_CHANGED กับ label A ส่วน label B ถูกอัปเดตใน text_input_logic_keyboard_event_cb() เมื่อ code == LV_EVENT_READY และช่องที่ถือแป้นพิมพ์อยู่คือ confirmed_textarea
-
ต้องการตรวจรูปแบบ IP address ที่พิมพ์ในช่อง B ก่อนนำไปใช้ ควรใส่โค้ดตรวจไว้ตรงไหน (เป้าหมายข้อ 1)
- ใน handler ของ LV_EVENT_VALUE_CHANGED ของช่อง B
- ใน handler ของ LV_EVENT_FOCUSED ของช่อง B
- ในกิ่ง LV_EVENT_READY ของ text_input_logic_keyboard_event_cb() ก่อนเรียก text_input_update_confirmed_label()
- ใน text_input_logic_mode_event_cb() ของ dropdown
ดูเฉลย
คำตอบ: C. ในกิ่ง LV_EVENT_READY ของ text_input_logic_keyboard_event_cb() ก่อนเรียก text_input_update_confirmed_label()
LV_EVENT_READY คือจังหวะที่ผู้ใช้กด OK บนแป้นพิมพ์ เป็นจุดเดียวที่ค่าของช่อง B ถูก commit การตรวจตอน VALUE_CHANGED จะตรวจค่าที่ยังพิมพ์ไม่เสร็จทุกตัวอักษร ส่วน FOCUSED เกิดตอนเปิดแป้นพิมพ์ซึ่งยังไม่มีค่าให้ตรวจ
-
ผู้ใช้แก้ข้อความในช่อง B แล้วกด Cancel บนแป้นพิมพ์ ตามโค้ดของ text_input_logic.c จะเกิดอะไร (เป้าหมายข้อ 1)
- ข้อความในช่อง B ถูกคืนเป็นค่าที่ยืนยันล่าสุด
- แป้นพิมพ์ซ่อน label B ยังเป็นค่าเดิม แต่ข้อความที่แก้ยังค้างอยู่ในช่อง B ถ้าเปิดแป้นพิมพ์แล้วกด OK ภายหลังจะ commit ข้อความนั้น
- label B เปลี่ยนเป็นข้อความที่แก้ เพราะ Cancel ก็ถือเป็นการยืนยัน
- ทั้งช่อง B และ label B ถูกล้างเป็นค่าว่าง
ดูเฉลย
คำตอบ: B. แป้นพิมพ์ซ่อน label B ยังเป็นค่าเดิม แต่ข้อความที่แก้ยังค้างอยู่ในช่อง B ถ้าเปิดแป้นพิมพ์แล้วกด OK ภายหลังจะ commit ข้อความนั้น
กิ่ง LV_EVENT_CANCEL ในโค้ดแค่ log แล้วเรียก text_input_hide_keyboard() ไม่มีการคืนค่า textarea แม้ README ของ episode จะเขียนว่า Cancel คืนค่าเดิม ถ้าต้องการพฤติกรรมนั้นต้องเก็บค่าที่ยืนยันล่าสุดไว้ แล้วเรียก lv_textarea_set_text() คืนค่าเองในกิ่ง CANCEL
-
เมื่อเลือกโหมด Number โค้ดเรียกทั้ง lv_keyboard_set_mode(…, LV_KEYBOARD_MODE_NUMBER) และ lv_textarea_set_accepted_chars(target, "0123456789") ทำไมต้องมีบรรทัดที่สอง (เป้าหมายข้อ 2)
- เพื่อให้แป้นพิมพ์เปลี่ยนเป็นโหมดตัวเลขเร็วขึ้น
- เพื่อให้ dropdown เปิดแป้นพิมพ์ได้
- เพราะ lv_keyboard_set_mode() ใช้ไม่ได้ถ้าไม่มีตัวกรอง
- แป้นพิมพ์โหมดตัวเลขยังมีปุ่มอย่าง +/- และจุด ตัวกรองที่ textarea จึงเป็นด่านที่รับประกันว่าช่องนั้นรับเฉพาะตัวเลข
ดูเฉลย
คำตอบ: D. แป้นพิมพ์โหมดตัวเลขยังมีปุ่มอย่าง +/- และจุด ตัวกรองที่ textarea จึงเป็นด่านที่รับประกันว่าช่องนั้นรับเฉพาะตัวเลข
โหมดของแป้นพิมพ์เปลี่ยนแค่ปุ่มที่แสดง ตัวกรอง accepted_chars ทำงานที่ textarea เองไม่ว่าอักขระจะมาจากปุ่มใด และเมื่อกลับโหมด Normal โค้ดตั้ง accepted_chars เป็น NULL เพื่อยกเลิกตัวกรอง การกันค่าผิดรูปแบบตั้งแต่ตอนรับเข้าช่วยให้ไม่ต้องไปแก้ทีหลัง
-
ช่องใดควรเป็นแบบ commit-on-OK มากกว่าแบบ realtime (เลือกทุกข้อที่ถูก) (เป้าหมายข้อ 3)
- รหัสผ่าน Wi-Fi ที่จะใช้เชื่อมต่อ
- ช่องค้นหาที่กรองรายชื่อเครือข่ายขณะพิมพ์
- ค่า setpoint อุณหภูมิที่ส่งไปสั่งฮีตเตอร์
- ช่องพรีวิวที่แสดงว่าชื่ออุปกรณ์จะหน้าตาอย่างไรบนจอ
- หมายเลข IP ที่ต้องตรวจรูปแบบก่อนใช้
ดูเฉลย
คำตอบ: A. รหัสผ่าน Wi-Fi ที่จะใช้เชื่อมต่อ · C. ค่า setpoint อุณหภูมิที่ส่งไปสั่งฮีตเตอร์ · E. หมายเลข IP ที่ต้องตรวจรูปแบบก่อนใช้
README ของ episode แยกไว้ว่า realtime เหมาะกับช่องค้นหาที่อยากเห็นผลทุกตัวอักษร ส่วน commit-on-OK เหมาะกับค่าที่ต้องตรวจก่อนนำไปใช้หรือมีผลต่อระบบจริง ค่าที่ยังพิมพ์ไม่เสร็จ เช่นรหัสผ่านครึ่งเดียว หรือ setpoint ที่เพิ่งพิมพ์เลขแรก ไม่ควรถูกนำไปใช้ทันที
อ้างอิงบทเรียนนี้
ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน
"รับข้อความด้วย textarea และ keyboard" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
ข้อความอ้างอิงภาษาอังกฤษ: "Text input with a textarea and keyboard" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0
บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/tesaiot/developer-hub/blob/9a8e3ed1d813bfd67fabf6b7ac15c6ff9750b465/hmi_ep03_text_input_keyboard · Code stays in the Developer Hub and is linked at pinned commits, never copied: the episodes, practice codes and main-branch examples are Apache-2.0; the master template and the OPTIGA client carry Infineon/Cypress EULAs.
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA