Про java swing - часть 4

February 26, 2008

Создание таблицы (JTable) и заполнение ее данными



Основным элементом управления для создания типовых приложений является jtable – таблица. В отличии от того подхода, который мы использовали в mfc с классом clistview (всмомните, что таблица являлась по сути отдельной разновидностью такого элемента как clistview, но с назначенным стилем отображения REPORT).

Так вот, в библиотеке swing класс Jtable служит только для представления табличных данных.

Простейший пример создания таблицы будет выглядеть так:
  1. public static void main(String[] args) {
  2.  
  3.         Vector column_names = new Vector();
  4.         // формируем список названий полей (колонок)
  5.         column_names.add("ФИО");
  6.         column_names.add("Возраст");
  7.         column_names.add("Вес");
  8.         column_names.add("Хитрый");
  9.  
  10.         // каждой записи соответствует отдельный объект Vector
  11.         // элементы которого представляют собой значения отдельных полей
  12.         Vector vasyan_info = new Vector();
  13.         vasyan_info.add("Василий Пупкин");
  14.         vasyan_info.add(new Integer(20));
  15.         vasyan_info.add(new Double(90.57));
  16.         vasyan_info.add(new Boolean(true));
  17.  
  18.         Vector petyan_info = new Vector();
  19.         petyan_info.add("Петюан Козлов");
  20.         petyan_info.add(new Integer(24));
  21.         petyan_info.add(new Double(78.2));
  22.         petyan_info.add(new Boolean(false));
  23.  
  24.         // теперь каждую из записей следует поместить внутрь еще одного списка
  25.         Vector vec_data = new Vector();
  26.         vec_data.add(vasyan_info);
  27.         vec_data.add(petyan_info);
  28.  
  29.         JFrame jf = new JFrame();
  30.         JTable jt = new JTable(vec_data, column_names);
  31.         // обратите внимание на то что я не могу непосредственно добавить на форму объект JTable
  32.         // его необходимо обернуть с помощью JScrollPane 
  33.         jf.add(new JScrollPane(jt), BorderLayout.CENTER);
  34.         jf.setVisible(true);
  35.  
  36.     }
Примечания: в примере создается вектор с именами колонок, а также вектор, каждый элемент которого является еще одним вектором представляющим отдельную запись и состоит из объектов (полей записи).

Важно при работе с таблицами всегда помещать таблицу внутрь обертки JscrollPane. Все дело в том, что именно в этом режиме обеспечивается корректное отображение заголовков таблицы.



Недостатоком данного примера является то, что все колонки таблицы представляются в виде строк и доступны для редактирования. А самое главное то, что все данные, которые должны быть представлены в таблице, должны быть заранее помещены в контейнер Vector, что в случае работы с базами данных часто составляет сложности. Другой вариант конструктора Jtable приводящий к аналогичным результатам:
  1. JTable(Object[][] rowData, Object[] columnNames)
Если сильно хочется, то можно отдельно получить доступ к элементу управления - заголовок таблицы - и разместить его отдельно от, собственно области таблицы для отображения данных, например, следующий код создает таблицу у которой заголовки расположены ниже ее основной части:
  1. // создаем форму и помещаем на нее таблицу
  2.    JFrame jf = new JFrame("Пример заголовков таблицы размещенных внизу");
  3.    JTable jt = new JTable(vec_data, column_names);
  4.  
  5.    jf.getContentPane().add (new JScrollPane(jt) , BorderLayout.CENTER);
  6.    jf.getContentPane().add (new JScrollPane(jt.getTableHeader()),  BorderLayout.WEST);


