合成器外观

创建自定义外观或修改现有外观可能是一项艰巨的任务。 javax.swing.plaf.synth包可用于轻松创建自定义外观。您可以通过编程或使用外部 XML 文件来创建 Synth 外观。下面的讨论致力于使用外部 XML 文件创建 Synth 外观。 API 文档中讨论了以编程方式创建 Synth c 的过程。

利用 Synth 的外观,您可以提供“外观”。 Synth 本身提供了“感觉”。因此,您可以将 Synth L&F 视为“皮肤”。

Synth 体系结构

回想一下上一主题,每个 L&F 都有责任为 Swing 定义的许多ComponentUI子类中的每一个提供具体的实现。 Synth L&F 会为您解决这个问题。要使用 Synth,您无需创建任何ComponentUI,而仅需要指定每个组件的绘制方式以及影响布局和大小的各种属性。

Synth 的运行比组件的运行更为细化-此细化水平称为“区域”。每个组件都有一个或多个区域。许多组件只有一个区域,例如JButton。其他人则有多个区域,例如JScrollBar。 Synth 提供的每个ComponentUIsSynthStyleComponentUI定义的每个区域相关联。例如,Synth 为JScrollBar定义了三个区域:轨道,拇指和滚动条本身。 Synth 的ScrollBarUI(为JScrollBar定义的ComponentUI子类)实现将SynthStyle与这些区域中的每个区域相关联。

合成器构建图。

SynthStyle提供 Synth ComponentUI实现使用的样式信息。例如,SynthStyle定义前景色和背景色,字体信息等。此外,每个SynthStyle都有一个SynthPainter用于绘制区域。例如,SynthPainter定义了paintScrollBarThumbBackgroundpaintScrollBarThumbBorder两种方法,用于绘制滚动条的拇指区域。

Synth 中的每个ComponentUIs使用SynthStyleFactory获得SynthStyles。有两种定义SynthStyleFactory的方法:通过 Synth XML 文件或以编程方式。以下代码显示了如何加载指示 Synth 外观的 XML 文件-在其封面之下,创建了一个SynthStyleFactory实现,该实现由 XML 文件中的SynthStyles填充:

SynthLookAndFeel laf = new SynthLookAndFeel();
  laf.load(MyClass.class.getResourceAsStream("laf.xml"), MyClass.class);
  UIManager.setLookAndFeel(laf);

编程 Route 涉及创建SynthStyleFactory的实现,该实现返回SynthStyles。以下代码创建一个自定义SynthStyleFactory,该自定义SynthStyleFactory为按钮和树返回不同的SynthStyles

class MyStyleFactory extends SynthStyleFactory {
     public SynthStyle getStyle(JComponent c, Region id) {
         if (id == Region.BUTTON) {
             return buttonStyle;
         }
         else if (id == Region.TREE) {
             return treeStyle;
         }
         return defaultStyle;
     }
 }
 SynthLookAndFeel laf = new SynthLookAndFeel();
 UIManager.setLookAndFeel(laf);
 SynthLookAndFeel.setStyleFactory(new MyStyleFactory());

XML 文件

可以在javax.swing.plaf.synth/doc-files/synthFileFormat.html.上找到 Synth XML 文件的 DTD 的说明。

当您加载 Synth 外观时,仅呈现那些定义(绑定到该区域的“样式”,如下所述)的 GUI 组件(或区域)。没有任何组件的默认行为-在 Synth XML 文件中没有样式定义,GUI 是一块空白画布。

要指定组件(或区域)的呈现方式,您的 XML 文件必须包含\ 元素,然后使用\ 元素将其绑定到该区域。作为示例,让我们定义一个包含字体,前景色和背景色的样式,然后将该样式绑定到所有组件。在开发 Synth XML 文件时,最好在您的 Synth XML 文件中包含这样的元素-然后,您尚未定义的任何组件都将至少具有颜色和字体:

<synth>
  <style id="basicStyle">
    <font name="Verdana" size="16"/>
    <state>
      <color value="WHITE" type="BACKGROUND"/>
      <color value="BLACK" type="FOREGROUND"/>
    </state>
  </style>
  <bind style="basicStyle" type="region" key=".*"/>
</synth>

