java - 在当前鼠标位置检测图像仅适用于部分图像

标签 java swing jtextpane

我有一个 JTextPane,我向其中添加文本,而一些文本具有通过 StyleConstants.setIcon() 设置的图像。我还向 JTextPane 添加了一个鼠标监听器,以检测鼠标何时被单击/悬停在图像上,但是它仅在图像的左侧部分检测到它。我做错了什么吗?

Screenshot of hovering over different parts of the image

SSCCE(将鼠标悬停在图像上会更改鼠标光标以指示它何时检测到图像):

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.Element;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;

/**
 * SSCCE to show how detecting an image under the current mouse position only
 * works on part of the image. It adds a simple image to the document of the
 * JTextPane and changes the mouse cursor when it detects the mouse hovering
 * over the image.
 */
public class JTextPaneImage {

    private static void createWindow() {

        // Create window
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Create JTextPane and add to window
        final JTextPane textPane = new JTextPane();
        textPane.setEditable(false);
        textPane.addMouseMotionListener(new MouseAdapter() {

            @Override
            public void mouseMoved(MouseEvent e) {
                AttributeSet style = getAttributes(e);
                if (style != null && StyleConstants.getIcon(style) != null) {
                    textPane.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
                } else {
                    textPane.setCursor(Cursor.getDefaultCursor());
                }
            }
        });
        frame.add(new JScrollPane(textPane));

        try {
            StyledDocument doc = (StyledDocument)textPane.getDocument();

            // Add some text
            doc.insertString(0, "Some text ", null);

            // Add the image
            SimpleAttributeSet style = new SimpleAttributeSet();
            StyleConstants.setIcon(style, createImage());
            doc.insertString(doc.getLength(), "test", style);
        } catch (BadLocationException ex) {
            Logger.getLogger(JTextPaneImage.class.getName()).log(Level.SEVERE, null, ex);
        }

        // Display everything
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    /**
     * Retrieves the style of where the mouse is positioned (assuming this is
     * a JTextPane).
     * 
     * @param e The mouse event containing the mouse position
     * @return The AttributeSet or null if none could be found
     */
    private static AttributeSet getAttributes(MouseEvent e) {
        JTextPane text = (JTextPane)e.getSource();
        Point mouseLocation = new Point(e.getX(), e.getY());
        int pos = text.viewToModel(mouseLocation);

        if (pos >= 0) {
            StyledDocument doc = text.getStyledDocument();
            Element element = doc.getCharacterElement(pos);
            return element.getAttributes();
        }
        return null;
    }

    /**
     * Creates a single 28x28 image filled with a single color.
     * 
     * @return The created ImageIcon
     */
    public static ImageIcon createImage() {
        BufferedImage image = new BufferedImage(28,28, BufferedImage.TYPE_INT_ARGB);
        Graphics g = image.getGraphics();
        g.setColor(Color.GREEN);
        g.fillRect(0, 0, 28, 28);
        g.dispose();
        return new ImageIcon(image);
    }

    public static final void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                createWindow();
            }
        });
    }
}

最佳答案

您使用强 text.viewToModel(mouseLocation) 来检测偏移量,然后从获得的偏移量中检索样式。

逻辑是返回更接近鼠标位置的偏移量。因此,当您单击 View 的右半部分时,将返回下一个偏移量( View 后的偏移量)。您可以尝试同样设置一个大字母(例如 m 大字体)。当您靠近字母时,结束插入符将设置在字母之后。这里的逻辑是一样的。

因此您在图像之后获得位置并从该位置获得样式,但在 ImageView 文本元素之后属性中没有图标并且您没有图像。

更新:

为了提供正确的行为,我建议使用 modelToView() 并传递获得的偏移量。从矩形中,您可以确定您点击的 X 位置是否 < 矩形的 X。如果插入矩形的 x 大于鼠标 X,您可以尝试以前的偏移。

更新 2:您可以覆盖 IconView 并使用 paint() 方法来存储 ImageView 的最后绘制的矩形。存储在 map 中最后绘制的矩形。在鼠标移动/单击时检查 map 以查找其中一个矩形是否包含该点。

您可以使用 View 的方法 getChildAllocation() 描述了类似的东西 here计算图像边界。从 Root View 开始,向下直到计算 X、Y 的正确 View 的叶子。如果叶 subview 是 IconView,那么您就完成了图像。

关于java - 在当前鼠标位置检测图像仅适用于部分图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24036650/

相关文章:

Java 添加全局 JTextPane 样式/属性?

java - 用 CSV 数据填充 JTable 的简单方法

java - 如何从剪贴板复制/粘贴样式文本?

java - JTextPane 换行符

java - 将图像和文本添加到 JTextPane

java - 表单参数未传递但处理了相同表单中的文件?

java - Spring boot Hibernate 一对多关系

java - 在 Java swing 中获取组合框值

Textmate 中的 Java 代码格式化

java - 删除 ArrayList 中第一次和最后一次出现之间的重复元素以优化路径