Следующий способ создания таблицы покажет все приемы использования концепции моделей и рендереров, которые мы узнали при работе со списками. В следующем примере создается таблица, отображающая файлы картинок в заданном каталоге. Разные колонки отображают имя файла, размер и окно-preview.
  1. public static void main(String[] args) {
  2.         // сначала получим список всех файлов внутри некоторого каталога - эти файлы и будут содержимым таблицы
  3.         final File[] files = new File("C:\\tmp").listFiles();
  4.         JFrame jf = new JFrame();
  5.         JTable jt = new JTable(
  6.                 // первое отличие от всех примеров ранее – мы  создаем таблицу на базе модели данных
  7.                 new AbstractTableModel() {
  8.                     // в состав модели входят многожество методов но только три нижеследующий обязательны
  9.  
  10.                     public int getRowCount() { // обязательно – количество строк в таблице
  11.                         return files.length;
  12.                     }
  13.  
  14.                     public int getColumnCount() {// количество колонок в таблице
  15.                         return 3;
  16.                     }
  17.                     // и последний жестко обязаталельный метод – возвращающий значение элемента,
  18.                     //  который должнен находиться в указанных координатах
  19.                     public Object getValueAt(int rowIndex, int columnIndex) {
  20.                         File f = files[rowIndex];
  21.                         if (columnIndex == 0) return f.getName();
  22.                         if (columnIndex == 1) return f.length();
  23.                         if (columnIndex == 2) return f;
  24.                         return null;
  25.                     }
  26.  
  27.                     // необязательный метод – возвращает класс – или тип данных который хранится в данной колонке
  28.                     public Class<?> getColumnClass(int columnIndex) {
  29.                         if (columnIndex == 0) return String.class;
  30.                         if (columnIndex == 1) return Long.class;
  31.                         if (columnIndex == 2) return File.class;
  32.                         return null;
  33.                     }
  34.  
  35.                     // важно данный метод должен вернуть название колонки – если вы не перекроете данный метод,
  36.                     //  то колонки получат имена по правилу A, B, C , D, …
  37.                     public String getColumnName(int column) {
  38.                         if (column == 0) return "Имя файла";
  39.                         if (column == 1) return "Размер";
  40.                         if (column == 2) return "Preview";
  41.                         return null;
  42.                     }
  43.  
  44.                     //метод должен вернуть признак того можно ли редактировать значение в колонке с указанным номером
  45.                     public boolean isCellEditable(int rowIndex, int columnIndex) {
  46.                         return columnIndex == 0;
  47.                     }
  48.  
  49.                     // и метод который вызывается когда значение в ячейке изменяется 
  50.                     // в этом примере выполняется переименование файла
  51.                     public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
  52.                         super.setValueAt(aValue, rowIndex, columnIndex);
  53.                         File f = files[rowIndex];
  54.                         File dest = new File(f.getParentFile(), aValue.toString());
  55.                         if (!f.renameTo(dest))
  56.                             JOptionPane.showMessageDialog(null, "Ошибка, не могу переименовать файл");
  57.                         else
  58.                             files[rowIndex] = dest;
  59.                     }
  60.                 }//end of --  AbstractTableModel --
  61.         );
  62.         // создается класс редактора – редактор привязывается не к конкретному столбцу а типу данных,
  63.         // который задается первым параметром при вызове setDefaultEditor
  64.         jt.setDefaultEditor(
  65.                 String.class,// тип данных для которого создается редактор
  66.                 new TableCellEditor() {// код самого редактора он представляет собой панель с текстовым полем,
  67.                     //  где находится имя файла и кнопками 'принять изменение' и 'отменить изменение'
  68.                     JTextField fld_txt = new JTextField();
  69.                     JPanel btns_zone = new JPanel(new GridLayout(1, 2));
  70.                     JPanel editor = new JPanel(new BorderLayout());
  71.  
  72.                     {// здесь я создаю внешний вид класса редатора свойства - имени файла
  73.                         // для этого создается панель с двумя кнопками - принять введенное значние и отказаться
  74.                         editor.add(btns_zone, BorderLayout.NORTH);
  75.                         editor.add(fld_txt, BorderLayout.CENTER);
  76.                         fld_txt.setPreferredSize(new Dimension(0, 25));
  77.                         JButton b_OK = new JButton("Принять");
  78.                         JButton b_CANCEL = new JButton("Отказаться");
  79.                         b_OK.addActionListener(
  80.                                 new ActionListener() {
  81.                                     public void actionPerformed(ActionEvent e) {
  82.                                         fireValueWasChanged(true);
  83.                                     }
  84.                                 }
  85.                         );
  86.                         b_CANCEL.addActionListener(
  87.                                 new ActionListener() {
  88.                                     public void actionPerformed(ActionEvent e) {
  89.                                         fireValueWasChanged(true);
  90.                                     }
  91.                                 }
  92.                         );
  93.                         btns_zone.add(b_OK);
  94.                         btns_zone.add(b_CANCEL);
  95.                     }
  96.                     //данный метод вызывается когда таблица нуждается в редаторе для выбранного пользователем поля
  97.                     public Component getTableCellEditorComponent
  98.                     (JTable table, Object value, boolean isSelected, int row, int column) {
  99.                         fld_txt.setText(value.toString());
  100.                         // я выполняю инициализацию текстового поля тем значением, 
  101.                         // которое находится в таблице и возвращаю панель редактирования
  102.                         return editor;
  103.                     }
  104.  
  105.                     public Object getCellEditorValue() {// получить отредактированное изменение
  106.                         return fld_txt.getText();
  107.                     }
  108.                     // проверка того можно ли редактировать ячейку, 
  109.                     // но в отличии от ранее вам встречавшегося метода isCellEditable
  110.                     // в модели таблицы, здесь выполнятся проверка того, 
  111.                     // может ли событие anEvent вызвать открытие редатора.
  112.                     // В моем примере любое событие приводит к появлению редактора, 
  113.                     // а можно только при нажатии кнопки F2
  114.                     public boolean isCellEditable(EventObject anEvent) {
  115.                         return true;
  116.                     }
  117.  
  118.                     public boolean shouldSelectCell(EventObject anEvent) {return false;}
  119.  
  120.                     //метод вызывается когда таблица спрашивает завершено ли редатирование поля
  121.                     public boolean stopCellEditing() {return false;}
  122.                     //метод вызывается когда редактирование должно быть прекращено
  123.                     public void cancelCellEditing() {}
  124.  
  125.                     // коллекция в которой хранится множество ссылок на слушателей событий с редактором.
  126.                     // Далее я реализую методы добавления и удаления слушателей из очереди
  127.                     List vListeners = Collections.synchronizedList(new ArrayList());
  128.  
  129.                     public void addCellEditorListener(CellEditorListener l) {
  130.                         vListeners.add(l);
  131.                     }
  132.  
  133.                     public void removeCellEditorListener(CellEditorListener l) {
  134.                         vListeners.remove(l);
  135.                     }// этот метод я буду вызывать при  нажатии на кнопки ПРИНЯТЬ и ОТКАЗАТЬСЯ
  136.  
  137.                     protected void fireValueWasChanged(boolean is_ok) {
  138.                         ArrayList anList = new ArrayList(vListeners);
  139.                         for (Iterator iterator = anList.iterator(); iterator.hasNext();) {
  140.                             CellEditorListener cellEditorListener = (CellEditorListener) iterator.next();
  141.                             if (is_ok)
  142.                                 cellEditorListener.editingStopped(new ChangeEvent(this));
  143.                             else
  144.                                 cellEditorListener.editingCanceled(new ChangeEvent(this));
  145.                         }
  146.                     }
  147.                 }
  148.         );
  149.         // теперь создаем класс – рендерера
  150.         jt.setDefaultRenderer(
  151.                 // рендерер также как редактор назначается не для конкретного столбца 
  152.                 //таблицы а именно для типа данных
  153.                 File.class,
  154.                 new TableCellRenderer() {
  155.                     public Component getTableCellRendererComponent
  156.                         (JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
  157.                         // обратите внимание какие данные передаются как 
  158.                         //параметры метода, здесь не только значение ячейки для которой необходимо
  159.                         // выполнить создание представления-рендерера,
  160.                         // но и координаты ячейки, и признак того входит ли данная 
  161.                         //ячейка в выделение и имеет ли фокус ввода.
  162.                         try {// возвращаем Jlabel на котором находится изображение картинки в строке
  163.                             return new JLabel(new ImageIcon(((File) value).getCanonicalPath()));
  164.                         } catch (IOException e) {
  165.                             e.printStackTrace();
  166.                         }
  167.                         return new JLabel("Ошибка: " + value);
  168.                     }
  169.                 }
  170.         );
  171.         jt.setRowHeight(100);// устанавливаем высоту строк таблицы
  172.         jf.getContentPane().add(new JScrollPane(jt), BorderLayout.CENTER);
  173.         jf.pack();
  174.         jf.setVisible(true);
  175.     }