让我们分析一下这个样式定义:

  • \ 元素是 Synth XML 文件的基本构建块。它包含描述区域渲染所需的所有信息。<style>元素可以描述多个区域,如此处所示。但是,通常,最好为每个组件或区域创建一个\ 元素。请注意,\ 元素具有一个标识符,即字符串“ basicStyle”。此标识符稍后将在\ 元素中使用。

  • \ 元素的\ 元素将字体设置为 Verdana,大小为 16.

  • \ 元素的\ 元素将在下面讨论。区域的\ 元素可以具有七个可能值中的一个或一个。如果未指定该值,则定义适用于所有状态,这是此处的意图。因此,在此元素中定义了“对于所有状态”的背景色和前景色。

  • 最后,刚刚定义的标识符为“ basicStyle”的\ 元素已绑定到所有区域。<bind>元素将“ basicStyle”绑定到“ region”类型。绑定适用于哪种区域类型由“ key”属性给出,在这种情况下为“.*”,即“ all”的正则表达式。

在创建一些工作示例之前,让我们看一下 Synth XML 文件的各个部分。我们将从\ 元素开始,说明如何将给定的\ 应用于组件或区域。

<bind>元素

每当定义\ 元素时,必须先将其绑定到一个或多个组件或区域,然后才能生效。<bind>元素用于此 Object。它需要三个属性:

  • style是先前定义的样式的唯一标识符。

  • type是“名称”或“区域”。如果type是名称,请使用component.getName()方法获取名称。如果type是一个区域,请使用javax.swing.plaf.synth包中Region类中定义的适当常量。

  • key是用于确定样式绑定到哪些组件或区域的正则表达式。

区域是一种识别组件或组件的一部分的方法。区域基于Region类中的常量,通过删除下划线进行修改:

例如,要标识 SPLIT_PANE 区域,可以使用 SPLITPANE,splitpane 或 SplitPane(不区分大小写)。

将样式绑定到区域时,该样式将应用于具有该区域的所有组件。您可以将一种样式绑定到多个区域,也可以将一种以上样式绑定到一个区域。例如,

<style id="styleOne">
   <!-- styleOne definition goes here -->
</style>

<style id="styleTwo">
   <!-- styleTwo definition goes here -->
</style>

<bind style="styleOne" type="region" key="Button"/>
<bind style="styleOne" type="region" key="RadioButton"/>
<bind style="styleOne" type="region" key="ArrowButton"/>

<bind style="styleTwo" type="region" key="ArrowButton"/>

您可以绑定到单独的命名组件,无论它们是否也绑定为区域。例如,假设您要在 GUI 中将“确定”和“取消”按钮与所有其他按钮区别对待。首先,您将使用component.setName()方法为“确定”和“取消”按钮命名。然后,您将定义三种样式:一种用于常规按钮(区域=“ Button”),一种用于 OK 按钮(名称=“ OK”),另一种用于 Cancel 按钮(名称=“ Cancel”)。最后,您将像这样绑定这些样式:

<bind style="styleButton" type="region" key="Button">
<bind style="styleOK" type="name" key="OK">
<bind style="styleCancel" type="name" key="Cancel">

结果,“确定”按钮同时绑定到“ styleButton”和“ styleOK”,而“取消”按钮同时绑定到“ styleButton”和“ styleCancel”。

当一个组件或区域绑定到多个样式时,这些样式将合并

Note:

就像一个样式可以绑定到多个区域或名称一样,多个样式可以绑定到一个区域或名称。这些多个样式将合并为区域或名称。优先级给予文件中稍后定义的样式。

<state>元素

\ 元素允许您定义依赖于其“状态”的区域外观。例如,您通常希望按钮为PRESSED的按钮看上去与处于ENABLED状态的按钮不同。 Synth XML DTD 中定义了\ 的七个可能值。他们是:

  • ENABLED

  • MOUSE_OVER

  • PRESSED

  • DISABLED

  • FOCUSED

  • SELECTED

  • DEFAULT

您还可以使用以“和”分隔的复合状态,例如 ENABLED 和 FOCUSED。如果未指定值,则定义的外观将应用于所有状态。

例如,以下是一种样式,用于指定每个状态的画家。除非状态为“ PRESSED(已按下)”,否则所有按钮的绘制方式都是特定的,在这种情况下,它们的绘制方式不同:

