中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档 | 网通镜像
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> 程序开发 > 编程语言 > Java > 测试
JSpinner @ JDJ
作者:未知 时间:2005-08-10 22:36 出处:Java频道 责编:chinaitpower
              摘要:JSpinner @ JDJ
It's ironic how sometimes the simplest ideas can turn out to be the most development-intensive. This month's Widget Factory participant is the seemingly modest JSpinner control, which lets you constrain user interface selections by using arrow buttons or up/down keystrokes to increment or decrement values, typically in a field. JSpinner comes with a whole family of siblings to handle numbers, currency, percentage, date, time, lists and custom values. It supports multiple field elements, custom renderers and a compound model to make it all possible.

The table at right shows the various spinner controls we'll be implementing. JSpinner and JSpinnerField are the basic classes. The others stand as good examples of what can be done with a well-designed premise. You'll rarely tend to use JSpinner directly. You'll usually reach for JSpinnerField or one of its subclasses to do specific work. Figure 1 shows the JSpinner family at work.

It's worth noting that this installment of the Widget Factory has a large number of listings, but most of the classes involve only a small amount of code. The main classes, like JSpinnerField, DefaultSpinModel, DefaultSpinRangeModel and DefaultRenderer, do most of the work. The SpinTime-Model and SpinDateModel are relatively uncomplicated, for example, as are various JSpinnerField extensions. Several of the listings are merely interfaces that maintain flexibility in our design, supporting reuse and extensibility.

Architecture
The JSpinner architecture uses several interfaces to maximize flexibility. The SpinModel interface defines the methods required to access the model, which represents values in the JSpinner architecture. The SpinModel contains one or more instances of a class that implements the SpinRangeModel. Listing 1 shows the SpinModel interface. The SpinModel contains ranges that can be accessed by a field ID. These identifiers map directly onto the field identifiers provided by the text Format classes in the Java API.

The SpinModel always has an active field and can get and set a list of field IDs. This is important when you need to switch between locales because the subfield order is not always the same. We also support the ChangeListener interface so that views can be updated when the model changes.

The SpinRangeModel is very much like the BoundedRangeModel provided by the Swing API, but it supports the use of decimal values. Listing 2 shows how a SpinRangeModel allows you to set and get the currently selected value, a minimum and maximum value and an increment (extent), and whether the model wraps or not when it hits a boundary. In contrast, the Bounded-RangeModel doesn't permit the maximum value ever to be selected and is restricted to integer values. It also knows nothing about wrapping values, so it was necessary to invent a new model for the JSpinner controls. The BoundedRangeModel also supports the ChangeListener interface, which is used by the SpinModel to detect changes.

The SpinRenderer interface is designed to support custom renderers in JSpinner controls. The only required method is called getSpinCellRendererComponent and returns the rendering component. Listing 3 shows the SpinRenderer interface. We expect a reference to a JSpinnerField, the current value object, a flag indicating whether we have the focus, a Format instance and the field identifier for the currently selected field. We'll cover this in more detail when we implement the DefaultSpinRenderer.

Figure 2 shows the basic relationship between JSpinner, JSpinnerField and the simplest model configuration.

The JSpinnerField class is the parent of all the other widgets implemented in this article. The JSpinner class handles the buttons and up/down activity. It operates directly on the model and can be used for other purposes requiring up/down activities. That's why it was given the big "J" prefix. You can create an arbitrary SpinModel if you like, regardless of whether you use a view to watch the results. Notice that change events from the SpinRangeModel are sent to the SpinModel. This happens automatically and you can watch for the SpinModel events, comfortable in the certainty that you'll never miss any other change events.

The JSpinner Family of Classes

Spinner
Provides a pair of up- and down-arrow buttons and operates on a SpinModel to increment or decrement values. It handles keyboard events as well. It's up to other components to watch the model and update their views when they receive a ChangeEvent.
JSpinnerField
A basic numerical spinner that uses a DefaultSpinRenderer and DefaultSpinModel to manage a single range of values. A SpinModel may contain more than one SpinRangeModel, but the JSpinnerField requires only one. This is the base class for all the other family members. It takes responsibility for certain mouse events, listening for model changes and focus handling.
JSpinnerPercent
The percentage spinner uses the NumberFormat.getPercentInstance to format a locale-dependent percentage field.
JSpinnerList
The string list spinner uses a ChoiceFormat to format a list selection. This is a simple field, useful for handling small lists of selected options.
JSpinnerCurrency
The currency spinner uses the NumberFormat.getCurrencyInstance to format a locale-dependent currency field. This is a compound field that supports incrementing and decrementing the integer and decimal values independently of each other.
JSpinnerTime
The time spinner uses the DateFormat.getTimeInstance to format a locale-dependent time field. This implementation uses a SpinTimeModel to map the Calendar object onto a SpinModel. It is a compound field with three elements.
JSpinnerDate
The date spinner uses the DateFormat.getDateInstance to format a locale-dependent date field. This implementation uses a SpinDateModel to map the Calendar object onto a SpinModel. It is a compound field with three elements.
JSpinnerColor
This is a color selection spinner example of using a custom SpinRenderer.

Modeling
The SpinModel and SpinRangeModel interfaces need concrete implementations to provide the functionality they expose. The DefaultSpinModel and DefaultSpinRangeModel provide a generic set of capabilities that most of the controls in this article use. Listing 4 shows the DefaultSpinModel class. The internal list of SpinRangeModel instances is maintained by a hashtable that is accessed by a fieldID Integer. A separate, ordered list of fieldIDs is held in a Vector object, as are the registered change listeners. We also keep an activeField value to indicate the currently active SpinRangeModel.

