摇摆电路_中级摇摆

在你开始前

关于本教程

本教程适合那些拥有将Swing应用程序组合在一起的经验的人,但是他们希望借助一些更高级的技术来积累这些知识,而这可能是您仅通过查看Swing API便无法掌握的。 如果您想学习本教程,则应该熟悉Swing的基本概念,例如Swing UI小部件,布局,事件和数据模型。 如果您仍然认为需要复习这些概念,请务必复习《 Swing入门》教程,该教程涵盖了所有这些领域,并为您提供了开始本教程所需的背景知识。

在本教程的过程中,除了基本组件和应用程序之外,还将向您介绍Swing的各个方面。 这些学习领域虽然较难学习和掌握,但功能也更强大,可让您创建更好的应用程序。 本教程中介绍的更高级的Swing概念是:

  • 了解JTable及其一些令人困惑和困难的概念
  • 编写线程安全的Swing代码
  • 创建一个自定义组件
  • 创建完全自定义的外观

工具和源下载

要完成本教程,您将需要以下内容:

样例应用

刷新器

在继续学习本教程中的新概念之前,让我们重新访问在上一教程中启动的示例应用程序,即航班预订系统。 对于那些跳过了第一个教程的人 ,此摘要应有助于您快速掌握应用程序的内容。

如果您还记得的话,该应用程序将尝试为桌面上的航班预订系统建模。 它允许用户选择出发地和目的地城市,然后搜索与搜索条件匹配的航班。 这些航班显示在表格中,用户可以选择所需的航班并购买任意数量的机票。

在这一点上,该应用程序非常基础。 仅通过查看它,您就应该能够了解它的作用,以及我们将在本教程中解决的当前限制。

航班预订系统

应用限制

看看现在存在的应用程序,您可能会认为它具有运行所需的一切。 好吧,这是一种看待它的方式-它确实具有工作所需的一切,但并没有工作良好所需的一切。 这就是将不良应用程序与不良应用程序区分开来的精妙之处,这些优良之处使UI体验更加全面。 尽管目前使用的应用程序可以用作航班预订系统,但现在有多少人希望在桌面上看到类似的内容? 大概不多。 它看起来很基础,缺乏修饰,而且也不是很友好。

这就是本教程的主要重点:改善Swing应用程序中的用户体验,并将功能应用程序转变为可销售的应用程序。 您将学习如何为您的应用程序提供外观和感觉上像专业应用程序所需的改进。 Swing提供了可以比您想象的更少的精力来完成此工作的工具,并且有许多第三方应用程序填补了Swing无法解决的空白,我将在本教程中讨论其中的一些。

示例应用程序增强

这是本教程中重点关注的领域,它将把基本应用程序变成专业的应用程序:

  • JTable的改进。 现在,向用户呈现搜索结果的表格非常无聊,根本没有交互性。 结果没有顺序,数据无法排序,用户无法修改表以改善其视图; 此外,它看起来很无聊,只有白色背景,黑色文字和蓝色高光。 我们可以更改所有这些内容-我们可以允许用户对列进行排序,可以更改每个单元格的外观,还可以更改表的首选项。

  • 线程问题。 线程是编程中最棘手和最难理解的领域之一。 不幸的是,使用Swing并没有变得更加容易。 幸运的是,有些未随Swing一起发布的Sun类使使用Swing中的线程更加容易,并且消除了通常与线程编程一起出现的许多陷阱。

  • 自定义组件。 由于Swing附带的普通小部件有时只是无聊,或者因为它们可能无法按照我们想要的方式工作,因此我们可以创建自定义组件来实现我们想要的功能。 在此应用程序中,“购买”按钮看上去很无聊-这是一个重要的按钮,应该反映出这一事实。 当我们完成它时,它将。

  • 自定义外观。 也许关于Swing的最酷的事情是它具有在不影响应用程序自身功能的情况下更改整个应用程序外观的能力。 尽管更改应用程序的外观非常容易,但是从头开始创建新的外观却非常困难。 因为我们希望航班预订系统拥有自己的外观,所以我们将创建一个简单的新外观示例,以说明如何实现。

中间JTable

介绍

如果您以前曾经使用过JTable,那么您肯定会遇到它带来的一些困难概念。 UI开发人员最常抱怨的JTable可能是基本版本太简单了,在实际应用程序中没有用。 但是,更高级的JTable变得有用了,它增加了并非所有开发人员都想应对的多层复杂性。 好吧,他们别无选择。

首先,让我们考虑一下航班预订JTable中的不足之处。 乍一看,您会发现为单元格或突出显示选择的颜色可能不是您想要的。 也许您不希望这些网格线出现。 因此,这是您可以更改的一件事: 外观 。

但是感觉如何? 基本的JTable还有什么问题? 好吧,它不提供任何排序​​机制。 这是一个很大的差距-JTable向用户显示数据,并且它应该让用户决定在屏幕上显示数据后如何安排数据。

那其他一些小花絮呢? 文本对齐怎么样? 用户通常习惯于使文本左对齐,数字右对齐。 如何编辑单元格的内容? 一些用户也期望这种行为。

好吧,好了,JTable重击就足够了。 让我向您展示如何利用其简单的JTable的潜力,而不是谈论如何缺少一个简单的JTable,并将航班预订系统中的普通JTable变成一个更好的表,用户可以对其进行调整以改善其UI体验。

JTable属性

开始更改JTable的最简单方法是开始更改其属性。 JTable类提供了许多功能,使您可以快速调整其外观,而无需进行任何过于困难的编码。 正如您现在可能已经知道的那样,这些用于更改属性的函数仅适用于简单的实例,但不足以满足更复杂的需求(在整个Swing中,这都是一个重复出现的主题)。 这些功能包括:

  • setAutoCreateColumnsFromModel()此函数使您可以告诉表自动从TableModel创建列; 通常情况下,应将其设置为true
  • setAutoResizeMode()当用户调整JTable中的列大小时,这将更改行为。 共有五个可能的值,并且每个值都会更改用户调整一列的大小时其他列的大小的变化方式:
    • AUTO_RESIZE_OFF其他列完全不变。
    • AUTO_RESIZE_NEXT_COLUMN仅更改下一列的大小。
    • AUTO_RESIZE_SUBSEQUENT_COLUMNS更改被调整大小后的每一列的大小。
    • AUTO_RESIZE_LAST_COLUMN仅调整最后一列的大小,以确保这些列始终占用与表本身完全相同的空间,因此不需要水平滚动
    • AUTO_RESIZE_ALL_COLUMNS在调整用户选择的列的大小时,所有列的更改相同
  • setCellSelectionEnabled()将其设置为true允许用户选择单个单元格; 在默认行为下,用户将选择一行。
  • setColumnSelectionAllowed()如果将其设置为true ,则当用户单击某个单元格时,将选择该单元格的整个列
  • setGridColor()更改表格的网格颜色。
  • setIntercellSpacing()更改每个单元格之间的间距,从而更改网格线的大小。
  • setRowHeight()更改表格的行高。
  • setRowSelectionAllowed()如果将其设置为true ,则当用户单击某个单元格时,将选中包含该单元格的整个行。
  • setSelectionBackground()更改选定单元格的背景颜色。
  • setSelectionForeground()更改选定单元格的前景色。
  • setBackground()更改未选定单元格的背景颜色。
  • setForeground()更改未选中的单元格的前景色。
  • setShowGrid()允许您完全隐藏网格。

如您所见,无需付出太多努力,您就可以对JTable做很多事情。 这些属性使您可以根据应用程序中的需求定制JTable。 但是,尽管这些小属性很好,但是它们并不能满足示例应用程序的一些更紧迫的需求。

表格渲染器

在上一节中,您看到了可以在选中和取消选中单元格时更改其前景色和背景色。 这是一个很好的功能,但有其局限性:如果您还想更改字体怎么办? 如果要根据该单元格的特定值更改颜色该怎么办?

表格单元格的这种更高级的绘制由称为TableRenderer的接口处理。 通过创建实现此接口的类,您可以创建自定义调色板来进行所需的任何绘画。 然后,JTable将把所有绘画职责移交给这个新类。

对于示例应用程序,我们将更改表的外观以匹配以下规则:

  • 给未选择的行白色背景和黑色纯文本前景。
  • 为选定的行提供绿色背景和黑色文本前景。
  • 给包含售罄航班的行提供深灰色背景和白色文本前景。

使用新的TableRenderer第一步是告诉JTable使用它而不是其自定义内置渲染器。 (JTable有一个默认的表格单元格渲染器,我们将对其进行覆盖以进行自己的绘画。)通过将渲染器附加到Object.class ,我们实际上是在告诉JTable对每个单元格使用自定义渲染器。

getTblFlights().setDefaultRenderer(Object.class, new FlightTableRenderer());

现在,这一步很快完成了,让我们看一下FlightTableRenderer本身。 FlightTableRenderer利用了内置的Swing类DefaultCellRenderer ,它提供了一个很好的起点。 我们可以扩展并覆盖此类的唯一方法来进行自己的绘画。 实际的绘画逻辑应该是不言自明的。

public class FlightTableRenderer extends DefaultTableCellRenderer
 {	
 public Component getTableCellRendererComponent(JTable table,
                                Object value,
                                boolean isSelected,
                                boolean hasFocus,
                                int row,
                                int column)
      {
         setText(value.toString());
		
         if (((Integer)table.getValueAt(row, 3)).intValue() < 1)
         {
            setBackground(Color.GRAY);
            setForeground(Color.WHITE);
         }
         else
         {
            if (isSelected)
            {
                setBackground(Color.GREEN);
                setForeground(Color.BLACK);
            }	
            else
            {
                setBackground(Color.WHITE);
                setForeground(Color.BLACK);
            }
         }
         return this;
      }
   }

将这段代码插入示例应用程序后,结果表看起来完全不同。 它还会向用户传达更多信息,因为它会将已经售罄的航班显示为灰色:

TableRenderer的实际应用

表格模型