<style id="buttonStyle">
  <property key="Button.textShiftOffset" type="integer" value="1"/>
  <insets top="10" left="10" right="10" bottom="10"/>

  <state>
    <imagePainter method="buttonBackground" path="images/button.png"
                         sourceInsets="10 10 10 10"/>
  </state>
  <state value="PRESSED">
    <color value="#9BC3B1" type="BACKGROUND"/>
    <imagePainter method="buttonBackground" path="images/button2.png"
                        sourceInsets="10 10 10 10"/>
  </state>
</style>
<bind style="buttonStyle" type="region" key="Button"/>

暂时忽略\ 和\ 元素,您会看到按下按钮的绘制方式与未按下按钮的绘制方式不同。

使用的\ 值是与该区域的状态最匹配的已定义状态。匹配由与区域状态匹配的值的数量确定。如果没有状态值匹配,则使用无值的状态。如果存在匹配项,则将选择匹配项最多的状态。例如,以下代码定义了三种状态:

<state id="zero">
  <color value="RED" type="BACKGROUND"/>
</state>
<state value="SELECTED and PRESSED" id="one">
  <color value="RED" type="BACKGROUND"/>
</state>
<state value="SELECTED" id="two">
  <color value="BLUE" type="BACKGROUND"/>
</state>

如果该区域的状态至少包含 SELECTED 和 PRESSED,则将选择状态 1.如果状态包含 SELECTED,但不包含 PRESSED,则将使用状态 2.如果状态既不包含 SELECTED 也不包含 PRESSED,则将使用状态零。

当当前状态与两个状态定义中相同数量的值匹配时,使用的是样式中定义的第一个值。例如,一个PRESSED按钮的MOUSE_OVER状态始终为 true(除非将鼠标悬停在该按钮上,否则您不能按下该按钮)。因此,如果先声明MOUSE_OVER状态,则将始终在PRESSED之上选择它,并且不会为PRESSED定义任何绘制。

<state value="PRESSED"> 
   <imagePainter method="buttonBackground" path="images/button_press.png"
                          sourceInsets="9 10 9 10" />
   <color type="TEXT_FOREGROUND" value="#FFFFFF"/>      
</state>
      
<state value="MOUSE_OVER">    
   <imagePainter method="buttonBackground" path="images/button_on.png"
                          sourceInsets="10 10 10 10" />
   <color type="TEXT_FOREGROUND" value="#FFFFFF"/>
</state>

上面的代码将正常工作。但是,如果您反转文件中MOUSE_OVERPRESSED状态的 Sequences,则永远不会使用PRESSED状态。这是因为PRESSED状态的任何状态也是MOUSE_OVER状态。由于先定义了MOUSE_OVER状态,因此将使用该状态。

颜色和字体

\ 元素需要两个属性:

  • value可以是java.awt.Color常量之一,例如 RED,WHITE,BLACK,BLUE 等。它也可以是 RGB 值的十六进制表示形式,例如#FF00FF 或#326A3B。

  • type描述了颜色的应用范围-可以是 Background,FOREGROUND,FOCUS,TEXT_BACKGROUND 或 TEXT_FOREGROUND。

For example:

<style id="basicStyle">
    <state>
      <color value="WHITE" type="BACKGROUND"/>
      <color value="BLACK" type="FOREGROUND"/>
    </state>
  </style>

\ 元素具有三个属性:

  • name —字体名称。例如,Arial 或 Verdana。

  • size —字体大小,以像素为单位。

  • style(可选)— BOLD,ITALIC 或 BOLD ITALIC。如果省略,您将获得普通字体。

For example:

<style id="basicStyle">
    <font name="Verdana" size="16"/>
  </style>

\ 元素和\ 元素中的每一个都有替代用法。每个属性都可以具有id属性或idref属性。使用id属性,可以定义一种颜色,以后可以使用idref属性重新使用。例如,

<color id="backColor" value="WHITE" type="BACKGROUND"/>
<font id="textFont" name="Verdana" size="16"/>
...
...
...
<color idref="backColor"/>
<font idref="textFont"/>

Insets

