理解JComboBox原理——鼠标点击ComboBox之外的区域,下拉列表如何自动隐藏

    我的开发环境是Eclipse 3.3.1,JDK 1.4.2_19

    最近都在做Swing相关的开发,对于swing组件底层的运转机制不甚理解,一直心有余悸。这两天在开发一个弹出框,类似JComboBox的下拉框,想要鼠标点击弹出框之外的区域时,弹出框会自动消失。

JComboBox 1     上图是偶做的一个弹出框,这其实是个JDialog,去掉修饰[详见方法setUndecorated(...)],在用户点击“新增人员”的时候,弹出一个JDialog在与JButton相对合适的坐标处,代码如下:

        dlg.setCompanyList(companyList);
        Point p = btnNewMember.getLocation();
        SwingUtilities.convertPointToScreen(p, FilterListFrame.this.paneMain);
        dlg.setLocation((int) p.getX(), (int) p.getY() + btnNewMember.getHeight());
        dlg.show();

 

变量说明:
    dlg        JDialog的子类创建的窗口对象,也就是图片中的“选择人员所在机构”弹出框
    btnNewMember        JButton对象,图中的“新增人员”按钮

关键逻辑:
    因为按钮btnNewMember是被添加到面板paneMain上,所以btnNewMember.getLocation()得到的是btnNewMember在面板paneMain中的坐标,使用SwingUtilities转换这个坐标为btnNewMember在屏幕中的绝对坐标,而后把可爱的弹出框锁定在按钮的正下方。


    而后鼠标点击弹出框之外的区域,如何隐藏弹出框呢?这个时候瞅见了JComboBox,如果能够懂得ComboBox的运转机制应该就能解决这个问题。
    就这样从愚人节的早上一直翻到愚人节晚上的12点左右,白天坐在办公桌前,晚上躺在窗口,怎么都看不明白。

    白天的时候查到Sun官方论坛有个帖子“Swing - About popup hide”,链接是 http://forums.sun.com/thread.jspa?threadID=5342058
    帖子里有个帅哥叫做“camickr ”,一言点醒梦中人,意思是——JComboBox的逻辑都被写入到UI类,你可以从源码中寻找线索,不过会被复杂的逻辑弄得很困扰……

    晚上的时候,我就纳闷了,不知道第几次翻开这些代码——
    1.JComboBox  浏览代码的关于鼠标监听器的部分,没有找到任何线索,想想camickr大哥(也说不定是个大叔~~⊙﹏⊙b汗)说的,逻辑都写进UI类了
    2.ComboBoxUI 抽象类,没什么可看的,进入实现类
    3.BasicComboBoxUI 在这里,找到属性popup,发现它的类型是ComboPopup,一直只知道有JPopupMenu,没想到还有个ComboPopup,难道是给ComboBox专用的?看来有玄机
    4.ComboPopup  晕,是个接口,实现类中我比较感兴趣的是 BasicComboPopup 和 MetalComboPopup
    5.BasicComboPopup, MetalComboPopup    在这两个类之间徘徊了很久,没有看出什么神秘的地方(就因为没发现,才让我晚上又转点了~~)

    反反复复地看着几个类,几个钟头下来终于没耐心了,因为我的开发工具是Eclipse,打开Debug模式,我在类JComboBox的方法 showPopup(), hidePopup(), setPopupVisible() 上设置断点,并且还在我怀疑的几个UI类上的处理鼠标点击事件的方法上加上断点,发现意料之外情理之中的事情了,鼠标点击JComboBox的时候,这些方法有的有反应,但是鼠标点击ComboBox之外的区域时,没有一个方法被断点停住脚步的。
    这说明什么呢?ComboBox以及相关的辅助类中的鼠标监听器是不会处理ComboBox外的鼠标事件的。

    其实用swing这么久,这个道理也是懂的,事件监听器不加到Component或JComponent上是不可能在这些组件上捕获到鼠标事件的。也就是说,ComboBox的上级面板paneMain(上文中提到的JPanel对象 )必然被加上了鼠标监听器。(^_^ 忘记一个很重要的事情,图片中右边那个弱不禁风的ComboBox就是我本文中分析ComboBox运转机制的对象)

    确定了这个正确的思考方向,回到BasicComboBoxUI和BasicComboPopup之中(这就是宿命~~因为运气好,转点之后,脑袋发晕就只盯着这两个类了,要不然通宵可能都搞不定),在所有的鼠标操作方法上设置断点,因为我觉得既然要给面板加上鼠标事件监听器,必然得通过ComboBox本身的事件来辅助完成这个操作(其实这是现在总结的,当时完全是没方向的乱试),5分钟不到就推翻了我的想法,没有经过这里。

    目光游走落到了类 BasicComboPopup 中叫 MouseGrabber 的静态内部类上,既然和鼠标点击有关,那么字样里有Mouse的自然特别吸引眼球,怪哉怪哉,代码里只调用了一次MouseGrabber的构造器,但是没有调用它的任何方法(奸笑一下,感觉 BasicComboPopup 的作者也跟我一样是熬通宵脑袋晕了吧~~),反正代码基本都过了一遍,抱着再看一次也损失不了的心态,打开 MouseGrabber 的代码,发现里面别有洞天。

    BasicComboPopup 中的静态内部类 MouseGrabber 就是鼠标点击 ComboBox 之外区域隐藏弹出框的关键,它通过辅助类 MenuSelectionManager 的帮助,获取弹出菜单之外的父级面板组件,而后在这些组件上加上鼠标、键盘等相关的各种监听器(⊙﹏⊙b汗,加这么多……竟然也不会慢,太神奇了……),这些监听器会监控用户操作,一旦发现鼠标点击了 ComboBox 之外的区域,或者鼠标滚轮发生滚动等事件,就会调用可爱的方法 cancelPopupMenu() 让弹出框人间蒸发……

    好家伙,这个 MouseGrabber 让我找的苦死了~~作者也藏得太深了~~
    不过了解了 ComboBox 的这个玄机,其他的问题就不难解决了,我的弹出框借鉴这个思路,也可以继续完善了~~ :)

 

 

2010年3月19日21:55:21

此文后记:

    后来一位热心的网友与我交流了许多,他寻到一篇网文让我参考,推荐的技巧是使用JPopupMenu来实现自定义下拉框的特性,在JPopupMenu中的面板里增加我们需要的各种组件,而后模拟JComboBox弹出框的操作方式。

    希望对后来者能有些借鉴。

你可能感兴趣的:(eclipse,UI,swing,浏览器,sun)