Данный пример можно развить: прежде всего сделаем так, чтобы переход к режиму редактирования осуществлялся только при двойном нажатии мыши. Метод IsCellEditable должен вернуть true в том случае, если событие произошедшее с ячейкой, должно активировать редактор ячейки. Объект события передается внутри anEvent, и имеет тип EventObject. Этот тип является базовым для множества объектов событий более конкретных (работа с мышью, клавиатура ...). В примере ниже я проверяю относится ли данное событие к категории "события с мышью" и если так, то дополнительно проверяю, чтобы количество кликов мыши было двойным. Только тогда возвращается значение true из метода isCellEditable, а значит выполняется переход в режим редактора.
  1. public boolean isCellEditable(EventObject anEvent) {
  2.  if (!(anEvent instanceof MouseEvent)) return false;
  3.  MouseEvent me = (MouseEvent) anEvent;
  4.  return me.getClickCount() == 2;
  5. }


Работа с моделью выделения



Также как для списка Jlist или JcomboBox, для таблицы есть связанная модель, управляющая тем, какие элементы были выделены. Она позволяет проверить какие строки таблицы выделены, также можно программно изменить выделение. В примере ниже создается таблица, заполненная случайными числами. Внизу формы расположена Jlabel, в которую выводится сумма элементов для выделенного диапазона таблицы (по умолчанию вы выделяете всю строку целиком).
  1. public static void main(String[] args) {
  2.     final Double r_arr[][] = new Double[30][10]; 
  3.     for (int i = 0; i < r_arr.length; i++) 
  4.         for (int j = 0; j < r_arr[i].length; j++) 
  5.             r_arr[i][j] = new Double(Math.random()); 
  6.     JFrame jf = new JFrame();
  7.     final JLabel lab_status = new JLabel("Выделите несколько строк и узнайте сумму элементов");            
  8.     JTable jt = new JTable( 
  9.             new AbstractTableModel() {
  10.                 public int getRowCount() { 
  11.                     return r_arr.length;
  12.                 }   
  13.  
  14.                 public int getColumnCount() {
  15.                     return r_arr[0].length; 
  16.                 }  
  17.  
  18.                 public Object getValueAt(int rowIndex, int columnIndex) { 
  19.                     return r_arr[rowIndex][columnIndex];
  20.                 }   
  21.             }    
  22.     );
  23.     jt.getSelectionModel().addListSelectionListener(                                                       
  24.             new ListSelectionListener() { 
  25.                 public void valueChanged(ListSelectionEvent e) {
  26.                     if (e.getValueIsAdjusting()) return;
  27.                     int start = e.getFirstIndex(); 
  28.                     int end = e.getLastIndex();
  29.                     double s = 0;
  30.                     for (int i = start; i <= end; i++)    
  31.                         for (int j = 0; j < r_arr[i].length; j++)      
  32.                             s += r_arr[i][j].doubleValue();     
  33.                     lab_status.setText("" + s);      
  34.  
  35.                 } 
  36.             }
  37.     );
  38.  
  39.     jf.getContentPane().add(new JScrollPane(jt));
  40.     jf.getContentPane().add(lab_status, BorderLayout.SOUTH);
  41.     jf.pack();
  42.     jf.setVisible(true);
  43. }