insets会增加绘制组件时的大小。例如,没有插图,标题为Cancel的按钮将足够大以包含所选字体的标题。用这样的\ 元素

<insets top="15" left="20" right="20" bottom="15"/>,

该按钮的大小将在字幕上方和下方增加 15 像素,在字幕左侧和右侧增加 20 像素。

用图像绘画

Synth 的文件格式允许通过图像自定义绘画。 Synth 的图像画家将图像分为九个不同的区域:顶部,右上,右,右下,下,左下,左,左上和中心。这些区域中的每一个都被绘制到目标中。上,左,下和右边缘被平铺或拉伸,而拐角部分(sourceInsets)保持固定。

Note:

\ 元素和sourceInsets属性之间没有任何关系。<insets>元素定义区域占用的空间,而sourceInsets属性定义如何绘制图像。<insets>和sourceInsets通常是相似的,但不一定如此。

您可以指定是否应使用paintCenter属性绘制中心区域。下图显示了九个区域:

九个图像区域。

让我们创建一个按钮作为示例。为此,我们可以使用以下图像(显示为大于其实际大小):

Button Image.

左上角的红色框为 10 像素正方形(包括框边框),它显示了绘画时不应拉伸的角区域。为此,应该将顶部和左侧的sourceInsets设置为 10.我们将使用以下样式和绑定:

<style id="buttonStyle">
   <insets top="15" left="20" right="20" bottom="15"/>
   <state>
      <imagePainter method="buttonBackground" path="images/button.png"
        sourceInsets="10 10 10 10"/>
   </state>
</style>
<bind style="buttonStyle" type="region" key="button"/>

\ 元素内的线指定应使用图像images/button.png绘制按钮的背景。该路径相对于传递到 SynthLookAndFeel 的 load 方法中的 Class。 sourceInsets属性指定图像中不会拉伸的区域。在这种情况下,上,左,下和右插图分别为 10.这将导致画家在图像的每个角上不拉伸 10 x 10 像素区域。

\ 将buttonStyle绑定到所有按钮。

\ 元素提供渲染区域的一部分所需的所有信息。它只需要几个属性:

  • 方法-指定javax.swing.plaf.synth.SynthPainter类中的哪些方法用于绘画。 SynthPainter类包含大约 100 个以paint开头的方法。在确定所需的那个时,您将删除paint前缀,将剩余的第一个字母更改为小写,然后将结果用作method属性。例如,SynthPainter方法paintButtonBackground成为属性buttonBackground

  • path-要使用的图像的路径,相对于传递到 SynthLookAndFeel 的 load 方法中的 Class。

  • sourceInsets-以像素为单位的插图,代表不应拉伸的角区域的宽度和高度,它们按此 SequencesMap 到顶部,左侧,底部和右侧。

  • paintCenter(可选):此属性使您可以保留图像的中心或将其删除(例如,在文本字段中,以便绘制文本)。

下面的清单显示了 XML 代码,用于根据按钮的\ 加载不同的图像

<style id="buttonStyle">
    <property key="Button.textShiftOffset" type="integer" value="1"/>
    <insets top="15" left="20" right="20" bottom="15"/>
    <state>
      <imagePainter method="buttonBackground" path="images/button.png"
                    sourceInsets="10 10 10 10"/>
    </state>
    <state value="PRESSED">
      <imagePainter method="buttonBackground" path="images/button2.png"
                    sourceInsets="10 10 10 10"/>
    </state>
  </style>
  <bind style="buttonStyle" type="region" key="button"/>

button2.png 显示了 button.png 的压下版本,向右移动了一个像素。线

<property key="Button.textShiftOffset" type="integer" value="1"/>

相应地移动按钮文本,如下一节所述。

<property>元素

\ 元素用于将键值对添加到\ 元素。许多组件使用键值对来配置其视觉外观。

\ 元素具有三个属性:

  • key —属性的名称。

  • type —属性的数据类型。

  • value —属性的值。

有一个属性表(componentProperties.html)列出了每个组件支持的属性:javax/swing/plaf/synth/doc-files/componentProperties.html

由于 button2.png 图像在按下时将可视按钮移动了一个像素,因此我们还应该移动按钮文本。有一个执行此操作的 button 属性:

<property key="Button.textShiftOffset" type="integer" value="1"/>

