Как стать автором
Обновить

JTable и Serializable или таблицы в Java и танцы с бубном при сохранении объектов в файлы

Время на прочтение 8 мин
Количество просмотров 28K

Введение


Так получилось, что как дизайнеру, мне необходим простор для творчества при реализации любых зачач в написании программ. Давно я положил глаз на такую платформу как Java, так-как всегда мечтал о кроссплатформенном программном обеспечении. И вот недавно, я решил освоить такой прекрассный компонент в Java, как JTable, ну и по той причине, что всегда любил использовать таблицы в своих программах.

В общем, я поставил перед собой не сложную задачу — создать таблицу, которую мог бы сохранять в файл как объект и паралельно отслеживать введенные пользователем данные подсвечивая ошибки и упрощая общение с таблицей моей программы путем подсвечивания наиболее важных элементов таблицы. Так-как я сторонник программирования по принципу пошаговой отладки при написании кода, наличие готовых кусков стабильного кода в сети Интернет, было для меня очень важным… Но… После тщательных поисков, экспериментально было установлено, что, в сети Интернет есть всего пару нормальных источников для получения более или мение хорошего кода.

Нет-нет, я вас не буду отправлять сюда (Популярная цитата пользователей javatalks.ru и др. — «перед тем как задать тупой вопрос, посмотрите здесь...»).

и сюда. (How to Use Tables — tutorial)

или сюда (Using Swing Components: Examples).

или в стандартный хелп по таблицам

Хотя, я так говорю не с проста, так-как посмотрев там и поняв, что доки Java немного устарели, как по восприятию, так и по наличию вразумительного рабочего кода (я использовал Java версии 7.01). Но все же, не почитав этих справочных материалов, вы никогда не поймете самой сути, логики и психологии таблиц Java.

По этой причине, ниже описанный стабильный пример сериализации таблицы JTable, будет у вас работать так, как вы этого хотите, лишь при условии понимания того, что вы делаете и что хотите получить. На последок добавлю — некоторые куски кода были собраны в Интернет на разных ресурсах (за что им всем огромное спасибо), но их всех объединяет одно — я сам их отлаживал и притирал к своей программе, то есть идеально работающего кода я так и не нашел. Хотя… нет. Я построил свой код, только потому, что взял с одного из англоязычных ресурсов рабочий код программы сериализации, который у меня вылетал при попытке изменения данных в таблице (ссылка на ресурс к сожалению потерялась)…
Но «о драконах» кода по порядку.

О как же ты красноречив, дракон великий Error Log


Ну, теперь самое время сказать мне: «Ты че устраиваешь танцы с бубном? Или не читал этого? Гугли получше и не забивай нам всем мозги бирюльками для дизайна… Мы и без него можем обойтись.» И правда, почитав этот перевод книги «Java 2. Том 2. Тонкости программирования. Автора — Кей Хорстман и Гарри Корнелл» (у меня 8-е печатное издание), я обнаружил отличный код:

//запись в файл
FileOutputStream fos = new FileOutputStream("temp.out");

		ObjectOutputStream oos = new ObjectOutputStream(fos);
		SerialTest st = new SerialTest();
		oos.writeObject(st);
		oos.flush();
		oos.close();
	
//чтение из файла
FileInputStream fis = new FileInputStream("temp.out");

	  ObjectInputStream oin = new ObjectInputStream(fis);
	  TestSerial ts = (TestSerial) oin.readObject();
	  System.out.println("version="+ts.version);


который не при каких обстоятельствах не хотел работать с таблицами, куда бы я не ставил магическую мантру «implements Serializable».

-Да ты не реализуй отдельный класс, а тули сразу в JTable при создании своего TableCellRenderer динамически и в нем делай все, что тебе нужно! — говорили одни…

-Неее… Тебе нужен отдельный класс, ведь Java — это не «хухры-мухры» и модели MVC никто не отменял. — говорили другие.

В общем, все эти споры и предложения реализации классов и методов, которые не работали или работали частично, меня очень расстроили и я даже хотел плюнуть на все и сделать таблицу с дефолтным «серым» графическим интерфейсом… Как вдруг, на одном из зарубежных форумов, обнаружил код, который по уверениям автора работал на все сто процентов и подходит под любые капризы дизайнера. Внимательно рассмотрев даный код я нашел метод, который создавал шаблон таблицы, если файл отсутсвовал на диске и заполнял ним таблицу. После проверки кода (очень близкий к указанному выше), я с удивлением обнаружил, что он работает! «О небо!» — подумал я — «Неужели мои молитвы услышаны и я могу отдать бубен обратно своей малышке дочери?!»… Но стоило мне внести в таблицу данные и нажать кнопку сохранить… как бубен, мне опять стал нужен…

