Трудности с пониманием механизма рендеринга JTable и JTree в Swing.

Часто при использовании JTable или JTree пользователь определяет свой собственный модуль визуализации ячеек.

Очень часто пользовательский компонент наследуется от DefaultTableCellRenderer и реализует метод рендеринга getTableCellRendererComponent. Оказывается, DefaultTableCellRenderer на самом деле наследуется от JLabel, поэтому возвращает себя (это) при вызове super (в методе рендеринга), и, таким образом, пользовательский рендерер может аналогичным образом возвращать себя (это).

И все это работает хорошо.

Мой вопрос, как это может быть?

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

Я искал в Интернете и копался в коде Swing и не смог найти ни одного действия клонирования или копирующего конструктора, которое фактически дублирует выходную метку. Я не смог найти никаких доказательств того, что (возможно) свинг использует отражение, чтобы каждый раз заново создавать экземпляр рендерера с нуля.

Я прочитал руководство Swing по JTables и там я мог найти следующие строки:

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

Они намекают, что действительно то, что я говорю, верно, но не объясняют, как это на самом деле делается.

Я не могу понять. Может ли кто-нибудь из вас?


person aviad cohen    schedule 02.12.2012    source источник


Ответы (3)


Это реализация шаблона легковеса.

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

Для каждой ячейки он вызывает средство визуализации с аргументами, соответствующими ячейке. Средство визуализации возвращает компонент. Этот компонент закрашивается в прямоугольник, соответствующий текущей ячейке таблицы.

Затем вызывается рендерер для следующей ячейки, и возвращенный компонент (у которого, например, другой текст и цвет) закрашивается в прямоугольник, соответствующий ячейке, и т.д.

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

person JB Nizet    schedule 02.12.2012
comment
+1 Метафора со скриншотом хорошая. Я думал, что наиболее часто используемым является «штамп», но скриншот может быть более четким. - person Robin; 03.12.2012
comment
Спасибо. метафора, конечно, делает свое дело :). теперь это понятно. - person aviad cohen; 03.12.2012

В дополнение к четкому объяснению @JB того, как JTable и JTree используют шаблон легковеса, обратите внимание, что оба класса предоставляют общедоступные методы getCellRenderer() и getCellEditor(). Изучите эти методы, чтобы увидеть, как JTable использует литералы классов в качестве среды выполнения. Введите Tokens, чтобы выбрать средство визуализации или редактор по классу, если ни один из столбцов не указан. Внутренне JTable использует Hashtable defaultRenderersByColumnClass например хранилище.

person trashgod    schedule 02.12.2012

Покопавшись, нашел следующее примечание по реализации из DefaultTableCellRenderer. документация:

Примечание по реализации. Этот класс наследуется от JLabel, стандартного класса компонентов. Однако JTable использует уникальный механизм для рендеринга своих ячеек и, следовательно, требует немного измененного поведения от своего средства рендеринга ячеек. Класс таблицы определяет средство визуализации одной ячейки и использует его в качестве штампа для визуализации всех ячеек в таблице; он отображает первую ячейку, изменяет содержимое средства визуализации этой ячейки, сдвигает начало координат в новое место, перерисовывает ее и т. д. Стандартный компонент JLabel не предназначен для использования таким образом, и мы хотим избежать запуска повторной проверки каждый раз, когда ячейка рисуется. Это значительно снизит производительность, поскольку сообщение повторной проверки будет передано вверх по иерархии контейнера, чтобы определить, будут ли затронуты какие-либо другие компоненты. Поскольку рендерер создается только на время жизни операции рисования, мы также хотим избежать накладных расходов, связанных с прохождением иерархии для операций рисования. Таким образом, этот класс переопределяет методы проверки, недействительности, повторной проверки, перерисовки и firePropertyChange, чтобы они были неоперативными, и переопределяет метод isOpaque исключительно для повышения производительности. Если вы пишете свой собственный модуль визуализации, помните об этом соображении производительности.

По сути, это то, что JB объяснил выше.

Спасибо за (быстрые) ответы

person aviad cohen    schedule 02.12.2012
comment
Подобные оптимизации находятся в CellRendererPane, используются делегатом TableUI и проиллюстрированы здесь. - person trashgod; 03.12.2012