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 只是一个”橡皮图章”),我们可以保证没有其他的监听器阻碍我们的代码。
倘若我们使用单元格渲染面板,我们需要覆盖掉它的paint方法来绘制真正的滑块。我们并没有绘制那些选项标签因为他是这个组件“真正的”子组件,注意滑块的绘制在一个单独的方法中完成,这样可以允许第三方的LAF只覆盖的这个滑块的绘制逻辑而不是改变整个绘制逻辑。
- @Override
- publicvoidpaint(Graphicsg,JComponentc){
- super.paint(g,c);
- this.paintSlider(g);
- }
- protectedvoidpaintSlider(Graphicsg){
- RectanglesliderBounds=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一文)。
显示了在不同look and feels下的滑标,从左到右,这些 LAF为:Windows (core), Metal (core), Motif (core), Liquid (third party), 和 Napkin (third party).如你所见,这个新组件提供了和当前所设置的LAF一致的外观。
现在要何去何从?阅读Swing核心组件的代码。下载并学习开源组件的源代码(像SwingX 或Flamingo),然后开始构造你自己梦想中组件吧!
【编辑推荐】