Теперь, это стало делом чести! Я не мог позволить каким-то мантрам, влиять на отсутствие или наличие бубна у моей малышки дочери и на нервные срывы у соседей из-за постоянного звучания бубна… Короче, после короткой битвы с выделением в таблице:

//это работает, но не всегда
table.clearSelection();


я решил обратиться к шаманам всего мира и к самим создателям… И я пошел на баг-трекер Оракла, где с удивлением заметил, что ошибочка то вот она.

И длится бой уж много лет, с версии Java «1.4.0-beta2»

Тут я увидел, что особо просветленные шаманы, вроде Kleopatra, там давненько уже отрубают крылья дракона Error Log простым вызовом:

//это я сам вставил, пусть данные сохраняться перед фиксацией таблицы
//иначе значение сбросится до примитивного "", что приводит к потере данных во всей строке
table.editingStopped(null);

//а вот, что предложили разработчики Java, по ссылке выше

/*Цитирую:
*
*  The first issue in the description deals with the improper behavior
*  of losing edits on focus exit the first time but for not subsequent
*  attempts. The problem was correctly diagnosed by Kleopatra in bug 
*  report 4518907. java.swing.JTable indeed does need to set 'editorRemover'
*  to null after calls to  
*  removePropertyChangeListener("focusOwner",  editorRemover); 
*  in both removeNotify() and in removeEditor().
*
*/

//и хоть в английском я не силен, я сделал как они рекомендуют
table.removeEditor();
//это можно не писать и без него работает отлично
table.removeNotify();


Ну вот и всё, дракон уж вроде побежден…
Ан нет, он в классы с TableCellRenderer был перемещен…

Уроки рисования, или «Нет-нет, мне нужно подсветить строку с ошибкой красненьким»


После не сложных плясок у нового костра разожженного очередными ошибками визуализации таблицы, я обнаружил, что будет уместным реализовать два класса отрисовки таблицы, один для ячеек и второй для названий (ярлыков) колонок:

//Класс отрисовки таблицы
class JCTableCellRenderer extends JLabel implements TableCellRenderer, Serializable {}

//класс отрисовки ярлыков колонок
class JColumnRenderer extends JLabel implements TableCellRenderer, Serializable {}


Теперь это все нужно объявить:

//Грузим наш внешний класс рендера таблицы JCTableCellRenderer
table.setDefaultRenderer(Object.class, new JCTableCellRenderer());


В начале метода loadColumn() пишем:

//Используем внешний класс JColumnRenderer для прорисовки TableColumn
//используется отдельно от JCTableCellRenderer, так-как это ускоряет вывод и существенно упрощает код
TableCellRenderer renderer = new JColumnRenderer();


Все сразу стало на свои места, все данные отлично читались и отрисовывались.
Теперь я начал искать в Интернете самое нужное мне, отрисовку ячеек, строк и колонок.
Как ни странно, но все ссылки с обещанием именно того, что нужно мне, вели сюда.

информация очень ценная, но по дизайну скудная и про TableCellRenderer, говорилось мало. Я начал сам эксперементировать с разными способами окраски таблицы и что я узнал в итоге?

//метод внутри класса
public Component getTableCellRendererComponent(	JTable table, Object value, 
boolean isSelected, boolean hasFocus, int row, int column) {...}

//для меня как дизайнера означал следующее:

//@param: table - глобально применяет окрашивание в указанным строкам, ячейкам, столбцам
//@param: value - применяет окрашивание к указанному значению в ячейке
//@param: isSelected - это понятно и без объяснений (применяет окрашивание к выделению)
//@param: hasFocus - применяет окрашивание к выделенной ячейке
//@param: row - применяет окрашивание в указанной по индексу строке
//@param: column - применяет окрашивание в указанной по индексу колонке


Примеры окрашивания в таблице JTable:

//раз уж мы наследовались от JLabel, то в начале метода ставим, что-то типа
JLabel c = new JLabel(value.toString());
//мы будем тыкать лейблы везде, чтобы отобразить все данные таблицы сразу - value.toString()

//если в любой ячейке есть текст "jpg", красим её красивым цветом
if(value.equals("jpg")){c.setOpaque(true);c.setBackground(new Color(152, 251, 152));}

//первая колонка у нас имеет свой индивидуальный окрас
if(column==0){c.setOpaque(true);c.setBackground(new Color(255, 248, 220));}

