如何编写自定义Swing组件(下)

UI Delegate
如果说模型接口是最编写自定义组件最重要的部分,那么UI delegate则是最复杂的部分。主要问题在于:如何编写绘制逻辑使得他在所有的look and feels上都一致?有时,除非你编写每个look and feels所对应的UI delegate(像 SwingX project 就是通过这种方式),否则是无法实现的。但是,在某些情况下你会发现你可以通过组装现有的swing组件来达到这种仿真的效果,在后面的部分,UI delegates代码将要注意那些平台相关的设置比如说颜色,字体和抗锯齿。


在"  Enhancing Swing Applications"一文中,作者描述了通过的继承定义好的look and feels的简单UI delegate实现的样板代码。以install*和unstall*方法开始,如果你不打算使用他们,第三方的look and feel 可能会在基本的功能基础上增加一些额外的功能(举个例子来说,在slider上增加鼠标滚轮滚动功能)


在我们这个例子中,我们可以看到这个自定义组件包含一个slider和一组labels(包括图标和文本),由于所有的JComponent都是容器类,我们可以轻松地通过在我们的installComponents方法中增加JSlider和JLabel(每个label是一个选项control point)组件来达到这种仿真的虚拟界面效果(别忘了在uninstallComponents方法中移除他们)。通过重用Swing核心组件的方式,我们的自定义组件能够使其在swing核心LAF和第三方LAF下都保持一致。


当我们增加附属组件时,为了在创建和缩放时定位这些附属组件,我们需要实现自定义的LayoutManager。这是一个非常简单的工作(甚至有点乏味):这些range在右侧纵向并排排列,相邻的range则根据其自身的weight值拥有相应空间的垂直区域,滑标则占有全部垂直空间,第一个和最后一个选项(control points)作为slider的起点和终点。


注意这个特殊的实现非常的困难并且几乎不可能使用单一的UI delegate来完成,举例来说,一些LAFs使用了native Api 来绘制各自的控制(像滑标的滑道与滑块)。一些第三方的LAF可能不遵循UIManager中的设置而是提供自己的颜色和自定义的Api


现在回到我们的实现(使用JSlider),我们现在面临一个有趣的问题:滑标可以通过设置snapToTicks的值来决定是否自动对齐到最近的滑块刻度。这个行为控制通过在BasicSliderUI delegate中安装mouse montion监听器实现。我们要怎么做?其中一个选择是移除这个监听器并且安装上我们所提供的实现。另一个方法是提供自定义的BoundedRangeModel实现,当设置非关联的range时修改它的值(value)。第一种方法并非最优-你无法信赖其他LAF下特殊的SliderUI delegate实现逻辑,有的实现甚至不会去调用父类的方法。第二种方法稍微好一点,不过我们选择另一种方法实现,原因稍后说明。


我们的实现对这些附属组件使用类似树/表的单元格渲染模式(Cell renderer),slider只是用来渲染并且不获取任何事件(参考CellRendererPane)。这能使我们从LAF绘图和鼠标自定义事件中获益。在我们这个特殊的例子中,如果用户在slider滑块外点击鼠标。我们通过直接设置相应的range的值(value)来代替原本的向鼠标点击方向滚动一个”块”,这就是我们为什么不使用前面提到的第二种方法的原因:我们自定义的鼠标监听器转换鼠标点击转换相应的range(相邻的或非关联的)并且设置他们的值,一旦这个唯一的监听器被安装到组件上(指这个CellRendererPane,而这个slider 只是一个”橡皮图章”),我们可以保证没有其他的监听器阻碍我们的代码。
最终的输出结果如图2.蓝色边框表示选项(control point)标签的绑定框(bounds)。红色边框表示单元格渲染器面板


 
图2:组件布局


倘若我们使用单元格渲染面板,我们需要覆盖掉它的paint方法来绘制真正的滑块。我们并没有绘制那些选项标签因为他是这个组件“真正的”子组件,注意滑块的绘制在一个单独的方法中完成,这样可以允许第三方的LAF只覆盖的这个滑块的绘制逻辑而不是改变整个绘制逻辑。

    @Override 
    public void paint(Graphics g, JComponent c) { 
        super.paint(g, c); 
        this.paintSlider(g); 
    } 

    protected void paintSlider(Graphics g) { 
        Rectangle sliderBounds = sliderRendererPane.getBounds(); 
        this.sliderRendererPane.paintComponent(g, this.slider, 
                this.flexiSlider, sliderBounds.x, sliderBounds.y, 
                sliderBounds.width, sliderBounds.height, true); 
    } 

测试应用程序
现在我们拥有了一个完整的自定义滑标组件,是时候该测试它了。这个测试应用程序创建了一个滑标,它包含一些相邻的和非关联的range,并为其注册了改变监听器(change listener)。一旦发生改变事件,我们计算图标的尺寸比例并绘制它(该图标使用Tango Desktop Project的SVG-to-Java2D converte来转换,具体参考  Transcoding SVG to Pure Java2D code一文).图3显示了这个应用程序在不同的图标大小下的运行效果。



 
图3,选择不同值的运行效果


图4显示了在不同look and feels下的滑标,从左到右,这些 LAF为:Windows (core), Metal (core), Motif (core), Liquid (third party), 和 Napkin (third party).如你所见,这个新组件提供了和当前所设置的LAF一致的外观。


 
图4。在不同look and feel 下的自定义滑标
结尾

现在要何去何从?阅读Swing核心组件的代码。下载并学习开源组件的源代码(像SwingX 或Flamingo),然后开始构造你自己梦想中组件吧!

 

 资源

Kirill Grouchnikov has been writing software since he was in junior high school, and after finishing his BSc in computer science, he happily continues doing it for a living. His main fields of interest are desktop

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值