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

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

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

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

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

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

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

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

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

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

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

3 ответа

Решение

Это реализация шаблона flyweight.

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

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

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

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

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

После некоторых копаний нашел следующую заметку о реализации из документации DefaultTableCellRenderer:

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

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

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

Другие вопросы по тегам