//эта строка у нас будет покрашена в розовый, если в седьмой колонке есть строка со значением "iff"
if(table.getValueAt(row, 7).equals("iff")){ c.setOpaque(true);
		c.setBackground(new Color(255, 105, 180));
		c.setForeground(Color.white);}

//проверим два целых числа колонок: четыре и пять, и если условие ложно красим строку красным цветом
//и... предупреждаем пользователя об ошибке в выбранной колонке нашей таблицы и пишем в модель таблицы

//кстати, не конвертируйте значений так: (String)table.getModel().getValueAt(row, 4)
//это вызовет ошибку компиляции

int a = Integer.parseInt(""+table.getModel().getValueAt(row, 4));
int b = Integer.parseInt(""+table.getModel().getValueAt(row, 5));
if(a > b)
{
	c.setOpaque(true);c.setBackground(Color.red);c.setForeground(Color.yellow);
	if(column==8)
	{
		c.setText("Error this value: colomn - \"Start\", row - " + (row+1));
		table.getModel().setValueAt(c.getText(), row, column);
	}
}
else
{
	//а это я проверил, вдруг файл на диске не найден, это тоже нужно показать пользователю
	//и восстановить значение в восьмой колонке, если пользователь исправил ошибку
	//хотя, указанным способом, можно просто самим исправить значение на нужное сразу после проверки
	//значений колонок четыре и пять на истинность
	
	if(column==8)
	{
		java.io.File f = new java.io.File((String) table.getModel().getValueAt(row, 9));
			
		if(f.exists())
		{
			table.getModel().setValueAt("<This file is exist>", row, column);
		}
		else
		{
			table.getModel().setValueAt("<This file is not exist!!!>", row, column);
			c.setOpaque(true);c.setBackground(Color.red);c.setForeground(Color.yellow);
		}
	}
}

//этот шикарный код взят с http://skipy-ru.livejournal.com/1577.html
//его назначение - в зависимости от темы приложения менять стиль таблицы

// using L&F colors
if(isSelected)
{
	c.setOpaque(true);

	c.setForeground(isSelected ?
	    UIManager.getColor("Table.selectionForeground") :
	    UIManager.getColor("Table.foreground"));
	c.setBackground(isSelected ?
	    UIManager.getColor("Table.selectionBackground") :
	    UIManager.getColor("Table.background"));
	c.setBorder(hasFocus ?
	    BorderFactory.createLineBorder(UIManager.getColor("Table.selectionForeground"), 1) :
	    BorderFactory.createEmptyBorder(2, 2, 2, 2));

//а здесь, я проверил на указанные значения ячейки и если они истинны
//мы изменяем цвет выделенных ячейки на нужный

	if(value.equals("jpg")){c.setOpaque(true);c.setBackground(new Color(0, 128, 128));}
	if(value.equals("png")){c.setOpaque(true);c.setBackground(new Color(250, 250, 210));}
	if(value.equals("iff")){c.setOpaque(true);c.setBackground(new Color(72, 61, 139));c.setForeground(Color.white);}
	if(column==0){c.setOpaque(true);c.setBackground(new Color(210, 105, 30));}
}


Ну вот, вроде бы и все.

Теперь все работает — сохраняется в файл и раскрашивается в нужный цвет.
Для реализации своего замысла я использовал среду разработки Eclipse v. 3.7

image

Ну и… Если вы дочитали до этого места, то спасибо вам за потраченное время.
Если вы хотите задать вопрос типа: «А как мне это сделать с базой данных?» или «Почему, реализуя циклы типа for при проверке значений в классе JCTableCellRenderer у меня ужасно висит таблица», то пожалуй добавлю, что на базах данных выше указанного кода я не применял (но почему-то уверен, что он работает стабильно), и еще — циклы реализованные в классах рисования, это такое же извращение как и таймеры на таймлайне клипов во Flash. Рекомендую всегда оптимизировать свой код по принципу — «чем проще, тем быстрее».

Нужные внешние классы и код самой программы прилагаются (открывать в Eclipse).

Удачи всем.
Теги:
Хабы:
+8
Комментарии 21
Комментарии Комментарии 21

Публикации

Истории

Работа

Java разработчик
344 вакансии

Ближайшие события

PG Bootcamp 2024
Дата 16 апреля
Время 09:30 – 21:00
Место
Минск Онлайн
EvaConf 2024
Дата 16 апреля
Время 11:00 – 16:00
Место
Москва Онлайн
Weekend Offer в AliExpress
Дата 20 – 21 апреля
Время 10:00 – 20:00
Место
Онлайн