Обнаружение изменений в таблице



В большинстве случаев вам не нужно заботиться об таких изменениях. Дело в том, что и модель и таблица автоматически обнаруживают изменения данных: если меняется модель, то таблица перестраивается. При редактировании ячейки таблицы изменения вносятся в модель также с минимальным нашим участием. Попробуйте в примере выше удалить фрагмент кода с использованием массива случайных чисел, и в методе getValue возвращать случайное число.

Однако в случае, если вы хотите явно известить таблицу о том, что произошли изменения следует использовать вызовы:
  1. AbstractTableModel amodel = (AbstractTableModel)jt.getModel();
  2. amodel.addTableModelListener(
  3.   new TableModelListener() {
  4.     // здесь создается объект-слушатель для событий, связанных с изменениями содержимого таблицы
  5.     public void tableChanged(TableModelEvent e) {
  6.        int row = e.getFirstRow();
  7. // Определяем координаты того, какая строка, столбец , его название и значение находящееся в указанной точке было изменено.
  8. // Следует отметить, что данный фрагмент кода далеко не идеален. 
  9. // И может корректно срабатывать только для небольшого диапазона событий.
  10. // Например, те события, которые связаны не с конкретными столбцами, а со столбцами "вообще" будут ошибочны.
  11. // Но ведь это только пример ...
  12.        int column = e.getColumn();
  13.        TableModel model = (TableModel)e.getSource();
  14.        String columnName = model.getColumnName(column);
  15.        Object data = model.getValueAt(row, column);
  16.        System.out.println ("Changed: row=" + row + " col=" + column+
  17.           model = " + model+ " colName = "+columnName+
  18.           data = " + data);
  19.  
  20.      }
  21.    }
  22.   );
  23.  
  24.   // теперь генерируем события программно
  25.   amodel.fireTableCellUpdated(0,0);
  26.   // здесь я вызываю метод извещения о том, что изменилось содержимое одной ячейки.
  27.   amodel.fireTableDataChanged();// изменилось все содержимое таблицы
  28.   amodel.fireTableRowsDeleted(1,2);// были удалены строки
  29.   amodel.fireTableRowsInserted(1,5);// добавлены строки
  30.   amodel.fireTableRowsUpdated(3,7);// изменено содержимое строк
  31.   amodel.fireTableStructureChanged();// изменена структура таблицы
  32.   amodel.fireTableChanged(new TableModelEvent(
  33.    // А этот способ извещения наиболее универсален. Т.к. здесь задаются все параметры проишествия по отдельности. 
  34.    // В составе класса TableModelEvent определены еще несколько констат типа: DELETE, INSERT, ALL_COLUMNS
  35.    amodel , 1,2,4, TableModelEvent.UPDATE
  36.   ));

Еще раз про render-ы и editor-ы



Примечание относительно render-ов: начиная с версии jdk 1.3 и более поздних разработчики ввели в состав Jtable автоматическую поддержку renderer-ов для следующих типов данных:
  • Boolean — отрисовыывается с помощью check box.
  • Number — с помощью Jlabel содержимое которой выравнено по правому краю
  • Double, Float — аналогично но при этом используется объект форматирования NumberFormat (локаль по умолчанию).
  • Date — с помощью Jlabel содержимое которой форматируется с помощью DateFormat (короткий стиль).
  • ImageIcon, Icon — Jlabel с выравненой по цетру картинкой.
  • Object — Jlabel – используется в качестве надписи значение которое вернул метод toString.


  1. public static void main(String[] args) {                                                                                        
  2.     Vector vasyan_info = new Vector();
  3.     // создаем вектор, хранящий сведения (колонки) для каждой из строк-записей 
  4.     vasyan_info.add("Василий Пупкин"); 
  5.     vasyan_info.add(new Integer(20));                                                                                           
  6.     vasyan_info.add(new Date(System.currentTimeMillis())); 
  7.     vasyan_info.add(true);// тип данных Boolean                                                                                 
  8.     vasyan_info.add(new ImageIcon("C:\\tmp\\exception24.jpg"));// объект-изображение
  9.  
  10.     Vector petyan_info = new Vector();  
  11.     petyan_info.add("Петюан Козлов"); 
  12.     petyan_info.add(new Integer(24));  
  13.     petyan_info.add(new Date(System.currentTimeMillis()));
  14.     petyan_info.add(false);         
  15.     petyan_info.add(new ImageIcon("C:\\tmp\\spic1.jpg.jpg"));
  16.  
  17.     // помещаем две созданные записи - вектора внутрь третьего - списка всех записей 
  18.     final Vector vec_data = new Vector();       
  19.     vec_data.add(vasyan_info);                                                                                                  
  20.     vec_data.add(petyan_info);  
  21.  
  22.  
  23.     JFrame jf = new JFrame();   
  24.     // создаем таблицу на основании собственной модели данных
  25.     JTable jt = new JTable(   
  26.             new AbstractTableModel() { 
  27.                 public int getRowCount() { 
  28.                     return vec_data.size(); 
  29.                 }
  30.  
  31.                 public int getColumnCount() { 
  32.                     return 5; 
  33.                 } 
  34.  
  35.                 public Object getValueAt(int rowIndex, int columnIndex) { 
  36.                     return ((Vector) vec_data.get(rowIndex)).get(columnIndex); 
  37.                 } 
  38.  
  39.                 public String getColumnName(int column) {
  40.                     return new String[]{"ФИО", "Возраст", "Дата рождения", "Хитрость", "Фотография"}[column];
  41.                 }  
  42.  
  43.                 public Class<?> getColumnClass(int columnIndex) {                                                               
  44.                     return new Class[]{String.class, Integer.class, Date.class, Boolean.class, ImageIcon.class}[columnIndex];
  45.                 }  
  46.  
  47.                 public boolean isCellEditable(int rowIndex, int columnIndex) {
  48.                     return columnIndex == 0; 
  49.                 } 
  50.             } 
  51.     );
  52.     // теперь нужно для колонки под номером 0 установить особый вид редактора - на основании падающего списка 
  53.     TableColumn fio = jt.getColumnModel().getColumn(0);
  54.     JComboBox comboBox = new JComboBox();   
  55.     comboBox.addItem("Козлов Василий Петрович");
  56.     comboBox.addItem("Мухин Чай Кофеиновыич");
  57.     comboBox.addItem("Глоков Глюк Глонович"); 
  58.     fio.setCellEditor(new DefaultCellEditor(comboBox));
  59.  
  60.     jt.setRowHeight(50); 
  61.     jf.getContentPane().add(new JScrollPane(jt), BorderLayout.CENTER); 
  62.  
  63.     jf.setVisible(true);
  64. }
В данном примере нехватает последней мелочи – наличия перекрытого метода setValue. Мы можем выбирать в падающем списке новые значения, но значение столбца не изменится. Именно метод setValue ответственен за сохранение изменнение из редактора ячейки в модель данных таблицы.

Основное применение для использования редакторов – ограничение ввода значений в поля таблицы. Основной принцип избежать ошибки ввода - запретить вводить все, кроме предопределенного набора значений. И здесь нам помогают падающие поля, текстовые поля с маской).

