гиперссылки в JEditorPane в JTable

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

У меня есть JTable, использующий пользовательский TableCellRenderer, который использует JEditorPane для отображения html в отдельных ячейках JTable. Как обработать нажатие на ссылки, отображаемые в JEditorPane?

Я знаю о HyperlinkListener, но никакие события мыши не проходят через JTable в EditorPane для обработки любых HyperlinkEvents.

Как обрабатывать гиперссылки в JEditorPane в JTable?


person Victor    schedule 29.07.2009    source источник
comment
Вы создали MouseListener и прикрепили его к JTable?   -  person OMG Ponies    schedule 30.07.2009


Ответы (3)


EditorPane не получает никаких событий, потому что компоненту, возвращенному из TableCellRenderer, разрешено только отображать, а не перехватывать события, что делает его почти таким же, как изображение, без разрешенного поведения. Следовательно, даже когда слушатели зарегистрированы, возвращаемый компонент никогда не «осведомлен» о каких-либо событиях. Обходной путь для этого заключается в регистрации MouseListener в JTable и перехвате оттуда всех соответствующих событий.

Вот некоторые классы, которые я создал в прошлом, чтобы позволить JButton roll-over работать в JTable, но вы также сможете повторно использовать большую часть этого для своей проблемы. У меня был отдельный JButton для каждой требующей этого ячейки. При этом этот ActiveJComponentTableMouseListener определяет, в какой ячейке происходит событие мыши, и отправляет событие соответствующему компоненту. Задача ActiveJComponentTableCellRenderer — отслеживать компоненты через карту.

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

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();
}

Чтобы использовать его, вы хотите зарегистрировать прослушиватель в качестве прослушивателя мыши и движения на столе и зарегистрировать средство визуализации в соответствующих ячейках. Если вы хотите перехватывать события типа actionPerformed, переопределите ActiveJComponentTableCellRenderer.initialiseComponent() следующим образом:

protected void initialiseComponent(T component) {
    component.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            stopCellEditing();
        }
    });
}
person shredderts    schedule 30.07.2009
comment
Вау, спасибо! похоже, это может сработать. однако в моем случае высота строк различается в зависимости от содержимого ячеек. есть ли в getRow(MouseEvent e) причина, по которой вы не используете table.rowAtPoint(e.getPoint())? - person Victor; 30.07.2009
comment
Я думаю, что rowAtPoint(...) может работать и позволяет избежать предположения о фиксированной высоте строк. Я думаю, что сделал это предположение непреднамеренно, когда просто пытался заставить его работать (я также потратил целую вечность, пытаясь понять, как заставить ячейки JTable передавать события базовым компонентам, и к концу немного разочаровался!). Дайте мне знать, если вы заставите его работать с помощью rowAtPoint. - person shredderts; 31.07.2009
comment
действительно, этот метод передает события мыши... но он по-прежнему не активирует события обновления гиперссылки. - person Victor; 31.07.2009
comment
Это связано с тем, что ActiveJComponentTableMouseListener не перехватывает щелчки мыши, оставляя это JTableCellEditor. Убедитесь, что вы зарегистрировали ActiveJComponentTableCellRenderer как средство визуализации и редактор, и он должен передавать вам события mouseClick. Другая проблема заключается в том, что вам нужно реализовать initialiseComponent в подклассе средства визуализации (см. выше) и заставить JTable прекратить редактирование. Без этого он не передает все события кликов. Удачи! - person shredderts; 01.08.2009

Если вы зарегистрируете MouseListener в JTable, вы можете легко получить текст в точке щелчка мыши. Это можно сделать, создав объект Point из MouseEvent, используя event.getX() и event.getY(). Затем вы передаете это Point в rowAtPoint(pt) и columnAtPoint(pt) JTable. Оттуда вы можете получить текст через JTable.getValueAt(row, column). Теперь у вас есть значение вашей ячейки, так что вы можете определить, является ли это ссылкой или нет, и делать с результатом то, что вы хотите.

person akf    schedule 30.07.2009
comment
это не сработает, так как будут ссылки, смешанные с текстом... и, возможно, несколько ссылок в одной ячейке. - person Victor; 30.07.2009

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

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

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
            }
        }
    }
};
}
person user3668188    schedule 30.07.2016