Look
and Feel Foundation Classes
Appendix
A: UML Sequence Diagram
Appendix
B: System Color Reference
Appendix
C: BlueButtonUI class
Java Look and Feel
Java Swing allows
for the creations for of an independent look and feel of the graphical user
interface (GUI) from the functionality of that GUI. This allows for great
freedom for development of GUIs and the look and feel of the GUI. It provides a
way for quicker application development by separating the look and feel from
the functionality, because two teams can work simultaneously on a project with
little interaction.
Note: The
reader of this paper should have an understanding of Java Swing and the Java
2D API before proceeding. Understand, also, that the reader should reference
both the Java J2SE API and PowerPoint presentation for this paper. |
Of the many
questions that might come up when thinking about customizing a look and feel
the most important is “why bother?” There are several answers to this question
depending on the context that the developer faces. If the developer is a game
developer then the answer is simple; the designs of the current look and feels
available are not good for games. A good reason to bother is marketability of
the application. A company wants their applications to stand out from the rest.
Customizing a look and feel allows for distinct look and feels that may include
company logos and trademarks. Moreover, the application might not be running on
a personal computer and therefore the only option is a customized look and feel
because the standard ones are for personal computers.
In order to
understand how to make a pluggable look and feel package, the look and feel
developer must, first, understand how Java accomplishes look and feel for its
Swing components. Java separates Swing into two set of classes: lightweight and
heavyweight. The heavyweight classes delegate painting to the operating system with
some user input depending on how much control the operating system hands over
to the user. These classes consist of all the top level classes: JFrame, JDialog,
JWindow, and JApplet. The other set of classes are the Java Swing lightweight
classes which all inherit from JComponent and delegate the painting to a
separate UI class. When a lightweight Swing class is instantiated, it calls the
UIManager to retrieve the UI class that is responsible for painting that
particular class to the graphical device from the UIDefaults class. The
UIManager gets the UIDefaults class from the LookAndFeel class that is set by
the setLookAndFeel method. While much of this seems confusing, it isn’t. Especially,
once an understanding of all the individual pieces is accomplished. Appendix A
shows a UML sequence diagram of this.
To get started on creating a pluggable
look and feel, the look and feel developer must decide between two design
methods of creating a look and feel in Java. One is to create the look and feel
by extending the javax.swing.plaf package the other is to extend an existing
look and feel package, usually javax.swing.plaf.basic. It not recommended
extending a look and feel from the javax.swing.plaf if the look and feel is
going to be for a personal computer. This is because the javax.swing.basic
package has extended almost the entirety of the javax.swing.plaf package for
the developer to use. This allows the look and feel developer to pick and
choose which things about the look and feel to customize without having to
extended and implement everything. Further, in its implementation of the
java.swing.plaf package a basic principle is followed that allows for
customizing a look and feel very easily. This principle is the centralization
of components, color and UI classes within the LookAndFeel class. And finally,
the javax.swing.plaf.basic package paints the lightweight Swing components in expected
ways. However, the recommendation changes if the look and feel developer is
creating a look and feel for a device other than the computer screen. Then the
preferred method is extending the javax.swing.plaf package from scratch.
There are some foundation
classes to look and feel in Java that the look and feel developer must be aware
of and understand, with respect to look
and feel, before the developer can understand the methodologies for customizing
look and feel in Java. These classes are the UIManager class, the LookAndFeel
class, the UIDefaults class, the UIResource interface and the UI classes. With
a mastery of these classes the look and feel developer can customize the look
and feel any way s/he wants.
Look and Feel Foundation Classes
The UIManager
class is responsible for setting and changing the pluggable look and feel for the
lightweight Swing classes through its setLookAndFeel method. To set a pluggable
look and feel the developer calls on the UIManager.setLookAndFeel static
method. Passed to the method is a Sting argument or an instance of a
LookAndFeel class. If it is a String argument that String should be the fully
qualified name of the LookAndFeel class for the pluggable look and feel. If the
developer was setting the Metal look and feel s/he would pass either the String
“javax.swing.plaf.metal.MetalLookAndFeel” or new
javax.swing.plaf.metal.MetalLookAndFeel(). In order to switch the pluggable
look and feel the developer sets the new look and feel and then calls the
javax.swing.SwingUtilities’s method updateComponentTreeUI(Component c) on the
top level Swing components, the components that are holding all the other Swing
components. These are usually one of the heavyweight Swing class, JWindow,
JFrame, JApplet or JDialog. The reason for this is that each lightweight Swing
class needs to update its UI class. The updateComponentTreeUI(Component c)
notifies the lightweight Swing classes to allow for just that. The UIManager
class is, also responsible for retrieving the UI classes from the pluggable
look and feel, through its static method getUI(JCompnent c), and setting those
UI classes for the lightweight Swing classes. This is achieved by retrieving
the UIDefaults class from the LookAndFeel class and getting the UI class from
the UIDefaults class.
The following code switches a look
and feel from Blue to Metal:
UIManager.setLookAndFeel(new
William.swing.plaf.blue.BlueLookAndFeel()); JFrame
f = new JFrame(); … UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel"); SwingUtilities.updateComponentTreeUI(f); |
The center of the
look and feel is the LookAndFeel class. This class holds all the meta information
and sets all the customization of the look and feel to the UIDefaults class. There
are five abstract methods that must be implemented: getDescription(), getID(),
getName(), isNativeLookAndFeel() and isSupportedLookAndFeel(). The
getDescription() method will return a description of the look and feel. The
getID() method will return the ID for the look and feel that applications and
services can use to identify it. The getName() method will return the name of
the look and feel. The Java API notes that if the developer is extending an
existing look and feel, such as Motif, Metal, Windows or Mac, but “doesn't make
any fundamental changes” (Java API) then getID() should not be modified but getName()
should note a difference to identify the extended look and feel from the parent
look and feel. For example if the developer extended Metal in such a way then the
getID() would still return “Metal” but the getName() might return “Blue Metal”.
The isNativeLookAndFeel() method should return true if the look and feel is the
native look and feel for that operating system, i.e. the Windows look and feel
returns true on Windows machines. The isSupportedLookAndFeel() method will
return true if the underlying operating system supports this look and feel. It usually
will only return false if there are legal reasons. Cross platform look and
feels will usually always return true.
The
Metal look and feel returns the following meta information on a Windows
machine: Name:
Metal ID:
Metal Description: The Java(tm) Look and Feel Is
Native: false Is
Supported: true |
There
are three other methods that the developer might want to consider overriding:
initialize(), uninitialize() and provideErrorFeedback(Component c). The
initialize() method is called by the UIManager class before it gets the
UIDefaults class and is used to set varies aspects of your look and feel. On
the reverse side is the uninitialize() method. This is called the before
replacing the look and feel and used to unset varies aspects of the look and feel.
The provideErrorFeedback(Component c) is called when an illegal action is
preformed by the user “such as pasting into an uneditable JTextField that has
focus” (Java API).
The UIDefaults class is a hash table
of all the pieces of the look and feel. It is this class to which the developer
will add all the components of the look and feel and from which the UIManager will retrieve
components. The UIManager will get the UIDefaults class from the LookAndFeel
class of the look and feel package through the getDefaults() method of the
LookAndFeel class. The UIDefaults class is made of key/value pairs, where the
key is almost always a String and the value usually implements the UIResource
interface. If the value is a UIResource then it is almost always a component
object (Insets, Color, UI Class, etc.) and when it does not implement
UIResource then it is almost always a String. In order to set the key/value
pair the developer will need to create a single dimensional Object array that
contains the keys and values by alternating between key and the value to that
key. Then, once the array is created, it is passed to the putDefaults(Object[]
objs) method of the UIDefaults object.
Example of such an array:
/* Adding two
key values to table */ UIDefaults
table … Object[]
key_values = new Object[4]; key_values[0]
= “desktop”; // 1st key key_values[1]
= “#FFFFFF”; // 1st key’s value key_values[2]
= “Button.margin”; // 2nd key key_values[3]
= new InsetsUIResource(1,1,1,1);// 2nd key’s value table.putDefaults(key_values); Or UIDefaults
table … Object[]
key_values = { /* Key Value */ “desktop”, “#FFFFFF”, “Button.margin”, new InsetsUIResource(1,1,1,1) }; table.putDefaults(key_values); |
Many of the key/value pairs within
the UIDefaults class will only be used internally to the look and feel package
and will be created by the developer for the developer’s use. However, it is
this class that holds the matching between the UI classes and the UIClassID’s
that the lightweight Swing classes use to obtain the UI class from the
UIManager. Therefore, the developer is obligated to set all the UI classes
key/value pairs for the lightweight Swing class. The methodology behind the
UIDefaults class is that this class allows the developer to centrally control
the look and feel by having the UI classes pull default values for components
from the UIDefaults through the UIManager’s static methods. So while only the
UIClassID/UI class pairs need be set, it behooves the developer to set all the
default components (insets, colors, borders, etc.) to key/value pairs within
this class.
The UIResource
interface is an interface that has no methods to implement. Its use is that of
a flag. It allows the developer of the look and feel package, if the developer
implements it, to distinguish between user set components (colors, icons, etc.)
of a lightweight Swing class and those components set by the look and feel.
Through this distinguishing the developer and the look and feel package can
allow the user to override the look and feel components for a particular
instance of a lightweight Swing object. This way when a look and feel is set
for an application in which the Swing developer has set different components on
different objects, those changes to those particular objects will not be
overwritten by the look and feel. All of the default components within the
current look and feels implement this interface and therefore the developer is
strongly encouraged to do the same for her/his components. In order to obtain
this distinguishing the developer must call instanceof UIResource on the object
in question. There are several classes that implement this interface for convenience:
IconUIResource, BorderUIResource, etc.
Example of distinguishing
UIResource:
Color
background = UIManager.getColor(“Button.background”); if(c.getBackground()==null
|| (c.getBackground() instanceof UIResource)) c.setBackground(background); |
To understand the rest of customizing a
look and feel, and the last type of foundation class (UI classes), we will talk
in relation to extending the javax.swing.plaf.basic look and feel package. To
extend the javax.swing.plaf.basic the developer will first extend the
BasicLookAndFeel class into his/her own LookAndFeel class and implement the
five abstract methods. This gives the developer a compliable look and feel
package that will paint all the lightweight Swing classes. This is because the
developer has, at his/her disposal, an implementation of every UI class by
extending the javax.swing.plaf.basic look and feel package. But more
importantly, extension of the javax.swing.plaf.basic package allows for three
distinct methodologies of customizing the look and feel: through color, through
components and/or through CompnentUI classes (UI classes). The developer may apply
each alone or together with one or two of the other methodologies. The
BasicLookAndFeel class breaks these into three methods: initSystemColorDefaults(UIDefaults
table), initComponentDefaults(UIDefaults table) and initClassDefaults
(UIDefaults table); each is responsible for a different methodology.
The
first, and easiest, way to customize the look and feel is through color. When
extending the BasicLookAndFeel class the developer is given a method that
encapsulates this particular way of customization, the initSystemColorDefaults(UIDefaults
table) method. To do this customization the developer will associate system color
keys with UIResource color objects or String object representing color using
the HTML format for color, #RRGGBB. If setting ColorUIResource, a convenience
Color class that implements UIResource, objects as values then the developer
will add the object array to the UIDefaults table or if using String representations
of color calls the loadSystemColors(UIDefaults table, Object[] colors, boolean
native) method of the BasicLookAndFeel class. This method converts the Strings
to ColorUIResource objects and sets the system colors the developer did not
set. The first two arguments of the method are obvious, the last argument, the
boolean argument, is true if the developer wants the changes to be overwritten
by the operating system’s native colors; but, most of the time, the developer
does not want this so it will be set to false. The determining factor in using
one method or the other is whether or not the developer will be setting all or
some of the system colors. If s/he is setting
all the colors the former method should be used otherwise the latter should be
used. By setting various keys the developer has color control of various
groupings of components of lightweight Swing objects. With a little trail and
error, mastering this method of customization is easy. Appendix B gives a quick
reference to system colors.
Example of setting every system
color:
protected void initSystemColorDefaults(UIDefaults table) { ColorUIResource pr1 =
new ColorUIResource(new Color(127,127,255)); ColorUIResource pr2 =
new ColorUIResource(new Color(0,0,127)); ColorUIResource pr3 =
new ColorUIResource(new Color(63,63,191)); ColorUIResource pr4 =
new ColorUIResource(new Color(0,0,255)); ColorUIResource blk = new
ColorUIResource(Color.BLACK); ColorUIResource wht =
new ColorUIResource(Color.WHITE); ColorUIResource gry =
new ColorUIResource(Color.GRAY); Object[] colors = {
"desktop"
, pr1, /* Color of the desktop background */
"activeCaption" , pr3, /* Color for captions
(title bars) when they are active. */
"activeCaptionText" , wht, /* Text color for text in
captions (title bars). */
"activeCaptionBorder"
, blk, /* Border color for caption (title bar) window borders. */ "inactiveCaption" , pr1, /* Color
for captions (title bars) when not active. */
"inactiveCaptionText"
, gry, /* Text color for text in inactive captions (title bars). */
"inactiveCaptionBorder", gry, /* Border
color for inactive caption (title bar) window borders. */
"window"
, wht, /* Default color for the interior of windows */
"windowBorder"
, blk, /* Color of the window border */
"windowText"
, blk, /* Color of the window text */ "menu" , pr1, /*
Background color for menus */
"menuText"
, blk, /* Text color for menus
*/ "text" , pr1, /* Text
background color */
"textText"
, blk, /* Text foreground color */
"textHighlight"
, pr4, /* Text background color when selected */
"textHighlightText"
, wht, /* Text color when selected */
"textInactiveText"
, gry, /* Text color when disabled */
"control"
, pr1, /* Default color for controls (buttons, sliders, etc) */ "controlText" , blk, /* Default
color for text in controls (buttons, sliders, etc) */
"controlHighlight"
, pr4, /* Highlight color for controls */
"controlLtHighlight"
, wht, /* Lighter highlight color for controls */
"controlShadow"
, pr2, /* Shadow color for controls */
"controlDkShadow"
, blk, /* Dark shadow color for controls */
"scrollbar"
, pr3, /* Scrollbar background (usually the "track") */ "info" , wht, /*
Background color for informational text */
"infoText"
, blk /* Color
for informational text */ }; table.putDefaults(colors); } |
Example of setting only some system colors:
protected void initSystemColorDefaults(UIDefaults table) { Object[] colors = { "desktop", "#CC5500",
"activeCaption",
"#FFFFFF",
"activeCaptionText", "#000000" };
loadSystemColors(table, colors, false); } |
It
becomes evident, however, that the preceding method is limited. So this leads
to the next method of customizing look and feel, through components. To achieve
this the developer overrides the initComponentDefaults(UIDefaults table)
method. Within this method the developer associates components with particular
keys that the UI classes will use to retrieve the default values of those
components. The convention for the key naming is to name it by the type of
ComponetUI class that is going to use it and the component’s type, i.e. key
“button.border” would be the border component used by the ComponentUI button
class. To truly master this methodology the developer should become familiar
with implementing all the different components, borders, icons, fonts, insets,
etc. It is easiest to create a factory class that will return instances of your
components, which will be inner classes of your factory class. This approach is
best if you have multiple types of a particular component. This is because all
types of a component will be centrally located in one class so the management
is easier. For example, all your borders will be inner classes of and created
by your border factory class. Just as the developer centralizes the colors,
components and UI classes to the UIDefaults table so a factory class centralizes
the management of a particular type of component. This methodology, customizing
through components, is best for customizing check boxes, radio buttons and/or
other icon based components because it is the icon that makes those lightweight
Swing classes unique. An illustration of this is in the following example (see
figure 1 below to see the effects of changing the check box icon.) Note that the
every color component by default is mapped to one of the system colors, which
were set by the initSystemColorDefaults method, by the javax.swing.plaf.basic
package. Therefore, it is advisable that the developer not set color components
unless s/he has truly thought it over or wants more exacting control over a
particular UI class color. See Java Swing 2nd Edition by O’Reilly Appendix A
for a complete list of all the components set by the BasicLookAndFeel class.
Example of setting components:
protected void initComponentDefaults(UIDefaults table) {
super.initComponentDefaults(table); Object[] components
= {
"CheckBox.icon" ,
new CheckBoxIcon(),
"Button.background", pr4,
"Button.foreground", wht,
"Button.font" ,
new Font("Times",Font.BOLD|Font.ITALIC,10),
"RadioButton.icon" , IconFactory.getRadioButton() };
table.putDefaults(components); } |
Note:
That
super.initComponentDefaults(table) was called here but not in the
initSystemColorDefaults method. |
Figure 1: illustrating how changing
an icon can completely change the look and feel of the JCheckBox
|
Notes
about images: Where
images should be located All
the images that the look and feel uses should be located within the package
directory structure. The developer can obtain an URL object representing the
locate of a resource relative to your class path by invoking
this.getClass().getResource(String relative_path) method. Example
of a relative_path: “/william/swing/plaf/blue/checked.gif”
where
the package is: william.swing.plaf.blue.* How
to load images Use
the javax.swing.ImageIcon class for retrieving Images will guaranteed the
image will be loaded in the Image object when getImage() method is called on
this object. This is easier and safer than use the DefaultToolkit from the
java.awt.Toolkit class for loading images |
Though
the preceding methodology of customization gives even greater control it is
still limited. For example, the developer cannot achieve rounded buttons
through changing components. Hence, the last methodology of customization of
look and feel gives the developer full control of the painting of the
lightweight Swing object to the developer, through UI classes. To achieve this
the developer must override the initClassDefaults(UIDefaults table) and set the
key/value pairs of the UI classes, which the developer has developed, to
lightweight Swing class the developer wishes to change. The keys will be the
String returned by the lightweight Swing class’s getUIClassID() method and the
values will be the a String that holds the fully qualified name, the package
name plus the class name , of the UI class. The convention for naming the UI
class is name of the look and feel + UIClassID of the lightweight Swing class. This
methodology is best for those lightweight Swing classes that can not be
customized to the developer’s needs through one of the first two methodologies.
Example of setting UI classes:
protected void initClassDefaults(UIDefaults table) { super.initClassDefaults(table); //package
that the ComponentUI classes belong too String pkg =
"william.swing.plaf.blue."; Object[] classes = {
"RootPaneUI", pkg + "BlueRootPaneUI",
"PanelUI" , pkg +
"BluePanelUI", "MenuBarUI"
, pkg + "BlueMenuBarUI",
"MenuUI" , pkg +
"BlueMenuUI",
"ButtonUI" , pkg +
"BlueButtonUI" };
table.putDefaults(classes); } |
The
UI class, or ComponentUI class, is the class that is ultimately responsible for
painting how the lightweight Swing objects is going to look to the graphical
device. Fortunately for the developer the javax.swing.plaf.basic package has
implemented each of the ComponentUI classes for each lightweight Swing class;
this leaves the developer free to focus on painting the lightweight Swing
object. In order to create a ComponentUI class the developer needs only to
extend the ComponentUI class s/he wants to create from the
javax.swing.plaf.basic package and override one method, the createUI(JComponent
c) method. In overriding this method the developer has two choices of the type
of object to return. The developer can return a singleton or can return a new
instance of the object. The developer should be familiar with returning a new
instance. To return a singleton[1]
the developer returns a static class variable of the ComponentUI. The advantage
of this is that it will take up less memory, but the trade-off is every
lightweight Swing class that uses this singleton will share the same state
information. However, this can be overcome by using the lightweight Swing
object to hold the state information for look and feel. Returning a singleton
is the preferred method. Only when the lightweight Swing object can not hold
absolutely critical state information and the developer knows there will be a
low number of instances of this object is returning a new instance acceptable.
Examples of singleton and new
instance:
//From william.swing.plaf.blue.BlueButtonUI //which is responsible for painting JButtons //As a singleton. //Note the static
class variable private static
BlueButtonUI blueButtonUI = new BlueButtonUI(); … public static
ComponentUI createUI(JComponent c) { return blueButtonUI; } Or //As a new
instance public static
ComponentUI createUI(JComponent c) { return new
BlueButtonUI(); } |
The developer has now created a
ComponentUI class but has not customized it. In order to customize the
ComponentUI the developer needs to be aware of a few other methods. These are
installUI(JComponent c), uninstallUI(JComponent c), the sizing methods (getMinimumSize(), getMaximumSize(),
getPreferredSize()), and most
importantly paint(Graphics g, JComponent c).
The
installUI(JComponent c) method is called by the lightweight Swing object when
it sets the ComponentUI and passes itself to the method.
It is used to set the JComponent’s
look and feel state information through its look and feel set methods, i.e.
setBackground(Color bg), if the user has not set those properties. This can be
checked by retrieving the information from its look and feel get methods, i.e.
getBackground(), and doing instanceof UIResource if true then the user has not
set the property. If the user has not set a particular property then the
developer should set it by retrieving the default value from one of the
appropriate static methods from UIManager, i.e. getColor(Object key). (See code
for checking UIResourse) The other two functions to be preformed by this method
are to install listeners on the JComponent for setting its function state
information and registering keyboard actions. More often then not if the
developer is extending the javax.swing.plaf.basic package then this method
won’t be overwritten, and even if the developer does overwrite this method the
javax.swing.plaf.basic class s/he is extending will most likely have an
installListeners(JComponent c) method. On the reverse side, the
uninstall(JComponent c) is called before a new ComponentUI class is set on the
old ComponentUI class and is used to remove any listeners, keyboard actions and
defaults. The developer probably won’t override this method. The sizing
methods, getMinimumSize(), getMaximumSize() and getPreferredSize() are used by
varies layout managers for finding sizing of the JComponent, however some
layout managers ignore these, i.e. GridLayout and BorderLayout. Finally, the
most important method for customizing a look and feel for a JComponent, is the paint(Graphics
g, JComponent c). The developer is expected to obtain most of state
information, both functional (is the button pressed) and graphical (what type
of borders to use), from the JComponent c object that is passed in and paint it
to the Graphics g object. Appendix C shows the william.swing.plaf.blue.BlueButtonUI
class which shows these methods and shows the use of some other useful classes.
Example of a simple RootPanelUI
class that will always have a dark blue background:
package
william.swing.plaf.blue; import
java.awt.Color; import
java.awt.Graphics; import
javax.swing.JComponent; import
javax.swing.JRootPane; import
javax.swing.plaf.ComponentUI; import
javax.swing.plaf.basic.BasicRootPaneUI; public
class BlueRootPaneUI extends BasicRootPaneUI { public BlueRootPaneUI() { super(); } public void paint(Graphics g, JComponent c)
{ JRootPane p = (JRootPane)c; //Note the user can not change the
background color of the JRootPanel //developers should stay away from the
practice of force components on the //user g.setColor(new Color(0,0,127)); } public static ComponentUI
createUI(JComponent c) { return new BlueRootPaneUI(); } } |
Note
on transparence: The
effects of setting the alpha bit in Java for the Color object can result in
unpredicted effects so the developer is advised to use it will caution.
|
That is how
Java look and feel can be created and with a some practice and exploration of
different lightweight Swing classes the developer can truly call himself or
herself a look and feel developer. However, there is still future research that
needs to be done on developing a look and feel for Java Swing. Those topics are
GTK for Java 1.4, lazy and active values and how motion is achieved. Java 1.4
allows for the creation of look and feel through the GTK+, a GNU project for
creating a toolkit for creating user interfaces (http://www.gtk.org/). Lazy and
active values are types of UIResources that are loaded differently depending if
it is lazy or active. The final topic for future research is how motion is
achieved in Java look and feel, how are frames painted for minimization or how
are menus scrolled? Even so, with these
methodologies the look and feel discussed here the developer can completely
create the look and feel s/he wants.
Appendix A: UML Sequence Diagram
UML Sequence Diagram showing how a JComponent or lightweight
Swing object sets a ComponentUI or UI object:
Marc Loy, Robert Eckstein, Dave Wood, James Elliott and
Brain Cole, Java Swing, 2nd, (
Appendix B: System Color Reference
desktop Color of the desktop background
activeCaption Color for captions (title bars) when
they are active
activeCaptionText Text color for text in captions
(title bars)
activeCaptionBorder Border color for caption (title bar)
window borders
inactiveCaption Color
for captions (title bars) when not active
inactiveCaptionText Text color for text in inactive
captions (title bars)
inactiveCaptionBorder Border
color for inactive caption (title bar) window borders
window Default color for the interior of
windows
windowBorder Color of the window border
windowText Color of the window text
menu Background color for
menus
menuText Text color for menus
text Text background color
textText Text foreground color
textHighlight Text background color when selected
textHighlightText Text color when selected
textInactiveText Text
color when disabled
control Default color for
controls (buttons, sliders, etc)
controlText Default color for text in controls
(buttons, sliders, etc)
controlHighlight Highlight
color for controls
controlLtHighlight Lighter highlight color for controls
controlShadow Shadow
color for controls
controlDkShadow Dark shadow color for controls
scrollbar Scrollbar background (usually the
"track")
info Background color for
informational text
infoText Color for informational text
Marc Loy, Robert Eckstein, Dave Wood, James Elliott and
Brain Cole, Java Swing, 2nd, (
Appendix C: BlueButtonUI class
The
william.swing.plaf.blue.BlueButtonUI class
package william.swing.plaf.blue; import java.awt.*; import java.awt.image.*; import javax.swing.*; import java.awt.geom.*; import javax.swing.plaf.*; import javax.swing.plaf.basic.*; import javax.swing.text.View; public class BlueButtonUI extends BasicButtonUI { //The singleton
istance of BlueButtonUI static BlueButtonUI b
= new BlueButtonUI(); //Default background
and foreground Color background; Color foreground; //There will be only
one font for this these buttons Font font;
public BlueButtonUI() {
super(); } //The factory method
returns the singleton public static
ComponentUI createUI(JComponent c) { return b; } public void installUI(JComponent c) { //Since we know this
is a JButton it is safe to cast as an AbstractButton AbstractButton b =
(AbstractButton)c; //Setting the
default values from the UIDefaults table background =
UIManager.getColor("Button.background"); foreground =
UIManager.getColor("Button.foreground"); font =
UIManager.getFont("Button.font");
//Checking for user set values for foreground and background
before setting them //Note that the font
compnonent is not checked therefore the value from the UIDefaults table will //override the
user’s values (This is not recommended) further not all the defaults are set
if(c.getBackground()==null || (c.getBackground() instanceof
UIResource))
c.setBackground(background);
if(c.getForeground()==null || (c.getForeground() instanceof
UIResource))
c.setForeground(foreground); //Using
BasicButtonUI installListeners method to install listeners super.installListeners(b); /*Note that there
are no keyboard registations, therefore hit any of the keys will not invoke
an event*/ } //Paints a rounded button that is semi-transparent with lines public void paint(Graphics g, JComponent c) { //Once again it is
safe to cast as an AbstractButton because we know it is a JButton AbstractButton b =
(AbstractButton)c; //The
ButtonModel holds a lot of the functional state of the button ButtonModel model =
b.getModel(); //Casting to a Graphics2D
for convenience, this is safew because we know that the g object is really a
Graphics2D object Graphics2D g2 =
(Graphics2D)g; //Sets the
arcs widths and heights int arc_w =
(int)c.getHeight()/2; int arc_h = arc_w; Insets i = c.getInsets(); //Gets the
area for the text and icon to be painted in with respects to the insets Rectangle viewRect =
new Rectangle(i.left,i.top,b.getWidth()-(i.right+i.left),b.getHeight() -
(i.bottom + i.top)); //the area that the
text will be drawn in that will be defined //by
SwingUtilities.layoutCompoundLabel Rectangle textRect =
new Rectangle(0,0,0,0); //the area
that the icon will be drawn in that will be defined //by
SwingUtilities.layoutCompoundLabel Rectangle iconRect =
new Rectangle(0,0,0,0); //I have opted to
set the base font size on the size of the button this will cause the font
size to skrink or grow with respect to the button size int fontSize =
(int)c.getHeight()/3; if(fontSize<8) fontSize = 8; g2.setFont(new
Font(font.getName(),font.getStyle(),fontSize)); //modify text for
display, will add ... if clipped and //determine the text
area and icon area String text =
SwingUtilities.layoutCompoundLabel( c,
g2.getFontMetrics(), b.getText(), b.getIcon(),
b.getVerticalAlignment(), b.getHorizontalAlignment(),
b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect,
iconRect, textRect, b.getText() == null ? 0 : b.getIconTextGap()); //Starting with a
BufferedImage because the graphics object from a BufferedImage respects
composite overlay directives //NOTE the Graphics
object passed in to this method does not respect these directives BufferedImage
buffImg = new BufferedImage(c.getWidth(), c.getHeight(),
BufferedImage.TYPE_INT_ARGB); Graphics2D gbi =
buffImg.createGraphics(); //Retrieving
the state of the colors from the component which were set in the installUI
method Color back =
c.getBackground(); Color fore =
c.getForeground(); //creating a
semi-transparent background for the button Color bg = new
Color(back.getRed(),back.getGreen(),back.getBlue(),127); //Defining the color
of my borders Color wh =
Color.WHITE; Color gr = Color.GRAY; //if button is
pressed change the background to dark and switch the border colors (this
makes it appear that the button is pressed in) if (model.isArmed()
&& model.isPressed()) { Color d =
back.darker().darker().darker(); bg = new
Color(d.getRed(),d.getGreen(),d.getBlue(),127); wh = Color.GRAY; gr = Color.WHITE; } //set background
color gbi.setColor(bg);
gbi.fillRoundRect(0,0,c.getWidth(),c.getHeight(),arc_w,arc_h); //lay in the strips gbi.setColor(Color.BLACK);
gbi.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN,1.0f));
gbi.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON); for(int
j=0;j<c.getHeight();) {
gbi.fillRect(0,j,c.getWidth(),2); j=j+4; } //paint button image
g2.drawImage(buffImg,0,0,c); //Draw
borders (NOTE a better implementation would have created a borders object) g2.setColor(wh);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setStroke(new
BasicStroke(2.0f)); Arc2D.Double ar1; ar1 = new
Arc2D.Double(0,0,arc_w,arc_h,90,90,Arc2D.OPEN); g2.draw(ar1); ar1 = new
Arc2D.Double(c.getWidth()-arc_w,1,arc_w,arc_h,0,90,Arc2D.OPEN); g2.draw(ar1);
g2.fillRect(arc_w/2-2,0,c.getWidth()-arc_w+2,2);
g2.fillRect(0,arc_h/2-2,2,c.getHeight()-arc_h+2); g2.setColor(gr); ar1 = new
Arc2D.Double(c.getWidth()-arc_w,c.getHeight()-arc_h,arc_w,arc_h,270,90,Arc2D.OPEN); g2.draw(ar1); ar1 = new
Arc2D.Double(0,c.getHeight()-arc_h,arc_w,arc_h,180,90,Arc2D.OPEN); g2.draw(ar1);
g2.fillRect(c.getWidth()-1,arc_h/2-2,1,c.getHeight()-arc_h+8);
g2.fillRect(arc_w/2-8,c.getHeight()-2,c.getWidth()-arc_w+16,2); //painting
text
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2.setColor(fore); //draw the text at
the x of the textRect and the y textRect plus the font ascent. //"The font
ascent is the distance from the font's baseline to the top of most //alphanumeric
characters."(From Java API Doc on java.awt.FontMetrics.getAscent())
g2.drawString(text,(int)textRect.getX(),(int)textRect.getY()+g2.getFontMetrics().getAscent()); //If there
is an icon paint it at the x and y of the iconRect
if(b.getIcon()!=null)
b.getIcon().paintIcon(c,g,(int)iconRect.getX(),(int)iconRect.getY()); } } |
5 really good Resources