В примере, где использовался редактор для имени файла, я создал собственный класс редактора как реализующий интерфейс от TableCellEditor . Для демонстрации внутреннего устройства любого редактора, в некоторых ситуациях имеет смысл использовать класс javax.swing.DefaultCellEditor, который является наследником от TableCellEditor и реализует часть рутинных методов (ту же генерацию событий). Например, ниже приводится прием с созданием редактора для поля представляющего собой цвет. Редактором ячейки является кнопка, при нажатии на которую появляется окно выбора цвета JcolorChooser:


  1. public static class ColorEditor extends AbstractCellEditor implements TableCellEditor  {
  2.         Color currentColor;
  3.         JButton button;
  4.         JColorChooser colorChooser;
  5.         JDialog dialog;
  6.  
  7.         public ColorEditor() {
  8.             button = new JButton();
  9.             button.addActionListener(new ActionListener() {
  10.                 // обработчик события нажатие на кнопку 
  11.                 // для отображение диалогового окна выбора цвета
  12.                 public void actionPerformed(ActionEvent e) {
  13.                         button.setBackground(currentColor);
  14.                         colorChooser.setColor(currentColor);
  15.                         dialog.setVisible(true);
  16.                 }
  17.             });
  18.             button.setBorderPainted(false);
  19.  
  20.             colorChooser = new JColorChooser();
  21.             dialog = JColorChooser.createDialog(button,
  22.                     "Pick a Color",
  23.                     true,  //окно должно быть модальным
  24.                     colorChooser,
  25.                     // обработчик события  "выбор нового цвета в редакторе" успешно завершен
  26.                     new ActionListener() {
  27.                         public void actionPerformed(ActionEvent e) {
  28.                             currentColor = colorChooser.getColor();
  29.                             fireEditingStopped();
  30.                             // извещаем всех о том, что редактирование было завершено успешно
  31.                         }
  32.                     },
  33.                     //обработчик события при нажатии на диалоге выбора цвета OK
  34.                     new ActionListener() {
  35.                         public void actionPerformed(ActionEvent e) {
  36.                            fireEditingCanceled();// извещаем всех о том, что редактирование было отменено 
  37.                         }
  38.                     }); //обработка нажатия кнопки CANCEL на диалоге выбора цвета
  39.         }
  40.         // данный метод является дополнительным относительно базового для нас класса TableCellEditor –
  41.         // и вводится в составе наследника AbstractCellEditor  -
  42.         // в нем мы должны вернуть текущее значение поля редактирования
  43.         public Object getCellEditorValue() {
  44.             return currentColor;
  45.         }
  46.  
  47.         public Component getTableCellEditorComponent(
  48.             JTable table, Object value, boolean isSelected, int row, int column) {
  49.             // мы должны вернуть компонент используемый для редактирования данной ячейки. 
  50.             // Компонент может быть различным в зависимости от того,
  51.             // какое значение этой ячейки (value), выбрана ли данная ячейка (isSelected), 
  52.             // а также координат (row, column) этой ячейки
  53.             currentColor = (Color) value;
  54.             return button;
  55.         }
  56.         // для того чтобы активировать редактор необходимо предварительное выделение ячейки
  57.         public boolean shouldSelectCell(EventObject anEvent) {
  58.             return true;
  59.         }
  60.  
  61.         public boolean isCellEditable(EventObject e) {
  62.             if (e instanceof MouseEvent)
  63.             // редактор должен быть активирован только в том случае,
  64.             // если был выполнен двойной клик по ячейке и ячейка выделена
  65.                 return ((MouseEvent)e).getClickCount() == 2;
  66.             return false;
  67.         }
  68.  
  69.     }
  70.  
  71.       public static void main(String[] args) {
  72.         Vector vasyan_info = new Vector();
  73.         // создаем вектор, хранящий сведения (колонки) для каждой из строк-записей
  74.         vasyan_info.add("Василий Пупкин");
  75.         vasyan_info.add(new Integer(20));
  76.         vasyan_info.add(new Date(System.currentTimeMillis()));
  77.         vasyan_info.add(true);// тип данных Boolean
  78.         vasyan_info.add(Color.red);// объект-цвет
  79.  
  80.         Vector petyan_info = new Vector();
  81.         petyan_info.add("Петюан Козлов");
  82.         petyan_info.add(new Integer(24));
  83.         petyan_info.add(new Date(System.currentTimeMillis()));
  84.         petyan_info.add(false);
  85.         petyan_info.add(Color.blue);
  86.  
  87.         // помещаем две созданные записи - вектора внутрь третьего - списка всех записей
  88.         final Vector vec_data = new Vector();
  89.         vec_data.add(vasyan_info);
  90.         vec_data.add(petyan_info);
  91.  
  92.  
  93.         JFrame jf = new JFrame();
  94.         // создаем таблицу на основании собственной модели данных
  95.         JTable jt = new JTable(
  96.                 new AbstractTableModel() {
  97.                     public int getRowCount() {
  98.                         return vec_data.size();
  99.                     }
  100.  
  101.                     public int getColumnCount() {
  102.                         return 5;
  103.                     }
  104.  
  105.                     public Object getValueAt(int rowIndex, int columnIndex) {
  106.                         return ((Vector) vec_data.get(rowIndex)).get(columnIndex);
  107.                     }
  108.  
  109.                     public String getColumnName(int column) {
  110.                         return new String[]{"ФИО", "Возраст", "Дата рождения", "Хитрость", "Цвет ушей"}[column];
  111.                     }
  112.  
  113.                     public Class<?> getColumnClass(int columnIndex) {
  114.                         return new Class[]{String.class, Integer.class, Date.class, Boolean.class, Color.class}[columnIndex];
  115.                     }
  116.  
  117.                     public boolean isCellEditable(int rowIndex, int columnIndex) {
  118.                         return true;
  119.                     }
  120.  
  121.                     // самая главная часть поддержки редактора ячеек - 
  122.                     // функция сохранения значения которое было введено в данную ячейку
  123.                     public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
  124.                         ((Vector) vec_data.get(rowIndex)).set(columnIndex, aValue);
  125.                     }
  126.                 }
  127.         );
  128.         jt.setDefaultEditor(Color.class, new ColorEditor());
  129.         jt.setDefaultRenderer(Color.class, new TableCellRenderer() {
  130.             JPanel lab = new JPanel();
  131.             Border border_black = BorderFactory.createLineBorder(Color.black, 3);
  132.             Border border_white = BorderFactory.createLineBorder(Color.white, 3);
  133.  
  134.             public Component getTableCellRendererComponent(
  135.                 JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
  136.                 if ( value != null)
  137.                     lab.setBackground((Color) value);
  138.                 else
  139.                     lab.setBackground(Color.white);
  140.                 if (hasFocus)
  141.                     lab.setBorder(border_black);
  142.                 else
  143.                     lab.setBorder(border_white);
  144.                 return lab;
  145.             }
  146.         });
  147.  
  148.         jt.setRowHeight(50);
  149.         jf.getContentPane().add(new JScrollPane(jt), BorderLayout.CENTER);
  150.         jf.setSize(640, 480);
  151.         jf.setVisible(true);
  152.     }

