“UI มหาสนุก”ตั้งชื่อให้ดูครึกครื้นกันหน่อย แต่ก็น่าจะทำให้ทราบว่าวันนี้เราจะมาเรียนรู้เกี่ยวกับพื้นฐานการเขียน User Interface ซึ่งแน่นอนว่า Application ส่วนใหญ่มีส่วนติดต่อกับผู้ใช้งาน ดังนั้นเรื่องนี้ถือว่าสำคัญมาก
ส่วนประกอบของ BlackBerry UI
Component เกี่ยวกับ UI ของ BlackBerry ถูกแบ่งออกเป็น 3 กลุ่มดังนี้
- Field : คือ control นั่นเอง เช่น TextField ,LabelFied หน้าที่ของ Fied คือแสดงผลและรอรับ Input ต่างๆจากผู้ใช้
- Manager : ทำหน้าที่จัดการ layout ของ Field ในหน้าจอ โดยที่ field แต่ละตัวสามารถอยู่ใน Manager เดียวเท่านั้น จริงๆแล้ว Manager ก็เป็น subclass ของ field เช่นกัน นั่นหมายความว่าใน Manager สามารถบรรจุ Manager อื่นได้ด้วย ซึ่งจะถูกใช้ในการสร้าง UI ที่มีความซับซ้อน
- Screen : คือหน้าจอดังได้เคยอธิบายไปบ้างแล้วในเรื่อง “สวัสดี BlackBerrry” มีหน้าที่จัดการ Layout ของ field รวมทั้ง Manager ด้วย
ลงมือสร้าง UIFun กันเลย
บรรยายมาพอสมควรแล้ว ถ้าได้ลงมือเขียนจริงๆคงจะสนุกไม่น้อย
ยังจำได้ไหม ถึงใครคนหนึ่ง .. นั่นมันเพลง จำได้ไหมครับว่าเราต้องทำอะไรบ้างในการสร้าง Application ที่มี UI ช่วยกันตอบ
-
ต้องมีคลาส Application ขอตั้งชื่อว่า UIFunApplication
-
ต้องมี MainScreen ให้ชื่อว่า UIFunMainScreen
เก่งมากครับสำหรับคนที่ให้ความร่วมมือ ทำเลยละนะ ใช้ความรู้เดิมเมื่อเสร็จแล้วได้โค้ดดังนี้
UIApplication
package com.mobiledevguru.uifun;
import net.rim.device.api.ui.UiApplication;
public class UIFunApplication extends UiApplication {
public UIFunApplication() {
// TODO Auto-generated constructor stub
UIFunMainScreen screen = new UIFunMainScreen();
pushScreen(screen);
}
public static void main(String[] args) {
UIFunApplication app = new UIFunApplication();
app.enterEventDispatcher();
}
}
MainScreen
package com.mobiledevguru.uifun;
import net.rim.device.api.ui.container.MainScreen;
public class UIFunMainScreen extends MainScreen {
public UIFunMainScreen() {
// TODO Auto-generated constructor stub
}
}
ออกแบบหน้าตาและการทำงาน
ตัวอย่างโปรแกรมของเราจะจำลองการทำงานของ การ Login ซึ่งมี TextField สำหรับกรอก Username ,password มี Dropdown ให้เลือก Domain มี checkbox เพื่อกำหนดว่าต้องการให้ระบบจดจำ password หรือไม่ จากนั้นมี ปุ่ม Login และ Clear เพื่อให้เห็นภาพ ผมวาดให้ดูประมาณนี้นะครับ
รูปที่ 1
ก่อนจะลงมือเขียนผมจะบอกว่าแต่ละส่วนที่เราออกแบบไว้ต้องใช้ Field ประเภทไหนบ้าง
-
ข้อความ Please enter your credential : LabelField
-
Username: EditField
-
Password : PasswordEditField
-
Domain : ObjectChoiceField
-
Remember Password : CheckboxField
-
ปุ่ม Clear และ Login : ButtonField
ขั้นตอนในการเพิ่ม Field เข้าไปในหน้าจอคือ สร้าง instance ของ field ขึ้นมาจากนั้น เรียกเมธอด add ของคลาส screen เราใช้วิธีการนี้กับทุกๆ Field ซึ่งผมได้แสดงโค้ดไว้ด้านล่างให้ลองพิมพ์ตาม คิดว่าอ่านแล้วเข้าใจโดยไม่ต้องอธิบายเพิ่มเติม
package com.mobiledevguru.uifun;
import net.rim.device.api.ui.component.BitmapField;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.CheckboxField;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.ObjectChoiceField;
import net.rim.device.api.ui.component.PasswordEditField;
import net.rim.device.api.ui.container.MainScreen;
public class UIFunMainScreen extends MainScreen {
BitmapField bitmapField;
EditField usernameField;
PasswordEditField passwordField;
ButtonField clearButton;
ButtonField loginButton;
ObjectChoiceField domainField;
CheckboxField rememberCheckbox;
public UIFunMainScreen() {
// TODO Auto-generated constructor stub
add(new LabelField("Please enter your credentials:"));
usernameField = new EditField("Username:", "");
add(usernameField);
passwordField = new PasswordEditField("Password:", "");
add(passwordField);
domainField = new ObjectChoiceField("Domain:", new String[] {"Home", "Work"});
add(domainField);
rememberCheckbox = new CheckboxField("Remember password:", false);
add(rememberCheckbox);
clearButton = new ButtonField("Clear");
add(clearButton);
loginButton = new ButtonField("Login");
add(loginButton);
}
}
จากนั้นลองรันดู (กด ctrl + f11) จะเห็นผลดังนี้
รูปที่ 2
ก็เกือบจะดีแล้ว แต่สิ่งที่ดูแปลกๆคือ ปุ่ม Clear และ Login เรียงกันคนละแถว ที่เป็นเช่นนี้เนื่องจาก Screen จะทำการเรียง Field ในแนวตั้งต่อกันไปเรื่อยๆ ถ้าเราจะแก้ปัญหานี้เราต้องเรียกใช้ Manager ที่มีชื่อว่า HorizontalFieldManager จากนั้นแทนที่จะ add ปุ่ม clear และ login ไปที่ Screen ให้เปลี่ยนมา add ไปไว้ที่ HorizontalFieldManager ดังนี้
clearButton = new ButtonField("Clear",ButtonField.CONSUME_CLICK);
loginButton = new ButtonField("Login",ButtonField.CONSUME_CLICK);
HorizontalFieldManager buttonManager = new HorizontalFieldManager(Field.FIELD_RIGHT);
buttonManager.add(clearButton);
buttonManager.add(loginButton);
add(new SeparatorField());
add(buttonManager);
จากโค้ดด้านบนสิ่งที่เราต้องเรียนรู้เพิ่มเติมคือ
-
เราสามารถกดหนด Style หรือรูปแบบการแสดงผลใน constructor ของ field เช่น HorizontalFieldManager(Field.FIELD_RIGHT) เป็นการกำหนดให้จัดเรียง field ที่อยู่ภายในแบบชิดขวา เราสามารถกำหนด Style หลายๆอย่างในครั้งเดียวได้โดยการใช้ bitwise or (|)
-
ในส่วนของ ButtonField มีการใช้ Style ButtonField.CONSUME_CLICK ซึ่งเป็นการระบุว่า ButtonField จะรับ event Click ถ้าเราไม่ทำแบบนี้การคลิ๊กจะถูกส่งผ่านไปที่ screen ซึ่งจะตอบสนองโดยการแสดงเมนูขึ้น
ลองรันดูจะได้ผลแบบที่ควรจะเป็นดังรูปด้านล่าง
รูปที่ 3
โต้ตอบกับผู้ใช้บ้างสิ
โปรแกรมที่เราเขียนเสร็จไปนั้นนอกจากแสดง หน้าตาแล้ว ไม่ทำงานอะไรบ้างเลย เราจะลองให้มันโต้ตอบกับผู้ใช้ดูบ้าง โดยให้แสดงข้อความ “Clear button Pressed!” เมื่อมีการกดปุ่ม Clear
ก่อนอื่นเราต้องปูพื้นฐานความรู้สักเล็กน้อย
ในการจัดการ Event ของ field BlackBerry API จะใช้ Design Pattern ที่ชื่อว่า Observer โดยการระบุให้ Object ที่มีฟังก์ชั่นตอบสนอง event นั้นทำหน้าที่เป็น Listener ผมขอนำเสนอเป็นขั้นตอนดังนี้
-
เพิ่มความสามารถให้คลาสทำหน้าที่เป็น Listener โดยการ implement Interface “FieldChangeListener” ดังนั้นเราจะเปลี่ยนแปลงส่วนประกาศของ class เป็นดังนี้
public class UIFunMainScreen extends MainScreen implements FieldChangeListener
-
implement ฟังก์ชั่น fieldChanged ฟังก์ชั่นนี้จะถูกเรียกทำงานเมื่อเกิด event ขึ้น
public void fieldChanged(Field field, int context) {
// TODO Auto-generated method stub
if (field == clearButton) {
Dialog.inform("Clear Buton Pressed!");
}
}
ฟังก์ชั่นนี้ทำงานง่ายๆ เพียงแค่ให้ Dialog แสดงข้อความว่า “Clear Buton Pressed!” โดยคำสั่ง Dialog.inform("Clear Buton Pressed!") แต่ที่ต้องมีการตรวจสอบว่า field == clearButton หรือไม่เนื่องจากเราจะให้คลาสนี้เป็น listener ของทั้งปุ่ม Clear และ Login
- ทำการเซ็ต Listenner ให้กับ ButtonField โดยการเรียกฟังก์ชั่น setChangeListener ดังนี้
clearButton.setChangeListener(this);
loginButton.setChangeListener(this);
ให้คุณลองทำตามขั้นตอนที่กล่าวมาดูแล้วตรวจสอบดูว่าเหมือนกับของผมหรือไม่
package com.mobiledevguru.uifun;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.component.BitmapField;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.CheckboxField;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.ObjectChoiceField;
import net.rim.device.api.ui.component.PasswordEditField;
import net.rim.device.api.ui.component.SeparatorField;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;
public class UIFunMainScreen extends MainScreen implements FieldChangeListener {
BitmapField bitmapField;
EditField usernameField;
PasswordEditField passwordField;
ButtonField clearButton;
ButtonField loginButton;
ObjectChoiceField domainField;
CheckboxField rememberCheckbox;
public UIFunMainScreen() {
// TODO Auto-generated constructor stub
add(new LabelField("Please enter your credentials:"));
usernameField = new EditField("Username:", "");
add(usernameField);
passwordField = new PasswordEditField("Password:", "");
add(passwordField);
domainField = new ObjectChoiceField("Domain:", new String[] {"Home", "Work"});
add(domainField);
rememberCheckbox = new CheckboxField("Remember password:", false);
add(rememberCheckbox);
clearButton = new ButtonField("Clear", ButtonField.CONSUME_CLICK);
loginButton = new ButtonField("Login", ButtonField.CONSUME_CLICK);
clearButton.setChangeListener(this);
loginButton.setChangeListener(this);
HorizontalFieldManager buttonManager = new HorizontalFieldManager(Field.FIELD_RIGHT);
buttonManager.add(clearButton);
buttonManager.add(loginButton);
add(new SeparatorField());
add(buttonManager);
}
public void fieldChanged(Field field, int context) {
// TODO Auto-generated method stub
if (field == clearButton) {
Dialog.inform("Clear Buton Pressed!");
}
}
}
- รันใน simulator และลองกดปุ่ม clear ดู
รูปที่ 4
- Clear ก็ต้องเคลียร์สิ ใช่แล้ว เราไม่ได้ต้องการให้มันแสดงข้อความแต่เราต้องการให้มันทำให้ Username และ password ว่าง แก้ไขโค้ดดังนี้และรันดูอีกครั้ง
public void fieldChanged(Field field, int context) {
if (field == clearButton) {
clearTextFields();
}
}
private void clearTextFields() {
usernameField.setText("");
passwordField.setText("");
}
สร้าง Screen แสดงผลการ Login
ที่ผ่านมาเราได้เรียนรู้การเขียน Listener เพื่อตอบสนอง event ต่อไป เราจะทำการสร้าง screen ขึ้นมาอีกหนึ่งตัวเพื่อแสดงผลการ Login หน้าจอตอบรับนี้จะแสดงข้อความว่า “Logged in!” รวมทั้ง username และ domain ข้อความทั้งหมดจะใช้ LabelField อีกทั้งมีการส่ง username และ domain ผ่าน constructor
package com.mobiledevguru.uifun;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.container.MainScreen;
public class LoginSuccessScreen extends MainScreen {
public LoginSuccessScreen(String username, String domain) {
add(new LabelField("Logged in!"));
add(new LabelField("Username: " + username));
add(new LabelField("Domain: " + domain));
}
}
ส่วนที่เราต้อง ให้ความสนใจคือหลังจากผู้ใช้คลิ๊กปุ่ม login จะเปลี่ยนไปแสดงอีกหนึ่งหน้าจอได้อย่างไร BlackBerry จัดการแสดงผล screen โดยรูปแบบของ Stack ดังนั้น เราเพียงแค่ส่ง instance ของ LoginSuccessScreen เข้าไปให้ฟังก์ชั่น pushStack ดังนี้
private void login() {
if (usernameField.getTextLength() == 0 || passwordField.getTextLength() == 0) {
Dialog.alert("You must enter a username and password");
}
else {
String username = usernameField.getText();
String selectedDomain = (String)domainField.getChoice(domainField.getSelectedIndex());
LoginSuccessScreen loginSuccessScreen = new LoginSuccessScreen(username, selectedDomain);
UiApplication.getUiApplication().pushScreen(loginSuccessScreen);
}
}
ทำงานโดยใช้เมนู
สำหรับผู้ใช้ เมนูถือว่าเป็นการใช้งานที่สะดวกมากที่สุดเนื่องจากมีปุ่มให้กดอย่างง่ายๆ เราจะเพิ่มเมนู login และ clear เข้าไปใน Aplication ของเราบ้าง วิธีที่ง่ายที่สุดคือ override ฟังก์ชั่น makeMenu ภายในเมนูจะประกอบด้วย menuItem ซึ่งเป็น abstract class แสดงการใช้งานทั้งหมดโดยโค้ดด้านล่าง
protected void makeMenu(Menu menu, int instance) {
super.makeMenu(menu, instance);
menu.add(new MenuItem("Login", 10, 20) {
public void run() {
login();
}
});
menu.add(new MenuItem("Clear",20,10) {
public void run() {
clearTextFields();
}
});
}
บรรทัดแรก super.makeMenu(menu, instance); เป็นการเรียกให้คลาสแม่ทำการสร้างเมนูที่จำเป็นอื่นๆ เช่นเมนู close
เรียก menu.add เพื่อเพิ่ม menuItem เข้าไป เราเลือกที่จะใช้ anonymous class เพื่อความสะดวก
Constructor ของ MenuItem รับ parameter 3 ตัวดังนี้
-
Text : ข้อความที่จะแสดงบนเมนู
-
Ordinal : ลำดับการแสดงผล ตัวเลขที่มีค่ามากกว่าจะอยู่ด้านบน
-
Priority: menuItem ที่มีค่าน้อยที่สุดจะถูกเลือกเป็นค่า default (ถูก highlight เมื่อเปิดเมนู)
ทดลองเลือกเมนูบน simulator จะพบเมนู Login และ Clear ดังรูปด้านล่าง
รูปที่ 5
ทิ้งท้าย
วันนี้เราได้เรียนรู้การสร้าง UI อย่างง่ายๆ เริ่มตั้งแต่ทำความรู้จัก UI อันประกอบด้วย field,manager และ form และสร้างหน้า Login ขึ้นมา จากนั้นยังทำการเขียน event handler เพื่อตอบสนองการใช้งานของผู้ใช้โดยการกำหนดให้มีคลาส Listener คอยดักและจัดการ event เราเรียนรู้การเปลี่ยน screen โดยการใช้ฟังก์ชั่น pushScreenและสุดท้ายกับการสร้างเมนู ส่วนของ UI เชื่อว่าสิ่งที่เราทดลองทำกันมาเพียงพอในการเริ่มต้นสร้าง Application โดยทั่วไป
อ้างอิง
หนังสือ : Beginning BlackBerry Development :By Anthony Rizk