An Example

这是一个使用上面定义的按钮样式的示例。按钮样式,外加具有绑定到所有区域的字体和颜色定义的“背景样式”(类似于上面标题为“ XML 文件”的部分中显示的“ basicStyle”),在buttonSkin.xml中组合在一起。之buttonSkin.xml

<!-- Synth skin that includes an image for buttons -->
<synth>
  <!-- Style that all regions will use -->
  <style id="backingStyle">
    <!-- Make all the regions that use this skin opaque-->
    <opaque value="TRUE"/>
    <font name="Dialog" size="12"/>
    <state>
      <!-- Provide default colors -->
      <color value="#9BC3B1" type="BACKGROUND"/>
      <color value="RED" type="FOREGROUND"/>
    </state>
  </style>
  <bind style="backingStyle" type="region" key=".*"/>
  <style id="buttonStyle">
    <!-- Shift the text one pixel when pressed -->
    <property key="Button.textShiftOffset" type="integer" value="1"/>
    <insets top="15" left="20" right="20" bottom="15"/>
    <state>
      <imagePainter method="buttonBackground" path="images/button.png"
                    sourceInsets="10 10 10 10"/>
    </state>
    <state value="PRESSED">
      <imagePainter method="buttonBackground" path="images/button2.png"
                    sourceInsets="10 10 10 10"/>
    </state>
  </style>
  <!-- Bind buttonStyle to all JButtons -->
  <bind style="buttonStyle" type="region" key="button"/> 
</synth>

我们可以加载此 XML 文件,以使用名为SynthApplication.java的简单应用程序的 Synth 外观。此应用程序的 GUI 包含一个按钮和一个标签。每次单击该按钮,标签都会递增。

Note:

即使buttonSkin.xml不包含标签样式,标签也已绘制。这是因为存在包含字体和颜色的常规“ backingStyle”。

这是SynthApplication.java文件的列表。

Try this:

单击启动按钮以使用Java™Web 开始(下载 JDK 7 或更高版本)运行 SynthApplication 示例。另外,要自己编译和运行示例,请查阅example index

带图标的绘画

单选按钮和复选框通常通过固定大小的图标呈现其状态。对于这些,您可以创建一个图标并将其绑定到适当的属性(请参阅属性表javax/swing/plaf/synth/doc-files/componentProperties.html)。例如,要绘制选中或未选中的单选按钮,请使用以下代码:

<style id="radioButton">
   <imageIcon id="radio_off" path="images/radio_button_off.png"/>
   <imageIcon id="radio_on" path="images/radio_button_on.png"/>
   <property key="RadioButton.icon" value="radio_off"/>
   <state value="SELECTED">   
      <property key="RadioButton.icon" value="radio_on"/>
   </state>
</style>
<bind style="radioButton" type="region" key="RadioButton"/>

Custom Painters

Synth 的文件格式允许通过JavaBeans 组件的 Long 期持久性嵌入任意对象。此功能在提供您自己的画家(超出 Synth 提供的基于图像的画家)时特别有用。例如,以下 XML 代码指定应在文本字段的背景中呈现渐变:

<synth>
  <object id="gradient" class="GradientPainter"/>
  <style id="textfield">
    <painter method="textFieldBackground" idref="gradient"/>
  </style>
  <bind style="textfield" type="region" key="textfield"/>
</synth>

GradientPainter 类如下所示:

public class GradientPainter extends SynthPainter {
   public void paintTextFieldBackground(SynthContext context,
                                        Graphics g, int x, int y,
                                        int w, int h) {
      // For simplicity this always recreates the GradientPaint. In a
      // real app you should cache this to avoid garbage.
      Graphics2D g2 = (Graphics2D)g;
      g2.setPaint(new GradientPaint((float)x, (float)y, Color.WHITE,
                 (float)(x + w), (float)(y + h), Color.RED));
      g2.fillRect(x, y, w, h);
      g2.setPaint(null);
   }
}

Conclusion

在本类中,我们介绍了使用javax.swing.plaf.synth包创建自定义外观的方法。本类的重点是使用外部 XML 文件定义外观。下一课提供一个示例应用程序,该应用程序使用 Synth 框架和 XML 文件创建搜索对话框。