Всплывающие подсказки



Также как и для любого другого элемента управления swing, вы можете задавать и управлять подсказкой (tooltip), используя метод setTooltip (String). Однако есть возможность управлять подсказками более детально (setTooltip задает глобальную подсказку не зависящую от того на какую именно ячейку таблицы была наведена мышь). Например, начнем мы с того что, что создадим подсказку для каждой отдельной ячейки отдельно подсказку для заголовка таблицы.
  1. public static void main(String[] args) {
  2.         JFrame jf = new JFrame();
  3.         final Color cols[] = new Color[]{Color.RED,
  4.                 Color.BLUE, Color.GREEN, Color.YELLOW, Color.MAGENTA};
  5.         JTable jt = new JTable(
  6.                 new AbstractTableModel() {
  7.                     public int getRowCount() {
  8.                         return cols.length;
  9.                     }
  10.  
  11.                     public int getColumnCount() {
  12.                         return 1;
  13.                     }
  14.  
  15.                     public Object getValueAt(int rowIndex, int columnIndex) {
  16.                         return cols[rowIndex];
  17.                     }
  18.  
  19.                     public Class<?> getColumnClass(int columnIndex) {
  20.                         return Color.class;
  21.                     }
  22.                 }
  23.         ) {
  24. // внимание: здесь я перекрываю метод createDefaultTableHeader. 
  25. // В составе класса Jtable данный метод вызывается всякий раз, когда необходимо создать заголовок таблицы.
  26. // Т.е. в первый раз и при изменении структуры хранящихся внутри таблицы данных.
  27. // Мы создадим не просто JtableHeader, но производный от него класс, в котором перекрывается метод getTooltipText.
  28. // Данный метод вызывается всякий раз, когда мышь пользователя задерживается над частью заголовка 
  29. // и, следовательно, необходимо получение значения подсказки.
  30.  
  31.             protected JTableHeader createDefaultTableHeader() {
  32.                 return new JTableHeader(columnModel) {
  33.                     public String getToolTipText(MouseEvent e) {
  34.                         java.awt.Point p = e.getPoint();
  35.                         int index = columnModel.getColumnIndexAtX(p.x);
  36.                         int realIndex = columnModel.getColumn(index).getModelIndex();
  37.                         return "<HTML>Вы навели мышь<BR>на колонку номер: " + index + 
  38.                                " <BR>с настоящим номером: " + realIndex;
  39.                     }
  40.                 };
  41.             }
  42.         };
  43.         jt.setDefaultRenderer(Color.class, new TableCellRenderer() {
  44.             public Component getTableCellRendererComponent(
  45.                   JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
  46.                 Color c = (Color) value;
  47.                 String text = "RGB: red=" + c.getRed() + "; Green=" + c.getGreen() + "; Blue=" + c.getBlue();
  48.                 JLabel label_cl = new JLabel(text);
  49.                 label_cl.setBackground((Color) value);
  50.                 // примечание: возможность установить фоновый цвет для многих элементов управления сработает только тогда,
  51.                 // когда данный элемент не является прозрачным. А прозрачность изменяется с помощью:
  52.                 label_cl.setOpaque(true);
  53.                 label_cl.setToolTipText(text);
  54.                 // здесь устанавливается подсказка для ячейки
  55.                 return label_cl;
  56.             }
  57.         }
  58.         );
  59.  
  60.         jf.getContentPane().add(new JScrollPane(jt));
  61.         jf.pack();
  62.         jf.setVisible(true);
  63. }


Categories: Java