JTable — использование TableModel

JTable использование TableModel Существует такая парадигма в программирование Модель-Вид-Контроллер. Если коротко, то она позволяет разделить весь код на несколько больших фукциональных блоков, код которых отделен максимально друг от друга. Основной принцип — разделяй и властвую. Java Swing в реализации своих компонентов также пытается следовать данной парадигме. В результате чего компоненты, такие как JTable, внутри себя разделяют код, который занимается только отрисовкой и код, который занимается поставкой данных для отрисовки. Для JTable данные поставляются моделью TableModel. TableModel — это интерфейс, который должна заимплементить наша собственная реализация модели для JTable. Давайте посмотрим, для чего может понадобиться создание своей модели для JTable, а далее создадим свою собственную TableModel для JTable.

В примерах ранее при отображении данных в JTable в качестве источника данных использовался массив. Однако, такой способ подходит не всегда. Что если у нас, к примеру, имеется база данных. Данные из базы получаются в виде набора неких сущностей со своими полями. Требуется в JTable отобразить список таких сущностей. Тогда целесообразно реализовать свою модель TableModel и «настроить» её на наши сущности.

Задача следующая. У нас имеется список сущностей MyBean.

public class MyBean {

		private String name;
		private String size;
		private String description;

		public MyBean(String name, String size, String description) {
			this.setName(name);
			this.setSize(size);
			this.setDescription(description);
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getName() {
			return name;
		}

		public void setSize(String size) {
			this.size = size;
		}

		public String getSize() {
			return size;
		}

		public void setDescription(String description) {
			this.description = description;
		}

		public String getDescription() {
			return description;
		}
	}
}

Необходимо отобразить данный список в JTable при помощи TableModel.

Посмотрим, какие методы нам необходимо реализовать у интерфейса TableModel. Первые два метода addTableModelListener и removeTableModelListener добавляют и удаляют слушателей модели. Для чего они нужны. Они нужны для того, чтобы JTable был в курсе всех модификаций модели. К прмеру, добавилась в модель новая сущность или удалилась. Что в этом случае делает модель. Модель TableModel генерирует специальное событие, в котором содержится необходимая информация о том, что собственно произошло с моделью. Далее модель пробегается по всем своим слушателям и оповещает их о произошедшем событии. Сейчас пока нет необходимости углубляться в данный механизм. Достаточно знать, что слушатели есть, их можно добавить и удалить. Самим вызывать данные методы не придется.

private Set<TableModelListener> listeners = new HashSet<TableModelListener>();

public void addTableModelListener(TableModelListener listener) {
	listeners.add(listener);
}

public void removeTableModelListener(TableModelListener listener) {
	listeners.remove(listener);
}

Идем дальше. Метод getColumnClass. Сущности могут быть разные. Поля у них естественно могут быть разными, однако, JTable должен знать, какие данные он должен отобразить в какой колонке. В MyBean все поля у нас строковые, поэтому в нашем случае метод таков.

public Class<?> getColumnClass(int columnIndex) {
	return String.class;
}

Метод getColumnCount возвращает количество столбцов которое будет отобрахаться в таблице. В MyBean у нас 3 поля, значит и возвращать у нас метод будет число 3.

public int getColumnCount() {
	return 3;
}

Метод getColumnName возвращает заголовок колонки по её индексу. У нас три поля, три колонки. Внутри метода проверяем индекс и возвращаем соответствующее имя колонки.

public String getColumnName(int columnIndex) {
	switch (columnIndex) {
		case 0:
			return "Имя";
		case 1:
			return "Размер";
		case 2:
			return "Описание";
		}
	return "";
}

Метод getRowCount возвращает количество строк, которое будет отображаться в таблице. Здесь beans это список. Чтобы JTable знал количество строк, которое нужно показать достаточно получить из beans размер.

public int getRowCount() {
	return beans.size();
}

