1

JButton中不透明像素的事件检测

使用在我上面的问题中找到的代码示例,我创建了几个带有不规则边缘的按钮,它们互锁,并且使用空布局来正确定位按钮。我遇到的问题是,尽管未在缓冲图像中的透明像素上检测到鼠标点击,但按钮仍采用矩形形状。这意味着稍后添加到面板的按钮会阻塞与其相邻的按钮部分。

我的问题是:有没有办法强制鼠标事件沿着 JButtons 的整个物理排列传播,直到它出现不透明像素,或者是否需要另一种解决方案?我看过涉及 Shape 的解决方案,但它们似乎非常昂贵,这就是为什么我想知道另一种方式。

我不太喜欢使用 JButtons,如果解决方案需要我离开它们,但我想找到一个便宜的解决方案,如果存在的话。

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;


public class JButtonExample {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                final JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                MyButton button1 = null, button2 = null;

                try {
                    button1 = new MyButton(ImageIO.read(new URL("https://dl.dropbox.com/s/dxbao8q0xeuzhgz/button1.png")));
                } catch (Exception ex) {
                    ex.printStackTrace();
                }

                button1.addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent me) {
                        super.mouseClicked(me);
                        MyButton mb = ((MyButton) me.getSource());
                        if (!isAlpha(mb.getIconImage(), me.getX(), me.getY()))
                            JOptionPane.showMessageDialog(frame, "You clicked button 1");
                    }

                    private boolean isAlpha(BufferedImage bufImg, int posX, int posY) {
                        int alpha = (bufImg.getRGB(posX, posY) >> 24) & 0xFF;
                        return alpha == 0 ? true : false;
                    }
                });

                button1.setBounds(10, 10, 72, 77);

                try {
                    button2 = new MyButton(ImageIO.read(new URL("https://dl.dropbox.com/s/v16kyha0ojx1gza/button2.png")));
                } catch (Exception ex) {
                    ex.printStackTrace();
                }

                button2.addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent me) {
                        super.mouseClicked(me);
                        MyButton mb = ((MyButton) me.getSource());
                        if (!isAlpha(mb.getIconImage(), me.getX(), me.getY()))
                            JOptionPane.showMessageDialog(frame, "You clicked button 2");
                    }

                    private boolean isAlpha(BufferedImage bufImg, int posX, int posY) {
                        int alpha = (bufImg.getRGB(posX, posY) >> 24) & 0xFF;
                        return alpha == 0 ? true : false;
                    }
                });

                button2.setBounds(65, 0, 122, 69);

                frame.getContentPane().setLayout(null);

                frame.add(button1);
                frame.add(button2);

                frame.setSize(210, 130);
                frame.setVisible(true);
            }
        });
    }
}

class MyButton extends JButton {

    BufferedImage icon;

    MyButton(BufferedImage bi) {
        this.icon = ((BufferedImage) bi);
        setContentAreaFilled(false);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(icon.getWidth(), icon.getHeight());
    }

    public BufferedImage getIconImage() {
        return icon;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(icon, 0, 0, null);
        g.dispose();
    }
}
4

1 回答 1

0

好的,我已经制定了一个解决方案,可以为我的用例以足够的准确性完成我想要它做的事情。虽然有些细节可能有点骇人听闻,但我想我现在已经做到了。要创建Area的代码改编自这里

为了在我的大型程序的其他区域简单易用,我将每个按钮保留为独立的JButton. 当在任何按钮上检测到鼠标点击时,它会计算出现在父面板上的点击位置,然后将该位置作为 a 传递给父面板Point。然后父级遍历按钮数组,直到找到包含该点的按钮,并触发适当的方法。如果单击不在任何按钮包含的区域内,则没有效果。如果原始鼠标点击没有发生在以 a 为边界的正方形区域上JButton,则不进行处理。

Area尽管与程序的其余部分相比,生成 an 所需的计算相对昂贵,但我可以通过Area在启动时创建所有对象来解决这个问题,因为程序会启动并且每个区域都不会被启动的情况非常罕见用过的。

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;


public class Example {

