ข้ามไปยังเนื้อหา

รับข้อความด้วย textarea และ keyboard

  1. เชื่อม lv_textarea กับ lv_keyboard และรับข้อความแบบ realtime และแบบยืนยันด้วย OK
  2. สลับโหมดแป้นพิมพ์ระหว่าง normal กับ number จาก dropdown
  3. อธิบายว่าเมื่อไรควรอัปเดตค่าทันที และเมื่อไรควรรอให้ผู้ใช้ยืนยัน

บอร์ดนี้ไม่มี hardware keyboard ติดมา ทุกครั้งที่ผู้ใช้ต้องพิมพ์ (ชื่อ Wi-Fi, password, ค่า setpoint) ต้องเรียก lv_keyboard ซึ่งเป็น widget สำเร็จรูปของ LVGL ที่วาด layout แป้นพิมพ์เองไม่ได้ทำงานแบบแยกส่วนกับ textarea แต่ต้อง ผูกเข้าด้วยกันผ่าน lv_keyboard_set_textarea(kb, ta) ให้ keyboard รู้ว่ากำลังพิมพ์ลง textarea ตัวไหนอยู่

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 เปลี่ยนโหมด ไม่ได้แค่เปลี่ยนหน้าตาแป้นพิมพ์”

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 ของมัน

  • คิดว่า 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 ฯลฯ แต่โค้ดจริงที่ commit 9a8e3ed รวม ทุก event ของ textarea ไว้ใน text_input_logic_textarea_event_cb ฟังก์ชันเดียว — เวลาอ่านโค้ดให้ยึดโค้ดจริงเป็นหลัก
Terminal window
# ในโฟลเดอร์ master template (ดูบทเรียน 1.1)
# 1) ลบไฟล์ของ episode เก่าใน proj_cm55/apps/
# 2) คัดลอกไฟล์ทั้งหมดของ episode นี้ลงใน proj_cm55/apps/
make build
make program # flash ผ่าน KitProg3

หรือเปิด ตัวอย่างนี้บน Developer Hub แล้ว flash เฟิร์มแวร์สำเร็จรูป

หน้าจอของ EP03 — Text Input Keyboard บน TESAIoT Dev Kit

ก่อนอ่านโค้ด ให้ทายว่าหน้าจอนี้มี object อะไรบ้าง และอะไรเปลี่ยนเมื่อผู้ใช้แตะหรือเมื่อค่าเซนเซอร์เปลี่ยน

  1. ทาย ก่อนแก้: เลือกค่าหนึ่งค่าที่ README ของตัวอย่างอธิบายไว้ในส่วน How แล้วเขียนว่าจะเห็นอะไรเปลี่ยนบนจอหรือใน log
  2. แก้และรัน build + flash แล้วเทียบกับที่ทายไว้ ถ้าไม่ตรง ให้หาว่าเข้าใจส่วนไหนผิด
  3. ทำเพิ่ม ต่อยอดหนึ่งอย่างที่ตัวอย่างยังไม่มี แล้วเก็บภาพหรือวิดีโอไว้ใน portfolio
  • event ใดบอกว่าผู้ใช้กด OK บนแป้นพิมพ์
  • ช่องกรอกรหัส Wi-Fi ควรใช้แบบ realtime หรือ commit-on-OK เพราะอะไร
  • โหมด number ป้องกันความผิดพลาดแบบไหน

