如何在Java Swing中构建点击组件?

4
我建立了一个自定义组件,只显示一条线。该线是在paint方法中作为Line2D从左上角到右下角绘制的。背景是透明的。我扩展了JComponent。这些线组件可以拖动,并且当鼠标指针距离绘制的线段最多15个像素时,它们会改变其线颜色。 但是,如果我将多个这些组件添加到另一个扩展JPanel的自定义组件中,它们有时会重叠。我想实现的是,如果鼠标指针距离线条超过15个像素,则鼠标事件应穿过组件。如何让它穿过是我的问题。 这是否可能?
提前感谢!
5个回答

3

我希望实现的功能是,如果鼠标指针距离线条超过15像素,则鼠标事件应该穿透组件。

如果您的子组件有鼠标监听器,则它将拦截发生在其上的每个鼠标事件。如果您想将 MouseEvent 转发到父组件,则需要手动执行此操作。例如,您可以通过扩展 MouseAdapter 实现自定义鼠标监听器:

public class yourMouseListener extends MouseAdapter{

    //this will be called when mouse is pressed on the component
    public void mousePressed(MouseEvent me) { 
         if (/*do your controls to decide if you want to propagate the event*/){
              Component child = me.getComponent();
              Component parent = child.getParent();

              //transform the mouse coordinate to be relative to the parent component:
              int deltax = child.getX() + me.getX();
              int deltay = child.getY() + me.getY();

              //build new mouse event:
              MouseEvent parentMouseEvent =new MouseEvent(parent, MouseEvent.MOUSE_PRESSED, me.getWhen(), me.getModifiers(),deltax, deltay, me.getClickCount(), false) 
              //dispatch it to the parent component
              parent.dispatchEvent( parentMouseEvent);
         }
    }
}

这对于组件的父级非常有效... 但是我需要事件传递到下一个组件,即放置在所单击组件下方的组件,而不是所有组件都添加到的父级。 - tk--
@Kalle:你的组件重叠了吗? - Heisenbug
好的。你可以像我的示例一样将鼠标事件转发给父级,然后在父级鼠标处理程序内决定将事件传递给哪个子元素。 - Heisenbug

2
在我大学的毕业项目中,我做了一个白板程序,也遇到了同样的问题。对于用户在白板上绘制的每个形状,我创建了一个JComponent,在绘制矩形时这种方式很好用,但是使用自由形线条工具时就比较困难。
最后我解决的方法是完全放弃JComponents。我有一个JPanel,其中包含一组自定义Shape对象(我认为是Vector)。每个对象都保存其自己的坐标和线条粗细等信息。当用户在白板上单击时,JPanel上的鼠标监听器会触发,并遍历每个Shape,调用每个Shape的contains(int x, int y)方法(x和y是事件的坐标)。因为Shape是在绘制时添加到Vector中的,所以我知道返回true的最后一个Shape是最上面的Shape。
这是我用于直线contains方法的代码。数学可能有些问题,但对我来说它起作用了。
public boolean contains(int x, int y) {

    // Check if line is a point
    if(posX == endX && posY == endY){
        if(Math.abs(posY - y) <= lineThickness / 2 && Math.abs(posX - x) <= lineThickness / 2)
            return true;
        else
            return false;
    }

    int x1, x2, y1, y2;

    if(posX < endX){
        x1 = posX;
        y1 = posY;
        x2 = endX;
        y2 = endY;
    }
    else{
        x1 = endX;
        y1 = endY;
        x2 = posX;
        y2 = posY;
    }


    /**** USING MATRIX TRANSFORMATIONS ****/

    double r_numerator = (x-x1)*(x2-x1) + (y-y1)*(y2-y1);
    double r_denomenator = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1);
    double r = r_numerator / r_denomenator;

    // s is the position of the perpendicular projection of the point along
    // the line: s < 0 = point is left of the line; s > 0 = point is right of
    // the line; s = 0 = the point is along the line
    double s =  ((y1-y)*(x2-x1)-(x1-x)*(y2-y1) ) / r_denomenator;

    double distance = Math.abs(s)*Math.sqrt(r_denomenator);

    // Point is along the length of the line
    if ( (r >= 0) && (r <= 1) )
    {
            if(Math.abs(distance) <= lineThickness / 2){
                return true;
            }
            else
                return false;
    }
    // else point is at one end of the line
    else{
        double dist1 = (x-x1)*(x-x1) + (y-y1)*(y-y1); // distance to start of line
        double dist2 = (x-x2)*(x-x2) + (y-y2)*(y-y2); // distance to end of line
        if (dist1 < dist2){
            distance = Math.sqrt(dist1);
        }
        else{
            distance = Math.sqrt(dist2);
        }
        if(distance <= lineThickness / 2){
            return true;
        }
        else
            return false;
    }
    /**** END USING MATRIX TRANSFORMATIONS****/

}

posX和posY组成了直线起始点的坐标,endX和endY是直线的终点坐标。如果点击位置在离直线中心线厚度的一半之内,它将会返回真值;否则您必须单击直线正中间。

绘制形状只需要将JPanel的Graphics对象传递给每一个形状,然后使用该对象进行绘制即可。


哦,如果可以的话,请使用每个形状一个组件的方法,这样更节省资源。 - MK.

2

我已经有一段时间没有涉及Swing了,但是我认为您需要在父组件中处理鼠标事件,然后使用lines循环遍历子组件并确定哪个组件应该处理该事件(当然,决定的逻辑仍然应该保留在line组件中,但父组件将显式调用该逻辑,直到其中一个组件接收到事件)。


将监听器添加到父级而不是尝试将事件传递给父级,这样做会得到加分。 - camickr
如果一个子组件可以配置为拦截事件而不是转发它们,那么为每个子组件添加一个鼠标监听器可能是一个不错的选择,而不是在父组件内部复杂化逻辑。 - Heisenbug
@camickr:也许你是对的。但是如果子组件可以是不同类型的,那么为了添加一个新类型,你还需要修改父组件代码。 - Heisenbug
@MK:或者,不必让所有子类实现ClickHandler接口,它们只需实现鼠标监听器接口即可,这几乎是相同的事情。 - Heisenbug
@0verbose除非它不起作用,因为Swing不会将这些事件传递给顶部组件中的任何一个。不管怎样,最有可能的是他不需要这些形状成为组件。 - MK.
显示剩余2条评论

1

我认为最简单的方法是捕获事件并调用parent.processEvent()。这样,您的组件将对事件透明,因为它会将它们传播到父级。


问题是,我的父级是画布,所有组件都添加到其中...而不是位于所点击的组件“下方”的组件... - tk--
因此,请使用parent.getComponentAt(x, y)。x,y从鼠标事件获取,并相对于父级重新计算它们。 - AlexR
@AlexR,getComponentAt(x,y) 只会返回最上层的子组件。 - MK.

0

我曾经为这种问题苦苦挣扎,尝试了所有与父级和glasspane有关的东西,直到我意识到重写contains方法正是你想要的。因为当父级触发某种getcomponent时,你的"Line"会回复它:"不,不是我,我不在那里!",然后循环将检查其他组件。 此外,当您需要为可拖动对象设置复杂的深度时,您可以使用JLayeredPane的后代。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接