JTable 中 JEditorPane 中的超链接

发布于 2024-07-30 11:22:15 字数 284 浏览 2 评论 0原文

我发誓...我希望这是我必须问的最后一个问题,但我快要疯了。

我有一个使用自定义 TableCellRenderer 的 JTable,它使用 JEditorPane 在 JTable 的各个单元格中显示 html。 如何处理对 JEditorPane 中显示的链接的单击?

我了解 HyperlinkListener,但没有鼠标事件通过 JTable 到达 EditorPane 来处理任何 HyperlinkEvent。

如何处理 JTable 中 JEditorPane 中的超链接?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

︶葆Ⅱㄣ 2024-08-06 11:22:16

如果您在 JTable 上注册一个 MouseListener,您可以轻松获取鼠标单击点处的文本。 这可以通过使用 event.getX()event.getY()< 从 MouseEvent 生成 Point 对象来完成/代码>。 然后,将该Point 传递到JTablerowAtPoint(pt)columnAtPoint(pt)。 从那里,您可以通过 JTable.getValueAt(row, column) 获取文本。 现在您已经有了单元格的值,因此您可以确定它是否是链接,并对结果执行您想要的操作。

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.

蓝眼泪 2024-08-06 11:22:16

为了解决同样的问题,我没有尝试让 JEditorPane 生成事件,而是处理 JTable 生成的 MouseEvent,让侦听器弄清楚我们何时单击了链接。

这是代码。 它保留 JEditorPanes 的映射,因此请确保没有内存泄漏,并且如果表中的数据可能发生更改,请适当地清除此映射。 它对我实际使用的代码进行了稍微修改 - 在我实际使用的版本中,它仅在实际链接位于 html 中时才生成 JEditorPane,并且当不存在此类链接时不会打扰 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
            }
        }
    }
};
}

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
            }
        }
    }
};
}
八巷 2024-08-06 11:22:15

EditorPane 没有接收任何事件,因为从 TableCellRenderer 返回的组件只允许显示,而不允许拦截事件,这使得它与图像几乎相同,不允许有任何行为。 因此,即使注册了侦听器,返回的组件也永远不会“意识到”任何事件。 解决方法是在 JTable 上注册一个 MouseListener,并从那里拦截所有相关事件。

下面是我过去创建的一些类,用于允许 JButton 翻转在 JTable 中工作,但是您也应该能够重复使用其中的大部分来解决您的问题。 我为每个需要它的单元格提供了一个单独的 JButton。 这样,这个 ActiveJComponentTableMouseListener 就会计算出鼠标事件发生在哪个单元格中,并将事件分派到相应的组件。 ActiveJComponentTableCellRenderer 的工作是通过 Map 跟踪组件。

它足够智能,可以知道何时已触发事件,因此您不会积压冗余事件。 为超文本实现这一点应该没有什么不同,并且您可能仍然需要翻转。 以下是类

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

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();
        }
    });
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文