คำตอบอยู่ใน README ของตัวอย่างและในโค้ด ถ้าตอบข้อใดไม่ได้ ให้กลับไปอ่านส่วน Why / What / How อีกครั้ง

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. ผู้ใช้พิมพ์ “abc” ลงช่อง Input B (commit on OK) แต่ยังไม่กด OK ข้อความ Confirmed Label จะแสดงอะไร (เป้าหมายข้อ 1)

    1. ค่าที่ยืนยันครั้งล่าสุด (ตอนเริ่มคือ “-”) เพราะ label B อัปเดตเฉพาะเมื่อแป้นพิมพ์ส่ง LV_EVENT_READY
    2. “abc” ทันทีที่พิมพ์แต่ละตัว
    3. “abc” เมื่อผู้ใช้แตะช่องอื่น
    4. ข้อความว่างเสมอจนกว่าจะรีเซ็ตบอร์ด
    ดูเฉลย

    คำตอบ: 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

  2. ต้องการตรวจรูปแบบ IP address ที่พิมพ์ในช่อง B ก่อนนำไปใช้ ควรใส่โค้ดตรวจไว้ตรงไหน (เป้าหมายข้อ 1)

    1. ใน handler ของ LV_EVENT_VALUE_CHANGED ของช่อง B
    2. ใน handler ของ LV_EVENT_FOCUSED ของช่อง B
    3. ในกิ่ง LV_EVENT_READY ของ text_input_logic_keyboard_event_cb() ก่อนเรียก text_input_update_confirmed_label()
    4. ใน 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 เกิดตอนเปิดแป้นพิมพ์ซึ่งยังไม่มีค่าให้ตรวจ

  3. ผู้ใช้แก้ข้อความในช่อง B แล้วกด Cancel บนแป้นพิมพ์ ตามโค้ดของ text_input_logic.c จะเกิดอะไร (เป้าหมายข้อ 1)

    1. ข้อความในช่อง B ถูกคืนเป็นค่าที่ยืนยันล่าสุด
    2. แป้นพิมพ์ซ่อน label B ยังเป็นค่าเดิม แต่ข้อความที่แก้ยังค้างอยู่ในช่อง B ถ้าเปิดแป้นพิมพ์แล้วกด OK ภายหลังจะ commit ข้อความนั้น
    3. label B เปลี่ยนเป็นข้อความที่แก้ เพราะ Cancel ก็ถือเป็นการยืนยัน
    4. ทั้งช่อง 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

  4. เมื่อเลือกโหมด Number โค้ดเรียกทั้ง lv_keyboard_set_mode(…, LV_KEYBOARD_MODE_NUMBER) และ lv_textarea_set_accepted_chars(target, "0123456789") ทำไมต้องมีบรรทัดที่สอง (เป้าหมายข้อ 2)

    1. เพื่อให้แป้นพิมพ์เปลี่ยนเป็นโหมดตัวเลขเร็วขึ้น
    2. เพื่อให้ dropdown เปิดแป้นพิมพ์ได้
    3. เพราะ lv_keyboard_set_mode() ใช้ไม่ได้ถ้าไม่มีตัวกรอง
    4. แป้นพิมพ์โหมดตัวเลขยังมีปุ่มอย่าง +/- และจุด ตัวกรองที่ textarea จึงเป็นด่านที่รับประกันว่าช่องนั้นรับเฉพาะตัวเลข
    ดูเฉลย

    คำตอบ: D. แป้นพิมพ์โหมดตัวเลขยังมีปุ่มอย่าง +/- และจุด ตัวกรองที่ textarea จึงเป็นด่านที่รับประกันว่าช่องนั้นรับเฉพาะตัวเลข

    โหมดของแป้นพิมพ์เปลี่ยนแค่ปุ่มที่แสดง ตัวกรอง accepted_chars ทำงานที่ textarea เองไม่ว่าอักขระจะมาจากปุ่มใด และเมื่อกลับโหมด Normal โค้ดตั้ง accepted_chars เป็น NULL เพื่อยกเลิกตัวกรอง การกันค่าผิดรูปแบบตั้งแต่ตอนรับเข้าช่วยให้ไม่ต้องไปแก้ทีหลัง

  5. ช่องใดควรเป็นแบบ commit-on-OK มากกว่าแบบ realtime (เลือกทุกข้อที่ถูก) (เป้าหมายข้อ 3)

    1. รหัสผ่าน Wi-Fi ที่จะใช้เชื่อมต่อ
    2. ช่องค้นหาที่กรองรายชื่อเครือข่ายขณะพิมพ์
    3. ค่า setpoint อุณหภูมิที่ส่งไปสั่งฮีตเตอร์
    4. ช่องพรีวิวที่แสดงว่าชื่ออุปกรณ์จะหน้าตาอย่างไรบนจอ
    5. หมายเลข 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://tesaiot.github.io/tesa-qualification-program/courses/tesaiot-firmware-stack/m02-hmi-menu-setting/l03-text-input-keyboard/

บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
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 ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA