Java Look and Feel Introduction 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. Overview of Look and Feel 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 UIManager class 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); LookAndFeel class 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). UIDefaults class 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. UIResource interface 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); Basic Look and Feel 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. Through Color 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); } Through Components 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 Through UI Classes 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); } UI Class 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 [1] singleton 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). Methods 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. Conclusion
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, (Sebastopol: O’Reilly, 2003), 1012.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, (Sebastopol: O’Reilly, 2003), 1059. and Java Source Code for javax.swing.plaf.basic.BasicLookAndFeel Sun Mircosystems Inc. Appendix C: BlueButtonUI class The william.swing.plaf.blue.BlueButtonUI class ackage william.swing.plaf.blue;
mport mport mport mport mport mport mport
java.awt.*; java.awt.image.*; javax.swing.*; java.awt.geom.*; javax.swing.plaf.*; javax.swing.plaf.basic.*; javax.swing.text.View;
ublic 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;
ublic 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
ublic 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 espect 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 s 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
//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()); Appendix D: Resources 5 really good Resources 1. A good book on Java Swing preferably one with a chapter or two on look and feel 1. Java Swing 2nd edition from O’Reilly is what I recommend and used 2. ISBN 0-596-00408-7 2. The Java API Documentation 1. Always a good resource 2. http://java.sun.com/apis.html#j2se 3. The Java base API source code 1. See what the guys at Sun did 2. http://java.sun.com/j2se/downloads.html 4. Someone else’s look and feel source code 1. See what someone else did, the guys at Sun tend to be more complicated then need be 5. Java Developer’s forums 1. Get help from other developers 2. http://forum.java.sun.com/
n which there is only one instance of it in memory