    private static int[][] pos = {{10, 10, 72, 77}, {65, 0, 122, 69}};

    public static MyButton[] buttons;

    private static URL[] src = new URL[2]; 

    private static MapPanel pane;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                final JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                try{
                    src[0] = new URL("https://dl.dropbox.com/s/dxbao8q0xeuzhgz/button1.png");
                    src[1] = new URL("https://dl.dropbox.com/s/v16kyha0ojx1gza/button2.png");
                } catch (Exception e){
                    e.printStackTrace();
                    System.exit(0);
                }

                pane = new MapPanel();
                pane.setLayout(null);

                buttons = new MyButton[2];

                for(int i = 0 ; i < buttons.length ; i++){
                    final int j = i;
                    try{
                        buttons[j] = new MyButton((ImageIO.read(src[j])), j, pos[j][0], pos [j][1]);
                    } catch (Exception e){
                        e.printStackTrace();
                        System.exit(0);
                    }
                    buttons[j].addMouseListener(new MouseAdapter(){
                        @Override
                        public void mouseClicked(MouseEvent me){
                            Point p = new Point(me.getX() + buttons[j].getX(), me.getY() + buttons[j].getY());
                            pane.check(p);
                        }
                    });
                    buttons[j].setBounds(pos[j][0], pos[j][1], pos[j][2], pos[j][3]);
                    pane.add(buttons[j]);
                }

                frame.setContentPane(pane);

                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setSize(210, 130);
                frame.setVisible(true);
            }
        });
    }
}

class MapPanel extends JLabel{

    public MapPanel(){
        super();
        this.setOpaque(true);
    }

    public void check(Point p){
    for(int i = 0 ; i < Example.buttons.length ; i++){
    if(Example.buttons[i].contains(p)){
            Example.buttons[i].clickDetected();
            break;
        }
        }
    }
}

class MyButton extends JButton {

    private BufferedImage icon;
    private int x, y, index;
    private Area area;

    MyButton(BufferedImage bi, int index, int x, int y) {
        this.icon = ((BufferedImage) bi);
        this.x = x;
        this.y = y;
        this.index = index;
        setContentAreaFilled(false);
        createArea();
    }

    @Override
    public Dimension getPreferredSize() {
        if(icon != null){
            return new Dimension(icon.getWidth(), icon.getHeight());
        } else {
            return super.getPreferredSize();
            }
    }

    public BufferedImage getIconImage() {
        return icon;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(icon, 0, 0, null);
        g.dispose();
    }

    private void createArea(){      
        GeneralPath gp = new GeneralPath();
        boolean cont = false;

        for(int xx = 0 ; xx < icon.getWidth() ; xx++){
            for(int yy = 0 ; yy < icon.getHeight() ; yy++){
                if(getAlpha(xx, yy) != 0){
                    if(cont){
                        gp.lineTo(xx, yy);
                        gp.lineTo(xx, yy+1);
                        gp.lineTo(xx+1, yy+1);
                        gp.lineTo(xx+1, yy);
                        gp.lineTo(xx, yy);
                    } else{
                        gp.moveTo(xx, yy);
                    }
                    cont = true;
                } else {
                    cont = false;
                }
            }
            cont = false;
        }

        gp.closePath();

        area = new Area(gp);        
    }

    @Override
    public boolean contains(Point p){
        if(area.contains(new Point((int)(p.getX() - this.x), (int) (p.getY() - this.y)))){
            return true;
        }
        return false;
    }

    private int getAlpha(int posx, int posy){
        return(icon.getRGB(posx, posy) >> 24) & 0x000000FF;
    }

    public void clickDetected(){
        JOptionPane.showMessageDialog(null, "You clicked button " + Integer.toString(this.index + 1) + ".");
    }
}

如果有人能指出此代码(或代码之外)的任何问题,我也将不胜感激。我假设GeneralPath创建区域的方式是通过在满足标准的每个像素(在本例中为非透明像素)之上创建一个 1px 正方形,然后从路径包含的区域创建一个区域对象。如果我错了,请纠正我。

于 2013-01-08T05:45:05.433 回答