hiperlinks em JEditorPane em um JTable
-
05-07-2019 - |
Pergunta
Juro...espero que esta seja a última pergunta que tenho que fazer assim, mas estou prestes a enlouquecer.
Eu tenho um JTable usando um TableCellRenderer personalizado que usa um JEditorPane para exibir html nas células individuais do JTable.Como processo o clique nos links exibidos no JEditorPane?
Eu conheço o HyperlinkListener, mas nenhum evento de mouse passa do JTable para o EditorPane para que qualquer HyperlinkEvents seja processado.
Como processo hiperlinks em um JEditorPane dentro de uma JTable?
Solução
O EditorPane não está recebendo nenhum evento porque o componente retornado do TableCellRenderer só é permitido para exibição, e não eventos de interceptação, tornando-se praticamente o mesmo como uma imagem, sem comportamento permitido nele. Assim, mesmo quando os ouvintes são registrados, o componente retornado nunca está 'consciente' de todos os eventos. A solução alternativa para isso é para registrar um MouseListener na JTable, e interceptar todos os eventos relevantes de lá.
Eis algumas classes que criei no passado para permitir JButton roll-over para o trabalho em uma JTable, mas você deve ser capaz de reutilizar a maior parte deste para o seu problema também. Eu tinha um JButton separada para cada célula exigindo isso. Com isso, este ActiveJComponentTableMouseListener funciona em qual célula o evento do rato ocorre em, e despacha um evento para o componente correspondente. É o trabalho do ActiveJComponentTableCellRenderer de manter o controle dos componentes através de um mapa.
É o suficiente inteligente para saber quando ele já está demitido eventos, para que você não conseguir um acúmulo de eventos redundantes. A implementação desta para hipertexto não deve ser tão diferente, e você ainda pode querer roll-over também. Aqui estão as classes
public class ActiveJComponentTableMouseListener extends MouseAdapter implements MouseMotionListener {
private JTable table;
private JComponent oldComponent = null;
private TableCell oldTableCell = new TableCell();
public ActiveJComponentTableMouseListener(JTable table) {
this.table = table;
}
@Override
public void mouseMoved(MouseEvent e) {
TableCell cell = new TableCell(getRow(e), getColumn(e));
if (alreadyVisited(cell)) {
return;
}
save(cell);
if (oldComponent != null) {
dispatchEvent(createMouseEvent(e, MouseEvent.MOUSE_EXITED), oldComponent);
oldComponent = null;
}
JComponent component = getComponent(cell);
if (component == null) {
return;
}
dispatchEvent(createMouseEvent(e, MouseEvent.MOUSE_ENTERED), component);
saveComponent(component);
save(cell);
}
@Override
public void mouseExited(MouseEvent e) {
TableCell cell = new TableCell(getRow(e), getColumn(e));
if (alreadyVisited(cell)) {
return;
}
if (oldComponent != null) {
dispatchEvent(createMouseEvent(e, MouseEvent.MOUSE_EXITED), oldComponent);
oldComponent = null;
}
}
@Override
public void mouseEntered(MouseEvent e) {
forwardEventToComponent(e);
}
private void forwardEventToComponent(MouseEvent e) {
TableCell cell = new TableCell(getRow(e), getColumn(e));
save(cell);
JComponent component = getComponent(cell);
if (component == null) {
return;
}
dispatchEvent(e, component);
saveComponent(component);
}
private void dispatchEvent(MouseEvent componentEvent, JComponent component) {
MouseEvent convertedEvent = (MouseEvent) SwingUtilities.convertMouseEvent(table, componentEvent, component);
component.dispatchEvent(convertedEvent);
// This is necessary so that when a button is pressed and released
// it gets rendered properly. Otherwise, the button may still appear
// pressed down when it has been released.
table.repaint();
}
private JComponent getComponent(TableCell cell) {
if (rowOrColumnInvalid(cell)) {
return null;
}
TableCellRenderer renderer = table.getCellRenderer(cell.row, cell.column);
if (!(renderer instanceof ActiveJComponentTableCellRenderer)) {
return null;
}
ActiveJComponentTableCellRenderer activeComponentRenderer = (ActiveJComponentTableCellRenderer) renderer;
return activeComponentRenderer.getComponent(cell);
}
private int getColumn(MouseEvent e) {
TableColumnModel columnModel = table.getColumnModel();
int column = columnModel.getColumnIndexAtX(e.getX());
return column;
}
private int getRow(MouseEvent e) {
int row = e.getY() / table.getRowHeight();
return row;
}
private boolean rowInvalid(int row) {
return row >= table.getRowCount() || row < 0;
}
private boolean rowOrColumnInvalid(TableCell cell) {
return rowInvalid(cell.row) || columnInvalid(cell.column);
}
private boolean alreadyVisited(TableCell cell) {
return oldTableCell.equals(cell);
}
private boolean columnInvalid(int column) {
return column >= table.getColumnCount() || column < 0;
}
private MouseEvent createMouseEvent(MouseEvent e, int eventID) {
return new MouseEvent((Component) e.getSource(), eventID, e.getWhen(), e.getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), e.getButton());
}
private void save(TableCell cell) {
oldTableCell = cell;
}
private void saveComponent(JComponent component) {
oldComponent = component;
}}
public class TableCell {
public int row;
public int column;
public TableCell() {
}
public TableCell(int row, int column) {
this.row = row;
this.column = column;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TableCell other = (TableCell) obj;
if (this.row != other.row) {
return false;
}
if (this.column != other.column) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + this.row;
hash = 67 * hash + this.column;
return hash;
}}
public class ActiveJComponentTableCellRenderer<T extends JComponent> extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
private Map<TableCell, T> components;
private JComponentFactory<T> factory;
public ActiveJComponentTableCellRenderer() {
this.components = new HashMap<TableCell, T>();
}
public ActiveJComponentTableCellRenderer(JComponentFactory<T> factory) {
this();
this.factory = factory;
}
public T getComponent(TableCell key) {
T component = components.get(key);
if (component == null && factory != null) {
// lazy-load component
component = factory.build();
initialiseComponent(component);
components.put(key, component);
}
return component;
}
/**
* Override this method to provide custom component initialisation code
* @param component passed in component from getComponent(cell)
*/
protected void initialiseComponent(T component) {
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
return getComponent(new TableCell(row, column));
}
@Override
public Object getCellEditorValue() {
return null;
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
return getComponent(new TableCell(row, column));
}
public void setComponentFactory(JComponentFactory factory) {
this.factory = factory;
}}
public interface JComponentFactory<T extends JComponent> {
T build();
}
Para usá-lo, você quer registrar o ouvinte como mouse, movimento ouvinte sobre a mesa, e registrar o renderizador nas células apropriadas. Se você quiser interceptar actionPerformed eventos do tipo, ActiveJComponentTableCellRenderer.initialiseComponent override () assim:
protected void initialiseComponent(T component) {
component.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
stopCellEditing();
}
});
}
Outras dicas
Se você registrar uma MouseListener
na JTable, você poderia facilmente obter o texto no ponto de clique do mouse. Isto seria feito através da geração de um objecto a partir do Point
MouseEvent
, utilizando event.getX()
e event.getY()
. Você, então, passar esse Point
em JTable
e rowAtPoint(pt)
de columnAtPoint(pt)
. De lá, você pode obter o texto via JTable.getValueAt(row, column)
. Agora você tem o valor de seu celular, para que você possa determinar se é um link ou não e fazer o que você gostaria com o resultado.
Para resolver o mesmo problema, em vez de tentar fazer com que o JEditorPane produzisse o evento, processei o MouseEvent produzido pelo JTable, fiz com que o ouvinte descobrisse quando estávamos clicando em um link ou não.
Aqui está o código.Ele mantém um mapa de JEditorPanes, portanto, certifique-se de não ter vazamentos de memória e de limpar esse mapa adequadamente se os dados na tabela puderem mudar.Ele foi ligeiramente modificado em relação ao código que eu realmente usei - na versão que realmente usei, ele só produzia o JEditorPane quando os links estavam no html e não se preocupava com o JEditorPanes quando esses links não existiam ...
public class MessageWithPossibleHtmlLinksRenderer extends DefaultTableCellRenderer {
private final Map<Integer, JEditorPane> editorPanes = new HashMap<>();
public MessageWithPossibleHtmlLinksRenderer(JTable table) {
// register mouseAdapter to table for link-handling
table.addMouseMotionListener(this.mouseAdapter);
table.addMouseListener(this.mouseAdapter);
}
private JEditorPane getOrCreateEditorPane(int row, int col) {
final int key = combine(row, col);
JEditorPane jep = editorPanes.get(key);
if (jep == null) {
jep = new JEditorPane();
jep.putClientProperty(JEditorPane.HONOR_DISPLAY_PROPERTIES, Boolean.TRUE);
jep.setContentType("text/html");
jep.setEditable(false);
jep.setOpaque(true);
editorPanes.put(key, jep);
}
return jep;
}
private static int combine(int row, int col) {
return row * 10 + col; // works for up to 10 columns
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
// modify here if you want JEditorPane only when links exist
if (value instanceof String && ((String) value).startsWith("<html>")) {
final JEditorPane jep = getOrCreateEditorPane(row, column);
jep.setText((String) value);
// code to adjust row height
return jep;
} else {
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
}
private AttributeSet anchorAt(MouseEvent e) {
// figure out the JEditorPane we clicked on, or moved over, if any
final JTable table = (JTable) e.getSource();
final Point p = e.getPoint();
final int row = table.rowAtPoint(p);
final int col = table.columnAtPoint(p);
final int key = combine(row, col);
final JEditorPane pane = this.editorPanes.get(key);
if (pane == null) {
return null;
}
// figure out the exact link, if any
final Rectangle r = table.getCellRect(row, col, false);
final Point relativePoint = new Point((int) (p.getX() - r.x), (int) (p.getY() - r.y));
pane.setSize(r.getSize()); // size the component to the size of the cell
final int pos = pane.viewToModel(relativePoint);
if (pos >= 0) {
final Document doc = pane.getDocument();
if (doc instanceof HTMLDocument) {
final Element el = ((HTMLDocument) doc).getCharacterElement(pos);
return (AttributeSet) el.getAttributes().getAttribute(HTML.Tag.A);
}
}
return null;
}
private final MouseAdapter mouseAdapter = new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
final AttributeSet anchor = anchorAt(e);
final Cursor cursor = anchor == null
? Cursor.getDefaultCursor()
: Cursor.getPredefinedCursor(Cursor.HAND_CURSOR);
final JTable table = (JTable) e.getSource();
if (table.getCursor() != cursor) {
table.setCursor(cursor);
}
}
@Override
public void mouseClicked(MouseEvent e) {
if (! SwingUtilities.isLeftMouseButton(e)) {
return;
}
final AttributeSet anchor = anchorAt(e);
if (anchor != null) {
try {
String href = (String) anchor.getAttribute(HTML.Attribute.HREF);
Desktop.getDesktop().browse(new URL(href).toURI());
} catch (Exception ex) {
// ignore
}
}
}
};
}