Метод getValueAt отвечает за то, какие данные в каких ячейках JTable будут показываться. Методу в качестве параметров передаетяс индекс строки и столбца ячейки JTable. Алгоритм работы здесь простой. По индексу строки мы из списка beans получаем соответствующую сущность, а по индексу колонки узнаем данные из какого поля MyBean необходимо показать.

public Object getValueAt(int rowIndex, int columnIndex) {
	MyBean bean = beans.get(rowIndex);

	switch (columnIndex) {
		case 0:
			return bean.getName();
		case 1:
			return bean.getSize();
		case 2:
			return bean.getDescription();
	}

	return "";
}

Метод isCellEditable проверяет, редактируема ли ячейка JTable индекс строки и столбца которой передаются мтоду в качестве параметра. Мы предполагаем, что пользователь может только просматривать данные JTable, но не редактировать их. Тогда метод всегда должен возвращать false.

public boolean isCellEditable(int rowIndex, int columnIndex) {
	return false;
}

С вышеописанным методом перекликается метод setValueAt. Так как JTable мы сделали нередактируемым, то тело данного метода можно оставить пустым.

Ну а теперь давайте посмотрим на весь код тестового примера и попробуем его запустить, чтобы посмотреть на получившийся результат, а также увидеть воочию взаимодействие JTable и TableModel.

import java.awt.Dimension;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

public class TestFrame extends JFrame {

	static int i = 0;

	public TestFrame() {

		super("Тестовое окно");
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		ArrayList<MyBean> beans = new ArrayList<MyBean>();

		for (int i = 0; i < 30; i++) {
			beans.add(new MyBean("Имя " + i, "Размер " + i, "Описание " + i));
		}

		TableModel model = new MyTableModel(beans);
		JTable table = new JTable(model);

		getContentPane().add(new JScrollPane(table));

		setPreferredSize(new Dimension(260, 220));
		pack();
		setLocationRelativeTo(null);
		setVisible(true);
	}

	public static void main(String[] args) {
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				JFrame.setDefaultLookAndFeelDecorated(true);
				new TestFrame();
			}
		});
	}

	public class MyTableModel implements TableModel {

		private Set<TableModelListener> listeners = new HashSet<TableModelListener>();

		private List<MyBean> beans;

		public MyTableModel(List<MyBean> beans) {
			this.beans = beans;
		}

		public void addTableModelListener(TableModelListener listener) {
			listeners.add(listener);
		}

		public Class<?> getColumnClass(int columnIndex) {
			return String.class;
		}

		public int getColumnCount() {
			return 3;
		}

		public String getColumnName(int columnIndex) {
			switch (columnIndex) {
			case 0:
				return "Имя";
			case 1:
				return "Размер";
			case 2:
				return "Описание";
			}
			return "";
		}

		public int getRowCount() {
			return beans.size();
		}

		public Object getValueAt(int rowIndex, int columnIndex) {
			MyBean bean = beans.get(rowIndex);
			switch (columnIndex) {
			case 0:
				return bean.getName();
			case 1:
				return bean.getSize();
			case 2:
				return bean.getDescription();
			}
			return "";
		}

		public boolean isCellEditable(int rowIndex, int columnIndex) {
			return false;
		}

		public void removeTableModelListener(TableModelListener listener) {
			listeners.remove(listener);
		}

		public void setValueAt(Object value, int rowIndex, int columnIndex) {

		}

	}

	public class MyBean {

		private String name;
		private String size;
		private String description;

		public MyBean(String name, String size, String description) {
			this.setName(name);
			this.setSize(size);
			this.setDescription(description);
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getName() {
			return name;
		}

		public void setSize(String size) {
			this.size = size;
		}

		public String getSize() {
			return size;
		}

		public void setDescription(String description) {
			this.description = description;
		}

		public String getDescription() {
			return description;
		}
	}
}
About these ads

