Трудности в понимании механизма рендеринга свинга 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 объяснил выше.
Спасибо за (быстрые) ответы