meaw meaw...
JTable Tip:
A column with multiple cell editors 

homearticlesbooksshowcasequiz
โดย สมชัย หลิมศิโรรัตน์
  เกริ่นนำ
การใช้ javax.swing.JTable ในภาษา Java เก็บข้อมูลเป็นตารางนั้น นับว่าสะดวกมาก แต่ JTable ที่ออกแบบมานั้นก็มีข้อจำกัด คือจะต้องใส่ข้อมูลชนิดเดียวกัน ไว้ในคอลัมน์เดียวกัน ข้อจำกัดอันนี้ ทำให้การสร้างโปรแกรม ที่แสดง Property ของข้อมูล นำเอา JTable มาใช้งานตรง ๆ เลยไม่ได้

เมื่อหลายปีก่อน ผมได้พยายามทำความเข้าใจการทำงานของ JTable และคิดหาวิธีแก้ไขข้อจำกัดอันนี้ขึ้นมา แต่ก็ไม่ได้เผยแพร่ให้ใครได้รู้ จนมาถึงวันนี้ ผมได้มีโอกาสเข้าไปคุยในเว็บบอร์ดหลายแห่ง และพบว่ามีหลายคนที่มีประสพการณ์ดี ๆ และมีอุดมการณ์ที่จะเผยแพร่ความรู้ โดยไม่ได้รับค่าตอบแทนใด ๆ ผมจึงอยากจะเผยแพร่ความรู้อันนี้ของผมบ้าง แต่ก่อนจะเขียนบทความนี้ขึ้นมา ผมก็ได้สำรวจและพบบทความหนึ่งที่แก้ปัญหานี้ด้วยเหมือนกัน อยู่ที่ JavaWorld แต่วิธีการต่างจากของผม ซึ่งผมก็ได้เข้าไปคุยแลกเปลี่ยนความคิดเห็นในเว็บบอร์ดที่ Narisa.com เพื่อให้หลาย ๆ คนได้วิจารณ์ว่าวิธีการที่ผมใช้ กับวิธีการของ JavaWorld มีข้อดีและข้อเสียอย่างไร ก็ได้รับความเห็นว่า วิธีของ JavaWorld นั้นยังมีข้อจำกัดอยู่ ในกรณีที่มีข้อมูลมาก ๆ หลาย ๆ row อาจจะทำให้ประสิทธิภาพการทำงานตกลงได้ ซึ่งวิธีการของผมจะไม่มีปัญหาตรงนี้ ผมจึงคิดว่าควรจะเขียนเรื่องนี้เป็นบทความให้ทุกคนได้อ่านกัน

พื้นฐานความรู้
เนื่องจากบทความนี้ต้องการเน้นเฉพาะเรื่องการแก้ปัญหา กรณีข้อมูลหลายชนิดใน column เดียวของ JTable เท่านั้น ผู้อ่านควรจะมีพื้นความรู้การใช้งาน JTable เป็นอย่างดีมาก่อนแล้ว หากท่านยังไม่มีแต่สนใจเรื่องนี้ ผมขอแนะนำให้อ่านวิธีการใช้งาน JTable ในหัวข้อ How to use Tables จากหนังสือ The Java Tutorial ซึ่งเป็นการรวมหนังสือ 3 เล่มไว้ที่เดียวกัน และมีไฟล์ให้ ดาวโหลด มาอ่านได้ฟรีด้วยครับ

ข้อจำกัด
การใช้งาน JTable นั้น ถูกออกแบบมาในลักษณะของ MVC (Model-View-Controller) ซึ่งมี javax.swing.table.TableModel เป็น interface สำหรับให้เราสร้าง model ของข้อมูลแบบของเราเองได้ ข้อจำกัดของ JTable ก็เกิดจากการที่ TableModel นั้น ถูกออกแบบมาให้มี method getColumnClass(int col) เพื่อใช้ตรวจสอบชนิดของข้อมูลในแต่ละ column แต่ไม่มี method ใด ๆ สำหรับตรวจสอบชนิดของข้อมูลในแต่ละ cell หรือ row เลย การ implement ใน javax.swing.table.AbstractTableModel ซึ่งเป็น Parent Class ของ javax.swing.table.DefalutTableModel นั้น เขียน method getColumnClass ไว้ดังนี้

public Class getColumnClass(int columnIndex) {
    return Object.class;
}
เราจะไม่สามารถใช้ method นี้แก้ปัญหาใด ๆ ได้ เพราะ parameter ของ method นี้มีเพียงตัวเดียวคือ หมายเลข column หากต้องการระบุว่าเป็นข้อมูลที่ cell ไหน จะตัองระบุทั้ง column และ row ใน JTable จะมี method getCellEditor(int row, int col) และ getCellRenderer(int row, int col) ซึ่งเป็น method สำหรับหาตัววาด/แก้ไข cell นั้น ๆ มา ใน method ทั้งสองอันนี้ จะเรียกใช้ method getColumnClass(int col) เพื่อหาชนิดของข้อมูลมา แล้วใช้ method getDefaultEditor/Renderer เพื่อหา Editor/Renderer มา นี่คือ source code บางส่วนของ JTable คัดลอกมาเฉพาะส่วนที่เกี่ยวข้อง
public Class getColumnClass(int column) {
    return getModel().getColumnClass(convertColumnIndexToModel(column));
}

public TableCellEditor getCellEditor(int row, int column) {
    TableColumn tableColumn = getColumnModel().getColumn(column);
    TableCellEditor editor = tableColumn.getCellEditor();
    if (editor == null) {
        editor = getDefaultEditor(getColumnClass(column));
    }
    return editor;
}

public TableCellRenderer getCellRenderer(int row, int column) {
    TableColumn tableColumn = getColumnModel().getColumn(column);
    TableCellRenderer renderer = tableColumn.getCellRenderer();
    if (renderer == null) {
        renderer = getDefaultRenderer(getColumnClass(column));
    }
    return renderer;
}
วิธีการแก้ไข
ในบทความของ JavaWorld นั้นแก้ปัญหานี้ด้วยการ override method getCellEditor ของ JTable ให้ไปค้นหา Editor ตาม row ที่กำหนด ซึ่งจะมี RowEditorModel ที่สร้างขึ้นมาใหม่ ให้เป็นตัวจัดการเก็บ Editor ไว้และเพิ่มเติม method สำหรับ set/getRowEditorModel เข้ามา แต่วิธีการของผมนั้น ผมจะเพิ่ม method getCellClass(int row, int column) เข้ามาแทนทั้งใน TableModel และตัว JTable โดยการสืบทอดมาเป็น CellIndependentTableModel และ CellIndependentTable ดังนี้

public class CellIndependentTableModel extends DefaultTableModel { 
    // ... constructor
    public Class getCellClass(int row,int col) {
        Object obj = getValueAt(row,col); 
        if (obj != null) return obj.getClass(); else return Object.class; 
    } 
}
ส่วนใน CellIndependentTable นั้น ผมก็ได้ override ให้ getCellEditor และ getCellRenderer ให้เรียกใช้ getCellClass แทน ดังนี้
public class CellIndependentTable extends JTable {
    // ... constructor
    public Class getCellClass(int row,int col) {
        TableModel model = getModel();
        if (model instanceof CellIndependentTableModel) {
            CellIndependentTableModel ptm = (CellIndependentTableModel)model;
            return ptm.getCellClass(row,convertColumnIndexToModel(col));
        }
        return model.getColumnClass(convertColumnIndexToModel(col));
    }

    public TableCellEditor getCellEditor(int row, int column) {
        TableColumn tableColumn = getColumnModel().getColumn(column);
        TableCellEditor editor = tableColumn.getCellEditor();
            if (editor == null) {
                editor = getDefaultEditor(getCellClass(row,column));
            }
        return editor;
    }

    public TableCellRenderer getCellRenderer(int row, int column) {
        TableColumn tableColumn = getColumnModel().getColumn(column);
        TableCellRenderer renderer = tableColumn.getCellRenderer();
        if (renderer == null) {
            renderer = getDefaultRenderer(getCellClass(row,column));
        } 
        return renderer; 
    }
}
ตัวอย่าง
ผมได้ทำตัวอย่างการใช้งานเป็น Property Editor ขึ้นมาซึ่งเป็นเป้าหมายหลักของปัญหานี้ โดย source code ทั้งหมดนั้นประกอบด้วยหลายไฟล์ จึงขอไม่นำมาลงในบทความนะครับ กรุณาดู source code ประกอบคำอธิบายไปด้วยจะทำให้เข้าใจได้ง่ายขึ้น

รูปที่ 1. คือ Renderer ของข้อมูลต่างชนิดกัน ซี่งมี (ดู source code TestCITable.java ประกอบ)

  • ID เป็น Integer
  • Name และ Family Name เป็น String
  • Weight เป็น Float
  • Single เป็น Boolean
  • Hair Color เป็น Color
  • File เป็น class ที่ผมสร้างขึ้นเองชื่อ FileName ไว้สำหรับเก็บชื่อไฟล์

รูปที่ 1. Renderer ของข้อมูลต่างชนิดกัน ใน Column เดียวกัน

เมื่อคลิกเพื่อแก้ไขข้อมูล ก็จะมีตัว Editor มาโต้ตอบกับเรา ขึ้นอยู่กับชนิดของข้อมูลในช่องนั้น ๆ เช่น รูปที่ 2. เลือกแก้ไข field ข้อมูล Single ซึ่งเป็นข้อมูลแบบ Boolean ผมได้ใช้ JComboBox ให้เลือกคำว่า True/False

รูปที่ 2. การใช้ JComboBox แทนค่า True/False สำหรับ Object ชนิด Boolean

รูปที่ 3. เลือกแก้ไข File ซึ่งเป็นข้อมูลแบบที่ผมสร้างเอง คือ class FileName โดย Editor จะผสมระหว่าง JTextField กับ JButton คือจะป้อนชื่อไฟล์เองหรือ กดที่ปุ่มเพื่อเปิดหน้าต่าง JFileChooser ก็ได้นะครับ

รูปที่ 3. การใช้ FileChooser เพื่ออำนวยความสะดวก และป้องกันความผิดพลาดในการใส่ชื่อไฟล์

รูปสุดท้าย จะเป็นการแสดงให้เห็นว่า เราสามารถใช้ table ตัวเดิม รองรับข้อมูลแบบอื่นๆได้ทันที โดยที่ผมทำปุ่ม Change Data ไว้แล้ว เมื่อกด ก็จะเปลี่ยนข้อมูลที่ผมเตรียมไว้ แสดงให้เห็นว่า ข้อมูลจะเป็นอะไรก็ได้ อยู่ที่ cell ไหนก็ได้ เหมือน Spread Sheet เลย

รูปที่ 4. การ Render ข้อมูลชนิดต่าง ๆ

ขอขอบคุณ คุณวีรศักดิ์ วิทวัสกุล หรือ นายข้าวโพดหวาน (oop guy) ผู้ดูแลเว็บบอร์ด ในส่วนของ Java & Object Oriented Developmemt ของเว็บ Narisa.com ที่ปรับปรุงโปรแกรมตัวอย่างนี้ให้เป็น jar ไฟล์ที่พร้อมรันได้เลย

สรุป
เทคนิคของผมนี้ จะช่วยให้เราสร้างโปรแกรมที่ทำงานกับข้อมูลแบบตาราง ได้อย่างไร้ขีดจำกัดอีกต่อไป ซึ่งอาจจะนำไปปรับปรุงทำเป็นโปรแกรม Spread Sheet ก็ได้ นอกจากนี้ในโปรแกรมตัวอย่างนี้ยังมีเทคนิคการสร้าง Editor/Renderer แบบต่าง ๆ อีกมากมายนะครับ

ประวัติส่วนตัวของผู้เขียน
สมชัย หลิมศิโรรัตน์ จบการศึกษาระดับปริญญาตรีทางวิศวกรรมไฟฟ้า จากมหาวิทยาลัยสงขลานครินทร์ มีความชื่นชอบเกี่ยวกับคอมพิวเตอร์มาตั้งแต่สมัยชั้นปี 1 หลังจบการศึกษาได้รับบรรจุเข้าเป็็นอาจารย์ให้กับภาควิชาวิศวะคอมพิวเตอร์ คณะวิศวกรรมศาสตร์ มหาวิทยาลัยสงขลานครินทร์เป็นเวลาหลายปี ก่อนที่จะได้รับทุนรัฐบาลไทย เพื่อมาศึกษาต่อในระดับปริญญาโทและเอก ณ ประเทศญี่ปุ่น ปัจจุบันสมชัยทำงานวิจัยเกี่ยวกับ Image Processing สำหรับผลไม้เมืองร้อน ซึ่งเป็นส่วนหนึ่งของการศึกษาระดับปริญญาเอก โดยหลังจากจบการศึกษาแล้ว สมชัยวางแผนที่จะกลับไปเป็นอาจารย์มหาวิทยาลัยเหมือนเดิม
สมชัยเป็นหนึ่งในบุคคลที่เป็นที่รู้จักกันดี สำหรับนักพัฒนาจาวาเมืองไทย เขาเป็นผู้ก่อตั้งกลุ่ม Thai Java User Group เพื่อใช้เป็นที่สำหรับแลกเปลี่ยนข่าวสาร ระหว่างนักพัฒนาจาวาด้วยกัน

Resources
ไฟล์ที่เกี่ยวข้องทั้งหมดสามารถดาวโหลดได้จากที่นี่
CITable1.2.jar - jar package และ source code ที่ปรับปรุงโดย คุณวีรศักดิ์ วิทวัสกุล
CITableTest.zip - source code ต้นฉบับ
ถ้าใครอยากดู source code แบบออนไลน์ก็สามารถดูได้จากที่นี่ ViewSource

สำหรับผู้อ่านที่สนใจเกี่ยวกับเรื่องของ JTable สามารถศึกษารายละเอียดเพิ่มเติมได้จาก
How to use Tables วิธีการใช้ JTable จากหนังสือ Java Tutorial.
Adding multiple JTable cell editors per column วิธีการเพิ่ม cell editor หลายชนิดในหนึ่ง column สำหรับ JTable ในแบบของ Javaworld.


Copyright © 2000-2003 www.jarticles.com