25 Responses to JTable — использование TableModel

  1. Gorets:

    кризис жанра закончился? приятно видеть новую статью. хотелось бы почитать про «полосу загрузки», в общем там все просто и понятно, но меня интересует, как, например, определить время, шаг, длину полосы

    • bondforever:

      Кризиса не было, был недостаток свободного времени :) А по полосе загрузки — определить время задачка инетерсная, надо подумать.

    • bondforever:

      Мне кажется, что однозначного и универсального какого-то способа определения времени выполнения нет. Время выполнения зависит от самой задачи. К примеру, если это копирование файлов, то даже здесь трудно предсказать время, можно только заранее определить количество копируемых файлов и в процессе выполнения (по мере того, как один за другим будут копироваться файлы) опеределять, какой процент работы выполнился и сколько еще осталось.

  2. Приветствую!
    Как всегда, замечательный пример — у Вас, сказать по правде, прекрасная способность доходчиво и ясно объяснять некоторые сложные вещи. Если бы кто-то хотел построить дополнительный Java Swing Tutorial — Вас бы без проблем взяли на работу и положили за эту работу хороший оклад. :)
    А теперь пара слов в дополнение. Только что в своем учебном примере реализовал работу с таблицей — но только не интерфейс TableModel, как у Вас, а модель класса DefaultTableModel.
    Из объяснения в Java Tutorial я понял, что выбор — что лучше использовать, TableModel (или AbstractTableModel) или DefaultTableModel — зависит от ситуации.

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

    Вашим вариантом тоже можно воспользоваться, но для этого надо создавать дополнительный класс и все такое. С другой стороны, такой производный класс предполагает больше возможностей, и чем сложнее задача, тем скорее выбор падет на TableModel.

    К такому выводу я пришел на данный момент…

    • bondforever:

      Привет! Даже и не знаю, что сказать про мои способности — со стороны виднее наверное :)
      А по поводу использования TableModel и DefaultTableModel. Собственно выбор, как было сказано выше зависит от ситуации, мне добавлять нечего :)

  3. xx:

    спасибо за инфу.
    очень удивился как сложно работать с таблицами в Java!
    блин.. мне бы просто выборку SQL при помощи JTable отрисовать, а тут код на пол страницы……. УЖАС!

  4. Arthur:

    Простите xx а Вы видели варианты попроще с кросс-платформенными таблицами кроме JAVA, тем более если учеть тот факт что для того чтобы тащить данные из таблиц в Java всего лишь надо 3-5 строк кода — Java cross-platform Rules the world.

    • Vovka:

      Да, например в С# есть у таблицы такое поле как source и если ему присвоить например dataset из библиотеки sqlserver-а то таблица абсолютно всю работу делает сам.

    • key:

      По крайней мере создавать(!) таблицы в Qt проще, чем в этом примере.

      • Дмитрий:

        Qt — возможно легче в плане таблиц. Но Java чем хороша- никто не запрещает сделать свой вариант любого компонента. Который затем очень просто использовать.

  5. Hitman:

    Пример разобран идеально!!! Но у меня возник вопрос: вот вы пишите про два метода «addTableModelListener» и «removeTableModelListener», а как реализовать такую же штуку, только не при добавлении\удалении сущности, а при изменении сущности(ну например параметра названия ее)?

  6. Привет, спасибо за интересный пример. Единственное, чего не хватает — это пример того как DefaultTableModel работает с TableModelListener.
    Я вот сейчас пишу програмку, всё ломаю голову.

    Отследить-то я могу изменения в таблице. А вот как обновлённую таблицу записать. Вот в этом проблема. При выводе на файл учитывается только последнее изменение. Это, если без SetValueAt().
    Но а как использовать-то?) Можно примерчик? Типа «юзер изменил таблицу — значения сохранились в модели»…

    Был бы очень признателен, это многим интересно, я думаю.

  7. kotakota:

    спасибо за статью

    и согласен с последним коментом

    было бы круто показать редактирование таблицы

  8. ComeRun:

    Здравствуйте!
    Начал читать и обрадовался — наконец-то кто-то дельно опишет как в общем виде реализовать связь между базой данных и моделью. Вы, как говорят, «начали за здравие, а закончили за упокой»…
    В конечном счете у Вас получилось как у всех: «сущьность» MyBean оказалась со строго «вшитыми» полями…
    А запросы-то могут быть с разными полями. Так что, для каждого запроса — своя «сущьность»?
    Тогда у меня в проекте должно было бы быть 10000 «сущьностей»???
    Но Вы старались, и за это Вам спасибо.

    • Сущность есть класс, реализующий интерфейс чьи методы могут возвращать любые нужные вам значения. Хочешь, три поля, с соотв. именами полей, хочешь, два. Тут надо просто анализировать метаданные в случае выборки из БД.

      А вот как работать в режиме записи в таблицу, вот тут бы я с удовольствием почитал :)
      АВТОРУ БОЛЬШОЕ СПАСИБО!!!

      • Например у себя переделал два метода:

        public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;
        }
        public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        MyBean b = beans.get(rowIndex);
        switch(columnIndex){
        case 0:
        b.setName((String)aValue);
        return;
        case 1:
        b.setSize((String)aValue);
        case 2:
        b.setDescription((String)aValue);
        }
        }

        Тут уже можно либо новый метод написать, типа ApplyCorrects() который и будет записывать новые данные в таблицу. А новые данные обозначить в новый список beens2 (с флагом модифицирования, или добавления).

  9. Ivan:

    В NetBeans jTable — можно визуально создать не с «моделью» а на основе просто «статических данных», понимаю, что «это не то», но тем не менее….

    А на счет «… мне бы табличку из SQL» — :)))

    Друзья! jTable — это интерфейсный контрол, а не «эксель-в-яве»

    Корень трудностей при «… выводе табличек из SQL» — в самом SQL и его релятивной природе, по-определению чуждой Объектной природе JAVA, и без танцев с ORM их не «подружить», то есть можно в том же нетбинсе в «пару кликов» создать по запросу к базе — класс-сущность и вывести его в jTable, но пока эти клики делаешь — перестаешь понимать, зачем вообще тут JAVA?

    Вы просто разрабатываете в ООП ваше решение и где нужно — юзаете вывод данных в том числе через jTable, всеми прелестями ООП и современных IDE, а когда дело доходит до долговременного хранения — просто сохраняете ваши объекты в ОБЪЕКТНОЙ БД :) (на пример db4o как я :))

    А вот если все — наоборот, вы отталкиваетесь от схемы БД, да еще релятивной (SQL) то тут — да, всё что в области задач — перелопачиваем под абстракцию БД и успешно перестаем понимать relf ltkbcm все мои объекты, классы и интерфейсы и вся остальная мощь и гибкость ООП ?

    — а просто растворилась во всех этих полях БД, ID и «связях-один-ко многим»

    но к сожалению очень многие продолжают воспринимать SQL как непреложное и первичное, но в Java — первично ООП! А костыли для коннекта с SQL — это только костыли!

    «Java на SQL» — это то же самое, что Орел с пропеллером вместо крыльев, или «гепард на колесиках»…

    Но я вообще-то не программист, просто люблю потрепаться на малознакомые мне темы :)

  10. Валерия:

    Здравствуйте!
    Спасибо автору за статью, подробно и доступно написано.
    Я совершенный новичок в Java, не мог бы кто-нибудь мне разъяснить один вопрос?
    Я никак не могу понять, как данные попадают в таблицу и нигде не нашла объяснение «на пальцах», мне именно оно нужно :)
    Вот написано «Метод getValueAt отвечает за то, какие данные в каких ячейках JTable будут показываться.» Хорошо. Вижу реализацию данного метода. Но он ведь нигде не вызывается! Как же значения оказываются в таблице? Как это работает?
    Очень нужна помощь, голову сломала :)
    Спасибо!

  11. Wamark:

    ================================================
    = Сделал более обобщённый класс на основе приведённого кода
    = может кому пригодится.
    = Автору статьи респект) swing использует достаточно сложную модель
    ================================================

    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Set;
    import javax.swing.event.TableModelListener;
    import javax.swing.table.TableModel;

    //класс который просто будет содержать таблицу строковых значений
    // просто чтобы работать с этой jTable!!!
    public class MyTableModel implements TableModel {

    //для учёта скрытых полей
    private int hd_visibleColCount = 0;
    private List hd_VirtualReferences;

    private Set listeners = new HashSet();

    private List<List> beans;
    public List columns;

    public MyTableModel(List<List> beans, List columns) {
    this.beans = beans;
    this.columns = columns;

    //заполнение данных для виртуальных(выводимых) колонок
    hd_VirtualReferences = new ArrayList();
    for(int i=0;i<columns.size();i++)
    {
    MyTableColumn curCol = columns.get(i);
    if (curCol.visible)
    {
    hd_visibleColCount++;
    hd_VirtualReferences.add(i);
    }
    }
    }

    public void addTableModelListener(TableModelListener listener) {
    listeners.add(listener);
    }

    public Class getColumnClass(int columnIndex) {
    return columns.get(hd_VirtualReferences.get(columnIndex)).myClass;
    }

    public int getColumnCount() {
    return hd_visibleColCount;
    }

    public String getColumnName(int columnIndex) {
    return columns.get(hd_VirtualReferences.get(columnIndex)).name;
    }

    public int getRowCount() {
    return beans.size();
    }

    public Object getValueAt(int rowIndex, int columnIndex) {
    return beans.get(rowIndex).get(hd_VirtualReferences.get(columnIndex));
    }

    public boolean isCellEditable(int rowIndex, int columnIndex) {
    return false;
    }

    public void removeTableModelListener(TableModelListener listener) {
    listeners.remove(listener);
    }

    public void setValueAt(Object value, int rowIndex, int columnIndex) {

    }

    /**
    * @return the beans
    */
    public List<List> getBeans() {
    return beans;
    }

    }

    public class MyTableColumn {
    public String name;
    public boolean visible;
    public Class myClass;

    public MyTableColumn(String name, boolean visible, Class myClass) {
    this.name = name;
    this.visible = visible;
    this.myClass = myClass;
    }

    public MyTableColumn(String name, boolean visible) {
    this.name = name;
    this.visible = visible;
    this.myClass = String.class;
    }
    }

    ========== ИСПОЛЬЗОВАНИЕ ==================================
    //инициализация колонок
    List<List> myBeans = new ArrayList();
    List myColumns = new ArrayList();
    myColumns.add(new MyTableColumn(«id», false, Long.class));
    myColumns.add(new MyTableColumn(«Номер группы», true));
    myColumns.add(new MyTableColumn(«Факультет», true));
    MyTableModel tm_Groups = new MyTableModel(myBeans, myColumns);
    //добавление строки
    List<List> myBeans = tm_Groups.getBeans();
    myBeans.add(Arrays.asList((Object)1, «гр1″, «Автоматики и чего-то там»));

  12. Дмитрий:

    Нормально! Как раз сдаю Swing на http://knowledgeblackbelt.com
    Осталось только JTable доизучать. А вообще стоит ли сейчас Swing глубоко изучать? Есть перспектива?

  13. Большое спасибо, и скрестить этот пример с Hibernate проще — простого!

  14. Lex:

    Большое спасибо за статью. Нужно было для курсового вывод на экран сделать, но нигде не мог найти объяснение «на пальцах». Все получилось и главное — усвоилось.

  15. ssi:

    Скажите, как к такой таблице добавить TableModelListener? У меня не получилось, — не работает…

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

Отслеживать

Get every new post delivered to your Inbox.

Join 65 other followers

%d такие блоггеры, как: