JETZT ONLINE BESTELLEN
Tips and Tools for Killer GUIs
First Edition Juli 2005
ISBN 978-0-596-00907-6
542 Seiten
EUR24.50
Weitere Informationen zu diesem Buch
Inhaltsverzeichnis |
Kolophon |
Rezensionen |
Inhaltsverzeichnis
- Chapter 1: Basic JComponents
- InhaltsvorschauSwing is a powerful toolkit, filled to the brim with complicated components, extension APIs, and large Model-View-Controller (MVC) systems. It can be quite daunting. The current edition of O'Reilly's Java Swing book now stretches over 1,200 pages! Swing now extends from the simplest
JButtonto the full Look and Feel API. I am still amazed at the power and flexibility of Swing, and quite aware of its complexity. Some of the more esoteric parts can take years to master. However, you don't need to go straight into theJTreeor Look and Feel APIs just to do something cool. There are still a lot of fun things waiting in the standard components we don't always think about.This chapter covers some of the basic components that every Swing developer uses: buttons, labels, menus, and the occasional scroll pane. From this base you will learn how to create image buttons, put watermarks into your text areas, and even build a new component or two. These are the components that seem boring, but with a little imagination, they can do a whole lot, and the techniques here lay the foundation for even more exciting hacks later in the book.This hack shows how to use Swing's built-in image support to create a completely custom image-based user interface.Most Swing applications get their look from a Look and Feel (L&F)—either a standard one provided by the VM or a custom one. L&Fs are a whole lot of work to build and still aren't completely custom. You can redefine a button to look like red stoplights, but then all buttons throughout your application will look like red stoplights. Sometimes all you really want is a look built entirely out of images, much like image-based web navigation.To give you an idea of where this hack is going, Figure 1-1 shows our target: a frame with a panel containing a label, a button, and a checkbox. The panel, label, and button will be completely drawn with images, using none of the standard L&F. The checkbox will be a standard checkbox, but it should be transparent to fit in with the image background.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 1–12: Introduction
- InhaltsvorschauSwing is a powerful toolkit, filled to the brim with complicated components, extension APIs, and large Model-View-Controller (MVC) systems. It can be quite daunting. The current edition of O'Reilly's Java Swing book now stretches over 1,200 pages! Swing now extends from the simplest
JButtonto the full Look and Feel API. I am still amazed at the power and flexibility of Swing, and quite aware of its complexity. Some of the more esoteric parts can take years to master. However, you don't need to go straight into theJTreeor Look and Feel APIs just to do something cool. There are still a lot of fun things waiting in the standard components we don't always think about.This chapter covers some of the basic components that every Swing developer uses: buttons, labels, menus, and the occasional scroll pane. From this base you will learn how to create image buttons, put watermarks into your text areas, and even build a new component or two. These are the components that seem boring, but with a little imagination, they can do a whole lot, and the techniques here lay the foundation for even more exciting hacks later in the book.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Create Image-Themed Components
- InhaltsvorschauThis hack shows how to use Swing's built-in image support to create a completely custom image-based user interface.Most Swing applications get their look from a Look and Feel (L&F)—either a standard one provided by the VM or a custom one. L&Fs are a whole lot of work to build and still aren't completely custom. You can redefine a button to look like red stoplights, but then all buttons throughout your application will look like red stoplights. Sometimes all you really want is a look built entirely out of images, much like image-based web navigation.To give you an idea of where this hack is going, Figure 1-1 shows our target: a frame with a panel containing a label, a button, and a checkbox. The panel, label, and button will be completely drawn with images, using none of the standard L&F. The checkbox will be a standard checkbox, but it should be transparent to fit in with the image background.
Figure 1-1: A component rendered with imagesThe first step toward image nirvana is the background. Because this type of component is quite reusable, I built a subclass ofJPanelcalledImagePanel, shown in Example 1-1.Example 1-1. A Custom subclass of JPanelpublic class ImagePanel extends JPanel { private Image img; public ImagePanel(Image img) { this.img = img; Dimension size = new Dimension(img.getWidth(null), img.getHeight(null));setSize(size); setPreferredSize(size); setMinimumSize(size); setMaximumSize(size); setLayout(null); } }The constructor takes the image to draw and saves it for later use in theimgvariable. Then it callssetSize() andsetPreferredSize()with the size of the image. This ensures that the panel will be the size of the image exactly. I had to set the preferred, maximum, and minimum sizes as well—this is because the panel's parent and children may not be using absolute layouts.Absolute layout means that there is no layout manager to position the components appropriately (which can be set by callingEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Don't Settle for Boring Text Labels
- InhaltsvorschauJLabel is a Swing staple; but it's easy to spruce up boring labels with drop shadows, outlines, and even 3D text.When you want to draw non-editable text, Swing provides only the
JLabel. You can change the font, size, color, and even add an icon. By using HTML in your components [Hack #52] , you can even add things like underline and bullets. This is fine for most jobs, but sometimes you need more. What if you want a drop shadow or an embossed effect? TheJLabelis simply inadequate for richer interfaces. Fortunately, the Swing Team made it very easy to extend theJLabeland add these features yourself.A great many text effects can be achieved with two simple features. First, you can draw text multiple times, with each iteration slightly offset or in a different color, to create effects like drop shadows and embossing. Second, you can adjust the spacing between letters in a word (a feature known as tracking in text-processing circles). Tracking is always specified in addition to the default tracking specified by a font. Thus, a tracking of +1 would be drawn as one extra pixel between each letter. A tracking of 0 would have the same spacing as no extra tracking at all.To implement all of this, you must override both the sizing and the painting code inJLabel, which of course calls for a subclass; see Example 1-5 for details.Example 1-5. Defining a richer JLabelpublic class RichJLabel extends JLabel { private int tracking; public RichJLabel(Stringtext, int tracking) { super(text); this.tracking = tracking; } private int left_x, left_y, right_x, right_y; private Color left_color, right_color; public void setLeftShadow(int x, int y, Color color) { left_x = x; left_y = y; left_color = color; } public void setRightShadow(int x, int y, Color color) { right_x = x; right_y = y; right_color = color; }RichJLabelextends the standardjavax.swing.JLabeland adds a tracking argument to the constructor. Next, it adds two methods for the right and left shadow. These are called shadows because they will be drawn below the main text, but whether they actually look like shadows depends on the color, as well as the x- and y-offsets passed into each method.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Fill Your Borders with Pretty Pictures
- InhaltsvorschauSwing comes with a set of customizable borders, but sometimes you want more than they provide. This hack shows how to create a completely imagebased border that can be resized.Swing has a prefabricated border, called the
MatteBorder, which can accept an image in its constructor. For simple tiled backgrounds, such as a checkerboard pattern, this works fine. However, if you want to have particular images in each corner, creating a fully resizable image border, then you'll need something more powerful. Fortunately, Swing makes it very easy to create custom border classes. The image border in this hack will produce a border that looks like Figure 1-12.
Figure 1-12: An image-based borderThe first step to any custom border is to subclassAbstractBorderand implement thepaintBorder()method. The class will take eight images in the constructor, one for each corner and each side; all the code is shown in Example 1-6.Example 1-6. Building an image-based borderpublic class ImageBorder extends AbstractBorder { Image top_center, top_left, top_right; Image left_center, right_center; Image bottom_center, bottom_left, bottom_right; Insets insets; public ImageBorder(Image top_left, Image top_center, Image top_right, Image left_center, Image right_center, Image bottom_left, Image bottom_center, Image bottom_right) { this.top_left = top_left; this.top_center = top_center; this.top_right = top_right; this.left_center = left_center; this.right_center = right_center; this.bottom_left = bottom_left; this.bottom_center = bottom_center; this.bottom_right = bottom_right; } public voidsetInsets(Insets insets) { this.insets = insets; } public Insets getBorderInsets(Component c) { if(insets != null) { return insets; } else { return new Insets(top_center.getHeight(null), left_center.getWidth(null), bottom_center.getHeight(null), right_center.getWidth(null)); } }The two methods after the constructor control the border insets. These are the gaps between the panel's outer edge (and its parent) and the inner edge of the panel where the panel's children are drawn.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Display Dates in a Custom Calendar
- InhaltsvorschauYou can download calendar components from third parties, but real hackers can use Swing to build a custom calendar widget on their own.When you design an application, you'll often want to use standard widgets to display information. Swing doesn't always give you what you need, though. Consider the calendar component: Swing doesn't come with one, so most users have to download widgets to integrate into their application. However, why not go with a cool and hip teen-friendly application with an attractive, image-based component, as shown in Figure 1-15?
Figure 1-15: Custom calendar componentThat would be a bit more fun, wouldn't it? This hack will show you how to build a completely custom calendar component usingjava.util.Calendarand a few images.First, consider what you'll need. You've got to have pretty images, a component to paint them on, and then some logic to handle the different parts of the date, including what day of the week starts off the current month. You should also provide asetDate()method, so that MVC frameworks can play well with your calendar. Let's get started.I created three images in Photoshop: one for the background, one for each day, and one for the current day. These are shown in Figures 1-16, 1-17, and 1-18.
Figure 1-16: calendar.png for the general background
Figure 1-17: day.png for the day backgrounds
Figure 1-18: highlight.png for the current dayI could have separated the day names and the title, but since they don't change, it was simpler to make them part of the image.The easiest way to create a custom component with fancy drawing is to start off with aJPaneland override thepaintComponent()method, as shown in Example 1-9.Example 1-9. A Calendar base componentpublic class CalendarHack extends JPanel { protected Image background, highlight, day_img; protected SimpleDateFormat month = new SimpleDateFormat("MMMM"); protected SimpleDateFormat year = new SimpleDateFormat("yyyy"); protected SimpleDateFormat day = new SimpleDateFormat("d"); protected Date date = new Date(); public void setDate(Date date) { this.date = date; } publicCalendarHack() { background = new ImageIcon("calendar.png").getImage(); highlight = new ImageIcon("highlight.png").getImage(); day_img = new ImageIcon("day.png").getImage(); this.setPreferredSize(new Dimension(300,280)); } public void paintComponent(Graphics g) { ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.drawImage(background,0,0,null); g.setColor(Color.black); g.setFont(new Font("SansSerif",Font.PLAIN,18)); g.drawString(month.format(date),34,36); g.setColor(Color.white); g.drawString(year.format(date),235,36); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add a Watermark to a Text Component
- InhaltsvorschauThis hack will show how to create a custom image background for the
JTextField, a complex Swing component that does not already support backgrounds or icons by default.One of Swing's most underused features is the ability to partially override drawing code. Most programs enhance widgets by using renderers or completely overriding the paint code. By only partially overriding the drawing, however, you can create some very interesting effects that blend both new and existing drawing commands.Some components, likeJListandJTable, use renderers to customize their look. To put a background in aJTextField, however, requires more. The plan is to subclassJTextField, prepare the resources for drawing a background (loading the image, etc.), and then draw a new background while preserving the normalJTextFielddrawing code for the text and cursor.The actual drawing will be done with aTexturePaint. Java2D allows you to fill any area with instances of thePaintinterface. Typically you use a color, which is an implementation ofPaint, but it is possible to use something else, such as a texture or gradient. This class will use aTexturePaintto tile an image across the component's background.The first step is to create aJTextFieldsubclass (shown in Example 1-10).Example 1-10. Preparing a field for watermarkingpublic class WatermarkTextField extends JTextField { BufferedImage img; TexturePaint texture; public WatermarkTextField(File file) throws IOException { super(); img = ImageIO.read(file); Rectangle rect = new Rectangle(0,0, img.getWidth(null),img.getHeight(null)); texture = new TexturePaint(img, rect); setOpaque(false); } }Example 1-10 creates a class calledWatermarkTextField. It is a subclass ofJTextFieldwith a custom constructor that accepts aFileobject containing an image. It also defines two member variables:imgandtexture. After the obligatory call tosuper(), the constructor reads the file into theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Watermark Your Scroll Panes
- InhaltsvorschauThis hack creates a text area with a tiled background image that is fixed, even when the text area scrolls, and also a fixed foreground image that appears above the text, much like the station badges now affixed to the lower-righthand corner of most TV broadcasts.The Swing framework was designed to let developers override portions of every component, both the visual appearance (the view) and the behavior (the model and controller). This design gives developers great flexibility. One of my favorites is the
JScrollPane. Its nested composite design allows developers to create some stunning effects.Once again, the idea is to override the drawing code of a standard component to create the visual effects [Hack #5] . The difference here is that you must deal with a composite object, theJScrollPane. AJScrollPaneis not a single Swing component—it's actually a wrapper around two scrollbars and the component that does the real scrolling is aJViewport. This viewport is the actual target component; you will subclass it to draw both above and below theViewcomponent (as seen in Example 1-12). TheViewis the Swing widget being scrolled; in this case, it is aJTextArea.Example 1-12. Modifying the viewport for watermarkingpublic class ScrollPaneWatermark extends JViewport { BufferedImage fgimage, bgimage; TexturePaint texture; public void setBackgroundTexture(URL url) throws IOException { bgimage = ImageIO.read(url); Rectangle rect = new Rectangle(0,0, bgimage.getWidth(null),bgimage.getHeight(null)); texture = new TexturePaint(bgimage, rect); } public void setForegroundBadge(URL url) throws IOException { fgimage = ImageIO.read(url); }TheScrollPaneWatermarkclass inherits fromJViewport, adding two methods:setBackgroundTexture() andsetForegroundBadge(). Each takes aURLinstead of a File to allow for images loaded from places other than the local disk, such as a web server or JAR file.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Put a NASA Photo into the Background of a Text Area
- InhaltsvorschauThis hack will repurpose an existing web page, one of NASA's photo sites, by pulling their "Astronomy Picture of the Day" into the background of a text area.You've already learned how to draw a watermark image in the background of a text area [Hack #6] using a
ScrollPaneWatermark. This hack will pull a photo down from the Web and reuse that class to put the photo in the background. The photo itself comes from NASA's "Astronomy Picture of the Day" page: http://antwrp.gsfc.nasa.gov/apod/. The URL to the image changes each day, but the page itself does not. To pull the image down you will load the page, find the image URL, then load the image itself and put it into theScrollPaneWatermark. Depending on the day, it may look something like Figure 1-22.
Figure 1-22: Text area with a background imageThe code in Example 1-14 defines a class calledBackgroundLoader, which implementsRunnableso it can be placed on its own thread. The constructor takes as an argument theScrollPaneWatermark, which the loader will put the image into. Therun()method contains a loop that will run every two hours, loading the page, finding theSRCURL, then loading the image into the watermark.Example 1-14. A thread to load a background imagepublic class BackgroundLoader implements Runnable { private ScrollPaneWatermark watermark; public BackgroundLoader(ScrollPaneWatermark watermark) { this.watermark = watermark; } public void run() { while(true) { try {String base_url = "http://antwrp.gsfc.nasa.gov/apod/"; URL url = new URL(base_url); Reader input = new InputStreamReader(url.openStream()); char buf[] = new char[1024]; StringBuffer page_buffer = new StringBuffer(); while(true) { int n = input.read(buf); if(n < 0) { break; } page_buffer.append(buf,0,n); } // Locate the Image URL (see next section) } catch (Exception ex) { System.out.println("exception: " + ex); ex.printStackTrace(); } } } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Animate Transitions Between Tabs
- InhaltsvorschauThis hack shows how to create animated transitions that play whenever the user switches tabs on a
JTabbedPane.One of Swing's great strengths is that you can hack into virtually anything. In particular, I love making changes to a component's painting code. The ability to do this is one of the reasons I prefer Swing over SWT. Swing gives me the freedom to create completely new UI concepts, such as transitions.With the standard paint methods, Swing provides most of what you will need to build the transitions. You will have to put together three additional things, however. First, you need to find out when the user actually clicked on a tab to start a transition. Next, you need a thread to control the animation. Finally, since some animations might fade between the old and new tabs, you need a way to provide images of both tabs at the same time. With those three things, you can build any animation you desire.To keep things tidy, I have implemented this hack as a subclass ofJTabbedPane, except for the actual animation drawing, which will be delegated to a further subclass. By putting all of the heavy lifting into the parent class, you will be able to create new animations easily.Example 1-16 is the basic skeleton of the parent class.Example 1-16. A skeleton for the transition managerpublic class TransitionTabbedPane extends JTabbedPane implements ChangeListener, Runnable { protected int animation_length = 20; public TransitionTabbedPane() { super();this.addChangeListener(this); } public int getAnimationLength() { return this.animation_length; } public void setAnimationLength(int length) { this.animation_length = length; }TransitionTabbedPaneextends the standardJTabbedPaneand also implementsChangeListenerandRunnable.ChangeListenerallows you to learn when the user has switched between tabs. Since the event is propagated before the new tab is painted, inserting the animation is very easy.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Blur Disabled Components
- InhaltsvorschauThis hack explores creating how to perform a blur transformation on a Swing component.Every Swing component draws to the screen via the
paintComponent() method. This is true even for components that offload the actual drawing to Look and Feel UI objects. Because all drawing goes through thepaintComponent()method at some point, this point is where you can do some interesting things by manipulating the graphics object during the paint process.Swing components draw to theGraphicsobject passed in through thepaintComponent()method. This means that if you replace theGraphicsobject with a custom version, you can capture a component's drawing into a bitmap instead of going straight to the screen.Blurring is a pixel-level operation, meaning the actual blurring is done pixel-by-pixel in a bitmap. By drawing the component to a bitmap, blurring that bitmap, and then drawing the bitmap in the place of the component, you can effectively have a blurred component without disturbing the rest of the Swing painting routines. The particular implementation in this hack uses a blurred effect to replace the normal graying of a component when it is disabled.The first step is to capture the button into a bitmap, as shown in Example 1-20.Example 1-20. Creating a blurrable buttonpublic classBlurJButton extends JButton { public BlurJButton(String text) { super(text); } public void paintComponent(Graphics g) { if(isEnabled()) { super.paintComponent(g); return; } BufferedImage buf = new BufferedImage(getWidth(),getHeight(), BufferedImage.TYPE_INT_RGB); super.paintComponent(buf.getGraphics()); // Blur the buffered image (see next section) } }TheBlurJButtonclass extends a normalJButtonand overrides thepaintComponent()method. If the button is enabled (neither disabled nor grayed out), then it calls the superclass's normal version ofpaintComponent()and returns. If the button is disabled, however, thenEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Building a Drop-Down Menu Button
- InhaltsvorschauThis hack shows how to build a color chooser as a proper drop-down component. It will behave like
JComboBoxbut without the extension headaches of Sun's version of the class.Most custom Swing components are created with simple subclasses of the standard base classes injavax.swing. This works fine most of the time, but every now and then you need to build something where there is no easy standard component to start with. Even worse, sometimes the obvious choice for your starting point is a component so convoluted that you can't figure out where to start. Still, you'd rather not reimplement the wheel. No, I'm not talking aboutJTreeorJTable—I'm referring to theJComboBox. It seems like such a simple component, but the implementation is fiendishly complex.Most large applications use components that feel like theJComboBox, but do something entirely different, like select a color or show a history list. A quick search through theJComboBoxAPI doesn't turn up any obvious extension points. You could customize it with some cell renderers, but if you need a component that doesn't show a list of data, you are pretty much out of luck. The source toJComboBoxis not very helpful either. The work is spread out over several UI classes in the various Look and Feel (L&F) packages. If you did customize one of those, your component would look out of place when used in a different L&F. The only real option is to write your own combo box, which is pretty easy except for the actual drop-down part. You need to show a component on top of the others, poking out of the frame occasionally, but without any decorations of its own. It should be just a borderless floating box. Digging through Swing's source code reveals the secret ingredient: aJWindow.JWindowis a subclass ofWindowbut not ofFrame. This means it has no decorations on the side, and it is hidden from the Dock and Taskbar. This is exactly what you want from a pop up. Care must be taken when creating it, however, as you must ensure the window appears only on top of the existing components, and that it disappears when something else gains focus or the window moves. Fortunately, you can do all of this with one composite component and a few event listeners.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Create Menus with Drop Shadows
- InhaltsvorschauThis hack explores a simple way to create drop shadows on menus throughout an entire application with minimal code changes.Many modern operating systems provide menus with interesting effects to make them jump off the screen. One of the most common is the drop shadow. Some programs even provide shadows themselves when the host operating system does not. For years, a lack of low-level graphics support has denied Swing programs access to these kinds of cool effects. But not any more! Most of the effects can be duplicated with Swing's robust theming ability.Most custom effects require either subclassing a component or messing with graphics overlays. I tried a variety of techniques to create this hack, but I kept coming across the same problem over and over. If I wanted to draw a shadow, I had to change the sizing of each menu item, plus its background, plus the pop-up frame itself. That is a lot of components to manage. It would be a lot simpler if I could tell the components to make themselves a little bit bigger and give me the extra slice of screen real estate to draw in. The solution was right under my nose: the border. Every Swing component can use a custom border, without subclassing, and the border will automatically resize the component to fit. If the border is lopsided, then it will create a kind of shadow effect. Perfect!Every standard Swing component is actually drawn by a UI helper class, and pop-up menus are no exception. I took the
BasicPopupMenuUIin thejavax. swing.plaf.basicpackage and created a subclass calledCustomPopupMenuUI(shown in Example 1-25). It only does two things special: adds a custom border to the pop up's parent panel and sets the panel to be transparent.Example 1-25. Extending the pop-up menu's UIpublic class CustomPopupMenuUI extends BasicPopupMenuUI { public static ComponentUI createUI(JComponent c) { return new CustomPopupMenuUI(); } public Popup getPopup(JPopupMenu popup, int x, int y) { Popup pp = super.getPopup(popup,x,y); JPanel panel = (JPanel)popup.getParent(); panel.setBorder(newShadowBorder(3,3)); panel.setOpaque(false); return pp; } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add Translucence to Menus
- InhaltsvorschauIn this hack I will show you how to add true translucency to your menus with only a slight modification to your program.Computer interfaces are pretty sophisticated these days. Years ago, we considered ourselves lucky to simply have menu bars at all; now, we need menus with sophisticated effects like animation, shadows, and translucency.You've already seen how to achieve visual effects by overriding the
paint()method of a parent component and then rendering the children into a buffer [Hack #9] . It would be nice to do the same thing here, but there's just one small problem. Overriding thepaint()method of theJMenuwouldn't do any good because theJMenudoesn't draw what we think of as a menu—a list of menu items that pop up when you click on the menu's title. TheJMenuactually only draws the title at the top of a menu. The rest of the menu is drawn by aJPopupMenucreated as a member of theJMenu. Unfortunately this member is markedprivate, which means you can't substitute your ownJPopupMenusubclass for the standard version.Fortunately there is a way out. Like all Swing components, the menu components delegate their actual drawing to a separate set of Look and Feel classes in thejavax.swing.plafpackage. If you override the rightplafclasses for the menu items and pop-up menu, then you should be able to create the desired translucent effect. It just takes a little subclassing.AllMenuItemsare implemented by some form of thejavax.swing.plaf. MenuItemUIclass. When creating custom UI classes, it is always best to start by subclassing something in thejavax.swing.plaf.basicpackage (in this case,BasicMenuItemUI) because it handles most of the heavy lifting for you, as shown in Example 1-28.Example 1-28. Extending the basic UIpublic class CustomMenuItemUI extends BasicMenuItemUI { public static ComponentUI createUI(JComponent c) { return new CustomMenuItemUI(); } public void paint(Graphics g, JComponent comp) { // paint to the buffered image BufferedImage bufimg = new BufferedImage( comp.getWidth(), comp.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = bufimg.createGraphics(); // restore the foreground color in case the superclass needs it g2.setColor(g.getColor()); super.paint(g2,comp); // do an alpha composite Graphics2D gx = (Graphics2D) g; gx.setComposite(AlphaComposite.getInstance( AlphaComposite.SRC_OVER,0.8f)); gx.drawImage(bufimg,0,0,null); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 2: Lists and Combos
- InhaltsvorschauLists are underrated and underappreciated, and developers who don't appreciate JLists often use JTables when they don't need to. But lists seem to be making a comeback in desktop applications, and with good reason. A lot of the data we deal with are single-dimension collections—search results, recent URLs, downloaded files, etc.—and by making the onscreen version of them more appealing and more usable, a list is the right way to present this data to the user.Make your 1,000-item list a lot more manageable.One of the nicest things you can do with a large list is to make it manageable with a filter box. This is a text area that, as you type into it, removes list elements so that only those that contain the typed text are visible.The hack to do this basically involves having a list model with two representations of its contents: everything that is in the list, and a subset with just the items to be displayed (i.e., those from the first list that match the filter). The model's get methods are then rewired to use only the second list.The implementation in this hack,
FilteredJList, is a single class with two inner subclasses:FilterModelandFilterField. The list owns the field, so a caller can create theJListfairly typically and then just ask for the field and add it wherever it makes sense in the layout.Start by declaringFilteredJListas a subclass ofJList, and provide a constructor and some convenience methods, as seen in Example 2-1.Example 2-1. FilterList constructor and convenience methodspublic classFilteredJList extends JList { private FilterField filterField; private int DEFAULT_FIELD_WIDTH = 20; public FilteredJList() { super(); setModel (new FilterModel()); filterField = new FilterField (DEFAULT_FIELD_WIDTH); } public void setModel (ListModel m) { if (! (m instanceof FilterModel)) throw new IllegalArgumentException(); super.setModel (m); } public void addItem (Object o) { (FilterModel)getModel()).addElement (o); } public JTextField getFilterField() { return filterField; }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 13–20: Introduction
- InhaltsvorschauLists are underrated and underappreciated, and developers who don't appreciate JLists often use JTables when they don't need to. But lists seem to be making a comeback in desktop applications, and with good reason. A lot of the data we deal with are single-dimension collections—search results, recent URLs, downloaded files, etc.—and by making the onscreen version of them more appealing and more usable, a list is the right way to present this data to the user.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
- Filter JLists
- InhaltsvorschauMake your 1,000-item list a lot more manageable.One of the nicest things you can do with a large list is to make it manageable with a filter box. This is a text area that, as you type into it, removes list elements so that only those that contain the typed text are visible.The hack to do this basically involves having a list model with two representations of its contents: everything that is in the list, and a subset with just the items to be displayed (i.e., those from the first list that match the filter). The model's get methods are then rewired to use only the second list.The implementation in this hack,
FilteredJList, is a single class with two inner subclasses:FilterModelandFilterField. The list owns the field, so a caller can create theJListfairly typically and then just ask for the field and add it wherever it makes sense in the layout.Start by declaringFilteredJListas a subclass ofJList, and provide a constructor and some convenience methods, as seen in Example 2-1.Example 2-1. FilterList constructor and convenience methodspublic classFilteredJList extends JList { private FilterField filterField; private int DEFAULT_FIELD_WIDTH = 20; public FilteredJList() { super(); setModel (new FilterModel()); filterField = new FilterField (DEFAULT_FIELD_WIDTH); } public void setModel (ListModel m) { if (! (m instanceof FilterModel)) throw new IllegalArgumentException(); super.setModel (m); } public void addItem (Object o) { (FilterModel)getModel()).addElement (o); } public JTextField getFilterField() { return filterField; }Notice that along with holding onto theFilterField, theJListalso creates its ownFilterModelin the constructor, and overridessetModel() to ensure that you can't push in an incompatible model. It also contains anaddItem() method, which really just delegates to theFilterModel.FilterModel, shown in Example 2-2, is where the magic happens.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add a Filter History
- InhaltsvorschauRemember previous searches and research with one click.Chances are good that if you've searched for something once, it's important enough that you might well search for it again. In Apple's Safari browser, a search widget at the upper right has a little magnifying glass that remembers your last 10 searches. Click the magnifying glass and a pop up appears with the previous searches. Select one and it populates the field and does the search immediately.Here's an implementation of the same idea, grafted onto the previous hack. In other words, this remembers previous filters. It doesn't remember every keystroke—why bother remembering the searches "J" and "Jo" when you're really just interested in "Joe"—and only adds a search term to the filter when the user presses return.In the previous hack, you just needed to have a text field and a
JList. Now aJButtonneeds to be attached to the text field, so the two are bundled together in the inner classFilterField. This class is responsible for:- Telling the model to refilter on each keystroke in the
JTextField, as before. - Remembering the
JTextField's contents as a saved search anytime the Return or Enter key is pressed. - Catching clicks on the
JButtonand popping up a menu with previous searches. - Populating the
JTextFieldwith a previous search when one is selected from the list. It doesn't need to explicitly tell the model to refilter because changing the text area will fire aDocumentEventthat is already accounted for by theJTextField'sDocumentListener.
Example 2-5 shows the newFilterFieldclass.Example 2-5. List filtering component with text field and history buttonclass FilterField extends JComponent implements DocumentListener, ActionListener { LinkedList prevSearches; JTextField textField; JButton prevSearchButton; JPopupMenu prevSearchMenu; public FilterField (int width) { super(); setLayout(new BorderLayout()); textField = new JTextField (width); textField.getDocument().addDocumentListener (this); textField.addActionListener (this); prevSearchButton = new JButton (new ImageIcon ("mag-glass.png")); prevSearchButton.setBorder(null);prevSearchButton.addMouseListener (new MouseAdapter() { public void mousePressed (MouseEvent me) { popMenu (me.getX(), me.getY()); } }); add (prevSearchButton, BorderLayout.WEST); add (textField, BorderLayout.CENTER); prevSearches = new LinkedList (); } public void popMenu (int x, int y) { prevSearchMenu = new JPopupMenu(); Iterator it = prevSearches.iterator(); while (it.hasNext()) prevSearchMenu.add ( new PrevSearchAction(it.next().toString())); prevSearchMenu.show (prevSearchButton, x, y); } public void actionPerformed (ActionEvent e) { // called on return/enter, adds term to prevSearches if (e.getSource() == textField) { prevSearches.addFirst (textField.getText()); if (prevSearches.size() > 10) prevSearches.removeLast(); } } public void changedUpdate (DocumentEvent e) { ((FilterModel)getModel()).refilter(); } public void insertUpdate (DocumentEvent e) { ((FilterModel)getModel()).refilter(); } public void removeUpdate (DocumentEvent e) { ((FilterModel)getModel()).refilter(); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Make JLists Checkable
- InhaltsvorschauAvoid losing 50 selections to an unshifted click.One horrible UI problem is dealing with vast collections of things that need to be presented to the user and made selectable. If, like me, you've ever had 1,000 emails in your inbox, you know what I mean. Worse, what if you pick a bunch of items to delete, but your finger slips off the key used for multi-selection (Alt on Windows, Command on the Mac, etc.) and you lose all of your previous selections? Overriding the native selection behavior can make this situation somewhat more palatable.Because a list like this behaves differently than a normal list, it should look different, too, so I've opted for a checkbox metaphor. Each item is shown with a checkbox, and as you click more items, they get checked, and if you select an already-checked item, it gets unchecked.This turns out to be a little harder than expected. I once did it without
JList, creating my own scrolling layout ofJPanelsand faking the list behavior. It turned up a funny Swing bug because I was usingGridBagLayoutfor the fake list, and it started totally bombing out after about 500 items were added to the list. This was becauseGridBagLayouthas a bug where it can't have more than 512 rows. Considering the bug (number 4254022 on the Java Bug Parade) was filed in 1999 and is still open, I'm figuring it won't get fixed by the time you read this.The basis of the checkable list is aJList. The tricky part here is that there isn't a way (that I've found) to steal the mouse clicks from theJListand consume them before the normal calls to theListSelectionModelare made. Instead, the strategy is to set up aListSelectionListenerand just fix everything afterJListhas done its thing.To implement the checkbox functionality, subclassJListand give it a customListSelectionListenerand aListCellRenderer. Acomplete listing is shown in Example 2-6.Example 2-6. A checkbox-metaphor JListEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Make Different List Items Look Different
- InhaltsvorschauAn in-progress download shouldn't look like a completed one.What made me love lists again were the OmniWeb browser and (later) Safari—particularly, their download managers. By way of negative example, take the download manager for Internet Explorer 5 for Mac…please. This GUI was a table of filenames, URLs, sizes, etc., with columns not even intelligently resized for their widths. OmniWeb, on the other hand, showed a running download with a progress bar, and a finished download with the file location and file size. Safari goes a step further with context-appropriate buttons: an X to cancel an in-progress download, a magnifying glass to locate an already-downloaded file, etc. But it's the same idea: different things shouldn't look the same.To do this in Swing, you need a hack that goes against everything in all the other Swing books: you need to stop subclassing
JComponentwhen you write aListCellRenderer. Instead, delegate thegetListCellRendererComponent( ) call to one of several components, choosing whichever best represents the item to be rendered.In fact, the whole tradition of subclassingJComponentforListCellRenderersis a pretty hateful practice because they're not really used asComponents anyway! They're certainly not added to the JList. Instead, a list cell is rendered off screen and those pixels are blitted to theJList. So, provided that what you return ingetListCellRendererComponentis what you want the cell to look like, it really doesn't matter how you get there.By way of demonstration, this hack shows the items in a given directory with different layouts, depending on the file type. All of the cells use an icon on the left, with a name in bold at the top of a two-line layout. However, if the item is a folder, the bottom line contains a count of the children in that folder. If the item is a text file—it ends with one of the various extensions associated with text files (e.g., .txt, .html, .javaEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Reorder a JList with Drag-and-Drop
- InhaltsvorschauLet users put things where they want.You may be so used to immutable lists that the idea of reordering a list with drag-and-drop seems unnatural. The first time I saw it—rearranging the order of network devices in Mac OS X to establish a priority (e.g., try Ethernet, then wireless, then modem)—I thought it was kind of odd. In fact, Apple felt it necessary to put a label on the list to tell users they could drag-and-drop the list items to rearrange them. Now that I'm used to it, it's totally cool, and I'd like to see it done in more places.To implement this functionality in a
JList, you basically just have to implement the full set of AWT drag-and-drop interfaces because the list will be both the source of the drag and the target of the drop. The other thing you need to do is to use some cell rendering tricks to provide a visual cue as to where the drop will occur.TheReorderableJList, shown in Example 2-12, is a JList that uses aDefaultListModel, which is mutable for the obvious reason that it will need to change in response to drag-and-drops. The bulk of it is concerned with implementing the drag-and-drop interfacesDragSourceListener,DropTargetListener, andDragGestureListener. It has an inner class implementingTranferableto hold the item being dropped, although this isn't absolutely necessary. I could have just held the dragged item in an instance variable andnulledtheTransferablein the drag-and-drop calls, but it doesn't hurt to do it the nice way.Example 2-12. A JList that can be reordered with drag-and-droppublic class ReorderableJList extends JList implements DragSourceListener, DropTargetListener, DragGestureListener { static DataFlavor localObjectFlavor; static { try { localObjectFlavor = new DataFlavor (DataFlavor.javaJVMLocalObjectMimeType); } catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); } } static DataFlavor[] supportedFlavors = { localObjectFlavor }; DragSource dragSource; DropTarget dropTarget; Object dropTargetCell; int draggedIndex = -1; public ReorderableJList () { super(); setCellRenderer (new ReorderableListCellRenderer()); setModel (new DefaultListModel()); dragSource = new DragSource(); DragGestureRecognizer dgr = dragSource.createDefaultDragGestureRecognizer (this, DnDConstants.ACTION_MOVE, this); dropTarget = new DropTarget (this, this); } // DragGestureListener public void dragGestureRecognized (DragGestureEvent dge) { System.out.println ("dragGestureRecognized"); // find object at this x,y Point clickPoint = dge.getDragOrigin(); int index = locationToIndex(clickPoint); if (index == -1) return; Object target = getModel().getElementAt(index); Transferable trans = new RJLTransferable (target); draggedIndex = index; dragSource.startDrag (dge,Cursor.getDefaultCursor(), trans, this); } // DragSourceListener events public void dragDropEnd (DragSourceDropEvent dsde) { System.out.println ("dragDropEnd()"); dropTargetCell = null; draggedIndex = -1; repaint(); } public void dragEnter (DragSourceDragEvent dsde) {} public void dragExit (DragSourceEvent dse) {} public void dragOver (DragSourceDragEvent dsde) {} public void dropActionChanged (DragSourceDragEvent dsde) {} // DropTargetListener events public void dragEnter (DropTargetDragEvent dtde) { System.out.println ("dragEnter"); if (dtde.getSource() != dropTarget) dtde.rejectDrag(); else { dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE); System.out.println ("accepted dragEnter"); } } public void dragExit (DropTargetEvent dte) {} // dragOver() listed below // drop() listed below public void dropActionChanged (DropTargetDragEvent dtde) {} // main() method to test - listed below // RJLTransferable listing below // ReorderableListCellRendering listing below }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Animate Your JList Selections
- InhaltsvorschauFading in and catching the eye.Not every GUI involves windows and mouse pointers, and the visual language of a GUI can be very different depending on what is provided by the environment. Typically, GUIs for things like console video games and settop boxes don't use a mouse metaphor, so there's no onscreen pointer that the user is tracking. As a result, these systems often give the user more profound feedback when they move around a list—highlights slide from one item to another, selected items fade in while deselected items fade out, etc.— so there's something the eye can track. You can do the same thing in Swing, with more cell-rendering hackery. You might not need it now, but it'll be handy if you ever design a kiosk with Swing.One way to show a changed selection is to show a brief animation of the cell selection. Instead of just being highlighted instantly, you fade the selected cell from its unselected background and foreground colors to its selected colors over the course of a short time (really short, like a half-second, so it isn't annoying).To do this, you'll need to create an animator thread that kicks off every time the selection changes. This short-lived thread repeatedly updates a highlight color and calls
repaint(). The cell renderer can then use the updated highlight color as it redraws the cells in the list. Example 2-18 shows this technique.Example 2-18. Animating the JList cell selectionimport java.awt.*; import javax.swing.*; import javax.swing.event.*; import java.util.*; public classAnimatedJList extends JList implements ListSelectionListener { static java.util.Random rand = new java.util.Random(); static Color listForeground, listBackground, listSelectionForeground, listSelectionBackground; static float[] foregroundComps, backgroundComps, foregroundSelectionComps, backgroundSelectionComps; static { UIDefaults uid = UIManager.getLookAndFeel().getDefaults(); listForeground = uid.getColor ("List.foreground"); listBackground = uid.getColor ("List.background"); listSelectionForeground = uid.getColor ("List.selectionForeground"); listSelectionBackground = uid.getColor ("List.selectionBackground"); foregroundComps = listForeground.getRGBColorComponents(null); foregroundSelectionComps = listSelectionForeground.getRGBColorComponents(null); backgroundComps = listBackground.getRGBColorComponents(null); backgroundSelectionComps = listSelectionBackground.getRGBColorComponents(null); } public Color colorizedSelectionForeground, colorizedSelectionBackground; public static final int ANIMATION_DURATION = 1000; public static final int ANIMATION_REFRESH = 50; public AnimatedJList() { super(); addListSelectionListener (this); setCellRenderer (new AnimatedCellRenderer()); } public void valueChanged (ListSelectionEvent lse) { if (! lse.getValueIsAdjusting()) { HashSet selections = new HashSet(); for (int i=0; i < getModel().getSize(); i++) { if (getSelectionModel().isSelectedIndex(i)) selections.add (new Integer(i)); } CellAnimator animator = new CellAnimator (selections.toArray()); animator.start(); } } public static void main (String[] args) { JList list = new AnimatedJList (); DefaultListModel defModel = new DefaultListModel(); list.setModel (defModel); String[] listItems = { "Chris", "Joshua", "Daniel", "Michael", "Don", "Kimi", "Kelly", "Keagan" }; Iterator it = Arrays.asList(listItems).iterator(); while (it.hasNext()) defModel.addElement (it.next()); // show list JScrollPane scroller = new JScrollPane (list, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); JFrame frame = new JFrame ("Checkbox JList"); frame.getContentPane().add (scroller); frame.pack(); frame.setVisible(true); } class CellAnimator extends Thread { Object[] selections; long startTime; long stopTime; public CellAnimator (Object[] s) { selections = s; } public void run() { startTime = System.currentTimeMillis(); stopTime = startTime + ANIMATION_DURATION; while (System.currentTimeMillis() < stopTime) { colorizeSelections(); repaint(); try { Thread.sleep (ANIMATION_REFRESH); } catch (InterruptedException ie) {} } // one more, at 100% selected color colorizeSelections(); repaint(); } // colorizeSelections() listing below // AnimatedCellRenderer listing below }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Turn Methods into List Renderers
- InhaltsvorschauBy using a little bit of reflection, you can make a generic
ListCellRendererthat can render data using any method at runtime.JLists, likeJTableandJTree, use a decorator pattern to customize how they look. This system of cell renderers works well, but it can require you to build a unique renderer class for each type of object you want to put into your lists. Often, all you really want to do is call a particular method on the objects in your list, but writing a complete class to just call one method is a lot of work for such a small task. This hack shows you how to use reflection to create a generic cell renderer that can be reused on any object without subclassing.The defaultJListcell renderer will just calltoString() on the objects in the list and draw the resulting string to the screen. This is fine for simple uses where you really are just looking at a list of strings or objects with appropriatetoString() methods. More complicated applications—and they all become more complicated eventually—require more complicated objects, and those objects might not have a convenient or usefultoString() method. Eventually, you have to write a custom renderer for the particular object you wish to store. But there is another way: reflection.Reflection lets you programmatically discover and access methods and fields in a java class at runtime. For this hack, you will use reflection to call an arbitrary method. This will allow the programmer using your generic renderer to specify a method using a string. This method will be used to render the component. Because you will be using reflection, you don't need to know the kind of objects in the list. As long as a method with the requested name exists, you can call it and get a value out. This will work even if some of the objects in the JList have different types. But let's not get ahead of ourselves. First, you need a basic cell renderer, as seen in Example 2-21.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Create a Collections-Aware JComboBox
- InhaltsvorschauYou've moved on from Vector; your combo boxes should, too.
JComboBoxis one of Swing's oldest components. Unfortunately, it accepts arrays of objects and Vectors only. Now that Collections objects like List have been part of the JDK for years, it would be nice to use them directly in a combo box without shuffling objects in and out of arrays. Fortunately, theJComboBoxuses an MVC (Model-View-Controller) architecture, so you can solve this problem with a simple implementation of aComboBoxModel.To start, you need to figure out what the custom model should do. For our purposes, it needs to accept a List in the constructor and preserve any ordering supplied. Another nifty feature would be automatic updates. If you add or delete values to the List, the combo box should update itself automatically. Example 2-22 is a good start.Example 2-22. A basic combo box to accept listspublic class ListComboBoxModel implements ComboBoxModel { protected List data; public ListComboBoxModel(List list) { this.listeners = new ArrayList(); this.data = list; if(list.size() > 0) { selected = list.get(0); } } protected Object selected; public void setSelectedItem(Object item) { this.selected = item; } public Object getSelectedItem() { return this.selected; } public Object getElementAt(int index) { return data.get(index); } public int getSize() { return data.size(); } protected List listeners; public void addListDataListener(ListDataListener l) { listeners.add(l); } public void removeListDataListener(ListDataListener l) { this.listeners.remove(l); } }This implementation is pretty much what you'd expect. Each method inComboBoxModelis implemented (along with its parent interface,ListDataModel). The constructor saves a reference to the List and selects the first element if there is one. TheselectedItemaccessor works as expected, using the selected variable.getElementAt()andgetSize()both pass the work on to the underlyingEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 3: Tables and Trees
- InhaltsvorschauA table component was one of the most obvious missing features in AWT, and among the most welcome additions when Swing came out. However, the
JTablemay be used too much—it's easy to throw anObject[][]at the constructor and get a full-blown GUI table, and some developers don't question the wisdom of this sort of coding.But despite the generosity of the SwingJTableAPI, there are a few things still missing. Wouldn't it be nice if the table model keep itself sorted, or if the column widths had a non-ugly default that takes their contents into account? Well, you didn't buy this book to argue API theory—the point here is to hack things into shape. So, let's get started.A one-digit column does not need 100 pixels of dead space. You know this; your JTables should, too.Does Figure 3-1 look like your typical JTable?
Figure 3-1: JTable with default column sizingIf it does, then we have a usability problem to discuss. By default, the columns of aJTableare all the same size. For this data, that's obviously a terrible decision—there's far too much space reserved for the numbers in the count column, and not nearly enough in the URL column. So, how are you going to fix this?If you said "turn on the horizontal scrollbar," please close this book, grasp it with both hands, and firmly smack yourself in the head with it. No, you are not turning on the horizontal scrollbar! Use the pixels available to you before you resort to the user-annoying desperation of horizontal scrolling. In this case, the count column has lots of pixels to spare; you just need to reallocate this extra space to the URL column.What makes programmatic column resizing difficult for many Swing programmers is that they can't even find the right methods to use. If all you ever work with isJTable(and maybe a few custom cell renderers), you'll notice that the JavaDoc for those classes says nothing about column widths. The problem may be that theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 21–27: Introduction
- InhaltsvorschauA table component was one of the most obvious missing features in AWT, and among the most welcome additions when Swing came out. However, the
JTablemay be used too much—it's easy to throw anObject[][]at the constructor and get a full-blown GUI table, and some developers don't question the wisdom of this sort of coding.But despite the generosity of the SwingJTableAPI, there are a few things still missing. Wouldn't it be nice if the table model keep itself sorted, or if the column widths had a non-ugly default that takes their contents into account? Well, you didn't buy this book to argue API theory—the point here is to hack things into shape. So, let's get started.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Size Your Columns to Suit Your JTable's Contents
- InhaltsvorschauA one-digit column does not need 100 pixels of dead space. You know this; your JTables should, too.Does Figure 3-1 look like your typical JTable?
Figure 3-1: JTable with default column sizingIf it does, then we have a usability problem to discuss. By default, the columns of aJTableare all the same size. For this data, that's obviously a terrible decision—there's far too much space reserved for the numbers in the count column, and not nearly enough in the URL column. So, how are you going to fix this?If you said "turn on the horizontal scrollbar," please close this book, grasp it with both hands, and firmly smack yourself in the head with it. No, you are not turning on the horizontal scrollbar! Use the pixels available to you before you resort to the user-annoying desperation of horizontal scrolling. In this case, the count column has lots of pixels to spare; you just need to reallocate this extra space to the URL column.What makes programmatic column resizing difficult for many Swing programmers is that they can't even find the right methods to use. If all you ever work with isJTable(and maybe a few custom cell renderers), you'll notice that the JavaDoc for those classes says nothing about column widths. The problem may be that theJTableis so generous with helpful methods that you'd never even notice that it's made up ofTableColumnobjects. Take a look at thatTableColumn'sJavaDoc, and you'll find getters and setters for minimum, maximum, and preferred widths for columns.As with components that defer to layout managers, the right property to reset is the preferred width—let the column tell theJTablehow wide it would like to be, but let theJTablemake the final decision based on the information available to it (after all, there could be other columns contending for space, there might not be enough space for all the columns'preferred size, etc.).Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add Column Selection to JTables
- InhaltsvorschauSo, why can't I select a column by clicking on its header?Here's something that seems strange about
JTables: you can click on the column headers, but only for the purpose of reordering columns—not for selecting the contents of that column. I don't know about you, but considering that I almost never reorder columns, it seems like the default behavior is backward. And if your users have had their expectations set by working with Excel or other spreadsheets, they'll surely expect the ability to select an entire column.The to-do list for adding column selectability to aJTableconsists of two items:- Change which kinds of multiselection are allowed.
- Wire up a
MouseListener.
Example 3-3 shows a very simple implementation.Example 3-3. A JTable that allows column selection by clicking on column headerspublic class ColumnSelectableJTable extends JTable { public ColumnSelectableJTable (Object[][] items, Object[] headers) { super (items, headers); setColumnSelectionAllowed (true); setRowSelectionAllowed (false); // set up action listener on table header finalJTableHeader header = getTableHeader(); header.addMouseListener (new MouseAdapter() { public void mouseReleased (MouseEvent e) { if (! e.isShiftDown()) clearSelection(); int pick = header.columnAtPoint(e.getPoint()); addColumnSelectionInterval (pick, pick); } }); } }The constructor is deliberately simple, taking only a two-dimensional array of contents and a one-dimensional array of headers. Of course,JTablehas many more constructor signatures than this, but this is the one that will be easiest to expose to a test class (do I have to mention that building out the other constructors is left to the reader as an exercise?).The next step is changing the defaults for multi-cell selection. The default is to allow row selection—exactly what you don't want. So, enable column selection and disable row selection. Next, you want to catch clicks on the headers so you can select columns in response to them. Unfortunately, theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Let Your JTables Do the Sorting
- InhaltsvorschauWhy doesn't Swing already offer this? Oh well, here's how to do it yourself.It's hard to imagine you'll do much serious work with
JTableswithout needing to sort the contents by one of the columns, or support changing between columns to use as the sort criteria. In fact, given how generous the Swing API usually is, it's kind of surprising that it doesn't already offer it. Oh well, it's not that hard to do for yourself.There are a couple of approaches you could take to solve this problem. You could create a subclass ofTableModel, one that keeps an internalComparatorto do the sorting and resorts every time anadd()orremove()type method is called. The drawback to this approach is choosing which of the model classes to subclass. If you go too high up the hierarchy by implementingTableModelor subclassingAbstractTableModel, you would miss some typical Swing functionality that developers expect, like the ability to add and remove rows provided byDefaultTableModel. On the other hand, if you subclassDefaultTableModel, other developers will be unhappy because subclassing your class requires them to pick up publicadd()anddelete()type methods that expose their data in ways they don't want.So, consider an alternative: two table models, one that theJTablesees and another that the developer sees. Specifically, the developer will pass herTableModelto the constructor of the sorting model, which will wire up for events on the model. Then, the developer will set the sorting model as theJTable'smodel. Changes in the base model will force the sorting model to resort its contents and then fire off events toJTableto drive updates to the onscreen representation.There are more details in the actual implementation of course, particularly when it comes to doing the sorting. Example 3-5 shows the code for theSortableTableModel.Example 3-5. Self-sorting TableModelpublic class SortableTableModel implements TableModel, TableModelListener { EventListenerList listenerList = new EventListenerList(); TableModel delegatedModel; int[] sortedIndicies; int sortColumn; Comparator comparator; Comparator[] comparators; public SortableTableModel (TableModel tm) { delegatedModel = tm; delegatedModel.addTableModelListener (this); comparators = new Comparator [tm.getColumnCount()]; sortedIndicies = new int [0]; setSortColumn (0); } // listener stuff public void addTableModelListener (TableModelListener l) { listenerList.add (TableModelListener.class, l); } public void removeTableModelListener (TableModelListener l) { listenerList.remove (TableModelListener.class, l); } public void fireTableModelEvent (TableModelEvent e) { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i] == TableModelListener.class) { ((TableModelListener) listeners[i+1]).tableChanged(e); } } } // contents stuff public Class getColumnClass(int columnIndex) if (delegatedModel.getRowCount() > 0) return delegatedModel.getValueAt(0, columnIndex).getClass(); else return Object.class; } // getColumnCount(), getColumnName(), getRowCount(), // getValueAt(), isCellEditable(), setValueAt() listings below // internal helpers public void setComparatorForColumn (Comparator c, int i) { // range check if (i > comparators.length) { Comparator[] newComparators = new Comparator[i+1]; System.arraycopy (comparators, 0, newComparators, 0, comparators.length); comparators = newComparators; } // add the comparator comparators[i] = c; } public void setSortColumn (int i) { sortColumn = i; // reset current comparator, possibly to null, which // will make us use "natural ordering" for those values comparator = null; if ((comparators != null) && (comparators.length > 0)) // is there one in the list of comparators? comparator = comparators[sortColumn]; // now do the sort resort(); } public int getSortColumn () { return sortColumn; } // resort() method listed below //SortingDelegate inner class listed below // SortingDelegateComparator inner class listed below public void tableChanged (TableModelEvent e) { switch (e.getType()) { case TableModelEvent.DELETE: { resort(); fireAllChanged(); break; } case TableModelEvent.INSERT: { resort(); fireAllChanged(); break; } case TableModelEvent.UPDATE: { resort(); fireAllChanged(); break; } } } protected void fireAllChanged() { TableModelEvent e = new TableModelEvent (this); fireTableModelEvent (e); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Create a JDBC Table Model
- InhaltsvorschauBring your database tables into Swing with a minimum of hassle.If you've worked with databases, you've probably also worked with the tools they provide for quick table maintenance and queries: command-line tools that are well suited to brief hack-and-slash work, but hard to work with once you start dealing with any serious amount of data. It's hard enough to write the SQL command to return 10 or 20 columns in a query—it's even worse when the results word-wrap over the course of a dozen lines, and you can't tell where one result ends and another begins.Wouldn't it be nice to be able to throw the contents of any database table into a Swing
JTable? Give it a few JDBC strings, toss it in aJFrame, and pow!—instant GUI.If you've worked with both JDBC and Swing, you'll grasp the concept in one sentence: use table metadata to build a SwingTableModelfrom the database table. If you haven't, here's the background you'll need: JDBC provides an abstract means of accessing databases. Java code to work with one database should work with another, the only difference is in the way that JDBC achieves aConnectionto the database, which is usually a matter of providingStringsfor:- A driver class, which provides implementations of the various
java.sqlinterfaces. - A URL with which to connect to the database. This implies the use of sockets, though that's not necessarily the case. Some small embeddable databases can live in the same JVM as your application.
- An optional username.
- An optional password.
Once you have theConnection, you can begin to send commands (creation, deletion, and altering of tables) or queries to the database by creatingStatementsfrom theConnection. You can also use theConnectionto get metadata about the database, like what kinds of features it supports, how long certain strings can be, etc. More importantly for this hack, it allows you to discover what tables are in the database, what columns they have, and what types of data are in those columns.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Export Table Data to an Excel Spreadsheet
- InhaltsvorschauI don't want an entire spreadsheet API, I just want to get a table of values into Excel.Most corporate intranet applications require interfacing with standard office software, which usually means Microsoft Word and Excel. Interfacing with Microsoft products can be tricky business. Whole suites of products have been created just to address this issue. One of the most commonly requested features is generating a report from the data in a
JTable. You could use a library like Poi (http://jakarta.apache.org/poi/) to read and write Excel files natively, but most of the time that's overkill—you probably don't need to support Excel formulas or complicated formatting. All most users really want to do is dump tabular data into a file that will open in Excel with a double-click. And with a little bit of cleverness, you can do just that.Excel uses a complicated database-oriented format for its native .xls files. This format defines the formulas, colors, charts and every other advanced feature Excel has supported over the years. Writing to the native .xls format is complicated but, fortunately, Excel supports other formats. The one I'm going to target is known as a tab-delimited text file, so called because tabs separate each field. This format is just plain text, so it will be super easy to write from Java, and open up in Excel with just a double-click.Tab-delimited files separate each field with a tab character and each row with a standard Unix line break,\n. Since Swing defines a convenientgetValueAt()method in theTableModelinterface, it's very easy to just loop through the table cells and write it out to a file, as seen in Example 3-14.Example 3-14. Exporting tab-delimited data from a TableModelpublic class ExcelExporter { public ExcelExporter() { } public void exportTable(JTable table, File file) throws IOException { TableModel model = table.getModel(); FileWriter out = new FileWriter(file); for(int i=0; i < model.getColumnCount(); i++) { out.write(model.getColumnName(i) + "\t"); } out.write("\n"); for(int i=0; i< model.getRowCount(); i++) { for(int j=0; j < model.getColumnCount(); j++) { out.write(model.getValueAt(i,j).toString()+"\t"); } out.write("\n"); } out.close(); System.out.println("write out to: " + file); }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Search Through JTables Easily
- InhaltsvorschauUse this nifty TableModel decorator to search your JTables with minimal fuss.Tables have a tendency to get very big; thousands of rows are not uncommon. But this causes some severe navigational issues for your users, like extremely small scrollbar handles, which make it difficult for them to find the information they need. One way to get around these navigational issues is to allow your users to search the table data rather than displaying it all. This hack shows you how to simply search your tables using the Apache open source Lucene search engine.Rather than a custom
TableModelwith integrated Lucene functionality, you can build aTableModeldecorator instead. This will allow you to search preexistingTableModelswithout modifying them directly.This works by keeping a set of links to an internal table model based on search criteria. For example, say you have 10 rows in your original table model, and you have a search that limits the results to 5 of those rows. Your innerTableModelwill remain unchanged, but yourTableModeldecorator will have links to only five of the innerTableModelrows—making it look like it only has five rows of data.Start by creating a class calledTableSearcherthat implementsTableModel.Next, create a simple decorator (or wrapper) that implements all of theTableModelmethods and forwards the calls to the inner model. Depending on your IDE, you may be able to automate the process (IntelliJ IDEA does it for me). You'll have to modify a few of the methods, but most are going to remain unchanged. You don't have to touch thegetColumnName()andgetColumnClass()methods, for example. Just forward them to the innerTableModel:public String getColumnName(int column) { return tableModel.getColumnName(column); } public Class getColumnClass(int column) { return tableModel.getColumnClass(column); }ThegetColumnCount()method is slightly different in that you have to check if theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Animate JTree Drops
- InhaltsvorschauWho said working with tree paths was hard? Now you can reorganize tree hierarchies with drag-and-drop.
JTreesare great for representing hierarchy, but they're not so hot as control widgets. You might want to drag items inside a tree, or accept a drop from some other part of your application, and it turns out not to be well suited to that. The problem is that theJTreeisn't really a container, so from the Swing programmer's point of view, you see the tree's visual representation, but not the nodes within it.The goal of this hack is to take aJTree, like the one shown in Figure 3-11, and allow you to reorganize it through drag-and-drop. The bulk of the work will be in animating and handling the drop. The payoff is that making a single tree reorderable will also get you most of the way to making it a good drag-and-drop participant with the rest of your application, since supporting drag-and-drop within theJTreerequires you to make the tree a drag source and a drop target.If any of the forgoing sounds familiar, it should. Bringing drag-and-drop to theJTreeis very similar to supporting it for theJList. In fact, theJTreeand theJListhave a lot in common—both use cell renderers, both are typically put inJScrollPanes, etc.
Figure 3-11: JTree with drag-and-drop reorderingIn fact, the code for this hack started as a straight port of the reorderable JList hack [Hack #17] , with obvious changes for the different helper classes (TreeCellRendererinstead ofListCellRenderer) and different handling of the model, since tree models are hierarchical.To recap what needs to be done:- The tree needs to implement the
DragGestureListener(to start a drag), theDropTargetListener(to handle the drop), and theDragSourceListener(only to get the end-of-drop callback). - The drag-and-drop implementations need to use the coordinates provided by drag-and-drop events to map to nodes of the tree as either the node to drag or as potential drop targets.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 4: File Choosers
- InhaltsvorschauAh, the poor
JFileChooser. It's probably not anybody's favorite class in Swing, and it's quite likely the anti-favorite of many developers. Prior to Java 1.4, the Metal version had an "icon view" button that actually didn't do anything—not that it was worth writing home about once it was implemented. Little glitches like this make JFileChooser the whipping boy of many Swing developers. Apple's Java guidelines for Mac OS X actually advocate giving up on theJFileChooseraltogether and going back to the AWTFileDialog!This chapter is here to…well, if not to praise theJFileChooser, then certainly not to bury it either. This is Swing after all, and that means there are all sorts of places that you can hack in and embellish functionality to give the user more power, or to make a component smarter about the contents it displays.Improve the native platform fidelity of theJFileChooserby adding a contextual menu that lets the user create new folders and delete files.The standardJFileChooserthat comes with Swing is quite limited. It doesn't follow shortcuts or any other linked files. It doesn't have image previews or even a right-click menu. These are all features that users expect to see. Worse, these are the kinds of details that continue to reinforce the belief that Swing apps are inferior to native ones.This hack will tackle the first limitation, the lack of a context menu. Some platform file choosers—Windows Explorer in particular—provide users with a context menu, also known as a right-click menu. This provides fast access to commonly used functions like Delete and New Folder. Java 5.0 finally added a context menu to the file chooser, but if you want to target the millions of users out there with older versions of Java, then you need your own implementation. This hack creates a right-click menu on theJFileChooserto give the user those missing features.The goal of this task is to create a contextual menu. This means it's a menu that pops up when you hit the right mouse button (or Control-click on one-button mice). It also isEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 28–32: Introduction
- InhaltsvorschauAh, the poor
JFileChooser. It's probably not anybody's favorite class in Swing, and it's quite likely the anti-favorite of many developers. Prior to Java 1.4, the Metal version had an "icon view" button that actually didn't do anything—not that it was worth writing home about once it was implemented. Little glitches like this make JFileChooser the whipping boy of many Swing developers. Apple's Java guidelines for Mac OS X actually advocate giving up on theJFileChooseraltogether and going back to the AWTFileDialog!This chapter is here to…well, if not to praise theJFileChooser, then certainly not to bury it either. This is Swing after all, and that means there are all sorts of places that you can hack in and embellish functionality to give the user more power, or to make a component smarter about the contents it displays.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add a Right-Click Context Menu to the JFileChooser
- InhaltsvorschauImprove the native platform fidelity of the
JFileChooserby adding a contextual menu that lets the user create new folders and delete files.The standardJFileChooserthat comes with Swing is quite limited. It doesn't follow shortcuts or any other linked files. It doesn't have image previews or even a right-click menu. These are all features that users expect to see. Worse, these are the kinds of details that continue to reinforce the belief that Swing apps are inferior to native ones.This hack will tackle the first limitation, the lack of a context menu. Some platform file choosers—Windows Explorer in particular—provide users with a context menu, also known as a right-click menu. This provides fast access to commonly used functions like Delete and New Folder. Java 5.0 finally added a context menu to the file chooser, but if you want to target the millions of users out there with older versions of Java, then you need your own implementation. This hack creates a right-click menu on theJFileChooserto give the user those missing features.The goal of this task is to create a contextual menu. This means it's a menu that pops up when you hit the right mouse button (or Control-click on one-button mice). It also is contextual, or context sensitive. This means the menu changes—or does something different—depending on what you currently have selected. In this case, there will be two actions: Delete will delete the currently selected file or directory, if one is selected; New Folder will create a new folder (called, not surprisingly, New Folder) in the current directory. Both of these actions depend on the current state of the file selection, so they are considered context sensitive.For the most part, Swing is quite extensible, but often only in ways that the Swing team thought of beforehand. TheJFileChooserhas ways to change the rendering of file icons, filtering the file list, adding components, and changing the text. It does not have any way to add pop-up menus, though. In short, we'll have to hack it, and there's no better way to start than by reusing another hack.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Display Shortcuts in the JFileChooser
- InhaltsvorschauThis hack will customize the
JFileChooserto recognize shortcut ( linked) folders and overlay them with a link graphic, mimicking the native Windows File Explorer.Another ofJFileChooser's glaring bugs is the lack of any support for linked directories. This is hardly surprising, as Java itself has no understanding of linked directories. Most operating systems support linked files, however, and often indicate to the user that a file is linked—for example, by drawing an arrow overlaid on top of the folder or directory icon. Compare the typicalJFileChooserin Figure 4-2 to the standard Windows file chooser in Figure 4-3. There's more than a small difference! No wonder it's hard to get folks to move to Swing.
Figure 4-2: Normal JFileChooserLike every Swing component, the look of theJFileChooseris controlled by the installed Look and Feel (L&F). However, theJFileChooseralso uses a custom class similar to a table cell renderer for drawing the actual files and folders. That class,FileView, is the best place to start hacking theJFileChooser's display.TheFileViewcontains five methods that determine the names, icons, and other attributes that are actually displayed in aJFileChooser. By overriding these methods, you can change the look or text of any file. To draw shortcuts as linked folders, you just need to override thegetIcon() and isTraversable() methods.getIcon() returns the icon to use when drawing the file.
Figure 4-3: Standard Windows file chooserisTraversable() tells the file chooser if a given file is a directory type of object, meaning the user can click and open it to list new files. These two methods will transform a shortcut.lnk file into a shortcut directory with the right icon.For the sake of brevity, this is a Windows-specific hack. You should be able to modify this hack easily to work with sym-links on Unix and Linux, as well as Mac OS X. Consider it homework!Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Real Windows Shortcut Support
- InhaltsvorschauSupport Windows shortcuts by actually opening and parsing files with the under-documented LNK format.It's one thing to properly display Windows shortcuts [Hack #29] by looking for the .lnk extension and changing the default icon to look like a link. But there's a glaring flaw: when you click on the shortcut it doesn't actually link anywhere! This hack will make the shortcuts really work by hacking into the undocumented shortcut files themselves.Since links are not supported natively by the filesystem, Windows fakes it by storing the shortcut metadata (path, icon, and other information) in a .lnk file. When you click on the shortcut, the windows file manager reads the LNK file, extracts the target file/directory path, and then opens a new window at the real location. Your Java program can do the exact same thing using a custom
FileSystemView. The only tricky part is actually parsing the LNK files.Microsoft has never documented the LNK file format, preferring native Windows developers to use system APIs for all manipulation. Creative hackers on the Web have reverse engineered most of the format, which fortunately includes the parts you need to extract target filepaths.Jesse Hager has compiled a great PDF describing the format in detail. I used that document to write the code in this hack. You can read the full document at http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf.The LNK files are binary data broken up into a header followed by a few optional blocks of data. The format provides offsets that make parsing it easy. The following code is the beginning of a LNK parser:public class LnkParser { public LnkParser(File f) throws Exception { parse(f); } public void parse(File f) throws Exception { // read the entire file into a byte buffer FileInputStream fin = new FileInputStream(f); ByteArrayOutputStream bout = new ByteArrayOutputStream(); byte[] buff = new byte[256]; while(true) { int n = fin.read(buff); if(n == -1) { break; } bout.write(buff,0,n); } fin.close(); byte[] link = bout.toByteArray();Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add Image Preview to File Choosers
- InhaltsvorschauThis hack will show you how to add an image previewer to a
JFileChooser, and it will set you on the way toward building your own customizations.We've already talked aboutJFileChooser's numerous limitations. Not surprisingly, many applications have their own custom choosers and extensions to support things like image previews. The standardJFileChooserwas designed to mimic only the most common features, but it does provide a way to add your own enhancements.The standardJFileChooserlooks like most native file choosers. It has a directory selector, a list of files, and select and close buttons. There may also be a toolbar of sorts. If you want to build your own customized file chooser, you could do it the same way platform-specific file choosers are implemented—through L&F code. This would entail subclassingjavax.swing. plaf.basic.BasicFileChooserUI, working around the private methods, and possibly reimplementing the whole thing, none of which is easy or fun. Fortunately, theJFileChooserAPI provides a simple extension hook in the form of thesetAccesory() method. This method lets you add anyJComponentto an existingJFileChooser, thereby adding your own features without mucking around in file chooser code.In this hack, you'll learn how to create an image previewer. This is a component that shows a thumbnail view of the currently selected file—if that file is an image. It will also show the dimensions of the image. Because Java 1.4 provides robust image support with thejavax.imageioAPI, this should be pretty easy. The first step is a customImagePreviewcomponent:public class ImagePreview extends JPanel implementsPropertyChangeListener { private JFileChooser jfc; private Image img; public ImagePreview(JFileChooser jfc) { this.jfc = jfc; Dimension sz = new Dimension(200,200); setPreferredSize(sz); }The code defines theImagePreviewclass, a subclass ofJPanel. I've hardcoded the size of the component to 200 x 200—large enough to tell what the image is, but small enough not to impact the file chooser very much.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Preview ZIP and JAR Files
- InhaltsvorschauThis hack will show you how to customize the file chooser to let users navigate and load files from inside a ZIP or JAR file archive.Most modern operating systems now have built-in support for compressed files, usually in the form of ZIP files. You can open up a ZIP file and navigate the contents fromwithin the standard file browser or dialog box, all without actually uncompressing anything. Surprisingly,
JFileChooserdoesn't support ZIP files, even though Java has built-in ZIP support in thejava.util.zip package. And, since JAR files are in the same format, you get two-for-one today!Before you read any further, I want to warn you that this is one of the longest and most complicated hacks in the book. I don't want to scare you away, but the code is pretty dense. Of course, you wouldn't expect any less from a hacks book, right? What you will learn from this hack, however, will let you build custom filesystem views for any type of datasource, including FTP, WebDAV, or even SQL databases. I think the complexity is worth it. Plus, you'll learn more about how the File object really works and how to hack it to pieces.TheJFileChooseruses aFileSystemViewto access the real filesystem. This view, unfortunately, assumes the existence of actualjava.io.Fileobjects. There is no way to represent a filesystem withoutFiles, which wouldn't be a problem except thatFileis a real class with many methods, not a simple interface. Fortunately,Fileis not declaredfinal. You can override each method with your own version to redirect the calls to another object, and that's exactly how this hack works. By creating proxies around the real ZIP file objects, you can fool theFileSystemViewinto working with items that aren't real files.ZIP files are represented in thejava.util.zippackage by aZipFileobject that contains oneZipEntryfor each compressed file it contains. To show the compressed files in the chooser, eachEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 5: Windows, Dialogs, and Frames
- InhaltsvorschauFor four chapters, we've hacked away at Swing widgets, from
JLabelstoJTables, without worrying too much about the context in which they're shown to the user. And yet, every Swing widget must ultimately be contained in some kind of window to be on the screen at all. It's not an exaggeration to say that many competent Swing programmers don't even know or care about the hierarchy of AWT Windows, Dialogs, and Frames or their Swing equivalents,JWindow, JDialog, andJFrame. Yet, it's these same programmers who don't know that commercial components like splash screens are all possible in Swing; they see dialogs and frames and assume everything has a titlebar. This is hardly true, though—you can easily remove the decorations of a dialog, or just work with the window superclass.Suffice it to say there's much you can do with windows and their subclasses. So much so, in fact, that it fills two chapters. This chapter will deal with hacks that deal with placing, moving, and resizing windows in ways that are fairly consistent with the design of the window classes. The next chapter will be a lot more aggressive in breaking the rules.Make your windows snap to the edges of the screen by using a special event listener.Back in the prehistoric days of desktop software, as graphics programs were being invented, they solved the problem of managing the drawing tools by creating mini-windows called palettes (and their later variation, toolbars). Eventually, the programs had so many palettes that the users grew frustrated trying to organize them. Lining them up on the edge of the screen was particularly nasty, so fledgling young programmers took it upon themselves to create snappable windows. These were windows that were magnetic (metaphorically speaking) and could align themselves to the screen's edges. This hack demonstrates how to recreate this technique with Java.The idea is simple: you check whenever the user moves the window. If the window is off the screen, then move it back to the edge. Moving the window is pretty easy. The trickier part is knowing when the window has moved. Fortunately, AWT has an answer: theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 33–40: Introduction
- InhaltsvorschauFor four chapters, we've hacked away at Swing widgets, from
JLabelstoJTables, without worrying too much about the context in which they're shown to the user. And yet, every Swing widget must ultimately be contained in some kind of window to be on the screen at all. It's not an exaggeration to say that many competent Swing programmers don't even know or care about the hierarchy of AWT Windows, Dialogs, and Frames or their Swing equivalents,JWindow, JDialog, andJFrame. Yet, it's these same programmers who don't know that commercial components like splash screens are all possible in Swing; they see dialogs and frames and assume everything has a titlebar. This is hardly true, though—you can easily remove the decorations of a dialog, or just work with the window superclass.Suffice it to say there's much you can do with windows and their subclasses. So much so, in fact, that it fills two chapters. This chapter will deal with hacks that deal with placing, moving, and resizing windows in ways that are fairly consistent with the design of the window classes. The next chapter will be a lot more aggressive in breaking the rules.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Window Snapping
- InhaltsvorschauMake your windows snap to the edges of the screen by using a special event listener.Back in the prehistoric days of desktop software, as graphics programs were being invented, they solved the problem of managing the drawing tools by creating mini-windows called palettes (and their later variation, toolbars). Eventually, the programs had so many palettes that the users grew frustrated trying to organize them. Lining them up on the edge of the screen was particularly nasty, so fledgling young programmers took it upon themselves to create snappable windows. These were windows that were magnetic (metaphorically speaking) and could align themselves to the screen's edges. This hack demonstrates how to recreate this technique with Java.The idea is simple: you check whenever the user moves the window. If the window is off the screen, then move it back to the edge. Moving the window is pretty easy. The trickier part is knowing when the window has moved. Fortunately, AWT has an answer: the
ComponentListenerinterface.In Java, every UI component (in both AWT and Swing) fires events whenever it moves, resizes, is shown, or is hidden. Any class can receive these events by implementing theComponentListenerinterface. For the purposes of this hack, you only need thecomponentMovedevent, so start by subclassingComponentAdapter, which provides default no-operation implementations of all ofComponentListener's declared methods. Then just override thecomponentMoved() method, as seen in Example 5-1.Example 5-1. A ComponentListener to snap a window into placepublic class WindowSnapper extends ComponentAdapter { public WindowSnapper() { } private boolean locked = false; private int snap_distance = 50; public void componentMoved(ComponentEvent evt) { if(locked) return; Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); int nx = evt.getComponent().getX(); int ny = evt.getComponent().getY(); // top if(ny < 0+snap_distance) { ny = 0; } // left if(nx < 0+snap_distance) { nx = 0; } // right if(nx > size.getWidth() - evt.getComponent().getWidth() - snap_distance) { nx = (int)size.getWidth()-evt.getComponent().getWidth(); } // bottom if(ny > size.getHeight() - evt.getComponent().getHeight() - snap_distance) { ny = (int)size.getHeight()-evt.getComponent().getHeight(); } // make sure we don't get into a recursive loop when the // set location generates more eventslocked = true; evt.getComponent( ).setLocation(nx,ny); locked = false; } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Make a Draggable Window
- InhaltsvorschauDrag a window by clicking on its background using a special event listener.Most windows let you move them by dragging the titlebar. Some program windows, however, don't have titlebars. In the age of eye-candy interfaces (see iTunes and WinAmp for prime examples) it is very common to have a window—possibly non-rectangular—without any titlebar or window controls at all. This makes for a pretty window, but how do you move it? Simply by dragging any available space on the window. Though not terribly intuitive, such programs are commonplace, and this book wouldn't be called Swing Hacks without providing a Java implementation of draggable windows, even when no titlebar is used.The simplest approach to this problem is to create a listener that simply catches all drags and moves the window:
public class MoveMouseListener implements MouseListener, MouseMotionListener { JComponent target; JFrame frame; public MoveMouseListener(JComponent target, JFrame frame) { this.target = target; this.frame = frame; } public void mouseClicked(MouseEvent e) {} public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseMoved(MouseEvent e) {} public void mouseDragged(MouseEvent e) { frame.setLocation(new Point(e.getX(),e.getY()); } }This class implementsMouseListenerandMouseMotionListenerwith no-ops for all methods exceptmouseDragged(), which moves the frame to the current mouse location. However, this approach has two problems. First, the mouse coordinates are going to be relative to the component, rather than the screen. Thus, a click on a 50 x 50 button in the bottom right of the screen might return (25, 25) when it should really be more like (1000, 700). The other problem is that the code moves the origin of the frame to the mouse cursor. This would look strange because the window would immediately jump so that its upper-left corner is right under the cursor. The proper behavior is for the window to stay in the same positionEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add Windows Resize Icons
- InhaltsvorschauThe Windows resize icons aren't built into Java. Here's how to make your own.Windows has two standard icons to let users know that they can resize a window. I can't tell you why there are two or how to decide between them, but I can tell you how to reproduce both of them in Java. Note that this hack is concerned with painting the icons accurately—it will be up to your code to handle events on components that use these icons and handle them appropriately.First take a look at the two icons. Figure 5-2 shows the icon used by Windows Explorer, MS Paint, and other applications; Figure 5-3 shows the icon used by Word and other Office applications.The easiest and most flexible way to implement these icons is through the
Iconinterface. UsingIconallows you to change an icon's appearance easily if you need to match new system defaults. It's also a lot easier to implement transparency with anIconthan by making an image from the corner icon using a screen capture and editing it in a professional graphics application like Photoshop.
Figure 5-2: The Windows Explorer resize icon
Figure 5-3: The Windows MS Office resize iconIcon is pretty simple, and it has only three methods:voidpaintIcon(Component c, Graphics g, int x, int y); int getIconWidth(); int getIconHeight();ThegetIconWidth() andgetIconHeight() methods should be pretty easy to implement—you just need the pixel size of your custom icons. The paintIcon() method is where all the interesting stuff happens.Figure 5-4 shows a huge blowup of what we'll call the Explorer icon.At a glance, you can see the six squares in a triangular pattern with a subtle white 3D effect on the squares. The easiest thing to handle is the size, so start with that. This icon is 12 x 12 pixels (one square in Figure 5-4 equals one pixel).
Figure 5-4: The Windows Explorer icon zoomed inEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add Status Bars to Windows
- InhaltsvorschauLots of applications use a bottom-of-window panel to communicate status. But Swing doesn't provide a consistent way to do this. So, you need to provide it yourself.Many applications in Microsoft Windows use a status bar—an area at the bottom of each window (to the left of the resize box if there is one) that can be used to communicate summary information to the user in a compact form. Typical contents of a status bar might include what a web browser is doing (e.g., "Connecting to www.oreilly.com"), or, as in Figure 5-6, a summary of the contents of a folder, showing the number of contained items, their size, etc.
Figure 5-6: Windows Explorer's status barThis is the standard MS Windows setup for a status bar:- An icon on the far right letting users know they can resize the application
- A label on the left for free form text
- Several labels on the right for details (e.g., 42.3 MB in Figure 5-6)
There is also a bit of custom painting involved to get the top and bottom shading right. First, you'll do the panel shading, and then loop back to the previous list and add all of the necessary components to the status bar.This hack copies the Windows Explorer status bar in Windows XP. Different applications are slightly different. The purpose of this hack isn't to start a religious war about which application to copy. You can just make minor changes if you want to copy a different application's status bar (such as Word or Outlook, which are different than Windows Explorer and also from each other).Start by creating a class calledJStatusBarextendingJPanel:public class JStatusPanel extends JPanel { //more to come }Then add a constructor and set the preferred height to be 23 pixels (the height of the Windows Explorer status bar—count 'em up if you don't trust me). You can ignore the preferred width of the status bar since you'll be adding the status bar to your frame with aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Save Window Settings
- InhaltsvorschauMake sure your windows always show up right where you left them, even after a program restarts, by saving the window position and size automatically.Swing is a rich toolkit that can be used to create many kinds of programs, but there are certain features that virtually all applications need, like window settings and preferences. This hack shows how to automatically store and retrieve window locations and dimensions in an existing program without using custom
Framesubclasses, or even making many changes to your existing code.Saving the size and location of a window is actually pretty easy. You can just store them in a file and retrieve them later. The difficulty is identifying each window, and doing it in a way that's as noninvasive as possible.The first step is to create a class that handles all of the work. Because managing windows will be a global function of the entire program, start with a simple singleton with a factory interface, as shown in Example 5-4.Example 5-4. Singleton for saving window settingspublic class WindowSaver implements AWTEventListener { private static WindowSaver saver; private Map framemap; private WindowSaver() { framemap = new HashMap(); } public static WindowSaver getInstance() { if(saver == null) { saver = new WindowSaver(); } return saver; }TheWindowSaverconstructor creates a private map to store all of an application's frames. When each frame is loaded, the saver will store a reference to it in this map. Later, when the application needs to save or reload each window, it will use the map to find each frame again. The constructor is private so that only one instance (and one map) of the program ever exists in the JVM.TheWindowSaveralso implementsAWTEventListener. This is how it can find each frame. Swing has a global event queue that allows you to get every event throughout the JVM. You can access this queue by registering anEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Earthquake Dialog
- InhaltsvorschauMake sure your users really know they got their password wrong.One of the funny ways that Mac OS X uses animation in its UI is when the user logs in. If she enters an incorrect login and/or password, the whole login dialog shakes violently for a second, like a road sign thwacked with a bat, or a cartoon character who has just run full-speed into a solid object (say, a picture of a tunnel painted over a wall).We like this effect a lot, so we thought we'd bring it to Swing. It's a pretty straightforward bit of animation, so we jazzed it up…with trigonometry!Here's an initial queston: do you want to subclass
JDialogand add the animation effect to that class, or create a class that animates the shaking on anotherJDialog? I thought that subclassing would be a bad choice becauseJOptionPanegenerates some very convenientJDialogs, and you wouldn't want to lose those. So, you'll have to have another class animate your dialogs.I've called itDialogEarthquakeCenterbecause it'll be a class that monitors the shaking, just like seismologists do in their earthquake centers.Obviously, theDialogEarthquakeCenterneeds a reference to the dialog that it will be shaking. It also needs a few other values, which I've set as constants:SHAKE_DISTANCE- The maximum distance in each direction the dialog should move.
SHAKE_CYCLE- The time in milliseconds for a complete cycle: center, right, center, left, back to center.
SHAKE_DURATION- Total time in milliseconds to shake the dialog.
SHAKE_UPDATE- How often (in milliseconds) to update the dialog's position and repaint. You might increase this if the CPU use is excessive, but animation smoothness decreases with less-frequent updates.
Beyond that, all you'll need to keep track of is where the dialog started (so you can put it back at the end of the animation), a running clock of how far you are into the animation, and where the dialog is located. Example 5-5 shows the code to put all this into action.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Spin Open a Detail Pane
- InhaltsvorschauYou don't want to bombard the user with details, but you don't want to hide them either. Here's a way to let the user pop open a More Info widget.You have to be careful not to weigh down your GUIs with so much information that the user can't see what really matters. On the other hand, there are times that the user may want more information than is obvious on one screen or panel. A simple way of dealing with this is to put a More Info button that pops up a new window. That leads to the annoyance of having too many windows on the screen, none associated in any way with their source.Mac OS X has a nice idea: the spin open disclosure widget. It works like this: you have a component of some sort—perhaps a simple label or a complex panel—with a triangle-shaped spinner below it. When the user clicks the spinner, a whole new widget opens up below the spinner, offering more information. In fact, the new widget can have significant functionality: to set file permissions from the Finder, you open a Get Info window and spin open an Ownership and Permissions section to set your own access (if you own the file), and a second Details spinner lets you set access levels for the owner, group, and others.This hack is fairly simple and relies on one fact: you can add a component to a layout and alternately make it visible and invisible. Its position relative to other components is preserved when it's invisible, but it takes up no onscreen space. So, a spin-open container consists of three components:
- The top component, which is always visible
- The spinner
- The bottom component, whose visibility can be set by clicking on the spinner
The layout of these three is pretty straightforward, as seen in Example 5-6, which lists theMoreInfoPanelclass but omits an inner class (for now).Example 5-6. Laying out the three panel componentspublic class MoreInfoPanel extends JPanel { public Component topComponent; protected SpinWidget spinWidget; public Component bottomComponent; public static final int SPIN_WIDGET_HEIGHT = 14; public MoreInfoPanel (Component tc, Component mic) { topComponent = tc; spinWidget = new SpinWidget(); bottomComponent = mic; doMyLayout(); } protected void doMyLayout() { setLayout (new BoxLayout (this, BoxLayout.Y_AXIS)); add (topComponent); add (spinWidget); add (bottomComponent); resetBottomVisibility(); } protected void resetBottomVisibility() { if ((bottomComponent == null) || (spinWidget == null)) return; bottomComponent.setVisible (spinWidget.isOpen()); revalidate(); if (isShowing()) { Container ancestor = getTopLevelAncestor(); if ((ancestor != null) && (ancestor instanceof Window)) ((Window) ancestor).pack(); repaint(); } } public void showBottom (boolean b) { spinWidget.setOpen (b); } public boolean isBottomShowing () { return spinWidget.isOpen(); } // See below for SpinWidget inner class }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Minimize to a Mini-Frame
- InhaltsvorschauWhen you want your program to have a smaller window but still be on the screen, try building a mini-mod.Since the advent of iTunes, it seems that all consumer-oriented applications must have meticulous interfaces that can dynamically adjust themselves. Gone are the days of simply minimizing an application. Now your program must have a small version rather than (or in addition to) hiding when minimized. The smaller version contains limited controls but can fit nicely at the bottom of the screen. This hack shows how to create a dynamic frame that can switch properly between sizes for a more modern-looking interface.Switching a frame between two sizes is quite easy: just call
setSize( ) and you're done. Doing it well is a bit more difficult, however. When you minimize the window, you also need to remove the window decorations, hide the menu bar, and remove the components that shouldn't be visible in the mini-view. This is a bit trickier, not the least of which because you can't turn off the window decorations of a frame once it has been created. But I'm getting ahead of myself. First, you need a sample application.Let's take a simple clock program. The normal window looks like Figure 5-11. The goal is to provide a mini version that looks like Figure 5-12.
Figure 5-11: A normal application window
Figure 5-12: A mini application windowThis program has a clock, a panel with more configuration options (represented here with just the label More configuration), a menu bar, and a pop-up menu for later use. Example 5-9 creates the interface and puts the components in the right places, but it doesn't do anything with them yet.Example 5-9. The beginning of a clock with a mini versionpublic class MiniMizeHack implements MouseListener, ActionListener { public JFrame frame; public JPanel panel; public JPopupMenu popup; public JMenuBar menubar; public JLabeltop; public JLabel bottom; public MiniMizeHack() { top = new JLabel(new ImageIcon("image.png")); bottom = new JLabel("More configuration here"); frame = new JFrame("Mini Mize Hack"); panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add("Center",bottom); panel.add("North",top); frame.getContentPane().add(panel); menubar = new JMenuBar(); JMenu menu = new JMenu("File"); menu.add(new JMenuItem("Open")); menu.add(new JMenuItem("Quit")); menubar.add(menu); JMenu window = new JMenu("Window"); JMenuItem mini = new JMenuItem("Minimize"); mini.addActionListener(this); window.add(mini); menubar.add(window); frame.setJMenuBar(menubar); popup = new JPopupMenu(); JMenuItem restore = new JMenuItem("Restore"); restore.addActionListener(this); popup.add(restore); } public void mousePressed(MouseEvent e) { maybeShowPopup(e); } public void mouseReleased(MouseEvent e) { maybeShowPopup(e); } public void mouseExited(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseClicked(MouseEvent e) { } private void maybeShowPopup(MouseEvent e) { if (e.isPopupTrigger()) { popup.show(e.getComponent(), e.getX(), e.getY()); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 6: Transparent and Animated Windows
- InhaltsvorschauIn the previous chapter, our window hacks generally played by the rules—we simulated the earthquake dialog [Hack #38] by animating calls to
setLocation(), and switched to a mini-size window [Hack #40] by callingsetSize() and removing some window decorations.This chapter's hacks approach from outside the Window API per se, by hacking the windows with Java 2D, stuffing things into the glass pane of aJDialogorJFrame, and more. Some of them are practical, some are just pretty, but all of these hacks offer something unexpected.Create translucent and shaped windows, while avoiding native code, with clever use of a screenshot.One of the most commonly requested Swing features is transparent windows. Also called shaped windows, these are windows that have transparent portions, allowing the desktop background and other programs to shine through. Java doesn't provide any way of creating transparent windows without using the Java Native Interface (JNI) (and even then the native platform must support transparency as well), but that's not going to stop us. We can cheat using one of my favorite techniques, the screenshot.The process of faking a transparent window is basically:- Take a screenshot before the window is shown.
- Use that screenshot as the background of the window.
- Adjust the position so that the screenshot and the real screen line up, creating the illusion of transparency.
This is the easy part. The hard part is updating the screenshot when the window moves or changes.To start off, create aJPanelsubclass that can capture the screen and paint it as the background, as shown in Example 6-1.Example 6-1. A transparent background componentpublic classTransparentBackground extends Jcomponent { private JFrame frame; private Image background; public TransparentBackground(JFrame frame) { this.frame = frame; updateBackground(); } public void updateBackground() { try { Robot rbt = new Robot(); Toolkit tk = Toolkit.getDefaultToolkit(); Dimension dim = tk.getScreenSize(); background = rbt.createScreenCapture( new Rectangle(0,0,(int)dim.getWidth(), (int)dim.getHeight())); } catch (Exception ex) { p(ex.toString()); ex.printStackTrace(); } } public void paintComponent(Graphics g) { Point pos = this.getLocationOnScreen(); Point offset = new Point(-pos.x,-pos.y); g.drawImage(background,offset.x,offset.y,null); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 41–47: Introduction
- InhaltsvorschauIn the previous chapter, our window hacks generally played by the rules—we simulated the earthquake dialog [Hack #38] by animating calls to
setLocation(), and switched to a mini-size window [Hack #40] by callingsetSize() and removing some window decorations.This chapter's hacks approach from outside the Window API per se, by hacking the windows with Java 2D, stuffing things into the glass pane of aJDialogorJFrame, and more. Some of them are practical, some are just pretty, but all of these hacks offer something unexpected.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Transparent Windows
- InhaltsvorschauCreate translucent and shaped windows, while avoiding native code, with clever use of a screenshot.One of the most commonly requested Swing features is transparent windows. Also called shaped windows, these are windows that have transparent portions, allowing the desktop background and other programs to shine through. Java doesn't provide any way of creating transparent windows without using the Java Native Interface (JNI) (and even then the native platform must support transparency as well), but that's not going to stop us. We can cheat using one of my favorite techniques, the screenshot.The process of faking a transparent window is basically:
- Take a screenshot before the window is shown.
- Use that screenshot as the background of the window.
- Adjust the position so that the screenshot and the real screen line up, creating the illusion of transparency.
This is the easy part. The hard part is updating the screenshot when the window moves or changes.To start off, create aJPanelsubclass that can capture the screen and paint it as the background, as shown in Example 6-1.Example 6-1. A transparent background componentpublic classTransparentBackground extends Jcomponent { private JFrame frame; private Image background; public TransparentBackground(JFrame frame) { this.frame = frame; updateBackground(); } public void updateBackground() { try { Robot rbt = new Robot(); Toolkit tk = Toolkit.getDefaultToolkit(); Dimension dim = tk.getScreenSize(); background = rbt.createScreenCapture( new Rectangle(0,0,(int)dim.getWidth(), (int)dim.getHeight())); } catch (Exception ex) { p(ex.toString()); ex.printStackTrace(); } } public void paintComponent(Graphics g) { Point pos = this.getLocationOnScreen(); Point offset = new Point(-pos.x,-pos.y); g.drawImage(background,offset.x,offset.y,null); } }First, the constructor saves a reference to the parentJFrame; then it callsupdateBackground(), which captures the entire screen usingjava.awt.RobotEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Make Your Frame Dissolve
- InhaltsvorschauCreate animated frame dissolves using two screenshots and some clever graphics code.Dissolve is a term from old motion pictures where the director would switch scenes by fading, or dissolving, from one image to another. Eventually directors came up with more interesting dissolves like the vertical wipe, the venetian effect (little thin strips that look like venetian blinds), and the classic fade to black. With a little bit of screenshot hackery, you can create similar effects in Swing, allowing your program to fade away or do some other interesting animation when the user quits.AWT doesn't support real transparent or shaped windows (though you can fake it with a screenshot pasted into a window that fills the screen [Hack #41] . Most dissolves involve applying some graphic effect to both the starting and ending images, which in this case means the application window itself and the rest of the user's desktop under the window. With this in mind, the basic plan is four steps:
- Capture an image of the window.
- Capture an image of the entire screen without the window.
- Cover up the entire screen with a new window.
- Show the dissolve animation.
To keep this simple, I have created a special class that does a simple fade-to-transparent animation. Once the class is built, you can create more complicated animations by overriding the paint method, leaving the messy details to the parent class. Here's the basic skeleton:class Dissolver extends JComponent implements Runnable {Frame frame; Window fullscreen; int count; BufferedImage frame_buffer; BufferedImage screen_buffer; public Dissolver() { }Dissolveris aJComponentthat implementsRunnableso that it can have an animation loop. It has member variables for the application frame to dissolve (frame), the window that covers up the screen (fullscreen), an animation counter (count), and the two buffers for storing the frame and the desktop background image (frame_bufferandscreen_bufferEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Create Custom Tool Tips
- InhaltsvorschauReplace the standard rollover tool tip with an attractive custom version, including a border and rounded corners.Every Swing component can have a tool tip, a little snippet of explanatory text that pops up when you let your mouse cursor linger over the component. These tool tips are often useful, but they usually look quite boring. This hack shows how to create visually interesting tool tips with a custom subclass.In Swing, all tool tips are instances of the
JToolTipclass. To create your own version, you need only subclassJToolTipand override thepaintComponent() method. In this hack, we'll create a tool tip with a rectangle that has a beveled border and a white background. The actual drawing can be taken care of with a few Java2D drawing commands. Example 6-4 is the code to draw the tool tip's background and border.Example 6-4. A nice-looking tool tipclass CustomToolTip extends JToolTip { public void paintComponent(Graphics g) { // create a round rectangle Shape round = new RoundRectangle2D.Float(4,4, this.getWidth()-1-8, this.getHeight()-1-8, 15,15); // draw the white background Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(Color.white); g2.fill(round); // draw the gray border g2.setColor(Color.gray); g2.setStroke(new BasicStroke(5)); g2.draw(round); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_DEFAULT); // draw the text String text = this.getComponent().getToolTipText(); if(text != null) { FontMetrics fm = g2.getFontMetrics(); int h = fm.getAscent(); g2.setColor(Color.black); g2.drawString(text,10,(this.getHeight()+h)/2); } }This code creates a round rectangle shape that is then reused to draw the background and border. Notice that anti-aliasing is turned on for drawing the shape, but it's turned back to the default (which could be on or off) before drawing the text. It would look strange to have anti-aliased text if the rest of the interface was still using standard aliased text—using the default is a safer idea.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Turn Dialogs into Frame-Anchored Sheets
- InhaltsvorschauOne of Mac OS X's best ideas is binding the dialog to the window it blocks. This hack shows you how to mimic this in Swing.One of my favorite features in Mac OS X is the sheet. This is a dialog box replacement that slides down from a window's titlebar. Figure 6-7 shows an example of a sheet in Apple's Safari web browser.Looking at it, you might think, "what's the big deal" or “how is this any different than a regular dialog?” Oh, it's far better:
- A sheet is visually anchored to the window that it blocks
- On platforms where dialogs have titlebars and close boxes, the relationship between a dialog and the window it blocks is not necessarily intuitive. On a related point….
Figure 6-7: Sheet in Mac OS X Safari browser - A sheet doesn't have a close box
- Dialog close boxes are one of the most hateful and stupid concepts in Windows and its many Linux imitators. What does the close box mean? Cancel? The default option? What does it mean when the dialog has multiple options of equal plausibility and thus no default? Perhaps the worst thing about the close box was back in the AWT era when Java developers—too lazy to add and wire-up an OK button to their dialogs—just figured users could dismiss the dialog with the close box. Mac OS 8 and 9 dialogs didn't have close boxes, so when a Java application brought up such a dialog, the application blocked itself forever. Duh. Sheets mean having to click one of the provided buttons, so the user's choices are unambiguous.
- A sheet is used to block one window
- This is an obvious side effect of being visually tied to a single window, but that's probably the most common case. As a side effect, this gives greater prominence to dialogs that block all windows for a single application ("Are you sure you want to Quit and lose all unsaved changes in all documents?") and dialogs that block all applications (“Are you sure you want to Shut Down?”).
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Animating a Sheet Dialog
- InhaltsvorschauBy animating the sheet's appearance and disappearance, you give the user a better clue that his attention is required. Plus, it looks cool.Another really great thing about the sheet functionality in Mac OS X is that it doesn't just suddenly appear—it slides out from the titlebar, as if unrolling from under the bar. This animation further reinforces the relationship between the sheet and the window because the short animation catches your eye and alerts you to the fact that something about the window has changed dramatically—namely, that it is now blocked by the sheet dialog.You already know how to get components into the glass pane [Hack #44] , so you should expect that the key to sheet animation is to perform the animation in the glass pane, on top of the other components. Of course, you might have also guessed that the tricky part of this is going to be showing successively larger parts of the dialog as the animation progresses.To make this work, you first need to create a custom component for the animating version of the sheet, separate from the sheet itself. Then, on each pass of the animation cycle, change the size of the custom component. It will always have the same width—the width of the real sheet—but its height will be some percentage of the height of the original, based on how much of the animation time has elapsed. This approach can work for both directions of the animation: when the sheet is incoming, the height will get progressively greater; when the sheet is going out, the height will decrease.To actually draw the animating sheet during the animation, you can use
BufferedImage.getSubimage( ) to grab a portion of the real sheet, and then draw that into its ownGraphics via paint() callbacks. When the animation completes, the animating sheet is removed from the glass pane and the real sheet is added.An interesting side effect of this is that the user can't click the buttons as the sheet appears or retracts because it's just an image of the sheet, not the sheet itself. Of course, the animation is so short (one second in Example 6-9, and Mac OS X's actually comes out faster than that), that it's unlikely a user could track the moving sheet with her mouse and successfully click a button anyway.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Slide Notes Out from the Taskbar
- InhaltsvorschauPop up a note above the taskbar when your application wants attention.On Windows, long-running applications sometimes will slide in a window above the taskbar to call attention to themselves when an interesting event occurs, such as a finished download or an IM buddy's appearance.If you want to do this in Java, you need to deal with a pretty significant problem: neither AWTnor Swing has any concept of the taskbar (where it is, how big it is, whether it's auto-hiding, or anything else). As a result, you don't know where to draw the window, and just taking a guess or hardcoding something is hazardous—too high and the window floats inexplicably on the desktop, too low and it gets buried under the taskbar.Furthermore, how is this going to work on other operating systems? On the Mac, the proper way to get attention is to bounce your application's dock icon. Since there's no API exposing that functionality, can you at least use a Windows-like slide-in window above the dock? Sure…if you can figure out how tall the dock is (it's user configurable), or whether the dock is even on the bottom of the screen (it might be on the right or left, too).Fortunately, it is possible to figure out what unobstructed space is available to you on the main display. After that, it's just a matter of offscreen imaging and animation.The key to figuring out your available space is to get the local
GraphicsEnvironment, which describes the display, and then callgetMaximumWindowBounds(). This method, introduced in Java 1.4, returns aRectanglerepresenting the largest centeredWindowthat could fit on the display, accounting for objects that intrude on the display's usable space, like the Windows taskbar or the Mac's monolithic menu bar.This means that on Windows, theRectanglewill have an upper-left corner at 0,0; on the Mac, it will be at 0,22, which leaves space for the Mac's menu bar. Meanwhile, the height of theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Indefinite Progress Indicator
- InhaltsvorschauDespite its numerous advanced widgets, Swing offers no efficient way to show that a task of unknown length is in progress. This hack presents two solutions to address this issue.Have you ever watched an application do something, but not tell you what that something is? Other applications let you know what's going on but don't really tell you how long they will need to complete the task. For instance, the Microsoft Windows copy dialog is famous for its silly (and lengthy) nonprogress indicators. As a user, I find this very annoying; as a programmer, I know how difficult it can be to determine the duration of a task.To address this issue, developers created particular widgets meant to show a task of unknown length is in progress. You can see such a widget in Mozilla's installer. It displays an indefinite progress bar—also called a Cylon—in which a small rectangle bounces back and forth between the two horizontal edges. I have also seen indefinite progress bars filling like regular progress bars, going backward once filled and starting all over again. The idea of an indefinite progress bar is great, but most existing implementations are just wrong. Users know what a progress bar looks like, and they also know how it is supposed to behave. It is a bad idea to present a familiar widget acting in a very surprising way. Unfortunately, the Swing designers followed this trend and added the
setIndeterminate(boolean) method toJProgressBar. Check out Example 6-14.Example 6-14. An indefinite progress barimport javax.swing.*; public class CylonBar { public static void main(String[] args) { JFrame f = new JFrame("Progress"); JProgressBar p = new JProgressBar(); p.setIndeterminate(true); f.getContentPane().add(p); f.pack(); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); } }This short example creates a new window containing an indefinite progress bar. When you launch the program, you can see a small rectangle bouncing back and forth within the progress bar's bounds, as in Figure 6-12. The result is much better on Mac OS X because it uses the native Look and Feel automatically, as shown in Figure 6-13. Despite this visual improvement, though, the result is still far from perfect.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 7: Text
- InhaltsvorschauText handling pervades the Swing API, from the labeling of a
JButtonto handling styled text in aJTextArea. When we talk about hacking Swing's text handling, we often mean two different things: hacking into the representation of the text (say, by making it searchable), or hacking into how that text is displayed. This chapter will help you see that Swing text isn't just about littleJTextFieldsfor entering your username.This hack will show you how to add incremental search to a text area as a simple document listener, making it very easy to integrate with your existing software.Many years ago, text editors had either no searching capabilities or a straight word search only. You would type a word into a dialog box and the program would search for that word, moving to its first location in the document. If you were lucky, there was a command to search again, instead of starting the process over. Then one day Emacs introduced a new kind of searching. The program would search as you typed in each character, updating the cursor with each keystroke. To search again, you only had to hit the Return key. If you wanted a less specific search, you could just hit the Backspace key and remove letters. Everything updated in real time. Incremental searching was born.Most GUI toolkits—Java Swing included—do not provide incremental search. Java does, however, give you the tools to build your own incremental search. The 1.4 release of Java finally added a long requested feature: regular expressions. These are complex but concise patterns that are internally compiled into searching and matching code. With a single regular expression (known more commonly as a regex) you could match email addresses, split a complex data field, parse SQL expressions, or recognize different date formats. This hack has much more humble needs, but Java 1.4's built-in regex support will make the search implementation very easy.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 48–55: Introduction
- InhaltsvorschauText handling pervades the Swing API, from the labeling of a
JButtonto handling styled text in aJTextArea. When we talk about hacking Swing's text handling, we often mean two different things: hacking into the representation of the text (say, by making it searchable), or hacking into how that text is displayed. This chapter will help you see that Swing text isn't just about littleJTextFieldsfor entering your username.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Make Text Components Searchable
- InhaltsvorschauThis hack will show you how to add incremental search to a text area as a simple document listener, making it very easy to integrate with your existing software.Many years ago, text editors had either no searching capabilities or a straight word search only. You would type a word into a dialog box and the program would search for that word, moving to its first location in the document. If you were lucky, there was a command to search again, instead of starting the process over. Then one day Emacs introduced a new kind of searching. The program would search as you typed in each character, updating the cursor with each keystroke. To search again, you only had to hit the Return key. If you wanted a less specific search, you could just hit the Backspace key and remove letters. Everything updated in real time. Incremental searching was born.Most GUI toolkits—Java Swing included—do not provide incremental search. Java does, however, give you the tools to build your own incremental search. The 1.4 release of Java finally added a long requested feature: regular expressions. These are complex but concise patterns that are internally compiled into searching and matching code. With a single regular expression (known more commonly as a regex) you could match email addresses, split a complex data field, parse SQL expressions, or recognize different date formats. This hack has much more humble needs, but Java 1.4's built-in regex support will make the search implementation very easy.The plan for this hack is to create a searching utility object that targets a
JTextComponent(the common superclass for all Swing text components such asJTextAreaandJTextField). It will also listen for action and document change events from a search component (usually aJTextField) to do the actual searching.The code in Example 7-1 defines theIncrementalSearchclass, which implements theDocumentListenerandActionListenerEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Force Text Input into Specific Formats
- InhaltsvorschauUse Java's powerful pattern matching to enforce rules on typed inputValidating input is an important GUI task, and some applications will validate your input when you tab off a field or even validate it on every keystroke. After all, it's a lot easier to deal with bogus data by not letting it into your system in the first place.One technique for validating user input is to use a regular expression and then evaluate the input against it. For example, a field that can be uppercase letters only must always match the expression [
A-Z]*, and one that can be any combination of uppercase, lowercase, numbers, and spaces must match [A-Za-z0-9]* (notice the space after9).Java's regex feature lets you createTextComponents that enforce matching against an expression. The basic idea is to watch for changes in the underlyingDocumentand do your pattern match then.Hopefully, you won't be surprised to know that you don't need to touch the view classes—JTextField, JTextArea, etc.—to add text constraint functionality. Text entry is happening in the model—in other words theDocument—so that's where you tie in your regex code. This hack, listed in Example 7-2, subclassesPlainDocumentto run the regex check on every call toinsertString().Example 7-2. A document allowing input that matches only a regeximport javax.swing.text.*; import java.util.regex.*; public classRegexConstrainedDocument extends PlainDocument { Pattern pattern; Matcher matcher; public RegexConstrainedDocument () { super(); } public RegexConstrainedDocument (AbstractDocument.Content c) { super(c); } public RegexConstrainedDocument (AbstractDocument.Content c, String p) { super (c); setPatternByString (p); } public RegexConstrainedDocument (String p) { super(); setPatternByString (p); } public void setPatternByString (String p) { Pattern pattern = Pattern.compile (p); // checks the document against the new pattern // and removes the content if it no longer matches try { matcher = pattern.matcher (getText(0, getLength())); System.out.println ("matcher reset to " + getText (0, getLength())); if (! matcher.matches()) { System.out.println ("does not match"); remove (0, getLength()); } } catch (BadLocationException ble) { ble.printStackTrace(); // impossible? } } public Pattern getPattern() { return pattern; } public void insertString (int offs, String s, AttributeSet a) throws BadLocationException { // consider whether this insert will match String proposedInsert = getText (0, offs) + s + getText (offs, getLength() - offs); System.out.println ("proposing to change to: " + proposedInsert); if (matcher != null) { matcher.reset (proposedInsert); System.out.println ("matcher reset"); if (! matcher.matches()) { System.out.println ("insert doesn't match"); return; } } super.insertString (offs, s, a); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Auto-Completing Text Fields
- InhaltsvorschauTyping in a whole URL is a pain. When the user starts to type, complete his text with previously entered options, and let the user select one instead of typing the whole URL.The auto-completing text field is instantly familiar from its use in browsers, where it is probably most needed. Nobody wants to have to try to type—or for that matter even remember—a huge URL to some page they've visited before, particularly not something like those Amazon.com URLs with inexplicable 20-digit numbers and bunches of seemingly arbitrary characters. On the other hand, not everything needs to be saved as a bookmark.The text field that pops up a window of recently viewed sites is a happy compromise. It jogs your memory by showing you completion options, and it saves lots of typing by letting you simply click one of the options and having that text inserted immediately into the text field.This hack takes a
JTextFieldand has it manage aJWindow, which contains aJListof possible completion values. The real work is done by an inner class that manages the list of completions and has ajavax.util.regex.Patternobject to match each potential completion against the field's current text. Example 7-4 is what you need to get going.Example 7-4. A JTextField that manages a pop-up list of completionsimport java.awt.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.text.*; import java.util.*; import java.util.regex.*; public class CompletableJTextField extends JTextField implements ListSelectionListener { Completer completer; JList completionList; DefaultListModel completionListModel; JScrollPane listScroller; JWindow listWindow; public CompletableJTextField (int col) { super (col); completer = new Completer(); completionListModel = new DefaultListModel(); completionList = new JList(completionListModel); completionList.setSelectionMode (ListSelectionModel.SINGLE_SELECTION); completionList.addListSelectionListener (this); listScroller = new JScrollPane (completionList, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); listWindow = new JWindow(); listWindow.getContentPane().add (listScroller); } public void addCompletion (String s) { completer.addCompletion (s); } public void removeCompletion (String s) { completer.removeCompletion (s); } public void clearCompletions (String s ) { completer.clearCompletions (); } public void valueChanged (ListSelectionEvent e) { if (e.getValueIsAdjusting()) { return; } if (completionList.getModel().getSize() == 0) {return;} listWindow.setVisible (false); final String completionString = (String) completionList.getSelectedValue(); Thread worker = new Thread() { public void run() {setText (completionString); } }; SwingUtilities.invokeLater (worker); } /** inner class does the matching of the JTextField's document to completion strings kept in an ArrayList */ class Completer implements DocumentListener { private Pattern pattern; private ArrayList completions; public Completer() { completions = new ArrayList(); getDocument().addDocumentListener (this); } public void addCompletion (String s) { completions.add (s); buildAndShowPopup(); } public void removeCompletion (String s) { completions.remove (s); buildAndShowPopup(); } public void clearCompletions () { completions.clear(); buildPopup(); listWindow.setVisible(false); } private void buildPopup() { completionListModel.clear(); System.out.println ("buildPopup for " + completions.size() + " completions"); Iterator it = completions.iterator(); pattern = Pattern.compile (getText() + ".+"); while (it.hasNext()) { // check if match String completion = (String) it.next(); Matcher matcher = pattern.matcher (completion); if (matcher.matches()) { // add if match System.out.println ("matched "+ completion); completionListModel.add (completionListModel.getSize(), completion); } else { System.out.println ("pattern " + pattern.pattern() + " does not match " + completion); } } } private void showPopup() { if (completionListModel.getSize() == 0) { listWindow.setVisible(false); return; } // figure out where the text field is, // and where its bottom left is java.awt.Point los = getLocationOnScreen(); int popX = los.x; int popY = los.y + getHeight(); listWindow.setLocation (popX, popY); listWindow.pack(); listWindow.setVisible(true); } private void buildAndShowPopup() { if (getText().length() < 1) return; buildPopup(); showPopup(); } // DocumentListener implementation public void insertUpdate (DocumentEvent e) { buildAndShowPopup(); } public void removeUpdate (DocumentEvent e) { buildAndShowPopup(); } public void changedUpdate (DocumentEvent e) { buildAndShowPopup(); } } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Write Backward Text
- InhaltsvorschauBaffle your friends by turning their text into its mirror image.The fact that everything in Swing goes through the Java2D rendering pipeline makes it really easy to apply all sorts of effects to Swing components. Text offers some fun possibilities. For one, you can challenge the user by turning his text into a mirror image of itself.The easiest text component to distort is the simple
JLabel. TheBackwardsJLabelclass in Example 7-6 subclassesJLabeland uses anAffineTransformin thepaint() method to do the flip.Example 7-6. Rendering a JLabel as a mirror imageimport java.awt.*; import javax.swing.*; import java.awt.geom.*; import javax.swing.text.Document; public class BackwardsJLabel extends JLabel { public BackwardsJLabel () { super(); } public BackwardsJLabel (Icon image) {super (image);} public BackwardsJLabel (Icon image, int align) {super (image, align);} public BackwardsJLabel (String text) { super (text);} public BackwardsJLabel (String text, Icon icon, int align) { super (text, icon, align); } public BackwardsJLabel (String text, int align) { super (text, align);} public void paint (Graphics g) { if (g instanceof Graphics2D) { Graphics2D g2 = (Graphics2D) g; AffineTransform flipTrans = newAffineTransform(); double widthD = (double) getWidth(); flipTrans.setToTranslation (widthD, 0); flipTrans.scale (-1.0, 1); g2.transform (flipTrans); } super.paint(g); }The constructors make trivial calls to their parent classes, so the key is the overriddenpaint() method. It first checks that you have aGraphics2Dand does the cast. AnyGraphics2Dhas anAffineTransformationthat defines transforms that are to be applied as theGraphics2Dis rendered. TheAffineTransformof aComponentwill usually have some important transforms already defined in it, so it's best not to replace its transform, but rather to use theGraphics2D.transform() method to modify the existingAffineTransformwith one of your own making.But what kind of transform do you want to do? A mirror image consists of two separate trasnformations: scaling the x-coordinates by a factor of -1 (flipping them around the axis, so that the pixels furthest to the right are now furthest to the left), and translating by the width of the component (so the pixels move from negative coordinates, where they wouldn't be seen, back into positive space). This transformation is illustrated in Figure 7-8.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Use HTML and CSS in Text Components
- InhaltsvorschauSpruce up your plain JLabels and buttons using HTML and CSS effects, such as underlines, color, and even embedded tables.You may know that you can display HTML using a subclass of
JTextPane, but did you also know that Swing supports simple HTML and CSS in virtually every text component? As long as you can trick it into showing it as HTML instead of the markup, you can do some pretty nifty things.Every text component in Swing can display HTML, but the component needs to know that the text is HTML, rather than a string that just happens to contain a bunch of angle brackets. Since there is nosetHTMLText(true)method onJTextComponent, you have to resort to being a little trickier. If the string passed to the component's constructor (orsetText()) method starts with <html>, then the component will switch to HTML mode. Here is a quick example:JButton b1a = new JButton("<html><i>my button</i>");This code will produce a button that looks like Figure 7-11.
Figure 7-11: Italic textYou don't need to match the <html> with an </html> tag at the end. Swing's HTML parser is pretty tolerant of malformed HTML, so for simple things you can just type whatever is shortest. The mode can only be set once, so if you put plain text into the component first and then HTML later, it will still be in plain text mode. You should note that slower computers will exhibit a noticeable delay the first time a component is shown with HTML. This is because Swing has to load up all of thejavax.swing.text.htmlclasses; however, they are cached for any further instances.To avoid this initial delay, you could load a hidden component in a separate thread during program startup.HTML can be used as a shortcut for text effects that would be cumbersome or impossible with standardFontobjects. For example,Fontdoesn't provide a way to draw underlined text, but with HTML you can do this:JLabel l1 = new JLabel("<html><u>underlined</u></html>");Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Use Global Anti-Aliased Fonts
- InhaltsvorschauThink Swing apps always look ugly because of the chunky fonts? Finally, you can do something about it!Java 1.2 introduced Java2D, complete with the ability to draw anti-aliased text. Unfortunately, anti-aliasing is off by default and turning it on requires a programmatic change on each UI component. This hack shows how to turn on anti-aliasing for an entire frame without customizing each component. It also introduces a special repaint manager that is the key to several other hacks in this book, such as the partial translucent menus [Hack #12] .To turn on anti-aliasing, you simply need to set a rendering hint:
Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Unfortunately, theGraphicsobject is not very long-lived. There is no global place to set a hint because there's a newGraphicsobject for everyrepaint(). Any property you set would be gone by the next paint call. The usual workaround is to subclass the component you want to anti-alias and override thepaint() method:class AAButton extends JButton { public voidpaint(Graphics g) { Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); super.paint(g2); } }This will work, but it means you have to create a custom subclass for every component in your application. Not a very appealing solution.Mac OS X provides anti-aliased rendering through a system property, but this only works because Apple thoughtfully added it to their JVM. Developers on other platforms are left out. Java 5.0 provides a standard system property for anti-aliasing, but that doesn't help the millions of 1.3 and 1.4 JVMs out there. Another option would be to use some form of code injection to modify each paint method at the bytecode level, but this requires an AOP tool, custom build scripts, and other things that are probably overkill for such a simple feature. There has to be a better way—and there actually is.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Anti-Aliased Text Without Code
- InhaltsvorschauDraw anti-aliased text without any code changes at all using two clever tricks introduced in Java 5.0.Since Java 1.2, UI programmers can draw anti-aliased text. Unfortunately, anti-aliasing must be enabled for every Swing component by writing a few lines of code for each of them [Hack #53] . This hack describes a clever way to turn on anti-aliasing for an entire frame by adding a customized repaint manager. As every programmer seeks for effortless solutions, we will discover how to do the same without writing any lines of code.Sun Microsystems released Java 5.0, a.k.a. Tiger, in September 2004. Among many improvements, like a new theme for the Metal L&F, this release of J2SE paves the way for application-wide text anti-aliasing support in Mustang, the upcoming release of Java. To this end, the Swing team added a special field in the hidden class
com.sun.java.swing.SwingUtilities2. Meant for internal purposes only, this class is left undocumented by Sun's engineering teams.If you look closely at its source code, provided in src.zip with Sun's JVM, you'll discover a very interesting method:drawTextAntialiased(JComponent c). This method returns abooleanvalue used by Swing's painting framework to know whether the specified component must be drawn with anti-aliased text. Here is its complete source code:private static boolean drawTextAntialiased(JComponent c) { if (!AA_TEXT_DEFINED) { if (c != null) { return ((Boolean)c.getClientProperty( AA_TEXT_PROPERTY_KEY)).booleanValue(); } return false; } return AA_TEXT; }As you can see, there are two ways to enable anti-aliased text. In the first case, the static variableAA_TEXT_DEFINEDis set to false, and a check is performed against the component's properties. Hence, a component in which the propertyAA_TEXT_PROPERTY_KEYis set to true will be anti-aliased. You can set this property to a given component with the following line of code:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Anti-Aliased Text with a Custom Look and Feel
- InhaltsvorschauAnother way to get smooth text is to use a custom Look and Feel to avoid the fragile Java 5.0 APIs.When the Java 5.0 anti-aliasing trick [Hack #54] was first discovered, some discussions arose on the Web. Many people strongly disagree with using it because it can be broken at any time by Sun Microsystems. Should we be deprived of anti-aliased text because of such a silly problem? Frédéric Lavigne, author of the famous Skin L&F and webmaster of www.javootoo.com, a great repository of Look and Feels for Swing, didn't think so and found an elegant and clever way to get the same result.His idea is to use a custom Look and Feel whose sole purpose is to enable anti-aliasing hints on the
Graphicsinstances used to draw the UI. He implemented his idea in the Wrap Look and Feel, which can be downloaded at wraplf.l2fprod.com. Another Look and Feel, SmoothMetal, enables anti-aliasing in your application. Yet, you are stuck with Metal Look and Feel when using it. Wrap Look and Feel acts as a decorator for the current Look and Feel. Thus, you can choose any Look and Feel you want and wrap it with Wrap Look and Feel to enable anti-aliasing. Doing so requires a single line of code:import com.l2fprod.common.swing.plaf.wrap.Wrapper; Wrapper.wrap();
No matter which Swing Look and Feel you set, thewrap() method will handle it properly. One line of code is good, but not good enough. We'd be better off with no line of code at all. Frédéric feels the same way and provides the excellentWrapitclass you can use to install the Wrap Look and Feel at runtime:java -classpath wraplf.jar;. Wrapit WebHunter
TheWrapitclass contains amain() entry point that will install the Wrap Look and Feel and then call themain() entry point of the class passed as the first argument after Wrapit. This Look and Feel is a powerful tool you can use to enhance the appearance of any Java application, whether you have the source code or not.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 8: Rendering
- InhaltsvorschauSometimes it's not what you put into your GUI, but how you draw it. The hacks in this chapter are based in some way on using (or abusing) how AWT and Swing render the graphic contents of a GUI. In several cases, we use Java2D to bring graphic transformations and color-handling to Swing components. In others, we use AWT's font handling to change components; not just
JTextComponents, but any components that need to draw text to render themselves. And in still other cases, we mess with the process by which Swing renders its contents.Zoom in on those pixels with a little creative abuse of the AWT's debuggingorientedRobotclass.Some graphics programs use a component that shows a magnified view of what the cursor is currently hovering over. This can be very helpful for doing pixel-accurate editing of a picture.It should be simple enough to do in Swing—get pixels from one component and put them in another—but there are some missing pieces. Specifically, how do you get the pixels out of the source component as anImageso you candrawImage() them into the magnified component? You could do this if you owned the source component and set it up with a double-buffer because creating the offscreen buffer would require creating anImage, which is exactly what you needed anyway. But for an arbitraryJComponent, you can't assume that level of access to the source's pixels.But there's another option back in AWT: theRobotclass, introduced in J2SE 1.3. It has acreateScreenCapture() method that can grab the screen, or just part of it, and return it as a Java2DBufferedImage. This is what we need to get things going.TheDetachedMagnifyingGlasswill need to keep track of theComponentit's viewing, the current mouse location in that component, a zoom factor, and its own size. It will also need an instance of the AWTRobotfor taking screen grabs. The other thing it needs to do is to have aMouseMotionListener, so that it will get updates on the cursor's position and, when it changes, do a new grab andEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 56–64: Introduction
- InhaltsvorschauSometimes it's not what you put into your GUI, but how you draw it. The hacks in this chapter are based in some way on using (or abusing) how AWT and Swing render the graphic contents of a GUI. In several cases, we use Java2D to bring graphic transformations and color-handling to Swing components. In others, we use AWT's font handling to change components; not just
JTextComponents, but any components that need to draw text to render themselves. And in still other cases, we mess with the process by which Swing renders its contents.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Create a Magnifying Glass Component
- InhaltsvorschauZoom in on those pixels with a little creative abuse of the AWT's debuggingoriented
Robotclass.Some graphics programs use a component that shows a magnified view of what the cursor is currently hovering over. This can be very helpful for doing pixel-accurate editing of a picture.It should be simple enough to do in Swing—get pixels from one component and put them in another—but there are some missing pieces. Specifically, how do you get the pixels out of the source component as anImageso you candrawImage() them into the magnified component? You could do this if you owned the source component and set it up with a double-buffer because creating the offscreen buffer would require creating anImage, which is exactly what you needed anyway. But for an arbitraryJComponent, you can't assume that level of access to the source's pixels.But there's another option back in AWT: theRobotclass, introduced in J2SE 1.3. It has acreateScreenCapture() method that can grab the screen, or just part of it, and return it as a Java2DBufferedImage. This is what we need to get things going.TheDetachedMagnifyingGlasswill need to keep track of theComponentit's viewing, the current mouse location in that component, a zoom factor, and its own size. It will also need an instance of the AWTRobotfor taking screen grabs. The other thing it needs to do is to have aMouseMotionListener, so that it will get updates on the cursor's position and, when it changes, do a new grab andrepaint().TheDetachedMagnifyingGlasscode is shown in Example 8-1.Example 8-1. JComponent to provide a magnified view of another JComponentpublic class DetachedMagnifyingGlass extends JComponent implements MouseMotionListener { double zoom; JComponent comp; Point point; Dimension mySize; Robot robot; public DetachedMagnifyingGlass (JComponent comp, Dimension size, double zoom) { this.comp = comp; // flag to say don't draw until we get a MouseMotionEvent point = new Point (-1, -1); comp.addMouseMotionListener(this); this.mySize = size; this.zoom = zoom; // if we can't get a robot, then we just never // paint anything try { robot = new Robot(); } catch (AWTException awte) { System.err.println ("Can't get a Robot"); awte.printStackTrace(); } } public void paint (Graphics g) { if ((robot == null) || (point.x == -1)) { g.setColor (Color.blue); g.fillRect (0, 0, mySize.width, mySize.height); return; } Rectangle grabRect = computeGrabRect(); BufferedImage grabImg = robot.createScreenCapture (grabRect); Image scaleImg = grabImg.getScaledInstance (mySize.width, mySize.height, Image.SCALE_FAST); g.drawImage (scaleImg, 0, 0, null); }private Rectangle computeGrabRect() { // width, height are size of this comp / zoom int grabWidth = (int) ((double) mySize.width / zoom); int grabHeight = (int) ((double) mySize.height / zoom); // upper-left corner is current point return new Rectangle (point.x, point.y, grabWidth, grabHeight); } public Dimension getPreferredSize() { return mySize; } public Dimension getMinimumSize() { return mySize; } public Dimension getMaximumSize() { return mySize; } // MouseMotionListener implementations public void mouseMoved (MouseEvent e) { Point offsetPoint = comp.getLocationOnScreen(); e.translatePoint (offsetPoint.x, offsetPoint.y); point = e.getPoint(); repaint(); } public void mouseDragged (MouseEvent e) { mouseMoved (e); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Create a Global Right-Click
- InhaltsvorschauGive your application a right-click context menu without having to add a listener to every component.Oftentimes, an application needs to have a pop-up menu that is accessible from more than one component. Sometimes the entire window should be right-clickable. Unfortunately, doing this the normal way in Swing would require adding a mouse listener to every component in the window, which isn't a very appealing solution, especially if your UI code is spread across many classes. It would be much nicer if there were a single place to add the context menu. This hack shows how to use a single glass pane to provide a right-clickable menu to the entire application.A glass pane is an invisible
JComponentthat covers an entireJFrame. The glass pane can be used to catch events or draw on top of the rest of the application. For this hack, we will use a glass pane to capture right-click events and trigger a pop up, alleviating the need to register a mouse listener with every component in the frame. The basic idea is for the glass pane to intercept all mouse events and forward them on to the application, except for the right-click. Right-clicks will trigger the pop-up menu instead. This way there is only one listener per frame, instead of potentially hundreds.To start off, you need a component that has a reference to the content pane of the frame and a pre-built pop-up menu. This is the beginning of just such a class:public class RightClickGlassPane extends JComponent implements MouseListener, MouseMotionListener { private JPopupMenu popup; private Container contentPane; public RightClickGlassPane(Container contentPane, JPopupMenu menu) { addMouseListener(this); addMouseMotionListener(this); this.contentPane = contentPane; popup = menu; } public void paint(Graphics g) { } // catch all mouse events and redispatch them public void mouseMoved(MouseEvent e) { redispatchMouseEvent(e, false); } public void mouseDragged(MouseEvent e) { redispatchMouseEvent(e, false); } public void mouseClicked(MouseEvent e) { redispatchMouseEvent(e, false); } public void mouseEntered(MouseEvent e) { redispatchMouseEvent(e, false); } public void mouseExited(MouseEvent e) { redispatchMouseEvent(e, false); } public void mousePressed(MouseEvent e) { redispatchMouseEvent(e, false); } public void mouseReleased(MouseEvent e) { redispatchMouseEvent(e, false); }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Block a Window Without a Modal Dialog
- InhaltsvorschauBlock the input in a single window during long operations without stopping your entire application.Since the dawn of GUIs, most toolkits have had the concept of a modal dialog box. This is a small window that restricts input to itself, blocking access to the rest of the program (or entire operating system in some cases). Modal windows often produce the desired effect, but sometimes you need a window that can block itself without blocking access to the whole application. The most common use for such a window is a long running process, like rendering frames of a movie or waiting for the network to respond. In this case, you would like to let the user still interact with the rest of the application but block the one window that represents the work in progress. Swing doesn't provide a modal window like this, but since when has that stopped us?To block a window, you could disable the components within it, but then you would need to recursively find each component and disable it manually. This is a big headache, and would make for a very ugly window (all those grayed-out components). All you really want to do is capture all input to the window and block that. Swing provides a great way to do this: the glass pane. The glass pane sits on top of all other components in a window, making it the perfect place to implement blocking behavior.Since you want your glass pane to be transparent, it's best to start with a plain
JComponentthat doesn't draw anything. Example 8-3 defines aWindowBlockerclass that extendsJComponentand implements theMouseInputListener(a compound interface that combines theMouseListenerandMouseMotionListener).Example 8-3. Listening for mouse eventspublic class WindowBlocker extends JComponent implements MouseInputListener { public WindowBlocker() { addMouseListener(this); addMouseMotionListener(this); } public void mouseMoved(MouseEvent e) { } public void mouseDragged(MouseEvent e) { } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mousePressed(MouseEvent e) { Toolkit.getDefaultToolkit().beep(); } public void mouseReleased(MouseEvent e) { }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Create a Color Eyedropper
- InhaltsvorschauEnhance your color pickers with an eyedropper tool that grabs a color from anywhere on the screen.Most paint tools give you an eyedropper, but I've never seen a Java program do it. Getting a screen pixel requires native access, which is usually blocked off from Java programs. Java 1.3 introduced a new method to the
Robotclass,getPixelColor(), which can retrieve the color anywhere on the screen. The problem is that you don't get mouse events once the cursor leaves yourJFrame. This is fine if you only want to select colors from your own application, but a color chooser needs to select from anywhere on the screen. Java 5.0 introduces new APIs for getting complete mouse events, but that doesn't help us today.The answer to this tricky problem, of course, is to cheat! This hack makes a screenshot and then paints it into aJFramecalledColorChooserDemo, which fills the entire screen. The screenshot is indistinguishable from the real desktop except that nothing in the background updates. However, since the screenshot is only needed while the user selects a color, this should be OK.ColorChooserDemoalso has aJLabelin the center of the screen, which displays the currently selected color. Once the user has finished selecting a color by releasing the mouse, the entire frame will disappear and the component that launched the chooser—usually aJButton—will get the color throughsetBackground(). While it's running,ColorChooserDemolooks like Figure 8-3.
Figure 8-3: Running the ColorChooserDemoThe first step is to set up the required components. TheColorChooserDemois a subclass ofJFramewith member variables to hold the screenshot (background_image), the panel to draw the image (image_panel), theJLabelto display the current color under the cursor (label), and a few support variables. The beginnings of this class are shown in Example 8-6.Example 8-6. Skeleton of the ColorChooserDemo classpublic class ColorChooserDemo extends JFrame implements MouseListener, MouseMotionListener { JPanel image_panel; Dimension screen_size; JComponent comp = null; Image background_image = null; Robot robot; JLabel label; public ColorChooserDemo(JComponent comp) { // get the screen dimensions screen_size =Toolkit.getDefaultToolkit().getScreenSize(); // set up the frame (this) this.addMouseListener(this); this.addMouseMotionListener(this); this.comp = comp; this.setUndecorated(true); this.setSize(screen_size.width, screen_size.height); // set up the panel that holds the screenshot image_panel = new JPanel() { public void paintComponent(Graphics g) { super.paintComponent(g); g.drawImage(background_image,0,0,null); } }; image_panel.setPreferredSize(screen_size); this.getContentPane().add(image_panel); // set up the display label label = new JLabel("Selected Color"); label.setOpaque(true); label.setSize(100,100); image_panel.setLayout(null); image_panel.add(label); label.setLocation((int)screen_size.getWidth()/2 - 50, (int)screen_size.getHeight()/2 - 50); }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Changing Fonts Throughout Your Application
- InhaltsvorschauGet a quick font face-lift, without having to write a whole Look and Feel.With no standards documents to obey and more flexible user expectations, web designers get much more freedom with their fonts than Swing developers expect. They get to set font styles with CSS, while we're expected to just leave well enough alone. Sure, you can change fonts on a component-by-component basis with
setFont(), but it's not like you can just say "from now on, I want allJLabelsto use the Cheese Deluxe Demi-Bold font." Well, OK, you could create a subclass ofJLabelto set that font in its constructor, but your change wouldn't be picked up by any ofJLabel's subclasses, like the default renderers for list, table, and tree cells. Fortunately, there is a much easier way than fighting with single inheritance.Swing components get many of their defaults (e.g., fonts, icons, borders), from aHashtableowned by theUIManagerclass. Actually, it is a subclass ofHashtable, calledUIDefaults, which offers strongly typed methods likegetFont(),getBorder(),getColor(), etc., each of which takes a key object.Now, since this is just aHashtable, you can put stuff in just as easily as you can get it out. All you have to do is know what the key is. As it turns out, for fonts, the keys areStringsthat end with a .font suffix. So, for demonstration purposes, you can iterate through the keys of theUIDefaults, and every time you find one that ends in.font, put the Font of your choice back into theUIDefaults.The goal of theChangeAllFontsexample is to change the default font of all Swing components, by changing all the appropriate keys it can find inUIDefaults. It starts by getting a font name from the command line and creating a 12-point plainFontinstance.Next, it gets theUIDefaultsobject as aHashtableand gets anEnumerationof its keys. It walks the enumeration and, for every key ending in .font, it usesput() to replace the previous font with the user-selected font.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Load New Fonts at Runtime
- InhaltsvorschauWho cares what fonts your users have? Bundle the fonts you want your application to use and load those fonts dynamically!Using fonts with any predictability used to be a nightmare in Java. For a while, you could only depend on having access to one serif, one sans-serif, and one monospaced font, and the constants you'd use to get those fonts changed between Java 1.0 and 1.1. Fortunately, you're now free to use any font installed on the user's machine and load it by name.Of course, not everyone has the same fonts. Even different installations of the same operating system will have different fonts available. I still use some TrueType fonts I've been toting from machine to machine for 15 years, and it's a safe bet that very few other people will have those same fonts.This would seem to limit your Swing application to using only the fonts you know are installed with an operating system—maybe Arial and Times New Roman on Windows, Lucida Grande and Palatino on Mac OS X, etc. But it's not so. You can load font files at runtime and make them available to your Java application, even if the font isn't installed on the user's machine.Using dynamically loaded fonts comes down to a single, critical, often overlooked AWT method in the
Fontclass:createFont(). This method, introduced in Java 1.3, takes two parameters: a font format (as anint), which to date has no legal value other thanFont.TRUETYPE_FONT, and anInputStream.This stream is typically aFileInputStreamfrom a .ttf TrueType file, or some equivalent. By equivalent, I meant that you could presumably put the font on the network and get a stream from aURLor put the font file inside a .jar, find it along the classpath withClassLoader.getResourceAsStream(), and load from that.Mac OS X's font suitcases—a holdover from the Classic Mac OS—aren't supported. Your fonts need to be in .ttf files.FontLoadingDemo, shown in Example 8-8, offers a straightforward application of this font-loading technique. It takes the path to aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Build a Colorful Vector-Based Button
- InhaltsvorschauBuild a resolution-independent OS X-style button using scalable graphics code.The button in this hack is resolution independent, meaning that it can resize and rescale automatically as the user's windows and display change, stretching and tiling the graphics to fill the new space. The button doesn't depend on being any particular size to look good. As higher-quality and higher resolution monitors become more common, users will start to expect attractive interfaces that scale and reflow with their increasingly expansive displays. This hack shows how to create an attractive
JButtonthat will scale with both size and resolution, opening the door for a completely vector drawn (meaning with shapes instead of images) Swing Look and Feel.Since this button must scale with the size of the screen, you can use a variable calledscale. Every piece of drawing code for this button is done relative toscale's value. If the scale value changes, the entire button will change accordingly. The scale value itself is based on the current font size of the component. If the component's font is resized (due to a change in screen resolution, for example), then the scale value will change accordingly, resizing the entire button. With a scale value in place, this hack is simply a matter of recreating the Aqua button look with Java2D calls. The goal is a button that looks like Figure 8-7.
Figure 8-7: A green vector JButtonNot a simple task, but it's not impossible either. All the work is done in Example 8-9.Example 8-9. Creating liquid buttonspublic class VectorButton extends JButton implements MouseListener { public VectorButton() { this.addMouseListener(this); } public Dimension getPreferredSize() { String text = getText(); FontMetrics fm = this.getFontMetrics(getFont()); float scale = (50f/30f)*this.getFont().getSize2D(); int w = fm.stringWidth(text); w += (int)(scale*1.4f); int h = fm.getHeight(); h += (int)(scale*.3f); return new Dimension(w,h); } public void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(this.getBackground()); g2.fillRect(0,0,this.getWidth(),this.getHeight()); float scale = (50f/30f)*this.getFont().getSize2D(); drawLiquidButton(this.getForeground(), this.getWidth(), this.getHeight(), getText(), scale, g2); }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add a Third Dimension to Swing
- InhaltsvorschauUser interfaces have stuck to 2D drawing for many years. Today, Swing and Java3D give you a chance to go one step further and add 3D widgets to your UI.Have you ever wondered how to add nice 3D components into your Swing applications? Java3D is a free API provided by Sun Microsystems for Linux and Windows, and by Apple for Mac OS X, that lets you create 3D scenes. Although well documented, Java3D seems impossible to use with Swing—at least at first glance.Imagine you decided to create a new, astonishing application called AmazonPick that would let the user search for books on the Amazon.com store. Your eye-candy user interface would even display the currently selected book as a 3D object; whenever the user selects another book, the 3D object would flip to show the new cover on its opposite side. Figure 8-8 shows how the application should look.
Figure 8-8: AmazonPick shows books as full 3D objectsUnfortunately, you won't be able to obtain these results without a little imagination. For instance, take a close look at Figure 8-8 and notice the gradient background of the window. Displaying such a background is very easy with Swing and the opaque properties of Swing components, as seen in the rather simple class in Example 8-10.Example 8-10. A demo program for 3D componentspublic BooksDemo() { super("AmazonPick"); JButton cover1 = UIHelper.createButton("", "cover1_small_button", true); JButton cover2 = UIHelper.createButton("", "cover2_small_button", true); JButton cover3 = UIHelper.createButton("", "cover3_small_button", true); JPanel buttons = new JPanel(); buttons.add(cover1); buttons.add(cover2); buttons.add(cover3); buttons.setOpaque(false); setContentPane(new GradientPanel()); getContentPane().setLayout(new BorderLayout()); getContentPane().add(buttons, BorderLayout.SOUTH); pack(); setResizable(false); setDefaultCloseOperation(EXIT_ON_CLOSE); UIHelper.centerOnScreen(this); }This code creates three buttons, each containing a picture of a book loaded by the utility classEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Turn the Spotlight on Swing
- InhaltsvorschauUsers often get lost when using applications, as if they were in total darkness. Why don't you turn on a spotlight to show them the way?Most applications involve a fair amount of data manipulation. The user creates, edits, deletes, and moves around data items. When the amount of data becomes important, it is vital for the user to be able to search for items easily. Yet, search result displays are often irritatingly complicated, and the results are a pain to browse through. This hack describes how to draw a user's attention to only the parts of the UI you want him to look at.
Figure 8-11: All the problems provoked by mixing Java3D and Swing have been solvedIf you have ever been to a comic show, to the theatre, or to the circus, you've probably noticed how spotlights are used to move your focus to a particular location on the stage. A search operation can be compared to a theatre stage, where actors have been replaced by search results. You just need to get the user to focus on the right location.Take a look at an example application, shown in Figure 8-12, which displays a predefined set of books and lets you search for one or more of them by entering a search query in the text field at the bottom of the window.Since the application is just a demo, you can only enter one of the following queries: books, sci-fi, adams, and pratchett. Each query, when validated by the Enter key, will find the related books and spotlight them, as shown in Figure 8-13.
Figure 8-12: The bookshelf application must let the user search for books
Figure 8-13: Searching for "sci-fi" highlights Science Fiction booksYou can even go one step further: the more light you shed, the less darkness there is. For instance, when several books are found, it is likely that the search query was not very precise. This means the user will be more interested in lots of items. When the query yields only a few results, it is likely that the user wants to see only a few specific items.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 9: Drag-and-Drop
- InhaltsvorschauSeveral years ago, the Swing team introduced a pair of APIs for data transfer:
java.awt.datatransferandjava.awt.dnd(Drag and Drop). The former abstracts the concept of data exchange to and from your application (participating with either Java or native applications) and provides clipboard-based copy-and-paste functionality. The latter particularizes these abstractions to the specifics of drag-and-drop behavior. While many developers use these APIs for working with unstyled clipboard text only, you can do much more. Both Drag and Drop events and the clipboard support images,URLs, Files, and even custom Java objects.Drag files from your application directly to the desktop, complete with translucent icons.This hack shows you how to go much further than mere clipboard access by digging into the lower levels of the Drag and Drop APIs and building a program that can save files directly to the desktop via dragging, complete with proper file icons and drag feedback.When you use an editor to write a large document, you often save it to a particular location on your filesystem—in a Projects folder perhaps. This is because you will keep the file around for a long time, so you want to store it for later use. Small documents, however, are often created for transient reasons. I often write a few paragraphs and then immediately post it to a weblog or attach it to an email. Some applications (particularly those on Mac OS X) let you save something quickly by dragging a small marker into another application or the desktop. The marker represents the file and lets you quickly move the entire file into another context (a blog editor, for example) without thinking about where to save the file (and trying to remember where you stashed it 10 minutes later).Since drag-to-save behavior is not a standard part of the Java platform, you will have to build it from scratch using the Drag and Drop APIs. First, you will need a class that can trigger the drop action. The plan is to detect the gesture, create a temp file to be saved, and then start the real drag with the appropriate cursor and user feedback. Here's a starting point:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 65–69: Introduction
- InhaltsvorschauSeveral years ago, the Swing team introduced a pair of APIs for data transfer:
java.awt.datatransferandjava.awt.dnd(Drag and Drop). The former abstracts the concept of data exchange to and from your application (participating with either Java or native applications) and provides clipboard-based copy-and-paste functionality. The latter particularizes these abstractions to the specifics of drag-and-drop behavior. While many developers use these APIs for working with unstyled clipboard text only, you can do much more. Both Drag and Drop events and the clipboard support images,URLs, Files, and even custom Java objects.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Drag-and-Drop with Files
- InhaltsvorschauDrag files from your application directly to the desktop, complete with translucent icons.This hack shows you how to go much further than mere clipboard access by digging into the lower levels of the Drag and Drop APIs and building a program that can save files directly to the desktop via dragging, complete with proper file icons and drag feedback.When you use an editor to write a large document, you often save it to a particular location on your filesystem—in a Projects folder perhaps. This is because you will keep the file around for a long time, so you want to store it for later use. Small documents, however, are often created for transient reasons. I often write a few paragraphs and then immediately post it to a weblog or attach it to an email. Some applications (particularly those on Mac OS X) let you save something quickly by dragging a small marker into another application or the desktop. The marker represents the file and lets you quickly move the entire file into another context (a blog editor, for example) without thinking about where to save the file (and trying to remember where you stashed it 10 minutes later).Since drag-to-save behavior is not a standard part of the Java platform, you will have to build it from scratch using the Drag and Drop APIs. First, you will need a class that can trigger the drop action. The plan is to detect the gesture, create a temp file to be saved, and then start the real drag with the appropriate cursor and user feedback. Here's a starting point:
class FileDragGestureListener extends DragSourceAdapter implements DragGestureListener { JTextArea text; Cursor cursor; public FileDragGestureListener(JTextArea text) { this.text = text; }TheFileDragGestureListenerimplementsDragGestureListenerand extends theDragSourceAdapter. Swing sends all drag events to aDragSourcelistener.Extending theDragSourceAdapter, instead of implementingDragSourcedirectly, lets your class avoid implementing all of the required methods.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Handle Dropped URLs
- InhaltsvorschauDrag-and-drop is like a box of chocolates; you never know what you're going to get….Bookmark menus are so 1995. Today, you should expect to be able to drag URLs to other applications and have those applications open web pages, store the address in a bookmark database, start an email in response to a
mailto: URL, etc. Java's networking chops are well-established and aren't the problem here. The issue is actually getting the URL itself from the drop.To accept drops of native objects, your GUI needs to designate someComponentas the onscreen drop target. Your code then implements theDropTargetinterface, which means your implementation will get callbacks when the user drags the mouse into your component, over it, out of it, etc. Most ofDropTarget'smethods can be left as no-ops; for now, only thedrop() method matters.What's interesting about native drag-and-drop (and copy-and-paste, for that matter) is that there's not necessarily one way to represent the data being transferred. Instead, you do a sort of negotiation with theTransferablepassed to you by theDropTargetDropEvent: it specifies, in order of robustness, whichDataFlavorsit can deliver, or you ask whether specificDataFlavorsare supported.In the demo code in Example 9-3, I have a method calleddumpDataFlavors(), which shows theDataFlavorsoffered to you by the drop. You can useSystem.out.println() on a particular flavor to get its MIME type, which describes the contents of the drop, and a representation class, which indicates how those contents will be provided to you byTransferable. getTransferData(). For example, some browsers will give you ajava.net. URL, whoseDataFlavorlooks like this:java.awt.datatransfer.DataFlavor[mimetype=application/x-java-url; representationclass=java.net.URL]
One thing that's surprising is the number ofDataFlavorsoffered by popular web browsers. Table 9-1 is a short listing of some of the browsers I tested.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Handle Dropped Images
- InhaltsvorschauI spy, with my little
drop()method, something that doesn't supportDataFlavor.imageFlavor…arrrgh!Handling drag-and-drop from native applications [Hack #66] can be tricky because they're not particularly consistent about how they represent the data being transferred to your application. Now, let's say you want to accept dropped images—not image files, but actual images inside of browser windows, digital photo viewers, word processing and page-layout applications, etc. There's a constant inDataFlavorfor images, so surely you can count on that being a flavor offered to you by theTransferable, right?No, of course not. That would be too easy.Using thedumpDataFlavors() strategy of the earlier URL hack, I checked out theDataFlavorsoffered by images dropped from some popular Windows and Mac applications. The results are pretty interesting—check out Table 9-2.Table 9-2: DataFlavor offerings for images on various platforms Application/platformDataFlavorsPreview 2.1 / Mac OS X1GraphicConverter 4.6 / Mac OS X1Finder / Mac OS X1Safari 1.3 / Mac OS X55Firefox 1.0 / Mac OS X57QuickTime Player 6.5 / Mac OS X1AppleWorks 6.2.9 / Mac OS X1MarinerWrite 3.6.4 / Mac OS X1iPhoto 4.0.3 / Mac OS X1Explorer / Windows XP1MSIE 6.0 / Windows XP27Firefox 1.0 / Windows XP80Paint / Windows XPN/AWindows Picture and Fax Viewer / Windows XPN/AWindows Media Player / Windows XPN/AQuickTime Player 6.5.1 / Windows XPN/AQuickTime Picture Viewer 6.5.1 / Windows XPN/AThe first thing you should notice is the poor support for dragging and dropping images in Windows: Paint, Picture and Fax Viewer, and the Quick-Time applications are not drag sources, and dragging an image from the playlist of Windows Media Player to the Swing application in this hack actually crashes Java.Furthermore, the supportedDataFlavorsare all over the map. A lot of these provide references to image files in the form of either ajavaFileListFlavorEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Handling Dropped Picts on Mac OS X
- InhaltsvorschauFor Mac applications that provide only the legacy Pict flavor of drops, QuickTime for Java offers a Mac-specific solution.You should already recognize the existence of hard-to-handle DataFlavors [Hack #67] passed by certain Mac applications. Of the Mac apps I tested, several old apps (many using the Carbon APIs, which were developed to migrate classic Mac apps to OS X) support only one
DataFlavor. When I drop from GraphicConverter, QuickTime Player, AppleWorks, and MarinerWrite, the only supportedDataFlavorwas reported as:java.awt.datatransfer.DataFlavor[mimetype=image/x-pict; representationclass=java.io.InputStream]
For those of you who don't use Macs, Pict is part of QuickDraw, the original Mac graphics API. The term is wildly overloaded—Pict can refer to a file format, a resource hidden in an application file, a wrapper around vector drawing commands, and as a wrapper around optionally compressed pixel data. It's in this latter form that Pict is a preferred format for passing image data on the Mac clipboard because Picts are easy for Mac applications to render and convert (through the QuickDraw library, of course).Unfortunately, Java doesn't know the first thing about Picts, so it's frustrating to see that Pict is the only supportedDataFlavor. Worse, the data is supplied as anInputStream, instead of a file or a URL, meaning you have to handle it in Java, instead of handing off to non-Java code that might be able to convert the Pict to something that can handle the format more gracefully.Fortunately, there is a Java solution to the problem, and it's called QuickTime for Java (QTJ). This API is a Java wrapper around the native QuickTime multimedia API. It's available for Mac OS X and Windows, but since a Windows application isn't going to pass you a Pict, you only need to worry about the Mac OS X case. One advantage of all this API-wrangling: QTJ is installed by default on Mac OS X, so you can count on it being there.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Translucent Drag-and-Drop
- InhaltsvorschauThe Java implementation of drag-and-drop offers poor visual feedback. This hack shows how to provide more information and a better-looking response to the user.A good way to create user-friendly interfaces is to provide the ability to drag-and-drop almost anything from, within, and onto those interfaces. Mac OS X is a perfect example of a good drag-and-drop use. Every time I try to drag something to drop it onto something else, it just works. AWT, and therefore Swing, let applications implement drag-and-drop but lack something Mac OS X already offers: really cool visual feedback to let the user know what's going on.J2SE has offered drag-and-drop facilities since version 1.2. All the necessary classes and interfaces can be found in the package
java.awt.dnd. Although not very easy to use at first, this package provides powerful features you can use to greatly improve the usability of your applications. Unfortunately, the Java drag-and-drop framework offers little visual feedback. In fact, the only feedback the user can get is a simple mouse cursor. For instance, you can show that a drag-and-drop operation is in progress with the following line of code:dropTarget.setCursor(DragSource.DefaultMoveDrop);
Figure 9-7 shows what the visual feedback looks like on Windows.
Figure 9-7: Default drag-and-drop visual feedback on WindowsNot only does this look bad, it also conveys very little information. Mac OS X users are used to a much richer environment, and why should Windows or Linux users expect less? As an example of what can be done, Safari—Mac OS X's default web browser—shows a translucent thumbnail of the picture you are dragging. Figure 9-8 shows what this looks like in action.
Figure 9-8: Mac OS X provides great looking visual feedback for drag-and-dropThe quality of the interaction is greatly enhanced by the quantity of information provided by the visual feedback. Another example is dragging a link from the web browser to the desktop, where a translucent text box containing the link title and the URL is shown. Wouldn't it be great to be able to do the same in your application? Thankfully, Swing is perfectly suited for that kind of job.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 10: Audio
- InhaltsvorschauSound is underrated as a useful tool for building good user interfaces. A lot of developers balk at the thought of sound support, imagining an office full of noisy machines, emitting a beeping cacophony more like a 1980s videogame arcade than a place of business. But on the other hand, don't you appreciate it when you get a nice little audible cue? For example:
- When your IM buddy logs in
- When your CD has finished burning
- When your gigantic upload has finished
- When someone is trying to hack into your network and you're not even looking at the screen
And beyond these kinds of uses, don't forget the whole realm of applications that are, by their nature, all about sound: music players, sound editors, voice chat, and VoIP, etc. Clearly,java.awt.Toolkit.beep() is not going to cut it.Java has two built-in options for playing simple sounds in memory: appletAudioClips and JavaSound. Because of their limitations, this chapter will also look at two extensions: Java Media Framework (JMF) and QuickTime for Java (QTJ). Later in the chapter, you'll find more sophisticated JavaSound coverage, including how to visualize an in-memory sound clip, and how to play sounds too big to fit in memory.If you're forced to write to the old Applet API for sound, here's how you do it. Good luck.Let me be very clear up front: applet-based sound sucks. If you are in a hurry to get sound into your application and can count on your users having Java 1.3 or better, go ahead and use JavaSound instead [Hack #71] . Appletbased sound is going to be most useful to those who must deliver applets (and only applets) to very old browsers and JVMs. And it's not going to be pretty.Java 1.0 and 1.1 shipped with no support for application-based sound. None. The only sound support was for applets, presumably so they could punt the responsibilities for audio to the enclosing browser. The idea in the early JDKs is built around anAudioClip, a class found in theEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 70–78: Introduction
- InhaltsvorschauSound is underrated as a useful tool for building good user interfaces. A lot of developers balk at the thought of sound support, imagining an office full of noisy machines, emitting a beeping cacophony more like a 1980s videogame arcade than a place of business. But on the other hand, don't you appreciate it when you get a nice little audible cue? For example:
- When your IM buddy logs in
- When your CD has finished burning
- When your gigantic upload has finished
- When someone is trying to hack into your network and you're not even looking at the screen
And beyond these kinds of uses, don't forget the whole realm of applications that are, by their nature, all about sound: music players, sound editors, voice chat, and VoIP, etc. Clearly,java.awt.Toolkit.beep() is not going to cut it.Java has two built-in options for playing simple sounds in memory: appletAudioClips and JavaSound. Because of their limitations, this chapter will also look at two extensions: Java Media Framework (JMF) and QuickTime for Java (QTJ). Later in the chapter, you'll find more sophisticated JavaSound coverage, including how to visualize an in-memory sound clip, and how to play sounds too big to fit in memory.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Play a Sound in an Applet
- InhaltsvorschauIf you're forced to write to the old Applet API for sound, here's how you do it. Good luck.Let me be very clear up front: applet-based sound sucks. If you are in a hurry to get sound into your application and can count on your users having Java 1.3 or better, go ahead and use JavaSound instead [Hack #71] . Appletbased sound is going to be most useful to those who must deliver applets (and only applets) to very old browsers and JVMs. And it's not going to be pretty.Java 1.0 and 1.1 shipped with no support for application-based sound. None. The only sound support was for applets, presumably so they could punt the responsibilities for audio to the enclosing browser. The idea in the early JDKs is built around an
AudioClip, a class found in thejava.awt.appletpackage—as David Flanagan says in Java Foundation Classes in a Nutshell (O'Reilly), "only because there is no better place for it."To demonstrateAudioClips, Example 10-1 shows a hacked up little applet.Example 10-1. Playing an AudioClip in an appletpublic class AppletSound extends Applet implements ActionListener { JButton fileButton, loadButton, playButton, loopButton, stopButton; JLabel urlLabel; JTextField urlField; AudioClip clip; public AppletSound() { setLayout (new GridLayout (2,1)); // first row layout JPanel topPanel = new JPanel(); urlLabel = new JLabel ("URL:"); topPanel.add (urlLabel); urlField = new JTextField (25); urlField.addActionListener (this); topPanel.add (urlField); loadButton = new JButton ("Load"); loadButton.addActionListener (this); topPanel.add (loadButton); fileButton = new JButton ("File"); fileButton.addActionListener (this); topPanel.add (fileButton); add (topPanel); // second row layout JPanel bottomPanel = new JPanel(); playButton = new JButton ("Play"); playButton.addActionListener (this); bottomPanel.add (playButton); stopButton = new JButton ("Stop"); stopButton.addActionListener (this); bottomPanel.add (stopButton); loopButton = new JButton ("Loop"); loopButton.addActionListener (this); bottomPanel.add (loopButton); add (bottomPanel); } public void stop() { clip.stop(); } public void actionPerformed (ActionEvent e) { Object source = e.getSource(); if (source == fileButton) { JFileChooser chooser = new JFileChooser();int pick = chooser.showOpenDialog(this); if (pick == JFileChooser.APPROVE_OPTION) { try { File file = chooser.getSelectedFile(); urlField.setText (file.toURL().toString()); } catch (MalformedURLException murle) { murle.printStackTrace(); } } } else if (source == loadButton ) { try { System.out.println ("field: " + urlField.getText()); URL clipURL = new URL (urlField.getText()); System.out.println ("loading " + clipURL); clip = getAudioClip (clipURL); System.out.println ("got clip"); } catch (MalformedURLException murle) { murle.printStackTrace(); } } else if (source == playButton ) { clip.play(); } else if (source == stopButton ) { clip.stop(); } else if (source == loopButton ) { clip.loop(); } } public static void main (String args[]) { JFrame f = new JFrame ("Applet Sound"); f.getContentPane().add (new AppletSound()); f.pack(); f.setVisible(true); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Play a Sound with JavaSound
- InhaltsvorschauGet a small clip to play from memory with a lot less hassle.The JavaSound API was developed to answer complaints about the inadequacies of the
AudioClipclass in the applet package…not the least of which was the fact that it couldn't be used in applications. JavaSound consists of two packages—javax.sound.sampledandjavax.sound.midi—plus two service provider interface (spi) sub-packages for adding support for new devices, formats, converters, etc.JavaSound was introduced as an extension to Java 1.2 (I know, I know, "Java 2 Standard Edition, version 1.2"), and it became part of Core Java in 1.3. In other words, you're pretty safe assuming that it's present on your user's machine. That's one big point in its favor.To show off JavaSound, the code in Example 10-3 exhibits a short application that allows the user to pick a file from the local filesystem and play it. A dialog shows the selected filename; OK it when the audio completes to exit the program.Example 10-3. Playing audio with JavaSoundpublic class CoreJavaSound extends Object implements LineListener { File soundFile; JDialogplayingDialog; Clip clip; public static void main (String[] args) { JFileChooser chooser = new JFileChooser(); chooser.showOpenDialog(null); File f = chooser.getSelectedFile(); try { CoreJavaSound s = new CoreJavaSound (f); } catch (Exception e) { e.printStackTrace(); } } public CoreJavaSound (File f) throws LineUnavailableException, IOException, UnsupportedAudioFileException { soundFile = f; // prepare a dialog to display while playing JOptionPane pane = new JOptionPane ("Playing " + f.getName(), JOptionPane.PLAIN_MESSAGE); playingDialog = pane.createDialog (null, "Application Sound"); playingDialog.pack(); // get and play sound Line.Info linfo = new Line.Info (Clip.class); Line line = AudioSystem.getLine (linfo); clip = (Clip) line; clip.addLineListener (this); AudioInputStream ais = AudioSystem.getAudioInputStream(soundFile); clip.open (ais); clip.start(); } // LineListener public void update (LineEvent le) { LineEvent.Type type = le.getType(); if (type == LineEvent.Type.OPEN) { System.out.println ("OPEN"); } else if (type == LineEvent.Type.CLOSE) { System.out.println ("CLOSE"); System.exit (0); } else if (type == LineEvent.Type.START) { System.out.println ("START"); playingDialog.setVisible(true); } else if (type == LineEvent.Type.STOP) { System.out.println ("STOP"); } } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Play a Sound with Java Media Framework
- InhaltsvorschauUse the Java Media Framework for better performance and support for more audio formats.Java Media Framework (JMF) is Sun's attempt to bring a broadly focused multimedia framework to Java, supporting audio, video, and other timebased media types. The idea is to provide Java desktop applications with these features across operating systems. Like JavaSound, it's meant to be extended so that Sun or third parties could add support for new file formats or codecs (the compression/decompression encoding schemes used inside those files).JMF offers another way to provide sound from an application. The advantages of doing so are that JMF may provide access to many more sound files than JavaSound will alone, and that JMF is somewhat easier to code than JavaSound, particularly for simple tasks. The disadvantages are that JMF capabilities vary wildly by platform, and that the end user will have to install JMF separately, which will be difficult or simply not allowed for some users.Download and install JMF from its home page at http://java.sun.com/products/java-media/jmf/index.jsp and you should be ready to go—no reboot required. The installers should have put everything into the correct path and set up your environment. If you have trouble getting JMF programs to run, or if you used the all-Java version that doesn't have a special installer, you can try adding the following environment variable:
JMFHOME="C:\Program Files\JMF2.1.1"
Next, add the two JMF Java libraries to your classpath:CLASSPATH="$JMFHOME\lib\jmf.jar;$JMFHOME\lib\sound.jar;.;$CLASSPATH" PATH="$JMFHOME\lib;$PATH"
The demo in Example 10-4 is basically a port of theCoreJavaSounddemo used in playing audio with JavaSound [Hack #71] , except that the eventhandling has been simplified to a "quit when done" implementation. JMF's playback metaphor involvesPlayers, which simply play media, andProcessors, which may take action on the media, such as adding effects or transcoding to other formats. This allows the simple stuff to stay simple: to play a file, you wire it up to aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Play a Sound with QuickTime for Java
- InhaltsvorschauUsing QuickTime, you can play even more kinds of sounds, but only on two operating systems.QuickTime for Java offers another way to significantly improve the media capabilities of your application. Its list of supported formats is huge (see http://www.apple.com/quicktime/products/qt/specifications.html for the current list) and always growing as Apple continues to improve it. That's a big advantage over JMF, which was dropped into maintenance mode in 1999 and largely ignored since then.The huge disadvantage with QuickTime for Java is that it works on Windows and Mac only. That's because QTJ, as it's typically called, is really just an object-oriented (OO) wrapper to call C functions in the native Quick-Time library. That gives you native-speed performance, but it also means the wrappers don't do anything without an underlying native implementation.For the purposes of this hack, let's say you only need to support Mac and Windows, or that you need to open files from the iTunes Music Store (QTJ can do it, which is apparently the only way to do it in Java), or for whatever reason QTJ looks like the right solution. Example 10-5 shows a port of the JavaSound audio player to a QTJ-based implementation.Example 10-5. Playing audio with QuickTime for Java
import quicktime.std.*; import quicktime.std.clocks.*; import quicktime.std.movies.*; import quicktime.*; import quicktime.io.*; import quicktime.app.time.*; public class QTJSound extends Object { File soundFile; JDialogplayingDialog; Movie movie; public static void main (String[] args) { JFileChooser chooser = new JFileChooser(); chooser.showOpenDialog(null); File f = chooser.getSelectedFile(); try { QTJSound s = new QTJSound (f); } catch (Exception e) { e.printStackTrace(); } } public QTJSound (File f) throws QTException { soundFile = f; // prepare a dialog to display while playing JOptionPane pane = new JOptionPane ("Playing " + f.getName(), JOptionPane.PLAIN_MESSAGE); playingDialog = pane.createDialog (null, "QTJ Sound"); playingDialog.pack(); // get and play sound QTSession.open(); QTFile qtf = new QTFile (f); OpenMovieFile omf = OpenMovieFile.asRead (qtf); movie = Movie.fromFile (omf); MyDemoCloser closer = new MyDemoCloser (movie); TaskAllMovies.addMovieAndStart (); movie.start(); playingDialog.setVisible(true); } class MyDemoCloser extends ExtremesCallBack { public MyDemoCloser (Movie m) throws QTException { super (m.getTimeBase(), StdQTConstants.triggerAtStop); callMeWhen(); } public void execute() { playingDialog.setVisible (false); System.out.println ("dialog closed"); // note: this can hang on Windows - consider // using QTSession.exitMovies() instead QTSession.close(); System.out.println ("closed QTSession"); System.exit(0); } } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add MP3 Support to JMF
- InhaltsvorschauMP3s are everywhere, and by installing a plug-in you can use them with Java Media Framework, too.It used to be said that every program will continue to grow until it includes an email reader. Today we could say the same for MP3 players. They are everywhere, and any program that has plug-ins will eventually be given a music player. Playing MP3s in Java used to be quite an ordeal, involving a suite of toolkits and codecs from different sources. Fortunately, it's a lot easier to play an MP3 file these days, and this hack shows how.JMF came out in 1998, supporting playback of a number of audio and video formats, but not MP3. Support for this popular format arrived with JMF 2.0 in 1999. Unfortunately, in 2002, Sun removed MP3 support from JMF because of licensing problems. Finally, in November of 2004, Sun released a fully licensed MP3 plug-in for public download on their web site. With this plug-in, you can play any MP3 file with only four lines of code.First, install Java Media Framework [Hack #72] . To add MP3 support, download the plug-in from http://java.sun.com/products/java-media/jmf/mp3/download.html. The download page offers an .exe installer for Windows and a ZIP for other platforms. In both cases, there is an mp3plugin.jar file that the install docs say you need to place in the ext/lib directory of any JRE you want to provide the plug-in to. With the JAR in your classpath, you install the plug-in with the following command:
java com.sun.media.codec.audio.mp3.JavaDecoder
On Mac OS X, the proper way to add JAR files to the classpath is to put them in /Library/Java/Extensions, instead of using the actual ext/lib directory, which is hard to find and will be wiped out by system installers and updates anyways.Example 10-6 is the code for pretty much the simplest MP3 playing program you can create. You will need toimport javax.media.*in addition to the usualEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Build an Audio Waveform Display
- InhaltsvorschauWith a little understanding of audio data formats, you can easily build a basic graphical audio display.Representing audio visually is extremely useful. You can use waveform displays to quickly tell audio files apart, like a file thumbnail, or for non-linear editing, such as deleting parts of the file and processing.Figure 10-4 shows a waveform displayed in Audacity, a free, open source audio editing application. This hack shows you how to build a basic waveform display from raw audio data.
Figure 10-4: Audacity with an audio waveform displayedThe end result of this hack is displayed in Figure 10-5. You'll start by reading in the entire audio file using anAudioInputStream. Then you'll convert the raw data from the stream into useful audio samples, organized by channel. With the converted channel audio data, you'll create a single waveform panel. Then you'll wrap up the complete audio display by combining several waveform panels to display multi-channel audio.
Figure 10-5: The waveform display you'll build in this hackYou'll need to know a few basic terms and concepts about audio before you get started.- Sample
- One measurement of audio data. For Pulse Code Modulated (PCM) encoding, a sample is an instantaneous representation of the voltage of the analog audio. There are other types of encoding, like μ-law and a-law, that are rarely used.
- Sampling Rate
- The number of samples in one second. Measured in Hertz (Hz) or kilo-Hertz (kHz). The most common sampling rate is 44.1 kHz (CD quality audio). Often, you'll find 22.05 kHz or 11.025 kHz on the Web, since the files are smaller and the conversion is easier.
- Sample Size
- The number of bits in one sample. It is typically a multiple of eight because data is stored in 8-bit bytes. The most common sample size is 16 bits, which is CD quality audio. Often you'll find 8-bit audio because the files are smaller. You'll rarely find anything less then 8-bit audio because the quality is pretty poor. Sample size is sometimes called bit depth.
Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Play Non-Trivial Audio
- InhaltsvorschauWhen loading an entire audio clip into memory is a bad idea (or just impossible), you have to take JavaSound responsibilities into your own hands.Playing JavaSound audio with a
Clip[Hack #71] is a pretty convenient way to play a short sound, like a sound effect for a desktop application. The only problem is that theCliploads all the audio into memory, which could have a couple of bad side effects:- It makes your application use more memory, which could cause problems.
- The audio you need might not fit into memory at all.
You might have run into this second point if you tried to load a really big audio file into aClip. For example, I took a 3 minute, 45 second track from a CD and converted it to 8-bit mono PCM in an AIFF file, which ended up being 9.4 MB. You can guess what happened:[aeris:HacksBook/Media/52] cadamson% java CoreJavaSound javax.sound.sampled.LineUnavailableException: Failed to allocate clip data: Requested buffer too large. at com.sun.media.sound.MixerClip.implOpen(MixerClip.java:536) at com.sun.media.sound.MixerClip.open(MixerClip.java:161) at com.sun.media.sound.MixerClip.open(MixerClip.java:249) at CoreJavaSound.<init>(CoreJavaSound.java:39) at CoreJavaSound.main(CoreJavaSound.java:17)
Unfortunately, most of the JavaSound code you'll find on the Web deals withClipsonly and not with getting aLinefor larger files, or potentially endless streams for that matter. Why? Perhaps because JavaSound doesn't do it for you—you are responsible for reading bytes and feeding them to JavaSound!This hack is going to play an uncompressed (i.e., PCM) AIFF or WAV file of arbitrary length by getting aDataLinefor the data and then repeatedly reading the data from disk and writing it to theDataLine.PCM stands for Pulse Code Modulation, which means that analog audio has been sampled at regular intervals and quantized (i.e., each sample is expressed as a numeric value). It's the lowest-level, most common denominator data that JavaSound understands, since it can be delivered directly to a sound system for playback.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Show Audio Information While Playing SoundHack
- InhaltsvorschauProviding visual feedback for JavaSound audio, or at least trying to….You might want to play a clip without any corresponding visuals; for example, if you were using it to signal the end of a long-running process such as uploading a file. On the other hand, if the sound is the focus of the application, as in a music-player application, you might need to show the user some information about the audio he's playing.You already know how to play audio from a file or stream [Hack #76] ; building on that, you can create a simple GUI that shows some of the basic traits of the audio, by pulling fields out of the
AudioFormatobject, which can be retrieved from theLineonce it has been created. These fields include the audio format, bits/sample, frame size and rate, and endianness (which indicates how two-byte values are to be interpreted: big-endian means the first byte is more significant, and little-endian means the second is).More impressively,DataLineprovides agetLevel() method that returns the current level of the audio being played, as a float from0.0(silence) to 1.0 (maximum volume). You can use this to create a graphical level meter by getting the level and coloring in that percentage of a component. For example, if the level is 0.5, you'd fill in half of the component.Drawing this level meter is pretty straightforward: create aJPanelwhosepaint() method clears theGraphics, gets the line level, and fills a rectangle starting at (0,0) with a height equal to the component's height and a width equal to the level times the component's width. Then you need to set up an animation loop—ajavax.swing.Timeris convenient because it avoids any thread-safety issues while doing the painting—to repeatedly callrepaint() on the meter.Combine this together and you have theDataLineInfoGUI, seen in Example 10-10. Note that to play the audio, it uses thePCMFilePlayerEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Provide Audio Controls During Playback
- InhaltsvorschauLet your users take control of JavaSound playback.To complete this set of JavaSound-related hacks, why not give the user the opportunity to control the sound as it plays? JavaSound provides a very dynamic means of getting at controls like gain and pan (more commonly thought of as volume and balance) through a discovery mechanism that you can use to support any kind of control that might exist, even a control you know nothing about.On the other hand, JavaSound presents a control not as a GUI widget, but just as an object that can affect the behavior of a
Line. This hack will help you provide the GUI side.TheControlclass simply defines agetType() andtoString() method. What's more interesting is its subclasses, each of which defines a different kind of control:BooleanControl- Controls a value that can be either true or false
EnumControl- Controls a value that can be one of n known values
FloatControl- Controls a value that is expressed as a floating-point number
CompoundControl- Controls multiple properties, and itself contains multiple controls
You can get theControlssupported by yourLinesimply by callingLine.getControls(), which returns an array ofControls. You can also ask for a specific control by using a constant of theControl.Typesubclass, such asBooleanControl.Type.MUTEorFloatControl.Type.MASTER_GAIN. Pass this constant toLine.isControlSupported() to see if the control is available for the given line, and then get the control object withLine.getControl().If you look at the subclasses ofControl, you'll see that each provides getter and setter methods appropriate to its data type.BooleanControl, for example, has agetValue() that returns abooleanand asetValue() that takes aboolean. FloatControlhas similar methods that work withfloats. Each also provides a number of what might be called "verbiage methods" that provide text for building a GUI. For example,FloatControloffers label names for its minimum, maximum, and middle values, and another that describes the units represented by the control (like "dB" for a gain-related control, or "frames per second" for one that is timing-related).Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 11: Native Integration and Packaging
- InhaltsvorschauYou can try really hard to develop a desktop application that looks good, feels right, and meets the user's needs, but if a Windows or Mac user has to drop down to a command line and type
java -jar MyCoolApp.jarto run it, it's not going to win you any points in the user experience department. There are points of integration with the native platform that you'll often want and need to access from a Swing application, or specific functionality you'll want to provide on a platform-by-platform basis, and that's what this chapter is about.Actually, this chapter was almost rendered irrelevant by the JDesktop Integration Components (JDIC) project on Java.net (https://jdic.dev.java.net/), which is addressing the most serious needs for desktop Java applications: creating platformappropriate double-clickables, providing access to the native web browser component, associating Java applications with certain kinds of documents, etc. JDIC may solve some of the biggest issues facing Java on the desktop…which leaves us all the more room for creative hackery.With one simple command you can tell Windows to open files, directories, and URLs on your behalf.Swing programmers have always had difficulty dealing with native operating systems because of Java's cross-platform nature. Even simple things like opening a web browser require building native hooks with JNI or building on top of custom libraries. This hack will show you how to open files, URLs, and start an email app without using any native libraries or custom C coding.Native integration in Java has always depended on the Java Native Interface, or JNI. Whether you code to JNI directly or use a third-party library, you are still dealing with native C code through a Java layer. This has always been problematic because in order to write a JNI library, you need to know a lot about the internals of the underlying operating system. Most Java developers went to Java toEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 79–87: Introduction
- InhaltsvorschauYou can try really hard to develop a desktop application that looks good, feels right, and meets the user's needs, but if a Windows or Mac user has to drop down to a command line and type
java -jar MyCoolApp.jarto run it, it's not going to win you any points in the user experience department. There are points of integration with the native platform that you'll often want and need to access from a Swing application, or specific functionality you'll want to provide on a platform-by-platform basis, and that's what this chapter is about.Actually, this chapter was almost rendered irrelevant by the JDesktop Integration Components (JDIC) project on Java.net (https://jdic.dev.java.net/), which is addressing the most serious needs for desktop Java applications: creating platformappropriate double-clickables, providing access to the native web browser component, associating Java applications with certain kinds of documents, etc. JDIC may solve some of the biggest issues facing Java on the desktop…which leaves us all the more room for creative hackery.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Launch External Programs on Windows
- InhaltsvorschauWith one simple command you can tell Windows to open files, directories, and URLs on your behalf.Swing programmers have always had difficulty dealing with native operating systems because of Java's cross-platform nature. Even simple things like opening a web browser require building native hooks with JNI or building on top of custom libraries. This hack will show you how to open files, URLs, and start an email app without using any native libraries or custom C coding.Native integration in Java has always depended on the Java Native Interface, or JNI. Whether you code to JNI directly or use a third-party library, you are still dealing with native C code through a Java layer. This has always been problematic because in order to write a JNI library, you need to know a lot about the internals of the underlying operating system. Most Java developers went to Java to get away from that sort of thing, so it's often not worth it for something simple like opening a URL. There is another way of talking to the native OS, though. You can use
Runtime.exec().Since 1.0, Java has had theRuntime.exec() static function to start another program directly and pass command-line parameters. It's easy to forget about command-line utilities, but for simple things they can be far, far easier than trying to deal with JNI. The disadvantages of calling a native program over a Java API are of course speed, since you are starting a new process, and the fact that the program is not cross-platform. This may be an acceptable tradeoff, however, since you could disable the feature that needs theexec() call or provide a different command for other platforms.Windows 2000 introduced a small program calledstart. Originally a separate install, thestartprogram is now just a command built into thecmd.exethat comes with Windows XP. It is a simple command but it can do some powerful things, such as opening a file with the default viewer, showing a directory in the file explorer, or even launching a web browser.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Open Files, Directories, and URLs on Mac OS X
- InhaltsvorschauOpen files, directories, and URLs in external programs right from your Swing app.We can't let Windows have all of the fun. Mac OS X has a similar and even easier to use program called open, which lets you
openany file, directory, or web page directly from the command line. This hack shows you how to embedopenin your own program.Back in the late 80s, NeXT, Inc. created the first true integration between a graphical user interface and a Unix-like operating system when they released NeXTSTEP. Part of this OS was a command-line program calledopen, which could open a file, directory, and (once the Web was invented) a URL. Apple purchased NeXT in the late 90s and NeXTSTEP became the core of Mac OS X. Along with this purchase came the open command, still as useful as ever.openhas a simple syntax:openfilename. By calling it from within your program, you canopenany file with its default application.openwill start launching the viewer automatically and return control to your program immediately. As in Microsoft Windows, you can call the program usingRuntime.exec():public static void main(String[] args) throws Exception { Runtime rt = Runtime.getRuntime(); rt.exec("open notes.txt"); }This program will open a file in the current directory (notes.txt), in the default viewer for text files, usuallyTextEdit. You can also specify an absolute path for the file:rt.exec("open /Users/josh/Desktop/notes.txt");If you pass a directory instead of a file, then OS X will open that directory in a new Finder window. This can be useful for showing the location of a recently downloaded file or demonstrating where to install new software:// open the current working directory rt.exec("open ."); // open the applications directory rt.exec("open /Applications");Finally, you can open any web page using the user's default web browser (probably Safari) by callingopenwith a URL:// open Yahoo! in the user's web browser rt.exec("open http://www.yahoo.com/");Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Make Mac Applications Behave Normally
- InhaltsvorschauSetting a few system properties will make your application seem more like other Mac apps.Of the desktop platforms your application is likely to run on, the Mac is the least like the others. Maybe it's because the various Linux desktops hemmed closely to Windows' ways of thinking, or maybe the GNOME guys had never used a Mac and didn't "think different." But the result is that certain assumptions you might reasonably make on Windows or Linux—like assuming that windows have menu bars and that any corner or edge of a window can be dragged to resize the window—aren't correct on the Mac.To smooth over the cross-platform differences somewhat, Apple does certain things differently in its Java implementation. For one thing, it will automatically put a Swing application into its native Look and Feel, Aqua, rather than defaulting into cross-platform Metal or Ocean as would happen on other platforms. In other words, you don't have to do anything special to pick up the Mac Look and Feel, although redundantly asking for and setting the native Look and Feel classname doesn't hurt either.Moreover, Apple provides some key/value pairs that you can set in the Java system properties to get even more Mac-like behavior. Because these properties all start with
appleorcom.apple, you can set them and not worry that they'll affect the behavior of your application on any other platform.Apple has been changing the names and behaviors of these system properties for a while, and some of them are deprecated or no-op'ed, so I'll just show four of the most useful ones here. To see the whole list, check out the Runtime System Properties of Apple's Java 1.4.1 release notes on http://developer.apple.com/releasenotes/Java/index.html.You can set the properties several ways. The obvious way is to use the-Dcommand-line argument:java -Dapple.awt.showGrowBox=true MyClass
However, this becomes tedious to type after you decide to use multiple properties. Another option is to simply callEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Control iTunes on Mac OS X
- InhaltsvorschauWith a few bits of AppleScript, you can monitor and control Macintosh iTunes from your own application.You can make some really great applications using plain Java, but the software world isn't what it used to be. The buzzword for new applications is integration. Modern programs are defined not only by what they do, but what they can talk to. The poster child for integration these days is iTunes, so what better way to show off the power of Java than by taking control of iTunes directly from your own Java app!The task of dealing with native applications is, by nature, platform specific. For example, though Apple ships identical looking copies of iTunes for both Windows and Mac, the integration APIs couldn't be more different.Most well-written Mac OS X applications support an API called Apple Events. Apple Events let a programmer send commands and requests to a running application from another program, through a process often called scripting. The application must be written to support Apple Events and every scriptable feature must be defined explicitly when the program is written. Since it was Apple that wrote iTunes, they did a very good job of exposing virtually every feature through Apple Events. All you need to do is tap into these events.Apple Events is an API, and you need a programming language to support it. There are a variety of languages to choose from, but the easiest one to start with is AppleScript, as the syntax is simple and OS X ships with a command-line interpreter. There are also direct Java bindings available, but for the kinds of simple things you are likely to want to do with iTunes,
exec()ing the interpreter will be much easier. All you have to do is callosascriptwith the AppleScript commands you want to run, and OS X will do the rest.AppleScript is a simple language with a somewhat natural language feel to it. If you want to tell iTunes to toggle the Play/Pause button, you can call this script from the command line:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Control iTunes Under Windows
- InhaltsvorschauUse a simple open source library to monitor and control Windows iTunes from your Swing application.Windows doesn't have a standard scripting API like Apple Events, but it does have another object model that iTunes supports. Using an open source library, this hack will show you how to script iTunes just as easily on Windows as you can on the Mac.The Component Object Model (COM) is a standard way for Windows components to expose functionality that other programs can call at runtime. com4j (https://com4j.dev.java.net/) is an open source library that creates connections from Java programs to COM objects. com4j has two parts: a command-line program to create the Java interfaces that your program will call, and a native library that binds your program to the COM object at runtime.com4j uses class annotations to do its magic, so you can only use it with Java 5.0 or greater.To get started, download the com4j package at https://com4j.dev.java.net/servlets/ProjectDocumentList. With the com4j stubber and the iTunes executable in your current directory, you can generate the interfaces like this:
java -jar tlbimp.jar -o jtunes -p test.jtunes iTunes.exeThis command will load the iTunes executable and look for COM definitions. Once they are located,tlbimpwill generate a bunch of Java interfaces in thetest.jtunespackage and put the .java files into the jtunes directory. If you look at the generated Java interfaces, you will see a whole slew of methods and objects for playing, querying tracks, and dealing with virtually every other feature of iTunes. com4j will also pull out any embedded documentation and insert the documentation as JavaDoc comments in the generated interfaces.This process is pretty quick, so you may find it useful to call it from Ant as part of your compile process.Once you have the interface stubs, you can create a program to control iTunes quite easily. You can use the same program that you did when controlling iTunes on the MacEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Construct Single-Launch Applications
- InhaltsvorschauOnly allow one instance of a program, notifying the existing instance when the user tries to launch a new one.Most graphical desktop applications are designed for multitasking. You start your program to work on something, then switch to another program and come back later. Oftentimes you'll leave a large program, like a word processor, running in the background to be used again when you need to open another document, say an email attachment you received. When you click on the attachment, your operating system won't start a new instance of the word processor; instead, it will send a message to the currently running instance to
openthe new file—saving lots of system resources.Java programs aren't designed with single-launch behavior in mind. They still use the old Unix style of single use, command-line launching. You start the program to do something and it finishes quickly. If you use the program again it will start a new instance, do the work, and finish. There is never any instance reuse, but modern desktop programs demand it. Because Java doesn't support single-launch applications, this hack shows you how to build it into your programs with a simple use of sockets.Building a single-launch application requires two parts. When the program starts, it needs to detect if another copy is already running. If there is, then the program can quit instead of continuing to launch. The new program also needs to tell the first copy about any command-line arguments—the filename toopen, for example. You could create a temp file in a known location and look to see if it already exists. This would take care of multiple program instances, but not passing arguments around. Plus, you would need to worry about race conditions and cleaning up the temp file when the last program exits. Thankfully, there's a much better solution: local sockets.A socket is a network connection defined by a hostname and a port. AEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Stuff Stuff in JARs
- InhaltsvorschauHide images, sounds, and more inside JAR files.Does your application need a special installer? Do you have to put images, sounds, icons, and properties all in their own folders or other special locations relative to your application? Does your application launch with a .sh script on Unix or a .bat on Windows?You do? Really? I was just speaking rhetorically. I kind of figured everyone was using JARs by now.JAR files—the acronym is short for Java ARchive—must be the best-known, least-used feature in Java. Many developers throw a JAR in their classpath to pick up some standard extension API or third-party library, but how many actually distribute their software that way?And JARs aren't just about code. It's really easy to put the files your program needs into a JAR. This has the added advantage of hiding your images, sounds, default settings, and so forth from end users.But to load these items, you need to make a change in how you load stuff in your code. Instead of specifying a known path or URL, you ask a
ClassLoaderto find these resources along the classpath. By doing this, you can get your resources from flat files while you're developing, and then easily switch to getting them from inside a JAR when the code is deployed in the field.The key is theClassLoader's getResource() andgetResources() methods, which take a path relative to the loaded class and return aURLand an array ofURLsrespectively. AgetResourceAsStream() method converts theURLto anInputStreamas a convenience.To clarify the relative path: say you have a directory that includes your compiled classes in a path like com/mycompany/mypackage/…, an images directory, and a sounds directory. A relative path would be one of the form images/something.png, sounds/something.aiff, etc. By using a resource on the classpath, there is no difference (to the user) between files in a JAR and files in sub-directories on a filesystem. Either way, you get aEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Make Quick Look and Feel Changes
- InhaltsvorschauCustomize Metal with custom fonts, colors, and even system-bound properties using just a few API calls.Swing is a very customizable UI toolkit. The most advanced way of changing the look of your application is with a custom Look and Feel (L&F), but they can be tricky to build. Swing's L&F API is very complicated, often requiring thousands of lines of code for a complete custom theme. Fortunately, if you want to change just a few colors or fonts, the L&F API provides a much easier way. This hack shows how to create simple visual changes using UI properties.Every Look and Feel that extends the
javax.swing.plaf.basic.*classes can accept special properties that define the behavior of each Swing component. For example, there is property to control the background color of everyJButton. If you change this property at the start of your program, then everyJButtonyou create will have that background color.The UI properties are stored in a static class called theUIManager. To set a property, just put in the name of the property and a value object like aColor. To set the background color of a button to green, you would do something like this:UIManager.put("Button.background", Color.green);It is important to set these properties before any components are created or they won't pick up the new settings.Below is the code for a simple program that shows a few components in a frame. It has a button, label, and text field along the top and a text area in a scroll pane in the middle. There is also a simple file menu at the top. Before creating any components, it sets the foreground and background colors for the button, label, text field, and panel to light and dark green:public static void main(String[] args) throws Exception { Color bg = Color.green.brighter(); Color fg = Color.green.darker(); UIManager.put("Button.background",bg); UIManager.put("Button.foreground",fg); UIManager.put("Label.background",bg); UIManager.put("Label.foreground",fg); UIManager.put("TextField.background",bg); UIManager.put("TextField.foreground",fg); UIManager.put("Panel.background",bg); UIManager.put("Panel.foreground",fg); JTextArea jta = new JTextArea(); jta.setText("text\ntext\ntext\ntext\ntext\ntext"+ "\ntext\ntext\ntext\ntext\ntext"); JScrollPane scroll = new JScrollPane(jta); JButton button = new JButton("A Button"); JLabel label = new JLabel("A Label"); JTextField text = new JTextField("A TextField"); JMenuBar mb = new JMenuBar(); JMenu file = new JMenu("File"); file.add(new JMenuItem("Open")); file.add(new JMenuItem("Close")); mb.add(file); JFrame frame = new JFrame("Custom LaF Defaults"); JPanel top = new JPanel(); top.setLayout(new BoxLayout(top,BoxLayout.X_AXIS)); top.add(button); top.add(label); top.add(text); JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); panel.add("North",top); panel.add("Center",scroll); frame.getContentPane().add(panel); frame.setJMenuBar(mb); frame.pack(); frame.setSize(300,200); frame.setVisible(true); }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Create an Inverse Black-and-White Theme
- InhaltsvorschauCreate a custom black-and-white theme for monochrome LCD displays using a few simple UIManager calls.The
UIManagerlets you set simple resources for color, fonts, and padding, but you may have noticed that if you want to theme all of the components, you need to set properties on each one. Since Swing has over 300 Look and Feel properties, this could become a problem. Fortunately, there is another way to make global changes without creating an entire custom L&F. Metal, the standard cross-platform L&F that comes with Swing, can use themes. This hack demonstrates how to create a Metal theme that forces the components to use only black and white.Swing lets you switch between different Look and Feels. You can use a native Look and Feel (such as the one that comes with Mac OS X) or a thirdparty Look and Feel if you have them installed. Swing also comes with a standard L&F called Metal. Metal is built into the JRE and is always available, making it the ideal L&F to customize.Like all L&Fs, Metal has many, many classes that you can subclass to make changes. However, it also comes with an interface calledMetalTheme. If you implementMetalTheme, then you can customize most of Metal without digging into the details. Most of the colors in Metal can be set, in fact, with just six values. Metal makes it even easier by providing a default implementation called, unsurprisingly,DefaultMetalThemethat you can use as a starting point.A Metal theme is defined by a series of colors and fonts. The most important ones are the three primary and three secondary colors. These define the standard set of colors used for every widget on screen. Certain components have additional colors, like the text selection, but almost everything is based on these six. Example 11-9 is a theme that uses only white and black. It is useful for embedded devices that can't afford the hardware or memory requirements of a color display.Example 11-9.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Chapter 12: Miscellany
- InhaltsvorschauNot everything about Swing fits into nice little groupings of functionality. Some of the cool stuff you can do involves cursors, event-dispatching, networking, and even the lights on the keyboard. So, here we present some hacks that were just unique (or weird) enough to defy easy categorization.Use the
setCursor() method and an animation thread to show a frame's busy status.One of Swing's lesser-known features is the ability to change the mouse cursor on a per-component basis. Because the cursor change happens very quickly, you could combine this ability with some simple threading to create an animated cursor. This hack will show you how to create an animated cursor useful for showing a busy status.Any Swing component—even a frame—can have a custom cursor. The application as a whole will retain the normal mouse cursor, but when the user moves over the appropriately configured component, the cursor will change to whatever is set for that component. This behavior lends itself nicely to restricting user access during long running processes because the rest of the application can look and feel responsive while the portion that represents the process is visually unusable by the custom cursor.The best way to manage a short animation is by pre-generating your images in an array. This lets you loop through the array rather than creating a bunch of nasty conditionals. If you later expand the animation, you can just add more images to the array, leaving the rest of the code untouched.First, you need to build anAnimatedCursorclass and generate the images in the constructor. In this case, the images are instances of thejava.awt.Cursorobject. I used standard, predefined cursors to keep the code easy, but you could also use cursors derived from custom images. AJFrameis passed into the constructor and stored for later use, as you can see in Example 12-1.Example 12-1. A simple animated cursorpublic class AnimatedCursor implements Runnable, ActionListener { private boolean animate; private Cursor[] cursors; private JFrame frame; public AnimatedCursor(JFrame frame) { animate = false; cursors = new Cursor[8]; this.frame = frame; cursors[0] = Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR); cursors[1] = Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR); cursors[2] = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR); cursors[3] = Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR); cursors[4] = Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR); cursors[5] = Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR); cursors[6] = Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR); cursors[7] = Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Hacks 88–100: Introduction
- InhaltsvorschauNot everything about Swing fits into nice little groupings of functionality. Some of the cool stuff you can do involves cursors, event-dispatching, networking, and even the lights on the keyboard. So, here we present some hacks that were just unique (or weird) enough to defy easy categorization.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
- Display a Busy Cursor
- InhaltsvorschauUse the
setCursor() method and an animation thread to show a frame's busy status.One of Swing's lesser-known features is the ability to change the mouse cursor on a per-component basis. Because the cursor change happens very quickly, you could combine this ability with some simple threading to create an animated cursor. This hack will show you how to create an animated cursor useful for showing a busy status.Any Swing component—even a frame—can have a custom cursor. The application as a whole will retain the normal mouse cursor, but when the user moves over the appropriately configured component, the cursor will change to whatever is set for that component. This behavior lends itself nicely to restricting user access during long running processes because the rest of the application can look and feel responsive while the portion that represents the process is visually unusable by the custom cursor.The best way to manage a short animation is by pre-generating your images in an array. This lets you loop through the array rather than creating a bunch of nasty conditionals. If you later expand the animation, you can just add more images to the array, leaving the rest of the code untouched.First, you need to build anAnimatedCursorclass and generate the images in the constructor. In this case, the images are instances of thejava.awt.Cursorobject. I used standard, predefined cursors to keep the code easy, but you could also use cursors derived from custom images. AJFrameis passed into the constructor and stored for later use, as you can see in Example 12-1.Example 12-1. A simple animated cursorpublic class AnimatedCursor implements Runnable, ActionListener { private boolean animate; private Cursor[] cursors; private JFrame frame; public AnimatedCursor(JFrame frame) { animate = false; cursors = new Cursor[8]; this.frame = frame; cursors[0] = Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR); cursors[1] = Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR); cursors[2] = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR); cursors[3] = Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR); cursors[4] = Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR); cursors[5] = Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR); cursors[6] = Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR); cursors[7] = Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Fun with Keyboard Lights
- InhaltsvorschauFlash the Caps Lock, Num Lock, and Scroll Lock keys for extra user feedback.The AWT and Swing APIs are huge and full of robust components and frameworks for building big applications. They also have some dark corners where the lesser-known functions live. While cruising through the JavaDoc for
java.awt.Toolkit, I ran across a function I had never noticed before, despite it being in the API for over four years. This hack explores building a keyboard busy indicator using theToolkit.setLockingKeyState() function.The root class of AWT,Toolkit, has a very interesting little function:setLockingKeyState(). You pass it theKeyEventfor the key you want to lock down and turn it on or off with theboolean. For most keyboards, this means the Caps Lock, Num Lock, and Scroll Lock keys (some keyboards may also have a Kana lock for Kanji support). Now that you have this nifty little function, what should you do with it?My first thought was a busy cursor. If you've got three lights in a row, why not blink them off and on in sequence? The code in Example 12-2 will flip each light on and off in order, creating a moving bar effect (depending on the order of your keyboard LEDs).Example 12-2. Lights, camera, actionclass SpinnerThread extends Thread { private boolean go; public void quit() { go = false; } public void run() { go = true; // get a toolkit Toolkit tk = Toolkit.getDefaultToolkit(); // save the old key states boolean old_num, old_caps, old_scroll; old_num = tk.getLockingKeyState(KeyEvent.VK_NUM_LOCK); old_caps = tk.getLockingKeyState(KeyEvent.VK_CAPS_LOCK); old_scroll = tk.getLockingKeyState(KeyEvent.VK_SCROLL_LOCK); // set all keys to off tk.setLockingKeyState(KeyEvent.VK_NUM_LOCK,false); tk.setLockingKeyState(KeyEvent.VK_CAPS_LOCK,false); tk.setLockingKeyState(KeyEvent.VK_SCROLL_LOCK,false);SpinnerThreadis aRunnableimplementation, meaning you can launch it withnew Thread(new SpinnerThread()).start(). TheEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Create Demonstrations with the Robot Class
- InhaltsvorschauUse the Robot class to control the mouse cursor and create interactive software features.One of the coolest things about Swing is that you can often use a class for something completely different than what it was originally intended. The
java.awt.Robotclass, for example, can move the mouse cursor programmatically. This feature was originally intended for use by automated testing tools (hence the name Robot), but I've found it very useful to demonstrate software features by moving the mouse cursor through the same actions as the user. Instead of just describing something in a help file, you can actually show the user what to do—and this hack explains how.To create a mouse animation, you need three things:- The ability to move the cursor
- The start and end points of the animation
- A way to smoothly interpolate the cursor position
Thejava.awt.Robotclass has a variety of methods for capturing program state and controlling the user interface, includingRobot.mouseMove(), which allows you to move the mouse cursor programmatically.The following code is the implementation of a methodmoveMouse(), which takes three arguments: a starting component, an ending component, and a duration for the animation (in milliseconds). Because most demonstrations involve showing the user a particular component, the easiest points to use are the centers of start and end components. The mouse will smoothly move from the center of the starting component to the center of the ending one. We normally think of components as being positioned relative to their parents, but since the Robot class uses absolute mouse positions, you'll need to convert the components to their screen locations:public void moveMouse(JComponent start, JComponent end, final int duration) throws Exception { final Robot robot = new Robot(); // get middle of start final Point start_coords = start.getLocationOnScreen(); start_coords.translate(start.getWidth()/2, start.getHeight()/2); // get middle of end final Point end_coords = end.getLocationOnScreen(); end_coords.translate(end.getWidth()/2, end.getHeight()/2); // create interpolation point and offsets int steps = duration/50; //Point current = new Point(start_coords); int distx = (end_coords.x - start_coords.x); int disty = (end_coords.y - start_coords.y); for(int i=1; i<=steps; i++) { int x = start_coords.x + i*distx/steps; int y = start_coords.y + i*disty/steps;robot.mouseMove(x,y); try { Thread.currentThread().sleep(50); } catch (Exception ex) {} } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Check Your Mail with Swing
- InhaltsvorschauAdd email checking to your application with just a few method calls.As email becomes a bigger part of our daily lives, I have seen it creep into more and more places. My email program alerts me when there is new mail. I can check my email via the phone. I log in to my web mail from an Internet cafe. Email is everywhere, so why shouldn't it be in your Swing application? This hack shows how to embed in your application an email checker that shows the current number of unread messages and can launch the user's email application.Dealing with email servers can be a complicated and tricky business. To help address these issues, Sun created the JavaMail API, which is a set of classes defining a vendor-neutral interface for accessing email servers.Sun's sample implementation provides IMAP support, which is what I will demonstrate here. If you have another kind of email server, such as Exchange, you could install your own service provider and use it the same way.The code in this hack needs to do two things. First, it must open a connection to the email server periodically and check for new mail. Second, it must launch the user's email program on a double-click. I have encapsulated the email checking and launching code into separate classes, making it very easy to add to an existing program.The
EmailCheckerclass, shown in Example 12-5, is a simpleRunnableimplementation that receives aJLabelto its constructor. The run loop will sleep for a certain amount of time (one minute in this case) then callcheckEmail(). Every time there is new mail, it will set the text of the label to something like "You have N new messages."Example 12-5. Checking for new messagesimport java.util.Properties; import javax.swing.JLabel; import javax.swing.SwingUtilities; import javax.mail.*; public class EmailChecker implements Runnable { private JLabel label; public EmailChecker(JLabel label) { this.label = label; } public void run() { while(true) { try { checkEmail(); Thread.currentThread().sleep(1000*60); // sleep 1 min } catch (Exception ex) { System.out.println("exception: " + ex); ex.printStackTrace(); } } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Don't Block the GUI
- InhaltsvorschauThread your heavy lifting so the event-dispatch thread stays responsive.Practically every AWT and Swing book you'll ever see keeps things simple by responding to button clicks, menu selections, and other actions by doing something in the event listener. That's probably good for helping you learn the various GUI widgets, but it sets you up for a really bad habit: putting increasingly long-lasting calls in your event callbacks.This is bad because the thread that calls
actionPerformed(),valueChanged(), and other event-based methods is the same thread that services GUI events throughout AWT and Swing. The AWT Event Dispatch Thread is responsible for polling for events, dispatching them to listeners, and for repainting everything. If you block it on some long-lasting call—such as database or network access, intense calculation, etc.—then mouse clicks and key-presses won't be processed, menus won't be available, portions of your GUI may not get repainted if they become obscured by other windows, etc. Oh, and the user will hate you. Just so you know.The trick, then, is to keep heavy lifting out of the event-dispatch thread. There's a very straightforward way to do this in Java: move complicated processing to its own thread, and let event dispatching continue immediately after starting this new thread. Then you just have to deal with cleanup when the launched thread finishes up.AWTBlockDemo, shown in Example 12-8, offers a test bed for exhibiting and fixing the problem. It offers aJTextFieldalong with twoJButtons: Load (blocking) and Load (non-blocking). A menu also offers the blocking and non-blocking load asJMenuItems, along with a Quit menu item.The text field takes a URL. When you click one of the load buttons or menu items, it loads the file at that address into the text area. The text area is pre-populated with the address forjava.awt.Componentin Sun's JavaDoc, a nice 300 KB file that will take a little while to load, even with a fast network connection.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Code Models That Don't Block
- InhaltsvorschauModels should know that they're doing work on another thread.You already know how to keep Swing responsive by moving expensive operations off the event-dispatch thread [Hack #92] . However, one downside to that approach is that it uses a separate inner class to coordinate the interaction between the work being done on the other thread and the Swing components. If you reused the same widgets in several places, you wouldn't want to have to write the "start a thread and populate when done" code over and over again. So don't. Why couldn't the model be responsible for this sort of behavior?Well, the model can—you just have to do a little thinking. Models in Swing are generally in two states:
null(no data) or populated with data. What if you had a third state, one that indicated that the model was still loading its data?Adapt Example 12-8 to create AWTBlockModels.java. To illustrate the loading, this hack has aJProgressBarthat you need to declare before the constructor, and which you add at the bottom ofinitMainLayout():progressBar = new JProgressBar (0, 100); getContentPane().add (progressBar, BorderLayout.SOUTH);
The strategy in this hack is to make theJTextArea'smodel responsible for its own threaded loading, so get rid of theloadURL() method. That code will move to the models, which subclassjavax.swing.text.PlainDocument. First, change the actions to use these documents:classBlockingLoadAction extends AbstractAction { public void actionPerformed (ActionEvent e) { BlockingURLDocument bud = new BlockingURLDocument (urlField.getText()); progressBar.setEnabled (true); progressBar.setValue (0); contentArea.setDocument (bud); progressBar.setValue (100); } } class NonBlockingLoadAction extends AbstractAction { public void actionPerformed (ActionEvent e) { NonBlockingURLDocument nbud = new NonBlockingURLDocument (urlField.getText()); contentArea.setDocument (nbud); // makeProgressBarUpdaterFor (nbud); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Fire Events and Stay Bug Free
- InhaltsvorschauMost developers think that writing an event-firing method is trivial. Most developers are wrong.As you develop your own Swing components, it's likely that you'll eventually need to have them fire off events; this comes up as soon as you have a model that needs to update a view. If you've strongly typed everything by writing new classes for the model, view, event, and listener, then you'll have to write your own fire method.Most developers assume this to be trivial. For example, to manage a list of
FooListeners, they'll typically maintain a Vector orArrayListand fire off the event with a block like this:Iterator it = listeners.iterator(); while (it.hasNext()) ((FooListener).it.next()).handleEvent (fooEvent);
And there you have it. It's simple. It's clean. It's elegant. It's wrong.To illustrate the problem and its various solutions, consider the listener class in Example 12-11.Example 12-11. A simple event listenerimport java.util.*; public class TestEventListener extends Object implements EventListener { String id; public TestEventListener (String id) { this.id = id; } public void handleEvent (EventObject o) { System.out.println (id + " called"); if (id.equals ("C")) { ((TestEventSource) o.getSource()).removeListener (this); } } }This listener hangs on to aStringand prints that string to standard out whenhandleEvent() is called. Also, if the string is a specific value—C in this case—it removes itself from the event source. If you can see why that's going to be a big deal, congratulations. If not, read on.Next, define an abstract class to exercise various means of firing the event. This is shown in Example 12-12.Example 12-12. Abstract class for testing event-firing techniquespublic abstract class TestEventSource { public abstract void addListener (TestEventListener l); public abstract void removeListener (TestEventListener l); public abstract void fireEvent (java.util.EventObject o); public void test() { addListener (new TestEventListener ("A")); addListener (new TestEventListener ("B")); addListener (new TestEventListener ("C")); addListener (new TestEventListener ("D")); addListener (new TestEventListener ("E")); fireEvent(new java.util.EventObject(this)); } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Debug Your GUI
- InhaltsvorschauStandard out and err aren't just for log files anymore.Debugging GUIs often means keeping one or two console windows open, so you can see the debugging messages you print to standard out (via
System. out.println() and the like), as well as stack traces printed to standard err when exceptions are caught. In the field, you might want to log these to a file with something likejava.util.logging, but at design time, or when investigating a bug, you want to see exactly when the exceptions happen, and runningtail -f mylog.txtin multiple terminal windows may not be practical, especially if you're trying to get a customer on the phone to do it.An alternative is for your own application to have debugging windows that collect everything printed to standard out and err, something that you or a user can bring up with a keypress or menu item.Fortunately, taking control of the standard output and error streams is pretty easy. The trick is to repoint it into your ownJTextAreas, as shown in Example 12-16.Example 12-16. Redirecting System.out and System.err to Swing windowsimport java.awt.*; import java.awt.event.*; import javax.swing.*; import java.io.*; JTextArea outArea, errArea; public StdErrOutWindows () { // out outArea = new JTextArea (20, 50); JScrollPane pain = new JScrollPane (outArea, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); JFrame outFrame = new JFrame ("out"); outFrame.getContentPane().add (pain); outFrame.pack(); outFrame.setVisible(true); // err errArea = new JTextArea (20, 50); pain = new JScrollPane (errArea, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); JFrame errFrame = new JFrame ("err"); errFrame.getContentPane().add (pain); errFrame.pack(); errFrame.setLocation (errFrame.getLocation().x + 20, errFrame.getLocation().y + 20); errFrame.setVisible (true); // set up streams System.setOut (new PrintStream (newJTextAreaOutputStream (outArea))); System.setErr (new PrintStream (new JTextAreaOutputStream (errArea))); } public static void main (String[] args) { new StdErrOutWindows(); // test System.out.println ("test to out"); System.out.println ("another test to out"); try { throw new Exception ("Test exception"); } catch (Exception e) { e.printStackTrace(); } } public class JTextAreaOutputStream extends OutputStream { JTextArea ta; public JTextAreaOutputStream (JTextArea t) { super(); ta = t; } public void write (int i) { char[] chars = new char[1]; chars[0] = (char) i; String s = new String (chars); ta.append(s); } public void write (char[] buf, int off, int len) { String s = new String (buf, off, len); ta.append(s); } } }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Debug Components with a Custom Glass Pane
- InhaltsvorschauShow component boundaries at runtime using a glass pane.Sometimes when I'm building a really complicated Swing layout, I start to lose track of what I'm looking at. Which component is this? Does that panel extend all the way to the end of the frame? A way to visualize the layout would be a useful addition to the usual development tools. This hack explores using a custom glass pane to highlight each component and its classname.A glass pane is a normally transparent Swing component that is drawn on top of all of the other components in a frame, as you saw when you put dialog-like "sheets" into the glass pane [Hack #44] . It is this ability that forms the center of the hack. The custom glass pane will traverse the entire tree of components in the frame, filling a translucent rectangle over each component. Deeper components will get painted multiple times resulting in a darker color. The glass pane will also watch the mouse cursor to determine which component the user is pointing at. That component's classname will then be drawn in the glass pane as well.The first step is to create a sample screen for the glass pane to draw on top of, as seen in Example 12-17.Example 12-17. A screen for the glass pane
public class ComponentGlassPane extends JComponent { public static void main(String[] args) { JFrame frame = new JFrame("Component Boundary Glasspane"); Container root = frame.getContentPane(); root.setLayout(new BoxLayout(root,BoxLayout.Y_AXIS)); final JButton activate = new JButton("Show component boundaries"); root.add(activate); root.add(new JLabel("Juice Settings")); JPanel panel = new JPanel(); panel.setLayout(new BoxLayout(panel,BoxLayout.X_AXIS)); panel.add(new JLabel("Flavor")); panel.add(new JTextField(" ")); root.add(panel); frame.pack(); frame.show(); final ComponentGlassPane glass = new ComponentGlassPane(frame); frame.setGlassPane(glass); activate.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { glass.setVisible(true); } }); }Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Mirror an Application
- InhaltsvorschauWith creative use of the AWT event log, you can bind two instances of an application together over a socket, creating a mirroring effect.One of the coolest—and severely underrated—features of Java is serialization. Because Java code runs entirely in a virtual machine, it's possible to send objects over the network to another program and have the objects still functional when they get there. One day while perusing the AWT documentation, I came across the
AWTEventListener. I wondered what interesting thing you could do by capturing all of the events in a program. I could write them to disk, of course, but it would be even cooler to send them over the network to another copy of the program. That way the two programs could reuse each other's events and become mirrors! With a global event queue and a bit of serialization, this turns out to be quite easy.To replicate events over the network, you need to do three things:- Capture all AWT events. This can be done with an
AWTEventListener. - Send the event objects over the network.
- Pick the objects up on the other end of the network and repost them in the second program.
It sounds pretty simple, but there are always a few dragons hiding in the mist.Every test program begins with a frame and a few components. This program is no different, withApplicationMirrorTest(shown in Example 12-18) creating a frame, button, and text field in its constructor.Example 12-18. Simple test program for mirroringpublic class ApplicationMirrorTest { public ApplicationMirrorTest() { JFrame frame = new JFrame(); frame.getContentPane().setLayout(new FlowLayout()); final JButton button = new JButton("action generator"); frame.getContentPane().add(button); JTextField tf = new JTextField("text field"); frame.getContentPane().add(tf); frame.pack(); frame.show(); }There is only one program, but it must run in two modes: one for sending AWT events and one for receiving. If the program starts and it's the first instance running, then it should wait to receive events. If it's the second instance running, then it should send events instead. But how does the program know if it is the first or second instance? The only real way is to look for a shared resource. If the resource is already taken, then this must be the second instance. As with creating single-launch applications on WindowsEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Add Velocity for Dynamic HTML
- InhaltsvorschauUse the Velocity template engine to mimic server-side web technologies in your Swing application.Servlets, JSPs, and other server-side technologies help separate an application's model from its view and allow you to build flexible and dynamic web applications. Of course, Swing applications have their own benefits, like fast user interaction without the need for web server communication. It would be cool to have the power of those server-side technologies right in your Swing application, but without the overhead of a local web server. You can actually mimic a lot of that functionality using a combination of Apache's Velocity template engine and a Swing HTML panel.As an example, suppose you want to display the weather for the next three days as part of your application. You need a nice graphic weather display showing your users the current weather, as in Figure 12-13.
Figure 12-13: A graphical weather pageVelocity is an open source template engine, released under the Apache Jakarta umbrella. At its simplest, Velocity allows you to add intelligent replacement from a text file. At its most extreme, Velocity allows you to call Java methods and use the entire VTL (Velocity Template Language) to create intelligent templates using loops, conditionals, and variables. In other words, you get the power of an MVC infrastructure like JSP, but in a very lightweight local-client technology.When using Velocity, you have two basic elements to deal with: the VelocityContext and the template. The VelocityContext holds objects that can be referenced from the template. The template is text with embedded VTL that controls the Velocity output.In this simple example,${name}and${what}indicate replaceable values:${name} is a total ${what}Here is a simple context:VelocityContext context = new VelocityContext(); context.put("name", Jonathan); context.put("what", Rockstar);When you run Velocity with this context and this template, it will print out:Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Get Large File Icons
- InhaltsvorschauUsing an undocumented Windows-only class, you can retrieve large, full-color file icons from the operating system.The
FileSystemViewprovides access only to file icons of a default size, which usually means 16 x 16 pixels. If you look at your desktop, however, you may see icons that are much bigger and with more detail and color. There is no standard way to get the larger icons, but on Windows you can use an undocumented (and unsupported) class to get access to them. Sun's JRE for Windows includes a hidden class calledsun.awt.shell.ShellFolderthat will let you retrieve larger (32 x 32) desktop file icons.This class is only available in Sun's JRE for Windows, so it won't work with other vendors or on other platforms.The class in Example 12-22 will take a filename and show its large icon in a window.Example 12-22. Grabbing a large iconpublic class LargeIconTest { public static void main(String[] args) throws Exception { // Create a File instance of an existing file File file = new File(args[0]); // Get metadata and create an icon sun.awt.shell.ShellFolder sf = sun.awt.shell.ShellFolder.getShellFolder(file); Icon icon = new ImageIcon(sf.getIcon(true)); System.out.println("type = " + sf.getFolderType()); // show the icon JLabel label = new JLabel(icon); JFrame frame = new JFrame(); frame.getContentPane().add(label); frame.pack(); frame.show(); } }ShellFolderis a wrapper for metadata of the selected file. With this object, you can retrieve both the icon and a text description of the file's type. A normal MP3 icon would be only 16 x 16 pixels (Figure 12-15), but if you ran the MP3 file throughLargeIconTextit would print the string type = MPEG Layer 3 Audio and show a much nicer 32 x 32 pixel media icon (Figure 12-16).
Figure 12-15: Normal MP3 icon
Figure 12-16: Large MP3 iconEnde der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar. - Make Frames Resize Dynamically
- InhaltsvorschauMake your application feel more responsive by turning on dynamic layout.By default,
JFramesdon't resize dynamically. This means that the frame will not redraw itself as the user is resizing it. A repaint will only occur after the user lets go of the mouse and the window is refreshed. This behavior often results in extra gray areas and an unresponsive-feeling application. However, you can fix this with just one method call!Just call one method on the defaultToolkit:Toolkit.getDefaultToolkit().setDynamicLayout(true);You can query the dynamic layout property like this:if(Toolkit.getDefaultToolkit().isDynamicLayoutActive()) { // do something }or like this:if(Toolkit.getDefaultToolkit().isDynamicLayoutSet()) { // do something }isDynamicLayoutSet() will tell you if dynamic layout was set programmatically, whileisDynamicLayoutActive() will tell you if dynamic layout is supported. You need to use both methods because some platforms don't support dynamic layout, and others don't let you turn it off.When you have dynamic layout turned on, the window will repaint each time the user moves the mouse. This will make the application feel responsive because there is always information on the screen being updated. If your frame contains an animated component, it will continue to play while the user resizes the window.The disadvantage of dynamic layout is that a resize will generate a whole lot of repaint requests in a very short time. Even if the user moves the window just one pixel, it will trigger a repaint on the entire frame (unlike scrolling, which usually requires just repainting a strip at the bottom). If your painting code is slow (or you have a lot of components on screen), then the dynamic layout could actually make your program feel slower. Be sure you make your painting as fast as possible, perhaps skipping some of the more complicated effects during the resize. You may also want to use dynamic layout only in a program with a small streamlined window, such as a media player or utility app.Ende der Inhaltsvorschau. Der weiterere Inhalt dieses Abschnitts ist hier nicht einsehbar.
Zurück zu Swing Hacks