To make life easier, we expose three constructors. The first simply creates an empty model. The second assumes we will use a single SpinRangeModel and takes the same set of arguments, creating the SpinRangeModel automatically. The third constructor assumes that we plan to use two SpinRangeModels and does the same thing, automatically creating both for us. More than two subfields would make the constructor too complicated, so we assume it's just as easy to add fields outside the constructor.

Listing 5 shows the DefaultSpinRangeModel class. The basics are pretty simple, with the constructor accepting each of the arguments and get/set accessors provided for each of the attributes. Worth noting, however, is that the stateChange event is not fired unless the setValueIsAdjusting method is called with a false argument. This is intended to defer the change event to avoid inconsistent states. My implementation is less robust than the Swing BoundedRangeModel, but it works well enough in practice.

The JSpinner class is provided in Listing 6. The constructor expects a SpinModel instance and creates the up and down buttons using the Swing BasicArrowButton class. We register JSpinner as both an ActionListener and a KeyListener to handle increment and decrement operations on the model.

Most of the code that acts on the model is in the increment and decrement methods that handle boundary conditions, deciding whether or not to wrap. The JSpinner class also handles right- and left-arrow keystrokes and changes the active field in the SpinModel. To make this work, you have to register JSpinner as a KeyListener from elsewhere, since the buttons never really get the focus.

JSpinnerField
The JSpinnerField (see Listing 7) is the simplest instance of the JSpinner family of controls, but because it's the parent of all the other family members, it's designed to handle general circumstances. As such, it contains more code than most of the other classes in this article. There are three constructors to let us create an empty JSpinnerField, one with a single SpinRangeModel, or one with an arbitrary model, renderer and field Format class. Because we need to refresh the view after construction, we delegate subcomponent creation to an init method. This allows subclasses to control the call to refreshSpinView, which tends to be specific to selected implementations.

The setLocale method sets a localized instance of the Format class associated with the control. This is also overridden by child implementations. The updateField-Order method is required to determine what the correct field order is for a given locale. This is handled in a separate utility class called LocaleUtil (see Listing 8), which effectively sorts the fields based on their starting location in the Format object. It also implements a findMouseInField method that lets us determine which field is active when the mouse is clicked on a JSpinnerField.

The JSpinnerField does the rendering through a SpinField class that expects a reference to the JSpinnerField object. As you can see in Listing 9, this class extends JPanel and uses a Swing CellRendererPane to do the actual rendering. The paintComponent call gets the current SpinRenderer and calls its getSpinCellRendererComponent method. We also override the getPreferredSize and getMinimumSize to return the renderer's preferred and minimum sizes.

Listing 10 shows the DefaultSpinRenderer, which extends JTextField and sets the editable flag to false. The getSpinCellRendererComponent method returns the current instance after calling setText with the current value object formatted by the Format instance. It also uses the LocaleUtil.getFieldPosition method to determine what the current selection range is for display if we have the focus.

Simple Extensions
Having just covered the DefaultSpinRenderer, let's take a look at the JSpinnerColor class, which uses a custom renderer to spin through a short set of colors. The ColorSpinRender, shown in Listing 11, is pretty uncomplicated; it ignores most of the getSpinCellRendererComponent arguments and expects to see a Color object as the value. We use a white border to indicate the focus.

Listing 12 shows how simple the JSpinnerColor class is to implement. We extend the JSpinnerField class with a custom spin renderer and a simple model that ranges from zero to the length of our list. We store our list of Color objects in a Vector for convenience. To avoid formatting issues, we declare an empty updateFieldOrder method. We override the refreshSpinView to update the view when the selection changes. All we have to do is get the active model field and range, and set the current list selection based on the active model value. Calling setValue on the SpinField gives the renderer access to the active object.

The JSpinnerList class (see Listing 13) does the same kind of thing without the extra complication of a custom renderer, given that it handles a string list. It overrides the setLocale method because explicit strings are not localizable. The JSpinnerList and JSpinnerColor widgets demonstrate how easy it is to subclass JSpinnerField to create customized behavior. There is no restriction in the data you choose to represent, since both the model and renderer are under your control. Of course, these implementations are not internationalizable unless you use resource bundles or account for it directly in your code.

Internationalizable Spinners
The last four variations on our theme capitalize on the JSpinnerField infrastructure to handle internationalization through the Java Format class. Listing 14 shows the JSpinnerPercent class, which extends JSpinnerField and implements very little code. The constructor creates a model that uses 0.01 as an increment and sets NumberFormat.getPercentInstance() as the format class. We override setLocale to reset the Format class if necessary.

Listing 15 shows the JSpinnerCurrency control, which is similar but extends the model to use two ranges. One handles the integer portion of our currency field and the other handles the decimal value. We set the NumberFormat.getCurrencyInstance() format in both the constructor and the setLocale methods. The only other thing we need to do is override the refreshSpinView method to properly set the value from the two model ranges. Figure 3 shows the relationship between classes in the JSpinnerCurrency control.

The JSpinnerTime and JSpinnerDate classes are in Listings 18 and 19, respectively. They both override the constructor, setLocale and refreshSpinView methods, but are otherwise unencumbered. Listing 20 shows the JSpinnerTest harness used to test the controls. Figure 4 shows the way it looks when the French locale is selected.

Summary
As you've seen, despite all the listings, JSpinner controls are actually quite simple to use. By providing customizable model and renderer interfaces, the variations you can implement are wide open, as they should be with any open architecture. All the internationalization issues are essentially transparent, thanks to the Format classes provided in the Java API. It would have been easy to write this article around a simpler implementation, but the strength of these widgets is largely in their customizability and in the lessons learned from effective design. I hope you'll agree that even this simple widget turned out to be instructive and worth the investment.

关闭本页
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有