hyperlinks in JEditorPane in a JTable

我们两清 提交于 2019-12-12 09:09:10

问题


I swear... i hope this is the last question I have to ask like this, but I'm about to go crazy.

I've got a JTable using a custom TableCellRenderer which uses a JEditorPane to display html in the individual cells of the JTable. How do I process clicking on the links displayed in the JEditorPane?

I know about HyperlinkListener but no mouse events get through the JTable to the EditorPane for any HyperlinkEvents to be processed.

How do I process Hyperlinks in a JEditorPane within a JTable?


回答1:


The EditorPane isn't receiving any events because the component returned from the TableCellRenderer is only allowed to display, and not intercept events, making it pretty much the same as an image, with no behaviour allowed on it. Hence even when listeners are registered, the returned component is never 'aware' of any events. The work-around for this is to register a MouseListener on the JTable, and intercept all relevant events from there.

Here's some classes I created in the past for allowing JButton roll-over to work in a JTable, but you should be able to re-use most of this for your problem too. I had a separate JButton for every cell requiring it. With that, this ActiveJComponentTableMouseListener works out in which cell the mouse event occurs in, and dispatches an event to the corresponding component. It's the job of the ActiveJComponentTableCellRenderer to keep track of the components via a Map.

It's smart enough to know when it's already fired events, so you don't get a backlog of redundant events. Implementing this for hypertext shouldn't be that different, and you may still want roll-over too. Here are the 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();
}

To use it, you want to register the listener to as mouse and motion listener on the table, and register the renderer on the appropriate cells. If you want to intercept actionPerformed type events, override ActiveJComponentTableCellRenderer.initialiseComponent() like so:

protected void initialiseComponent(T component) {
    component.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            stopCellEditing();
        }
    });
}



回答2:


If you register a MouseListener on the JTable, you could easily get the text at the mouse click point. This would be done by generating a Point object from the MouseEvent, using event.getX() and event.getY(). You then pass that Point into JTable's rowAtPoint(pt) and columnAtPoint(pt). From there, you can get the text via JTable.getValueAt(row, column). Now you have the value of your cell, so you can determine whether it is a link or not and do what you'd like with the result.




回答3:


To solve the same problem, instead of trying have the JEditorPane produce the event, I instead processed the MouseEvent produced by the JTable, had the listener figure out when we were clicking on a link or not.

Here's code. It keeps a map of JEditorPanes, so do make sure you don't have memory leaks, and that you clear this map appropriately if the data in the table can change. It's slightly modified from the code I actually used - in the version I actually used, it only only produced JEditorPane when actually links were in the html, and didn't bother with JEditorPanes when no such links existed...

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


来源:https://stackoverflow.com/questions/1203734/hyperlinks-in-jeditorpane-in-a-jtable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!