Вопрос

Я клянусь...Надеюсь, это последний вопрос, который мне придется задать подобным образом, но я вот-вот сойду с ума.

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

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

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

Это было полезно?

Решение

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

Вот некоторые классы, которые я создал в прошлом для того, чтобы позволить перенос JButton работать в 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();
        }
    });
}

Другие советы

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

Чтобы решить ту же проблему, вместо того, чтобы пытаться заставить 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
            }
        }
    }
};
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top