在入门教程中,您学习了如何使用TableModel ,尤其是如何使用它们来更轻松地管理数据。 但是,除了处理表中数据的显示方式外, TableModel还可以通过两种不同的方式更改表的行为。

  • TableModel告诉JTable如何对齐每一列中的数据。 这到底是什么意思? 好吧,例如, String通常左对齐,数字和日期通常右对齐。 代表货币金额的数字通常以小数点对齐。 TableModel包含一个名为getColumnClass()的方法,该方法可以处理所有对齐问题。 幸运的是,JTable具有许多内置的TableRenderer ,它们知道如何处理某些类,包括IntegerStringDouble 。 您可以通过在表模型中提供以下功能来更改对齐方式:

    public Class getColumnClass(int col)
       {
          if (col == 2 || col == 3)
             return Integer.class;
          else
             return String.class;
       }

    不幸的是,此调用与我们为FlightCellRenderer创建并附加到Object.class的代码冲突。 对于我们所拥有的有限数量的代码来说,这只是太多示例的一种情况,因此您必须相信我这是可行的,并且JTable知道如何对齐某些类。 在我们的示例中,使用现有的FlightCellRendererFlightCellRenderer数据正确对齐,我们需要稍微修改代码,并为setHorizontalAlignment()添加行以setHorizontalAlignment()我们希望的方式对齐数据。

    if (column == 2 || column == 3)
          setHorizontalAlignment(SwingConstants.RIGHT);
       else
          setHorizontalAlignment(SwingConstants.LEFT);
  • TableModel允许编辑单个单元格。 与默认的单元格渲染器非常相似,JTable为某些内置类(例如StringInteger提供了默认的单元格编辑器。 换句话说,它知道如何为这些内置类提供编辑功能。 TableModel只需要告诉JTable允许编辑哪些单元格以及完成编辑后如何设置值,然后JTable将处理其余的单元格。

    public boolean isCellEditable(int row, int col)
       {
          return col == 3;
       }
    		
       public void setValueAt(Object value, int row, int col)
       {
          if (col == 3)
             ((Flight)data.get(row)).setRemainingTixx(new Integer(value.toString())
             .intValue());
       }

    现在,您可能会问:“如果我不想使用Swing的内置编辑器之一怎么办?” 好吧,我最初的回答是:“真可惜。” 创建一个自定义编辑器(例如,一个可以干净地编辑日期的编辑器)非常困难。 如您在其他领域中所见,让Swing处理内置类的编辑的简单性被创建用于编辑JTable中未内置类的新类的复杂性所抵消。 创建新的类来编辑单元格的问题不在本教程的讨论范围之内,如果您确定绝对需要此功能,请不要说我没有警告过您。 对于那些有兴趣学习如何创建自定义单元格编辑器的人,请查看“ 相关主题”以获取提供操作方法的网站。

排序

表的用户-无论是桌面应用程序还是Web应用程序中的表-都希望能够按列对这些表中的数据进行排序。 换句话说,他们希望能够单击列标题,并查看重新排列的数据以匹配在被单击列上索引的升序或降序。

不幸的是,JTable没有内置此功能。 这是一个真正的耻辱,因为几乎每个用户都期望它。 某些潜在的将来的Swing版本中已经内置了此功能。 但是因为有很多人期望它,所以JTable已经在第三方中提供了它,并且很容易将其添加到我们的航班预订系统中。 具有讽刺意味的是,Sun自己在JTable文档中提供了大多数人用来对表进行排序的“第三方应用程序”。 让您想知道为什么Sun不只是将其自动包含在Swing中,而不是使人们浏览文档来查找它,不是吗?

Sun提供的类称为TableSorter ,在任何JTable中都非常容易使用。 TableSorter类本身会自动处理所有内容,根据所按下的鼠标按钮以升序或降序对数据进行排序,并使用箭头表示排序的方向。

仅需两行代码,我们便可以向航班预订系统添加排序功能:

TableSorter sorter = new TableSorter(flightModel, getTblFlights().getTableHeader());
   getTblFlights().setModel(sorter);

下图是我们的应用程序中排序功能的外观。 注意单词“ Tixx”后的箭头。 它指示该列已排序,还指示排序的方向-升序或降序:

TableSorter在行动

JTable摘要

因此,在短短的几节中以及一些新的类中,我们设法将JTable从陈旧而无聊的表转换为... ...少了一些无聊的表(我们不要自欺欺人)。 无论如何,我们通过一些简单的课程为JTable添加了很多东西,并且您应该能够通过学习这些课程并在其基础上进一步增加JTable的内容。 快速回顾一下我们涵盖的内容:

  • 您可以在JTable上调用set方法来快速更改表本身的一些简单属性。 其中一些属性是JTable中的网格颜色,网格大小,行高和其他“糖衣”项目。
  • 您可以通过创建一个新的TableRenderer来更改每个单元格的外观。 通过创建TableRenderer ,我们还承担了绘制单元格的全部责任,实际上告诉JTable我们将处理所有绘制。 创建自己的TableRenderer时请记住这一点-任何错误都会很快变得明显。 另外,通过创建自己的TableRenderer ,您将负责数据的对齐,否则可以由TableModel处理。
  • 您可以使用TableModel更改列中数据的对齐方式(前提是您尚未覆盖TableRenderer ),还可以在单​​元格本身中进行编辑。
  • JTable自动处理许多类型的数据,包括StringIntegerDouble 。 您可以通过对这些类使用内置的渲染器和编辑器来利用这一事实。 不幸的是,对于那些JTable不能自动提供的类,您必须提供自己的渲染器(我们在此处了解到)和编辑器(这是复杂的,超出了本教程的范围)。
  • 最后,您学习了如何使用Sun的TableSorter类向TableSorter添加排序功能。 尽管Sun为此类提供了Swing文档,但它不是受支持的Swing类,并且需要随您创建的任何类一起提供。 在以后的Swing版本中,将内置排序功能。

下图显示了航班预订系统中的JTable现在的外观:

JTable改进后的航班预订系统

线程安全的摆动

UI冻结时

当我提到“ 穿线 ”一词时,我想我听到人群中传来阵阵吟。 是的,即使Swing也无法幸免于线程问题,如果您像我一样,那也不是您想听到的。 线程问题通常是很难编写的东西-概念是抽象的,并且当发生错误时,它们很难测试并且很难修复。

在Swing中,这些概念本身至少更容易理解和编码。 例如,在我们的航班预订系统中,假设用户单击按钮来购买机票-在应用程序的当前版本中,此过程很快完成,因为我们使用的是假数据库。 但是,假设该应用程序正在与位于其他位置的真实数据库通信,例如在经典的客户端服务器应用程序中。 当用户单击“购买”按钮时,我们需要调用并更新数据库,并等待数据库返回。 您是否真的要让您的用户在处理购买过程中等待整个时间(可能长达一分钟)? 希望不会!

当有人编写这样的动作时,Swing中会发生多个问题。 用户首先会注意到,他或她现在已被锁定在应用程序之外,并且无法再与之交互; Swing在单个线程(事件调度线程)上运行,这意味着当线程坐在并等待数据库调用完成时,会将任何其他用户交互(例如,尝试选择另一个目标城市)都放入队列中,以进行线程完成,因此等待对数据库的调用完成。 我们无法将用户拒之门外!

Swing的另一个令人讨厌的功能是,当Swing线程繁忙并且应用程序需要重新绘制自身时(例如,如果最小化然后最大化框架),则数据库调用也会阻止repaint命令,并且用户最终看到一个灰色的矩形。 此问题将在下一版本的Swing中解决,但与此同时我们必须处理它。

仅这两个问题就可以向您展示正确处理Swing中的线程问题有多么重要-如果您将用户锁定在应用程序之外,并且每次应用程序需要时将其变成灰色矩形,则无法致电应用程序专业人员运行一些耗时超过几毫秒的代码。 幸运的是,有解决这些常见问题的方法。

在事件调度线程上工作

Swing包含一个名为SwingUtilities.invokeLater()的方法,该方法实际上解决了一个问题,该问题在某种程度上与我们在上一节中所考虑的问题类似。 此功能可以为非UI相关线程在事件调度线程上执行任务,而不是为在事件调度线程之外的单独线程上执行耗时的操作找到解决方案。 到底为什么有人要这样做? 好吧,最容易理解的示例是GUI应用程序的启动。 考虑一下应用程序在启动期间通常会做什么:打开服务器连接,加载首选项,构建GUI等。这是将GUI的构建委托给事件分发线程的绝佳机会,让主应用程序线程继续执行其他操作任务作为事件调度线程处理与Swing相关的问题。 该解决方案提高了应用程序的启动性能。

我们最初是这样启动航班预订系统的:

public static void main(String[] args)
 {
   FlightReservation f = new FlightReservation();
   f.setVisible(true);
  }

但是,我们应该利用SwingUtilities.invokeLater()方法提供的性能改进,并允许事件分发线程构建Swing组件(在这个小型应用程序中,性能提高是不明显的,因此您将拥有在这里发挥您的想象力)。

public static void main(String[] args)
   {
      SwingUtilities.invokeLater(new Runnable()
      {
         public void run()
         {
            FlightReservation f = new FlightReservation();
            f.setVisible(true);
         }
      }
      );
      // possibly open files, get DB connections here
   }

但是,这仍然不能解决我们原来的问题:将缓慢的进程移出事件调度线程。 接下来,我们将解决这个问题。

第三方Swing线程解决方案

具有讽刺意味的是(沿着您已经看到的TableSorter行),有一个解决方案可以解决我在过去两节中提出的问题,该问题已发布在Sun的Swing文档中-尽管像TableSorter ,它并未发布作为Java版本的一部分。 我不确定为什么Sun会这样做。 也许是要确保您阅读文档。 无论如何,Sun提供的解决方案解决了在事件调度线程上运行耗时的进程的问题。

该类称为SwingWorker ,在其基础上,它的工作原理与任何其他线程一样。 但是,它是专门为与Swing应用程序一起量身定制的,方法是将事件调度线程中应执行的逻辑与事件调度线程中应执行但取决于逻辑结果的逻辑明确分开不在事件调度线程上。 令人困惑? 一旦看到实际效果,就不应该这样。

让我们看看当用户按下“搜索”按钮并分解调用的各个部分并决定应该在哪些线程上操作时会发生什么。

// should occur on the event-dispatch thread as it deals with Swing
   final String dest = getComboDest().getSelectedItem().toString();
   final String depart = getComboDepart().getSelectedItem().toString();

   // should NOT occur on the event-dispatch thread, as it could be time consuming
   List l = DataHandler.searchRecords(depart, dest);

   // should occur on the event-dispatch thread, but is dependent on the results from 
   the previous line
   flightModel.updateData(l);

SwingWorker类要求所有不应该在事件分发线程上执行的代码都放在其construct()方法中。 该类还要求所有应该在事件分发线程上运行但依赖于construct()方法的结果的代码都应在finished()方法中。 这两个方法之间的关键联系在于, construct()方法返回一个Objectfinished()方法可以通过调用get()来访问该Object 。 考虑到这一点,我们可以修改上面的代码,实现SwingWorker从事件调度线程中删除阻塞代码,并确保我们的应用程序不会阻止用户与其进行交互。

final String dest = getComboDest().getSelectedItem().toString();
   final String depart = getComboDepart().getSelectedItem().toString();
   SwingWorker worker = new SwingWorker()
   {
      public Object construct()
      {
         List l = DataHandler.searchRecords(depart, dest);
         return l;
      }
				
      public void finished()
      {
         flightModel.updateData((List)get());
      }
   };
   worker.start();

定制组件

介绍

您曾经有过这样的感觉,有时您所拥有的只是不够的吗? 您是否认为Swing提供了可能所需的一切,以使UI看起来完全符合您的期望? 机会是,你的答案是肯定的 ,以第一个问题,并没有给第二。 尽管Swing提供了许多组件和小部件,但在那里的创意人士总是要求更多。

幸运的是,Swing提供了解决此问题的必要工具。 您想要一个新组件吗? 没问题。 您只需要几行代码就可以完成任务。 是否要更改现有组件的行为,以使其适合您希望在自己的应用程序中显示的方式? 这也很容易做到。 通过使用Java的类层次结构,修改现有的Swing小部件以创建自定义组件就像对现有小部件进行子类化一样简单。 如果要创建一个全新的自定义组件,就像构建块一样,只需将现有的小部件合并为一个更大的小部件即可。

在本节中,我们将以两种方式创建自定义组件,从修改现有的Swing组件开始。

修改现有组件

修改现有组件的第一步自然是确定要更改的组件。 对于航班预订系统,在此示例中,我们将修改“购买”按钮,因为它看起来像应用程序中的其他按钮一样重要。 我们将创建一个称为JCoolButton的东西,它将隐藏按钮的边框,直到鼠标悬停在其上方为止。 此时,它将显示边框。

修改组件的第一步是将其子类化-在这种情况下,我们将子类化JButton。 通过子类化构造函数并指向一个新函数(在本示例中称为init() ),您可以创建自己的行为,该行为不同于默认组件。 要记住的重要一点是,当您将Swing小部件作为子类并打算让其他人重用它时,必须子类化每个构造函数以确保任何用户都能得到您想要的行为。

看一些示例代码。 您将看到JCoolButton如何将JButton修改为仅在鼠标悬停在其上方时绘制边框,从而在按钮上创建悬停效果。

public JCoolButton()
 {
 super();
      init();
   }
	
   private void init()
   {
      setBorderPainted(false);
      addMouseListener(new MouseAdapter()
      {	
         public void mouseEntered(MouseEvent arg0)
         {
            setBorderPainted(true);
         }

         public void mouseExited(MouseEvent arg0)
         {
            setBorderPainted(false);
         }
      }
      );
   }

这是没有鼠标悬停的按钮:

没有鼠标悬停

这是鼠标悬停的地方:

随着鼠标悬停

对JCoolButton的更多修改

您可能会注意到JCoolButton还不是很完美,因为当鼠标不在鼠标上方时,渐变背景仍会被绘制,这会使它显得笨拙。 我们需要添加一些代码,以使JCoolButton在没有鼠标悬停在其上时完全融合到后台JPanel中,从而增强按钮的炫酷效果。

这为我们带来了有关Swing组件如何工作的重要课程。 了解组件的实际绘制方式,以什么顺序绘制以及覆盖各个功能的后果是非常重要的。 本课从JComponent中的paint()函数开始。 因为它在JComponent中,所以每个Swing组件都包含它,而实际上,它是负责绘制Swing中每个组件的函数。

paint()文档告诉我们,当调用该方法时,它将依次调用JComponent中的其他三个方法:首先是paintComponent() ,然后是paintBorder() ,最后是paintChildren() 。 从调用方法的顺序来看,您可以看到Swing从底部到顶部进行了绘制。 但是,要学习的重要课程是,为了覆盖组件的外观,您需要覆盖paintComponent()方法,该方法直接负责绘制单个组件在屏幕上的显示方式。

在我们的JCoolButton中,让我们解决以下问题:即使鼠标不在按钮上方,蓝色渐变背景也会被绘制。 我们希望它直接融合到背景中。 我们可以通过重写paintComponent()方法并跟踪鼠标何时位于按钮上方以及何时不在按钮上方,并为每种状态绘制按钮来实现此目的。 在查看代码之前,请注意一个重要注意事项:当您重写paintComponent()方法时,您将负责所有绘制。 换句话说,您负责文字,背景,装饰-基本上是边框以外的所有内容。

public void paintComponent(Graphics g)
   {
      super.paintComponent(g);
      if (!mouseOver)
      {
         g.setColor(getParent().getBackground());
         g.fillRect(0,0,getSize().width, getSize().height);	
         g.setColor(getForeground());
         int width = SwingUtilities.computeStringWidth(getFontMetrics(getFont()),
            getText());
         int height = getFontMetrics(getFont()).getHeight();
         g.drawString(getText(), getWidth()/2 - width/2, getHeight()/2 + height/2);	
      }
   }

这是我们新修改的没有鼠标悬停的按钮:

没有鼠标悬停

这是鼠标悬停的地方:

将鼠标移到

构建一个全新的组件

您的广告素材类型可能会受到现有Swing组件的约束-不仅是它们的外观,而且是它们的工作方式。 您可能会认为没有现有的组件可以完成您希望窗口小部件执行的操作。 您有一个愿景,并且想要在您的UI中实现。

Swing使您能够实现自己的梦想,让您的创造力发狂,并创建具有所需功能的任何组件-仅需四个步骤:

  1. 确定您需要组合以创建新的小部件的现有Swing小部件。 现有的Swing组件提供了应该用于新窗口小部件的构建块。 它们的优势显而易见-它们是经过广泛测试的完整组件。
  2. 视觉上将小部件组合在一起,使组件看起来像您想要的样子。 最好的方法是将选定的组件添加到JPanel,并确保使用布局管理器(因为您不确定其他用户可能想要的大小)。
  3. 使组件正确交互在一起,以从组件获得所需的行为。 这将涉及创建私有函数来处理事件,将数据从一个部分传递到另一部分,使整个事物正确地进行着色-基本上,您需要执行任何操作以使其表现出所需的效果。
  4. 将公共API环绕完成的组件,以便其他人可以理解如何轻松使用它。

JMenuButton

好的,足够抽象的描述了。 让我们来看一些例子! 我们将创建一个新组件,并完成上一节概述的四个步骤,向您展示如何创建一个全新的组件。

我们将要创建的组件是JMenuButton,这是一个与常规JButton相似的新组件,但具有其他功能:用户可以按下JButton侧面的箭头按钮以显示其他选择。 如果此描述令人困惑(我确定是这样),请考虑一下Web浏览器上的“后退”按钮:单击它会将您带回到您正在阅读的最后一个网页,但是单击并按住它会弹出一个列表。您最近浏览历史记录中的页面。 从本质上讲,这是一个JMenuButton,我们将在Swing中构建其中的一个。 您会注意到,Swing当前不存在类似的内容,但是所有构建块都可以构建它。

看一下最终产品,我们的目标是:

JMenuButton

进一步说明:由于没有合适的位置,我们不会在航班预订系统中实现此组件。 但是,请根据需要在自己的应用程序中随意使用它。

步骤1:选择组件

我们将需要四个组件来创建JMenuButton:

  • 一个JButton用作主按钮(组件的后部)。 这是组件中的主要选择。
  • 一个JButton,用作箭头按钮。 The arrow button displays the pop-up menu that contains the alternative choices in the component.
  • A JPopupMenu that will be displayed whenever the arrow button is pressed and the alternative choices are displayed.
  • JMenuItems that will go into the JPopupMenu and serve as the alternative choices.

Step 2: Lay out the components

As I pointed out in Building a completely new component , the easiest way to create a new component is to add existing components to a JPanel. In our example, we will use a JPanel as the base of the component, and place the two buttons on top of it. In fact, our JMenuButton subclasses from JPanel, and not any of the components that it contains. Choosing the layout for our components on the JPanel is straightforward enough, and we can just use a BorderLayout .

this.setLayout(new java.awt.BorderLayout());
   this.add(getBtnMain(), java.awt.BorderLayout.CENTER);
   this.add(getBtnArrow(), java.awt.BorderLayout.EAST);

Step 3: Internal interaction

The third step in creating a new component is to get it working properly (obviously). In this example, we need to get the arrow button to launch the pop-up menu whenever it is pressed. Note that the main button doesn't necessarily do anything internally. Externally, it needs to be listened to, but internally, it doesn't change the state or appearance of the component, and thus can be ignored in this step. Also, the individual JMenuItems in the JPopupMenu can be ignored internally as well -- we don't even have any added when we start, and they don't change the component, either.

Here's the code that will handle the internal interaction of the components:

getBtnArrow().addActionListener(new ActionListener()
   {
      public void actionPerformed(ActionEvent e)
      {
         getPopup().show(getBtnMain(), 0, getHeight());
      }
   }		
   );

Step 4: Create a public API

The final step is perhaps the most important in creating a new component -- you must wrap the component in an easy-to-use public API, so that other people who want to use your component can do so easily. After all, what's the point of creating something new if you don't let anyone else use it? Creative genius should be shared, after all.

Any time you are creating a public API wrapper around your component, you should aim to keep the format the same as existing Swing components -- use get/set methods, and try not to change any behavior that UI developers would expect from a Swing component (for example, don't change any methods in JComponent that UI developers would expect to act the same from component to component).

There are two areas that we need to be concerned with in our JMenuButton. First, we need to be able to add alternative choices to the JMenuButton. We can create a new method to handle this:

public void add(JMenuItem item)
   {
      getPopup().add(item);
   }

Second, we need to be able to handle action events that the main button and the alternative choices could trigger -- after all, users need to know when a button is pressed or an alternative choice is selected.

public void addActionListener(ActionListener l)
   {
      getBtnMain().addActionListener(l);
      for (int i=0; i<getPopup().getSubElements().length; i++)
      {
         JMenuItem e = (JMenuItem)getPopup().getSubElements()[i];
         e.addActionListener(l);
      }		
   }
	
   public void removeActionListener(ActionListener l)
   {
      getBtnMain().removeActionListener(l);
      for (int i=0; i<getPopup().getSubElements().length; i++)
      {
         JMenuItem e = (JMenuItem)getPopup().getSubElements()[i];
         e.removeActionListener(l);
      }		
   }

而已。 That's the end of the process to create JMenuButton. After only a few sections, we've managed to create a new and functional Swing component. By following the four steps outlined in this section, you too can create your own component.

The JMenuButton isn't meant to be a professional and polished component -- it merely exists to serve as an example. Thus, if you choose to use it in your own applications, I'd suggest additional public methods and further testing.

Custom look and feel

背景

One of the coolest features of Swing is its ability to easily change the look and feel of an application. The look and feel of an application is a combination of two things (and remember these terms, as I'll refer to them throughout this section):

  • The look, which is how the components appear visually: what colors they use, what fonts they use, shading, etc.
  • The feel, which is how the components interact with users: how they react to a right-click, how they react to mouse drags, etc.

The look and feel of an application is governed by the Swing class javax.swing.LookAndFeel ; we'll refer to an instance of this generically as a LookAndFeel. There's a small but distinct difference between the look and the feel, one that we'll examine in detail when we talk about Synth .

Another ironic thing about how custom LookAndFeels work in Swing: the concepts and code needed to create a new javax.swing.LookAndFeel class are complex (and beyond the scope of this tutorial), but once the javax.swing.LookAndFeel is done and packaged, it is incredibly easy to make your application use it.

In fact, you can change the look and feel of your entire application by adding only one line of code!

UIManager.setLookAndFeel(new WindowsLookAndFeel());

Swing's LookAndFeels

Swing comes installed with multiple LookAndFeels pre-installed. These LookAndFeels match up to the common OSes on the market right now, but have an unfortunate side-effect: the LookAndFeel that resembles Windows XP is only available on Windows, the Macintosh LookAndFeel is only available on Mac OS, and the GTK LookAndFeel is only available on Linux systems. Sun has also made the Ocean LookAndFeel, an attempt to provide a good-looking cross-platform look and feel.

Let's take a look at how our flight reservation system looks in the different installed LookAndFeels (though these will not include Mac or GTK, because I'm writing this tutorial on a Windows machine). Remember, only one line of code needs to be changed to completely change how our updated flight reservation system application looks.

Here's the Windows LookAndFeel:

Windows LookAndFeel

Here's the Motif LookAndFeel:

Motif LookAndFeel

And here's the Ocean LookAndFeel:

Ocean LookAndFeel

UIManager

The easiest way to start changing the look and feel of your apps is to learn how to use the UIManager. The UIManager provides access to everything that has to do with the installed look and feel -- and by everything, I mean everything. Every possible color, every possible font, and every possible border can be changed and manipulated by the UIManager. The UIManager acts as a HashTable that contains all these values, and ties them to String s that act as the key in the hashtable relationship.

Therein lies the difficult part of working with the UIManager -- these String s that act as the keys are not documented anywhere on the Sun site. Fortunately, by examining the installed LookAndFeels that come shipped with Swing, you can see what keys Sun used and use them yourself. It's unfortunate that these keys aren't documented anywhere, as this only adds to the complexity of creating a new custom look and feel.

We'll use the UIManager to change the colors for the labels from blue to green and the font that the entire application uses:

Font font = new Font("Courier", Font.PLAIN, 12);
 UIManager.put("Button.font", font);
   UIManager.put("Table.font", font);
   UIManager.put("Label.font", font);
   UIManager.put("ComboBox.font", font);
   UIManager.put("TextField.font", font);
   UIManager.put("TableHeader.font", font);
   UIManager.put("Label.foreground", Color.GREEN);

Here's our newly tweaked application:

Changing the Default LookAndFeel

Packing it all into a LookAndFeel

Of course, this type of coding isn't conductive to object-oriented programming, as you don't want to retype these lines of code in every application that needs to have this look and feel. You also can't easily let others use your new creation this way.

Sun's solution is to provide the javax.swing.LookAndFeel class, which lets you easily package up all the information needed to create a look and feel for your application. It also lets you describe how all the pieces come together to create the look and feel you want to distribute to everyone.

There are two ways to create a LookAndFeel. One is to subclass the javax.swing.LookAndFeel class itself, though this is the more difficult option. The better solution is to subclass one of the existing LookAndFeels provided by Swing -- either the javax.swing.plaf.metal.MetalLookAndFeel or the javax.swing.plaf.basic.BasicLookAndFeel (a building block look and feel that doesn't have any visuals but is provided to serve as a basis for others to build on).

There's also some basic information that every LookAndFeel needs to contain -- things that tell Swing about the LookAndFeel and let others know about it as well, should you decide to package it up and distribute it.

  • getDescription() : A description of the look and feel.
  • getID() : A unique ID that can be used to identify the look and feel.
  • getName() : The name of the look and feel.
  • isNativeLookAndFeel() : Indicates whether this look and feel is native to the OS; any custom look and feel should return true from this function.
  • isSupportedLookAndFeel() : Should also return true for any custom look and feel.

Let's look at this code in our new custom LookAndFeel class, the FlightLookAndFeel .

public String getDescription()
   {
      return "The Flight Look And Feel is for the Intermediate Swing tutorial";
   }
	
   public String getID()
   {
      return "FlightLookAndFeel 1.0";
   }
	
   public String getName()
   {
      return "FlightLookAndFeel";
   }
	
   public boolean isNativeLookAndFeel()
   {
      return true;
   }
	
   public boolean isSupportedLookAndFeel()
   {
      return true;
   }

More on the FlightLookAndFeel

The final step in creating the FlightLookAndFeel class is to actually describe what the look and feel should change. In the example discussed in UIManager , we were merely overriding certain colors and fonts that were already in the MetalLookAndFeel . We are going to reuse this code to create our look and feel.

There's a class that functions like the UIManager called the UIDefaults . The difference between these is subtle -- the UIManager should be used outside of the LookAndFeel classes, as it represents all the look and feel values after it is loaded. The UIDefaults represents these same values as they are being loaded.

The LookAndFeel class has a getDefaults() method that loads up these values, and then uses them to create the look and feel. In order to get our own values in there instead of the Metal ones we are overriding, we must first call the MetalLookAndFeel 's getDefaults() function to load every value that we are not overriding (if we didn't do this, we'd end up having a weird-looking look and feel, missing colors, sizes, and so on). After letting the MetalLookAndFeel have the first stab at creating everything it needs, we can just override the values we want and create the FlightLookAndFeel the way we want it to look.

public UIDefaults getDefaults() 
   {
      UIDefaults def = super.getDefaults();
      Font font = new Font("Courier", Font.PLAIN, 12);
      def.put("Button.font", font);
      def.put("Table.font", font);
      def.put("Label.font", font);
      def.put("ComboBox.font", font);
      def.put("TextField.font", font);
      def.put("TableHeader.font", font);
      def.put("Label.foreground", Color.GREEN);
      return def;
   }

More on custom LookAndFeels

While the previous sections were sufficient to create a custom look and feel that dealt with color and fonts only, it really isn't a complete enough lesson in how to create a complete custom look and feel, one that would be comparable to the WindowsLookAndFeel or the MotifLookAndFeel . These go far beyond just modifying the colors and fonts; they change nearly everything from the default implementation -- how each component is drawn, how it reacts to mouse events, where components are placed when it has items added to it, and so on. Creating a complete look and feel is not an easy task.

This tutorial will not address the many steps need to create a custom look and feel. In fact, an entire tutorial could be created just to teach you how to create a custom look and feel; it is unfortunately that complex an undertaking. To give you a sense of things, the task requires the user to create a new class for every Swing component, outlining how it should look and feel. That's roughly 60 classes that you would need to create -- not something that can be done in an afternoon, and especially not something that could be summarized in a few sections in this tutorial.

As further proof as to the complexity of creating a complete custom look and feel, the number of examples available on the Internet is amazingly small. Only about 20 to 30 commercial LookAndFeels are available for download -- and that's with Swing being on the market for seven years.

Fortunately, in J2SE 5.0, Swing introduced a new class that has made the process much easier and reduced the time needed to create a custom look and feel from about three months (as it required in 1.4) to three weeks (as it now requires). We'll discuss it in the next section.

合成器

Synth is the newest LookAndFeel addition to Swing, but it's kind of a misnomer. It isn't really a LookAndFeel at all, which you'd find out if you tried to add it to your application: your application would turn completely white. Nor can you even modify the complete look and feel of an application once you begin using Synth. Synth allows you to only change the look of an application -- the borders, the colors, the fonts. It does not allow you to change how the application feels.

Synth in a nutshell is a skin that you install on your application. The skin contains information describing how to use external images and custom paint code to create the look, parsing this information from a single XML file that is loaded when the Synth LookAndFeel is installed in the application. The biggest advantage is the time savings it offers users -- instead of having to subclass 60 Java classes, you merely need to create one XML file and some graphics. The result of this advantage is a potential explosion in the number of custom LookAndFeels available for developers to choose from; this is Sun's ultimate goal in releasing this new class. In case you haven't been following the drama, Swing for years was criticized for being ugly. The Ocean LookAndFeel alleviated that complaint somewhat, and Synth offers the potential for creative developers to make some very nice LookAndFeels in the near future.

For a complete lesson on how to use Synth and how it will fit into your application, check out my Advanced Synth article published a few months ago (see Related topics for a link), which will take you step-by-step through the process of creating a new custom look and feel using Synth.

Wrapping up

摘要

This tutorial dealt with some intermediate-level issues that you may encounter when working with Swing when developing UI applications. It is meant to build upon the knowledge you gained in the Introduction to Swing tutorial, or to build on your already existing Swing knowledge. It dealt with some common issues that UI developers come across in their applications. I've attempted to pick out a combination of the most interesting and most important issues.

As I've discussed many times throughout both tutorials, these tutorials are by no means exhaustive in their coverage of Swing -- there's just far too much to learn to be summarized in two tutorials. Hopefully what you've learned in this tutorial will not only improve your knowledge in a few fields of Swing, but pique your interest in Swing as a whole and lead you to delve more into some areas of it.

In this tutorial, through only a few quick lessons, we've managed to turn our example application, the flight reservation system, from a basic application that merely provided functionality to a more professional application that solved all the issues that Swing applications need to deal with. We started with an application that looked like this:

flight reservation system at beginning

And turned it into this:

flight reservation system at end

For completeness, let's review the important lessons learned from this tutorial:

  • Intermediate-level JTable: In addition to learning how many of a table's properties can be changed to quickly change how a table looks, you also learned the following things about the JTable:
    • TableRenderer s: You learned that you aren't constrained by the JTable's built-in color scheme for its cells. By creating your own TableRenderer , you can control how each cells appears in the table, when it is selected or deselected, and even how to provide feedback about the application (as when we grayed out unavailable flights).
    • TableModel : In addition to controlling the data, the TableModel can also control how data is aligned in the column, and whether a cell can be edited.
    • TableEditor s: The JTable has many built-in editors for common classes that appear in data ( String s, Integer s), but creating a custom editor for other data is difficult and beyond the scope of this tutorial.
    • TableSorter : Users have come to expect that they'll be able to sort their table's data in a meaningful way by clicking on the column headers. The JTable that is shipped with Swing does not support sorting at all, but Sun provides a TableSorter class (that isn't shipped with the JDK) that takes care of sorting for you.
  • Thread safety: Thread safety is an important issue in Swing, as poor thread management can lead to a user getting locked out of the application or an ugly gray rectangle appearing instead of the application UI.
    • SwingUtilities.invokeLater() should be used whenever a thread besides the event-dispatch thread needs to perform UI work.
    • The SwingWorker class (another class not shipped in the JDK but published by Sun) should be used on the event-dispatch thread whenever a time-consuming action (like querying a database) must be performed.
  • Custom components: While Swing provides nearly every component you could want, sometimes these components don't exactly work the way you want them too, or sometimes even the many components of Swing aren't enough to provide the widget your application needs. Modifying an existing component allows you to change the way a Swing widget works in your application. The best way to do this is to subclass the existing Swing widget and override the paintComponent() method to tailor the component to your needs. Creating a new custom component allows you to create original one-of-a-kind components not contained in Swing. The steps to follow to create a new component are to identify the components you'll need, lay them out properly, get them working with each other to get the new component working properly, and finally to wrap a public API around the new component to make it easy to work with.
  • Custom look and feel: Creating a custom look and feel is a very difficult, complex, and time-consuming process, and probably deserves a tutorial all its own. However, there are easy ways to quickly change some behavior in your application that you learned in this tutorial:
    • Use the UIManager to override the properties used in an existing LookAndFeel, and supply your own colors, fonts, and borders to tailor the existing look and feel to your needs.
    • Use the new Synth LookAndFeel, an addition to Swing that allows you to create custom look and feels far easier and quicker than before.

翻译自: https://www.ibm.com/developerworks/java/tutorials/j-medswing/j-medswing.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值