Paul Fischer - Introduction To GUI With Java Swing - 2005
Paul Fischer - Introduction To GUI With Java Swing - 2005
We work with leading authors to develop the strongest educational materials in computing, bringing cutting-edge thinking and best learning practice to a global market. Under a range of well-known imprints, including Addison-Wesley, we craft high quality print and electronic publications which help readers to understand and apply their content, whether studying or at work. To nd out more about the complete range of our publishing, please visit us on the World Wide Web at: www.pearsoned.co.uk
Paul Fischer
Pearson Education Limited Edinburgh Gate Harlow Essex CM20 2JE England and Associated Companies throughout the world Visit us on the World Wide Web at: www.pearsoned.co.uk
The right of Paul Fischer to be identied as author of this work has been asserted by him in accordance with the Copyright, Designs and Patents Act 1988. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, electronic, mechanical, photocopying, recording or otherwise, without either the prior written permission of the publisher or a licence permitting restricted copying in the United Kingdom issued by the Copyright Licensing Agency Ltd, 90 Tottenham Court Road, London W1T 4LP. The programs in this book have been included for their instructional value. They have been tested with care but are not guaranteed for any particular purpose. The publisher does not offer any warranties or representations nor does it accept any liabilities with respect to the programs. All trademarks used herein are the property of their respective owners. The use of any trademark in this text does not vest in the author or publisher any trademark ownership rights in such trademarks, nor does the use of such trademarks imply any afliation with or endorsement of this book by such owners. ISBN 0321 22070 6 British Library Cataloguing-in-Publication Data A catalogue record for this book is available from the British Library Library of Congress Cataloguing-in-Publication Data Fischer, Paul, 1956Introduction to graphical user interfaces with Java Swing / Paul Fischer. p. cm. Includes bibliographical references and index. ISBN 0-321-22070-6 1. Java (Computer program language) 2. Graphical user interfaces (Computer systems) I. Title. QA76.73.J38F58 2005 005.13 3 dc22 2004062293 10 9 8 7 6 5 4 3 2 1 08 07 06 05 Typeset in 10/12pt Caslon and Frutiger by 59 Printed in Great Britain by Henry Ling Ltd, at the Dorset Press, Dorchester, Dorset The publishers policy is to use paper manufactured from sustainable forests.
Contents
Introduction
1 1 3 4 4 5 5
1.1 General 1.2 How graphical interfaces work 1.3 A note on code formatting 1.4 A note for teachers 1.5 Some books on Swing and related topics Acknowledgements
I Basics
2
9 9 14 22
A rst GUI
23 23 24 27 31 34 35
3.1 The specication of the application 3.2 The counter model 3.3 The counter view 3.4 The counter control (listeners and events) 3.5 Summary Exercises
4
4.1 4.2 4.3
A second GUI
The specication of the application The model part The view part
37 37 38 39
vi
Contents
43 44 45
Displaying a drawing
47 47 48 49 52
5.1 Method paintComponent 5.2 The graphics commands 5.3 A simple graphical application Exercises
53 54 54 55 56 63
6.1 The mouse listener 6.2 The mouse motion listener 6.3 Mouse events 6.4 A rst mouse application Exercises
Interactive graphics
64 64 65 68 71 73 73 74
7.1 Specication of the GUI 7.2 The model part 7.3 The view part 7.4 The control part 7.5 Running the application 7.6 Summary and remarks Exercises
Menus
75 75 76 79 81
8.1 Specication of the GUI 8.2 The view part 8.3 The control part Exercise
9
9.1 9.2
More on listeners
Basics of listeners Implementing listeners
82 82 83
Contents
vii
90 92
10
10.1 10.2
93 93 99
11
11.1 11.2
Scrolling
Scrolling text components Scrolling panels
12
Dialogues
12.1 The basic editor application 12.2 File selection dialogues 12.3 User-dened dialogues 12.4 Radio buttons 12.5 Exchange of information between dialogue and program 12.6 Predened option dialogues Exercises
13
More on graphics
13.1 The class Graphics2D 13.2 Finding the screen parameters 13.3 Scaling a drawing Exercise
14
An example project
14.1 Specication 14.2 The model part 14.3 The view part 14.4 The control part 14.5 Summary Exercises
viii
Contents
147
16
16.1 Borders 16.2 Lists 16.3 Tables 16.4 Trees 16.5 Combo boxes 16.6 Split panes 16.7 Tabbed panes Exercises
17 III
Grid-bag layout
Advanced topics
18 Styling text
213
18.1 Positions 18.2 Text attributes 18.3 Document listeners 18.4 Class DefaultStyledDocument 18.5 An example of using documents Exercises
Contents
ix
19
19.1 19.2 19.3 19.4
Printing in Java
Interface Printable Class PrinterJob An example application A generic class for printing
20
20.1 20.2 20.3 20.4
21
21.1 21.2 21.3
22
22.1 22.2 22.3 22.4 22.5
23
23.1
Applets
Applets in Swing
271 271
Appendix A Appendix B
B.1 B.2
279
Contents
B.3 Accessing variables with get and set B.4 Passing references B.5 The classpath Index
Introduction
1.1 General
Modern operating systems such as Microsofts Windows, Apples MacOS, and the different Unix-based versions such as Linux or Solaris use a graphical interface to communicate with the user. The communication consists of information displayed by programs and actions and commands issued by the user. This book is an introduction to graphic programming in Java. It is assumed that the reader knows the basic concepts of Java such as object-orientation, inheritance, interfaces, exceptions and use of packages. There are two libraries for graphics components in Java: the Abstract Windowing Toolkit (AWT) and Swing. The rst is the older one. It contains all the components needed to design graphical user interfaces. However, using AWT is not easy and the library is not free of bugs. The components of the Swing library are easier to work with and are much better implemented. Some Swing components need classes from the AWT library. To make these classes available we have to import them by
import java.awt.*; import javax.swing.*;
Sometimes it is advisable not to include the whole library by using * (because it is so large) but only the classes needed. Here we introduce the fundamental graphical components of the Swing library. The aim is to enable the reader to design an interactive graphical interface. This includes displaying graphics and text, making buttons react and the use of the mouse. We present only the most important graphical components and control concepts in this book. There are many more components that are not considered. Only the essential features of the components are described. Information on additional components features can be found in the Java documentation. Important facts and sources of frequent errors are marked by a ! in the margin. The example programs are designed independent of a development environment. They can be compiled and run from the command line. All programs are contained in the main package its for Introduction to Swing. This package contains further (sub-)packages, each of which contains the programs for a specic
Introduction
topic. Packages correspond to directories/folders. The directory structure looks like this:
Unix/Linux: its/[subPackageName]/[sourceFileName].java MS-Windows: its\[subPackageName]\[sourceFileName].java
where you have to insert the appropriate names for [subPackageName] and [sourceFileName]. To compile and then run a program go to the super-directory of its and issue the following commands:
javac its\[subPackageName]\[sourceFileName].java java its.[subPackageName].[sourceFileName]
Note that dots are used instead of the slashes in the java command. The le path separators are different on different operating systems. Look at Section B.5 in Appendix B for solutions to some common problems. The its-package can be downloaded as a ZIP-le from the books home page (http://www.imm.dtu.dk/swingbook/). It is in Windows le format so if you are on Linux or Unix you might see a CR or an <M> at the end of every line. Use dos2unix to get rid of it. The correct directory structure is reconstructed when the ZIP-le is reconstructed. To test it, go to the super-directory of its and type commands:
javac its\Test\Test.java java its.Test.Test
You should get the picture in Figure 1.1. If you get error messages, see Section B.5. The example programs follow the paradigm of object-orientation in that we dene a class for every customized component. Most applications are started from a separate start class (driver). The names of the start classes end with Driver. All the example programs are very simple because we want to concentrate on the graphical concepts. This means that we also omit tests that check for safety and plausibility. For example, if the size of a graphical component is set to certain values we do not check whether the values are positive, neither do we check
Introduction
whether a le we want to open really exists. If writing an application for serious purposes, you have to insert these tests. Also, exceptions are not used to cope with errors; for some errors a message will be displayed on the console. When writing a larger application, it is advisable to make use of Javas exception mechanism. The author is grateful for any corrections and suggestions. Please do not hesitate to report typographical errors or to point out parts that appear unclear. Also tell me if you would like to see an example of something specic or if there are any problems concerning the example programs. If the reader is looking for a special component or a feature of a component that is not explained in the book, you should rst consult the Java documentation. Answers to frequently asked questions and program updates will also be placed on the books home page. The authors address is Paul Fischer IMM, Technical University of Denmark Building 322 DK-2800 Kgs. Lyngby Denmark email paf@imm.dtu.dk
Introduction
When calling a method of a graphic component we sometimes unnecessarily add the key word this to make clear to which component the method belongs. Here is an example:
public MyPanel(){ // constructor of graphical component JLabel myLabel = new JLabel("Test"); // another component this.add(myLabel); // add the label to this panel this.setSize(200,200); // set the size of this panel // not of the label }
Introduction
Press/Prentice Hall, 2002. Is mainly an introduction to non-graphical Java but also addresses the basics of Swing.
Graphic Java, Volume 2: Swing by D. Geary, Sun Microsystems Press/Prentice
Hall, 2001; 1680 pages. Explains most Swing components in much detail and covers a number of advanced techniques.
UML Distilled, Second Edition by M. Fowler and K. Scott, Addison-Wesley,
Acknowledgements
The author would like to thank Anne Haxthausen, Jens Thyge Kristensen, Hans Henrik Lvengreen and Jrgen Steensgaard-Madsen for suggesting many important improvements. A special thanks to Thyge for his thorough proof-reading of the lecture notes on which this book is based.
PART
Basics
The basic element of any graphical operating system is a window. In this chapter we shall rst learn how to create a window and how to display it on the screen. Then we shall see how one can embed other graphical objects into the window and how they can be arranged in different ways.
2.1 Frames
The main components of graphical applications are the so-called windows. These are rectangular areas in which text, pictures or other information can be displayed. Windows may also contain elements for user interaction, such as menus, buttons or areas for text input. Most other graphical components discussed in this book cannot be displayed alone but have to be placed inside a window. The actual appearance of a window depends on the operating system, especially the width and type of the border around a window. The position and colour of buttons might vary. In Java, the term frame is used for what is generally called a window and we shall henceforth stick to that notation1 . In Swing, frames are realized by the class JFrame. A frame is a rectangular area with a title bar on top. The title bar also contains buttons for closing, maximizing or making an icon of the frame. As mentioned above, the type and position of these buttons depend on the platform (Windows, MacOS, Linux, Solaris, etc.). Below the title bar is an area into which further graphical components can be embedded. This area is divided into two parts: a space for a menu bar at the upper edge and the content pane below. The content pane may have further graphical components embedded. If no menu bar is added then the content pane is extended upwards. Usually there is a small border around the frame to separate it from the background. The basic functions such as resizing or moving the frame with the mouse are automatically supplied and do not have to be implemented by the programmer. Figure 2.1 shows the structure of a frame.
1
There is actually a component called window in the Swing library. It is frameless, i.e. it does not have a title bar, a border or additional buttons.
10
Basics
Buttons
Content pane
Figure 2.1 Structure of a frame. The title text can be set. The position and appearance of the buttons depend on the operating system. Further graphical components can be embedded into the content pane
We now present the constructor, list some methods of JFrame and explain their behaviour:
JFrame() setVisible(boolean b) setTitle(String title) setSize(int width, int height) setLocation(int horizontal, int vertical) pack() setDefaultCloseOperation(int operation)
here means that the information for drawing the frame is provided. The frame is, however, not shown on the screen. This is done by calling the method setVisible, explained below.
setVisible(boolean b) makes the frame appear on the screen if b = true. If a frame is visible then setVisible(false) makes it disappear but does not destroy the information for drawing it. So, calling setVisible(true) on this
frame another time will make it visible again; we do not have to use the constructor a second time.
setTitle(String title) sets the title appearing in the title bar to title. setSize(int width, int height) sets the width of the frame to width and the height to height. These are outer measures in screen pixels. setLocation(int horizontal, int vertical) moves the frame, so that its upper left corner is at position (horizontal,vertical). See also Figure 2.2.
11
ab
Figure 2.2 The Java coordinate system is upside down. The origin (0,0) is the upper left corner of the screen. The positive x-axis points right, the positive y-axis points downwards. If embedding components into another one, e.g. into the content pane of a frame, then the origin is the upper left corner of the parent component
pack() resizes the frame so that it tightly ts around components embedded into
close button of the frame is clicked. See the comments below. Let us briey discuss method setDefaultCloseOperation. By default, the frame becomes invisible when its close button is clicked. The application that made it visible is, however, still running, i.e. the program is not terminated. There are some predened constants in the class JFrame which can be used for operation. We shall use JFrame.EXIT_ON_CLOSE.
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
Then the whole application is automatically terminated when the close button is clicked. Not adding this line would result in the following problem. Suppose an application is started over and over again as happens in the test phase of a new program and every time displays a new frame. Though the frames are all made invisible by clicking the close buttons, the applications are still running and consuming resources. If many applications run in parallel and share the processor, each of them is slowed down. Problems might also arise if all the free memory were consumed. Simply terminating an application by clicking the close button is not always a good idea. In general some cleaning up would be performed before exiting the program. For example, one would save changes made to les, store data computed or received while the program was running, etc. We shall see later in Section 9.3 how this can be achieved. Let us apply our knowledge and create a rst frame and display it. We shall derive our own frame class from JFrame and add some new functions. Our class is called SimpleFrame. As it is derived from Swing class JFrame, it inherits its functionality. The constructor of a SimpleFrame is extended: it sets the size and
12
Basics
location of the frame and also ensures proper termination as explained above. We now list the program and explain it in detail. File: its/SimpleFrame/SimpleFrame.java
1. package its.SimpleFrame; 2. 3. import javax.swing.JFrame; 4. 5. public class SimpleFrame extends JFrame 6. { 7. public SimpleFrame() 8. { 9. this.setSize(200,200); 10. this.setLocation(200,200); 11. this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 12. } 13. 14. // Makes the frame visible. 15. public void showIt(){ 16. this.setVisible(true); 17. } 18. 19. // Makes the frame visible and sets the title text. 20. public void showIt(String title){ 21. this.setTitle(title); 22. this.setVisible(true); 23. } 24. 25. // Makes the frame visible and sets the title text 26. // and the position of the window. 27. 28. public void showIt(String title,int x, int y){ 29. this.setTitle(title); 30. this.setLocation(x,y); 31. this.setVisible(true); 32. } 33. 34. // Makes the frame invisible. 35. public void hideIt(){ 36. this.setVisible(false); 37. } 38.}
Let us look at the code: the class SimpleFrame is dened in a package of its own which is also called SimpleFrame. It is a sub-package of the its package. We have to
13
specify [package].[subpackage], in our case package its.SimpleFrame. Next, we import class JFrame from the javax.swing library, so it becomes accessible in our program (import javax.swing.JFrame;). We then specify the class name SimpleFrame and that this class is derived from JFrame:
public class SimpleFrame extends JFrame
The constructor and the methods of SimpleFrame are now described in more detail:
public SimpleFrame() public public public public void void void void showIt() showIt(String title) showIt(String title,int x, int y) hideIt()
SimpleFrame() augments the constructor of JFrame and sets the size to 200 200 pixels by calling setSize(200,200) in Line 9. Otherwise one would see only
the title bar because the content pane does not now contain anything. We also set the position of the upper left corner of the frame to 200 pixels below the upper edge of the screen and 200 pixels to the right. This is done by calling setLocation(200,200) in Line 10. Note that the Java coordinate system is upside-down: the positive y-axis points down; see also Figure 2.2. Finally the constructor calls method setDefaultCloseOperation in Line 11 to guarantee correct termination as described above. Note that keyword this in Lines 9 to 11 can be omitted. As mentioned in the introduction, we added it to make clear that these statements refer to this frame.
showIt() makes the frame appear on the screen by calling setVisible with argument true. showIt(String title) sets the title in the title bar to title and makes the frame
information is preserved and the frame can be made visible again without calling a constructor. To test the class SimpleFrame we use a driver program. We might have dened a main-method in SimpleFrame for this purpose but we did not because we shall use SimpleFrame as the basis for further applications. The driver class is SimpleFrameDriver. It generates two frames in Lines 7 and 8. The rst frame receives a title and is made visible in Line 9. The second one is in addition moved
14
Basics
to position (300, 300) in Line 10. Both frames are empty, i.e. there is nothing in their content panes. File: its/SimpleFrame/SimpleFrameDriver.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. package its.SimpleFrame; public class SimpleFrameDriver { public static void main(String[] args) { SimpleFrame sFrame1 = new SimpleFrame(); SimpleFrame sFrame2 = new SimpleFrame(); sFrame1.showIt("SimpleFrame 1"); sFrame2.showIt("SimpleFrame 2",300,300); } }
The result should look like Figure 2.3. This picture is taken under WindowsXP; it might appear slightly different on other platforms. As mentioned above, the frame can be resized and moved with the mouse. These basic functions are automatically supplied.
15
purposes: they can be used as a canvas that one draws on or they can be used as containers to embed further graphical components. In Swing the class JPanel implements panels. We list the constructor and some methods and then present an example program that demonstrates the use of the methods:
JPanel() setBackground(Color c) setPreferredSize(Dimension d)
to remember that these values are only recommendations for the size of a component. Depending on the size of other components, the runtime system might choose different values! The values actually used are determined by a LayoutManager at runtime. This exibility is important for Java to be platformindependent. We describe layout managers later in this chapter. We now want to embed some panels into a frame. To make them visible and distinguishable we colour them differently. As mentioned in the introduction, we shall derive our own class for every non-trivial concept. It might look like some kind of overkill here, where only the colour of the panel is set. However, we want to follow the paradigm of object-orientation right from the beginning. In the following listing we derive our own class ColorPanel from JPanel. Its two constructors allow a panel to be constructed, each with a given background colour, and given width and height. File: its/SimpleFrameWithPanels/ColorPanel.java
package its.SimpleFrameWithPanels; import java.awt.*; import javax.swing.JPanel; public class ColorPanel extends JPanel { // Generate a JPanel with background color col. public ColorPanel(Color col) { 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
16
Basics
11. this.setBackground(col); 12. } 13. 14. // Generate a JPanel with background color col, 15. // width width, and height height 16. public ColorPanel(Color col,int width,int height) 17. { 18. this.setPreferredSize(new Dimension(width,height)); 19. this.setBackground(col); 20. } 21. 22. }
We embed the ColorPanels into a SimplePanelFrame which is derived from SimpleFrame. Thus a SimplePanelFrame inherits the functions of a SimpleFrame; in particular an application will be terminated if the frame is closed. We generate ve
ColorPanels in white, red, yellow, green and blue. The white one has a width of
50 pixels and a height of 20 pixels. For the others no size is specied, so they will have the default size. The panels are then embedded into the frame. Graphical components are embedded into others as follows. Let us call the component into which we want to embed the parent component and the component to be embedded the child component. Those Swing components into which others can be embedded have a method add. Then, to embed a component childComp into another component parentComp, the syntax is
parentComp.add(childComp)
The add-method might have more arguments which, for example, specify alignments or positions. There is a difference when embedding into a frame. Here we have to specify that we want to embed into the content pane. Besides the content pane a JFrame has more panes which we do not discuss here. It can be referred to by using method getContentPane of JFrame. Then the syntax to embed a component childComp into a frame parentFrame is:
parentFrame.getContentPane().add(childComp)
Let us now specify how the components are to be arranged in the content pane. In order to have platform-independence the designers of Java have given some exibility to the graphic system. The programmer only species the structure of the arrangement of the components, not their absolute positions. For example, one species that component A is to the right of component B instead of requiring that component A is at position (x, y). At runtime, the positions of the components are determined. This is done by the so-called layout manager which is associated with the parent component. There are different predened layout managers, some of which we describe here. The programmer can dene individual ones. A JFrame has by default a BorderLayout, more precisely the content pane has a layout manager of type BorderLayout. It allows the user to place one (big) central component and up to four components at the borders. The positions are
17
Figure 2.4 A frame generated by SimplePanelFrameDriver with a border layout. Note that the panel at West has been constructed with width 50 and height 20. While the width is obeyed, the height has changed to ll the available space
specied by the constants CENTER, NORTH, SOUTH, EAST and WEST. These constants are dened in class BorderLayout; see Figure 2.5a). If a border component is not present, then the central component extends in that direction. The central component usually contains the main information. The border components contain additional information such as a status bar in the South-component. To insert a component childComp into the content pane at location pos we use method
this.getContentPane().add(childComp,pos)
where pos is one of the constants BorderLayout.CENTER, BorderLayout.NORTH, BorderLayout.SOUTH, BorderLayout.EAST or BorderLayout.WEST. The code for class SimplePanelFrame and the listing for the driver class SimplePanelFrameDriver follow. The rst class is derived from SimpleFrame. The ve panels are created in its constructor and then embedded into the content pane. The driver class generates a SimplePanelFrame and makes it visible. The result is shown in Figure 2.4. There one can see that all the border panels are sized to span the entire width or height of the content pane. The other dimensions are the default width and height, which are 10 on most platforms. The white panel CPWest at West has width 50 as specied in its constructor. The height specication of 20 is ignored in the layout; the height of panel CPWest is extended to ll the available height inside the frame. The central component is also extended to ll all the space in the middle. File: its/SimpleFrameWithPanels/SimplePanelFrame.java
package its.SimpleFrameWithPanels; import java.awt.*; import javax.swing.JFrame; import its.SimpleFrame.SimpleFrame; 1. 2. 3. 4. 5. 6.
18
Basics
7. public class SimplePanelFrame extends SimpleFrame 8. { 9. public SimplePanelFrame() 10. { 11. ColorPanel CPWest = new ColorPanel(Color.white,50,20); 12. ColorPanel CPEast = new ColorPanel(Color.red); 13. ColorPanel CPNorth = new ColorPanel(Color.yellow); 14. ColorPanel CPSouth = new ColorPanel(Color.green); 15. ColorPanel CPCenter = new ColorPanel(Color.blue); 16. this.getContentPane().add(CPWest,BorderLayout.WEST); 17. this.getContentPane().add(CPEast,BorderLayout.EAST); 18. this.getContentPane().add(CPNorth,BorderLayout.NORTH); 19. this.getContentPane().add(CPSouth,BorderLayout.SOUTH); 20. this.getContentPane().add(CPCenter,BorderLayout.CENTER); 21. } 22. }
File: its/SimpleFrameWithPanels/SimplePanelFrameDriver.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. package its.SimpleFrameWithPanels; public class SimplePanelFrameDriver { public static void main(String[] args) { SimplePanelFrame spFrame = new SimplePanelFrame(); spFrame.showIt("Simple Panel Frame"); } }
We now introduce two more layout managers: ow layout manager (FlowLayout) and grid layout manager (GridLayout). Both classes as well as BorderLayout implement the interface LayoutManager. Layouts are not restricted to frames. Every Swing component into which other components can be embedded (a socalled container) has a layout. The default layout is a border layout. In order to change the layout of a parent component parentComp to another layout newLayout one uses the command
parentComp.setLayout(newLayout)
If a component has a ow layout then the embedded components are placed row-wise from left to right. If a row is full, the next one is started. Row layout (mostly) respects the dimensions of the embedded components. The order of the calls parentComp.add(child) determines the order of the embedded components in the parent component. The height of a row is determined at
19
(a)
(b)
(c)
Figure 2.5 Three layout managers. (a) The arrangement of the components in a border layout. (b) The row-wise arrangement of the embedded components. The dashed lines indicate the upper and lower boundaries of the rows. The height of each row is individually determined by the height of the tallest component in it. The spacing between the components and between the rows can be modied. (c) A 2 3 grid layout into which ve components are embedded. All columns and rows are equally wide and high.
runtime by checking all components in the row. See Figure 2.5b. Here are some constructors of FlowLayout:
FlowLayout() Flowlayout(int align) Flowlayout(int align, int hdist, int vdist)
The rst constructor generates a layout which by default has ve pixels between the components in a row and ve pixels between the rows. In addition, the second constructor species the alignment of the components, where align is one of
FlowLayout.RIGHT, FlowLayout.LEFT, FlowLayout.CENTER
This determines whether the components in every row are packed to the right, the left or whether they are centred. The third constructor also species the horizontal distance hdist between the components in a row and the vertical distance vdist between rows. Program LayoutDriver shows some examples. The size of the components for which we did not dene dimensions are set to default minimum values; these are 10 10 on most systems. The layout manager for a component comp recomputes the layout every time the component comp is redrawn, especially after a resizing of comp. If after resizing more or fewer components t into a row then they are newly arranged. Try it in LayoutDriver. See also Figure 2.5. The third layout manager described here is the GridLayout which orders the components in a grid. The parent component (the content pane in our example) is divided into r c rectangular cells, where c is the number of cells per row and r is the number of rows. All cells have the same size. The components are embedded into the cells row-wise from left to right. If there are more cells than embedded components the layout manager tries to ll all rows as far as possible and might generate fewer than c columns! If there are fewer cells than components then columns are added. Basically the grid layout manager ignores the column number. In order to have a xed number c > 0 of columns, the row number must be set to r = 0 and the column number to c. Then there will always be c columns and the
! !
! !
20
Basics
Figure 2.6 Output of LayoutDriver. Standard ow layout; ow layout with bigger spacing; grid layout
row number is adjusted depending on the number of embedded components. In program LayoutFrame we generate a 2 4 grid for ve components. The layout manager then generates a 2 3 grid, where only one cell is empty. Sometimes one embeds dummy components into a grid layout. These are Swing components, usually labels, with no function other than to ll certain cells. They can be used to produce an empty cell between two other cells. Chapter 6 contains an example. The dimensions of the embedded components are basically ignored by the grid layout; the components completely ll the cells. See also Figure 2.5c. Below we list the constructor and some methods for grid layouts. The number of rows r and columns c and also the width of the gaps can be passed between the columns (hdist) and rows (vdist):
GridLayout(int r, int c); setHgap(int hdist); setVgap(int vdist);
The following listings LayoutFrame and LayoutDriver show examples for the layouts. The rst denes a frame for which the layout can be selected by passing a LayoutManager in the constructor. Program LayoutDriver generates three LayoutFrames and makes them visible. The rst one is given a standard ow layout. The second one receives a ow layout with specied distances between rows/columns and left alignment. The third frame is given a 2 4 grid layout. The locations in the showIt instructions are set in such a way that the three frames appear side by side. Figure 2.6 shows the result. File: its/Layouts/LayoutFrame.java
1. 2. 3. 4. 5. 6. 7. package its.Layouts; import import import import java.awt.LayoutManager; its.SimpleFrameWithPanels.ColorPanel; java.awt.Color; its.SimpleFrame.*;
21
8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25.
public class LayoutFrame extends SimpleFrame { public LayoutFrame(LayoutManager layout) { this.getContentPane().setLayout(layout); ColorPanel CP1 = new ColorPanel(Color.red,30,30); ColorPanel CP2 = new ColorPanel(Color.yellow,40,20); ColorPanel CP3 = new ColorPanel(Color.green); ColorPanel CP4 = new ColorPanel(Color.blue); ColorPanel CP5 = new ColorPanel(Color.white,80,20); this.getContentPane().add(CP1); this.getContentPane().add(CP2); this.getContentPane().add(CP3); this.getContentPane().add(CP4); this.getContentPane().add(CP5); } }
File: its/Layouts/LayoutDriver.java
package its.Layouts; import java.awt.FlowLayout; import java.awt.GridLayout; public class LayoutDriver { public static void main(String[] args) { FlowLayout flowLayout1 = new FlowLayout(); LayoutFrame flow1Frame = new LayoutFrame(flowLayout1); flow1Frame.showIt("Flow Layout 1",60,60); FlowLayout flowLayout2 = new FlowLayout(FlowLayout.LEFT,40,30); LayoutFrame flow2Frame = new LayoutFrame(flowLayout2); flow2Frame.showIt("Flow Layout 2",300,60); GridLayout gridLayout = new GridLayout(2,4); LayoutFrame gridFrame = new LayoutFrame(gridLayout); gridFrame.showIt("Grid Layout",540,60); } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22.
22
Basics
Exercises
2.1 What happens if you click on the close button of one of the windows? Explain the observed behaviour. Then remove the line.
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
from the constructor of SimpleFrame. Add the following code at the beginning of the main-method in class SimpleFrameDriver:
int k = 0; while(true){ System.out.println("SimpleFrameDriver: Still running! "+k); k++; }
This will print lines to the console as long as the application is running. Then compile and start the application and click on the close buttons of the windows. Observe what has changed and explain it. 2.2 Check what happens if the central component in a border layout of a frame is not present. Add different combinations of border components, e.g. East and South. Play around with layouts to get a feeling of what the layout managers do. Resize the frames, change the parameters in the layouts (distances, row/column numbers), etc.
2.3
A rst GUI
The purpose of graphical interfaces is to display data and to allow a communication between the user and a program at runtime. In this chapter we shall learn how to design such graphical interfaces. In this example the user can communicate with the program by pressing buttons. The program updates the display in reaction to that.
In this chapter we shall see the rst example of a real GUI (graphical user interface). Real here means that we have a userprogram interaction. The interaction is realized using buttons in the graphical display. Although the program is very simple we would like to introduce the concept of a modelviewcontrol approach at this point. The (non-graphical) model part of the program deals with storing, maintaining or manipulating the data. The graphical view part displays the data and provides the components for user interaction, e.g. buttons. The (again non-graphical) control part ensures that the user actions result in the desired responses by the program. The control part is the bridge between the model and view parts. The separation of the model, view and control structures is often the crucial concept to a successful save and fast implementation. It also helps beginners to better recognize the essential concepts and their interplay. In Java such a separation is easily possible by using object-orientation, i.e. different classes or at least different methods for the different parts. In complex applications separate packages might be used for the different parts. In the following we start by specifying the GUI we want to implement. Then we construct the non-graphical model and test that it works correctly. This is followed by the design of the graphical display. Finally we implement the control structure, i.e. the userprogram interaction.
24
Basics
Frame
Titlebar
Up
14
Down
Button
Label
Button
Figure 3.1 The specication of the layout for the counter GUI
increment increments the value of the counter by 1. decrement decrements the value of the counter by 1. reset sets the value of the counter to 0. getValue returns the current value of the counter.
Next, we specify what the graphics should look like and how it is to work. The GUI we have in mind should display the current value of the counter and allow the user to increment or decrement it. To this end the GUI has two buttons. Pressing the rst one (labelled Up), e.g. clicking it with the mouse, will increment the counter. Pressing the other one (labelled Down) will decrement the counter. Figure 3.1 shows how we want this GUI to look. This concludes the specication of the GUIs functionality and layout.
A rst GUI
25
5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25.
// The constructor initializes the counter to 0 public CounterModel() { value = 0; } public void increment(){ value++; } public void decrement(){ value--; } public void reset(){ value = 0; } public int getValue(){ return(value); } }
A counter is such a simple object that we can immediately see that our implementation is correct, i.e. meets the specications. For slightly more complicated entities, however, one can easily overlook a mistake. The problem of checking whether a program meets its specications is hard to solve, in fact it is in general unsolvable. Therefore one has to rely on empirical tests. This means that one sets up a test plan. This contains a (large) number of possible inputs to the program and the correct responses of the program. The plan should activate all parts of the program, e.g. all methods of all classes. Then one checks whether the observed and expected responses coincide. Of course, such a plan is no guarantee that the program is indeed correct because it might not contain an existing input that causes an error. However, a good test plan often does discover mistakes in the program. To give an idea of what a test plan can look like we add the listing of class CounterModelTest which implements a test plan for the counter model. It addresses all methods at least once and compares the expected and observed results. The comparison is done by method checkValue(a,b), which compares a and b and prints the result of the comparison to the screen. File: its/CounterGUI/CounterModelTest.java
package its.CounterGUI; 1. 2. 3. 4.
26
Basics
5. 6. private static boolean passed = true; 7. 8. public static void main(String[] args) { 9. CounterModel cm = new CounterModel(); 10. 11. checkValue(0,cm.getValue()); 12. cm.increment(); 13. checkValue(1,cm.getValue()); 14. cm.decrement(); 15. checkValue(0,cm.getValue()); 16. for (int i = 0; i < 37; i++) { 17. cm.increment(); 18. } 19. checkValue(37,cm.getValue()); 20. for (int i = 0; i < 21; i++) { 21. cm.decrement(); 22. } 23. checkValue(16,cm.getValue()); 24. cm.reset(); 25. checkValue(0,cm.getValue()); 26. 27. if(passed){ 28. System.out.println("Test passed."); 29. } 30. else{ 31. System.out.println("Test NOT passed."); 32. } 33. } 34. 35. private static void checkValue(int expectedValue, int observedValue){ 36. if(expectedValue == observedValue){ 37. System.out.println("Values are both equal to "+expectedValue); 38. } 39. else{ 40. System.out.println("ERROR expected value "+expectedValue+ 41. " and observed value "+observedValue+" differ!"); 42. passed = false; 43. } 44. } 45. }
A rst GUI
27
Values are both Values are both Values are both Values are both Test passed.
to to to to
0 37 16 0
3.3.1
Labels
A label is a rectangular component which displays text that cannot be edited by the user (but might be changed by the program). Class JLabel realizes labels in Swing. Here we present a constructor and a few methods.
public JLabel(String text) public JLabel(ImageIcon picture) public String getText() public void setText(String text) public void setText(String text, int alignment) public void setForeground(Color c) public void setBackground(Color c) public void setOpaque(boolean b)
JLabel(String text) constructs a label which displays the text. JLabel(ImageIcon picture) constructs a label which displays the image picture. For details on using images see Chapter 15. getText() returns the text currently displayed in the label as a String. setText(String text) replaces the text currently displayed in the label by text.
transparent by default and their background colour is not visible. One sees the background colour of the parent component shining through. To change the
28
Basics
background colour of a label one has to rst make it opaque (non-transparent) using the method setOpaque.
setOpaque(boolean b) makes the label transparent if b is false and nontransparent if b is true.
3.3.2
Buttons
Buttons are rectangular areas (usually with a line around) which like labels can display text. They differ from labels in the fact that they can trigger events. An event occurs whenever a button is pressed. The Java runtime system monitors buttons and recognizes when such an event occurs. Note that a button is not necessarily pressed by clicking the mouse, it might also be a nger on a touch screen monitor. Therefore, buttons are treated separately from the mouse. In Section 3.4 we shall learn in detail how the runtime system informs our program that an event has occurred. For now, the following explanation should sufce: in order to notice when a button is pressed, something has to keep an eye on the button. This is done by a listener, a (non-graphical) component from the AWT library. The listener has to be assigned to the button in order to monitor it. If an event occurs at that button the runtime system informs the listener. The listener can then analyse the event and initiate a certain action. Class JButton implements buttons in Swing. We only present the constructor and the method to assign a listener to the button.
public JButton(String text); public void addActionListener(ActionListener listener);
3.3.3
We use two buttons to change the value of the counter and a label to display its value. There are many ways to arrange these components. One would be to glue the buttons and the label directly into the content pane of the frame. We use a different approach here: an intermediate panel into which the buttons and the label are embedded. The corresponding class is called CounterPanel. Then a CounterPanel is glued into the frame; see Figure 3.2. The advantage of this approach is that one can reuse the CounterPanel as a ready-made module in other applications. We rst have to decide which layout manager supports the intended GUI. In our case, a border layout is appropriate for both the panel and the frame. We glue the buttons into the west and east positions of the panel and glue the label into the central one. Actually, panels have a border layout by default. We nevertheless set the layout to make clear that this is one step of a GUI implementation. Every CounterPanel holds its own instance of CounterModel in the variable counter. This variable is private so that the counter cannot be manipulated directly from outside the class CounterPanel. Instead CounterPanel offers two methods, increment() and decrement(), which simply call the respective methods of counter model counter.
A rst GUI
29
Figure 3.2 The construction of the counter GUI. The intermediate JPanel is introduced to guarantee modularity. The panel can be reused in other applications
The frame of our counter application is implemented in the class CounterFrame. This class is derived from SimpleFrame so that the application is terminated when the frame is closed. A CounterFrame has a CounterPanel embedded into the central position of its content pane. We list the code of the classes CounterPanel, CounterFrame and the driver class CounterDriver below. When the application is started, the frame appears. We can press the buttons and see that their colour changes for a short period in response. This function is automatically supplied. The application does not, however, change the value of the counter in the label. It cannot do this at this point because we have not yet told the application to do this. The control part introduced in Section 3.4 will be responsible for that. In the listing of CounterPanel; Lines 29 to 31 will later be used to enable the listener. Currently they are comments, so do not have any effect. File: its/CounterGUI/CounterPanel.java
package its.CounterGUI; import import import import import javax.swing.JPanel; javax.swing.JButton; javax.swing.JLabel; java.awt.BorderLayout; javax.swing.SwingConstants; 1. 2. 3. 4. 5. 6. 7. 8.
30
Basics
9. public class CounterPanel extends JPanel { 10. 11. private CounterModel counter; 12. private JLabel valueLabel; 13. 14. public CounterPanel() { 15. counter = new CounterModel(); 16. 17. BorderLayout bordLay = new BorderLayout(); 18. this.setLayout(bordLay); 19. 20. JButton upButton = new JButton("Up"); 21. JButton downButton = new JButton("Down"); 22. valueLabel = new JLabel(""+counter.getValue(),SwingConstants.CENTER); 23. 24. this.add(upButton,BorderLayout.WEST); 25. this.add(downButton,BorderLayout.EAST); 26. this.add(valueLabel,BorderLayout.CENTER); 27. 28. // The next three lines will later be used to incorporate //the listener. 29. // CounterListener countList = new CounterListener(this); 30. // upButton.addActionListener(countList); 31. // downButton.addActionListener(countList); 32. } 33. 34. public void increment(){ 35. counter.increment(); 36. valueLabel.setText(""+counter.getValue()); 37. } 38. 39. public void decrement(){ 40. counter.decrement(); 41. valueLabel.setText(""+counter.getValue()); 42. } 43. }
File: its/CounterGUI/CounterFrame.java
1. 2. 3. 4. 5. 6. package its.CounterGUI; import javax.swing.JFrame; import java.awt.BorderLayout; import its.SimpleFrame.SimpleFrame;
A rst GUI
31
7. 8. 9. 10. 11. 12. 13.
public class CounterFrame extends SimpleFrame { public CounterFrame() { CounterPanel counterPane = new CounterPanel(); this.getContentPane().add(counterPane,BorderLayout.CENTER); } }
File: its/CounterGUI/CounterDriver.java
package its.CounterGUI; public class CounterDriver { public static void main(String[] args) { CounterFrame cfr = new CounterFrame(); cfr.showIt("Counter"); } } 1. 2. 3. 4. 5. 6. 7. 8.
32
Basics ActionEvent. This class is also found in the AWT library (in java.awt.events). An object of type ActionEvent contains information about the event noticed by
the runtime system. For our counter application we implement a listener in the class CounterListener. A CounterListener implements the Java interface ActionListener. The implementation requires the denition of only one method actionPerformed:
public void actionPerformed(ActionEvent evt)
This is the method called by the runtime system whenever the button is pressed. You do not have to call this method yourself, and you should not do this. The runtime system also generates an action event evt and passes it to the method actionPerformed. The programmer puts the code into the body of method actionPerformed1 . This code is then executed in response to pressing the button. The information contained in the action event can be used to gather further information on what triggered the event. The code for the class CounterListener is listed below. We shall explain it after the listing. File: its/CounterGUI/CounterListener.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24.
1
package its.CounterGUI; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; public class CounterListener implements ActionListener{ private CounterPanel countPane; public CounterListener(CounterPanel counp) { countPane = counp; } // This method is called by the runtime system. // The programmer has to add the code to be executed // as a response to the event. public void actionPerformed(ActionEvent evt){ // Beginning of own code String actionCommand = evt.getActionCommand(); if(actionCommand.equals("Up")){ countPane.increment(); } else if(actionCommand.equals("Down")){ countPane.decrement();
This formulation has to be taken with a grain of salt. Some of the code might actually be in other methods which are then called from inside actionPerformed.
A rst GUI
33
25. 26. 27. 28. 29. 30. 31.
In the code for the class CounterListener we nd the word implements instead of extends. This is due to the fact that ActionListener is an interface and not a class. The constructor receives a CounterPanel as an argument. The listener then knows which counter panel it has to update. Check Section B.4 for more details on this. The heart of class CounterListener is the implementation of the method actionPerformed. We rst determine which of the two buttons had been pressed. This is done by inspecting the action event object evt that the method actionPerformed received from the runtime system. The line
String actionCommand = evt.getActionCommand();
extracts the action command out of the event object evt. The action command is usually the text of the button which has been pressed. In our case it can only be Up or Down. We use an ifthenelse structure to check which one it was2 . Depending on the outcome of the test we call the method increment or decrement of the counter panel. At this point we see why the listener is given a reference to a CounterPanel in the constructor. The listener thereby knows which panel it has to update in response to the event. In the listing of CounterPanel the comment signs at Lines 29 to 31 are removed. These lines are
CounterListener countList = new CounterListener(this); upButton.addActionListener(countList); downButton.addActionListener(countList);
The rst one creates an instance of CounterListener. The listener receives a reference (this) to the panel in the constructor. The next two lines associate the listener to both buttons of the GUI. This completes the implementation of our GUI. Figure 3.3 shows what the application looks like. The structure of the its.CounterGUI package can be found in Figure 3.4 as an UML-like diagram3 .
The last case can only be reached if the action command is neither Up nor Down. We added this case to make sure that we become aware of an error that could be due, for example, to changing the text of the buttons in the counter panel. UML stands for Unied Modelling Language. UML is used to specify the structure of objectoriented programs also by using graphical representations by the class structure. For more details see the book by Fowler and Scott in Section 1.5.
34
Basics
JFrame
JPanel
JLabel
JButton
ActionListener
CounterDriver main()
Figure 3.4 The class diagram for the its.CounterGUI package. The test class CounterTest has been omitted
3.5 Summary
In order to develop an interactive GUI the following steps are necessary: 1. Implement and test the model. 2. Implement the view, especially: (a) Decide which graphical components to use. (b) Decide how to arrange them. (c) Decide which layout manager(s) to use.
Exercises
35
3. Create an action listener, especially: (a) Dene in method actionPerformed what has to happen in response to an event. (b) Use the ActionEvent object to get information about the type of event that occurred. 4. Assign the action listener to the relevant graphical components of the view. The userprogram interaction then works as follows:
The user presses a button. The runtime system detects the user action and creates an event object which
argument.
The code in method actionPerformed is executed.
Exercises
3.1 Add a third button to the CounterPanel. It should be located at the bottom and labelled Reset. When pressing this button, the counter should be reset to 0. Then write an application that displays the new panel as in Figure 3.5. Write an application that displays two counters as shown in Figure 3.6. Try to recycle components already dened, e.g. CounterPanel. Write an application that displays four buttons labelled 1, 2, 3 and 4 and a label. Arrange the components as shown in Figure 3.7. The label initially displays the text No button pushed. When one of the four buttons is pushed the text in the label changes to Last button pushed was no. X, where X is the number of the button.
3.2 3.3
Up
14
Down
36
Basics
Up
14
Down
Up
22
Down
A second GUI
In this chapter we construct another graphical interface. The purpose of this one is to pass text to a running application.
Title
Dannebrog
Editable text area Repetition of text in upper case Number of E in text Button
DANNEBROG 1
Fixed texts
Figure 4.1 The specication of the GUI for text analysis
38
Basics
File: its/TextAnalysisGUI/TextAnalysisModel.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28.
package its.TextAnalysisGUI; public class TextAnalysisModel { private private private private int totalNumberOfEs; int currentNumberOfEs; int totalNumberOfTexts; String currentText;
public TextAnalysisModel() { totalNumberOfEs = 0; totalNumberOfTexts = 0; currentText = ""; } public void analyse(String str){ currentText = str.toUpperCase(); currentNumberOfEs = 0; for (int i = 0; i < currentText.length(); i++) { if(currentText.charAt(i) == 'E'){ currentNumberOfEs++; }//if }//for i totalNumberOfEs += currentNumberOfEs; totalNumberOfTexts++; }// analyse public int getCurrentNumberOfEs(){
A second GUI
39
29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43.
return(currentNumberOfEs); } public String getCurrentText(){ return(currentText); } public int getTotalNumberOfEs(){ return(totalNumberOfEs); } public int getTotalNumberOfTexts(){ return(totalNumberOfTexts); } }
We do not list the test class here but it should be obvious which functions it has to check.
4.3.1
Text elds
Text elds display a single line of text. The text can be edited by the user. The class JTextField realizes text elds in Java. To edit a text, click inside the text eld and a cursor will appear. The text eld automatically scrolls horizontally if the text gets long. This functionality is built in. Text elds are non-transparent and have a white background and black text by default.
public JTextField(String text); public String getText(); public void setText(String text); public void setForeground(Color c); public void setBackground(Color c);
JTextField(String text) constructs a text eld which displays the text. setText(String text) replaces the text currently displayed in the text eld by text. getText() returns the text currently displayed in the text eld as a String. setForeground(Color c) sets the text colour to c. setBackground(Color c) sets the background colour to c.
40
Basics
4.3.2
We now describe how to construct the GUI. Figure 4.2 shows the structure. We take a frame with border layout. The class is called TextAnalysisFrame and is derived from SimpleFrame. At the bottom (South) we glue a JButton into the frame. In the middle (centre) we glue a panel of type TextAnalysisPanel. This panel is dened to take the text components. It has a 3 2 grid layout. The grid contains in this order a label, a text eld and four more labels. The components are coloured to make them easier to distinguish and the gap between rows and columns is increased. Through these gaps we can see the yellow background of the panel. Other ways of constructing the GUI are possible. One could use another panel that contains the Analyse button and the text analysis panel. In this case the listener should be instantiated in the new panel. To achieve the desired functionality, we dene the method startAnalysisAndDisplayResult in TextAnalysisPanel. This method reads the text from text eld inputField and passes it to the text analysis model by calling method analyse of TextAnalysisModel. The panel itself does not know how the text is analysed. The
JTextField
JFrame
Da
JPanel
te xt :
b ne
ro
OG BR NE N DA
t En
n Ce
tre
Sou th
: xt t te en t rr en cu rr in Cu Es f: o Noxt te
er
JLabel
se ly na A
JButton
A second GUI
41
result of the analysis (the text in upper case and the number of Es in it) is then acquired from the text analysis model by calling the appropriate get-methods of the TextAnalysisModel. The results are displayed and the text in the text eld is erased, i.e. replaced by the empty string. We now list the two classes dening the view.
File: its/TextAnalysisGUI/TextAnalysisFrame.java
package its.TextAnalysisGUI; import its.SimpleFrame.SimpleFrame; import java.awt.*; import javax.swing.JButton; public class TextAnalysisFrame extends SimpleFrame { public TextAnalysisFrame() { this.setSize(300,150); TextAnalysisPanel taPanel = new TextAnalysisPanel(); this.getContentPane().add(taPanel,BorderLayout.CENTER); JButton analyseButton = new JButton("Analyse"); analyseButton.setBackground(Color.blue); analyseButton.setForeground(Color.yellow); this.getContentPane().add(analyseButton,BorderLayout.SOUTH); TextAnalysisListener taList = new TextAnalysisListener(taPanel); analyseButton.addActionListener(taList); } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24.
File: its/TextAnalysisGUI/TextAnalysisPanel.java
package its.TextAnalysisGUI; import import import import import java.awt.Color; java.awt.GridLayout; javax.swing.JLabel; javax.swing.JPanel; javax.swing.JTextField; 1. 2. 3. 4. 5. 6. 7.
42
8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53.
Basics
public class TextAnalysisPanel extends JPanel { private JLabel lastTextLabel; private JLabel numberOfEsLabel; private JLabel numberOfTextsLabel; private JTextField inputField; private TextAnalysisModel analysisModel; public TextAnalysisPanel() { analysisModel = new TextAnalysisModel(); this.setBackground(Color.yellow); this.setLayout(new GridLayout(3,2,10,10)); JLabel questionLabel = new JLabel("Enter text:"); JLabel replyLabel = new JLabel("Current text:"); JLabel numberTextLabel = new JLabel("No. of Es in current text:"); lastTextLabel = new JLabel(""); numberOfEsLabel = new JLabel("--"); inputField = new JTextField(""); questionLabel.setOpaque(true); questionLabel.setBackground(Color.black); questionLabel.setForeground(Color.white); replyLabel.setOpaque(true); replyLabel.setBackground(Color.black); replyLabel.setForeground(Color.white); numberTextLabel.setOpaque(true); numberTextLabel.setBackground(Color.black); numberTextLabel.setForeground(Color.white); numberOfEsLabel.setOpaque(true); numberOfEsLabel.setBackground(Color.red); numberOfEsLabel.setForeground(Color.white); lastTextLabel.setOpaque(true); lastTextLabel.setBackground(Color.red); lastTextLabel.setForeground(Color.white); this.add(questionLabel); this.add(inputField); this.add(replyLabel); this.add(lastTextLabel);
A second GUI
43
54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68.
this.add(numberTextLabel); this.add(numberOfEsLabel); } public void startAnalysisAndDisplayResult() { String text = inputField.getText(); analysisModel.analyse(text); lastTextLabel.setText(analysisModel.getCurrentText()); int noOfEs = analysisModel.getCurrentNumberOfEs(); numberOfEsLabel.setText(Integer.toString(noOfEs)); inputField.setText(""); } }
Class TextAnalysisFrame generates an instance of TextAnalysisListener and assigns it to the button labelled Analyse.
TextAnalysisListener taList = new TextAnalysisListener(taPanel); analyseButton.addActionListener(taList);
Below are the listings of the class TextAnalysisListener.java and the driver class testanalysisTest.java. Figure 4.3 shows the result.
44
Basics
File: its/TextAnalysisGUI/TextAnalysisListener.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. package its.TextAnalysisGUI; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class TextAnalysisListener implements ActionListener { private TextAnalysisPanel taPanel; public TextAnalysisListener(TextAnalysisPanel t) { taPanel = t; } public void actionPerformed(ActionEvent evt) { taPanel.startAnalysisAndDisplayResult(); } }
File: its/TextAnalysisGUI/TextAnalysisDriver.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. package its.TextAnalysisGUI; public class TextAnalysisDriver { public static void main(String[] args) { TextAnalysisFrame taFrame = new TextAnalysisFrame(); taFrame.showIt("Text analysis"); } }
A second GUI
45
(a)
(b)
Figure 4.4 (a) A legal and (b) an illegal embedding structure. The illegal structure contains a cycle consisting of components B, C and D. The arrow from component B to component A means that B is embedded in A
The important rule to obey with hierarchical embeddings is that they have to be cycle-free. This means that there is no cyclic embedding such as component A into component B, and component B into component C and then component C into component A. Formally, the embedding structure has to form a rooted tree; see also Figure 4.4. A cyclic embedding will result in a runtime error.
Exercises
4.1 Augment the text analysis application in two steps: (1) Add two more rows to the TextAnalysisPanel which display the number of all texts analysed so far and total number of Es seen in these texts. The model supplies this information. (2) Add another button labelled Reset. When this button is pushed the total number of texts and the total number of Es seen are set to 0. This also requires a change of the model part. 4.3 Construct a trafc light GUI. At the bottom there is a button. Clicking the button makes the trafc light go to the next phase, i.e. red, red-yellow, green, yellow, and back to red. Above, the current lights should be shown as colours or text; see Figure 4.5.
RED YELLOW
46
Basics
red
blue
yellow
Displaying a drawing
In this chapter we shall learn how a program can display a drawing in a panel. For now, the drawing is predened and cannot be altered by the user.
To display graphics we use panels as canvases. The whole rectangular area of a panel can be used to draw on, no matter whether it is currently visible or not. The central means for drawing is method paintComponent of class JPanel. This method redraws the panel. It is automatically called by the runtime system when necessary; for example, when the GUI is resized. All the commands to draw something into the panel should therefore be put into that method. More precisely we override paintComponent to add our own commands. It is important never to call paintComponent directly in order to initiate a redrawing. To do that, one calls the repaint()-method of the panel. This is done to avoid conicts between redrawing and other operations of the runtime system. A call to repaint does not immediately repaint a component. Instead it tells the runtime system that one would like the component to be redrawn. The system calls paintComponent after other pending actions have been completed; see also Chapter 20.
It receives a parameter g of type Graphics. This is an abstract class from the AWT library. A Graphics object connects the Java drawing commands to the actual drawing mechanism of the current computer. The class Graphics also provides methods to draw into a panel. As we will never call paintComponent directly, we do not have to construct a Graphics object; this is done by the operating system. To add our own drawings to a panel we extend paintComponent by adding graphics commands. To this end we override paintComponent. Of course we do
48
Basics
not want to lose the original function of paintComponent, namely to draw the panel itself, especially its background. Hence we call the original method using the super command. This has to be the rst command in our implementation of paintComponent. Then our own graphic commands follow. Hence we have the following generic structure:
public void paintComponent(Graphics g) super.paintComponent(g); // Own graphics command // which draws on the panel. } {
We are then sure that rst the empty panel is drawn, and then our drawing is displayed. In particular, any existing old drawing is erased before our drawing is begun.
We explain the commands below; see also Figure 5.1. The coordinates are in pixels in the Java coordinate system. The shapes are drawn in the order in which the graphic commands are executed. If shapes overlap, the one drawn later covers those drawn previously.
drawLine(xstart,ystart,xend,yend) draws a line segment between the points
rectangle. The upper left corner of the rectangle is at (xleft, ytop). It is width pixels wide and height pixels tall.
drawOval(xleft,ytop,width,height) draws the contour of an ellipse. The axes
of the ellipse are parallel to the coordinate axes. The upper left corner of the bounding rectangle of the ellipse is at (xleft, ytop) and is called the reference point. The horizontal axis of the ellipse has length width, the vertical one height.
Displaying a drawing
49
0 0 10 20 30 40 50 60 70
10
20
30
40
50
60
70
80
90
100
20 10 10
20
Figure 5.1 The effect of the commands drawLine(10,20,30,10), drawRect(40,25, 20,10) and drawOval(80,40,10,20) The dotted lines and the dimensions do not ap. pear in the graphic. The bounding rectangle for the oval is shown as a dotted line
drawString(text,xleft,ybottom) draws the string text. The lower left corner
of the bounding rectangle of the text is at (xleft, ybottom). This is the reference point.
fillRect draws a lled rectangle using the current colour, otherwise like drawRect. fillOval draws a lled ellipse using the current colour, otherwise like drawOval. setColor(col) sets the colour of the drawing to col. This colour is used until the setColor command is used again.
50
Basics
File: its/SimpleGraphics/SimpleGraphicsPanel.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. package its.SimpleGraphics; import java.awt.*; import javax.swing.JPanel; public class SimpleGraphicsPanel extends JPanel { public SimpleGraphicsPanel() { this.setBackground(Color.white); this.setPreferredSize(new Dimension(300,300)); } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.black); g.drawLine(10,10,100,100); g.setColor(Color.red); g.drawLine(10,100,100,10); g.setColor(Color.green); g.drawOval(120,60,70,40); g.setColor(Color.yellow); g.fillOval(230,150,30,30); g.setColor(Color.red); g.fillOval(245,150,30,30); g.setColor(Color.black); g.fillOval(238,160,30,30); g.setColor(Color.cyan); g.fillOval(10,120,100,60); g.setColor(this.getBackground()); g.fillOval(50,140,100,60); g.setColor(Color.blue); g.drawString("Swing is nice.",100,200); } }
The next two listings dene class SimpleGraphicsFrame and SimpleGraphicsDriver. In the constructor of SimpleGraphicsFrame a SimpleGraphicsPanel is created and glued into the frames content pane. The pack() command ensures that the frame is packed around the embedded panel and the latter is visible in its full size. Class SimpleGraphicsDriver is the driver class. Figure 5.2 shows the result.
Displaying a drawing
51
Figure 5.2 Result of SimpleGraphicsFrame. The covering of shapes occurs in the order in which the graphic commands are processed
File: its/SimpleGraphics/SimpleGraphicsFrame.java
package its.SimpleGraphics; import its.SimpleFrame.SimpleFrame; import java.awt.BorderLayout; public class SimpleGraphicsFrame extends SimpleFrame { public SimpleGraphicsFrame() { this.setTitle("Simple Graphics"); SimpleGraphicsPanel SGP = new SimpleGraphicsPanel(); this.getContentPane().add(SGP,BorderLayout.CENTER); pack(); } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.
File: its/SimpleGraphics/SimpleGraphicsDriver.java
package its.SimpleGraphics; public class SimpleGraphicsDriver { 1. 2. 3. 4. 5.
52
Basics
6. public SimpleGraphicsDriver() 7. { 8. SimpleGraphicsFrame SGF = new SimpleGraphicsFrame(); 9. SGF.showIt(); 10. } 11. public static void main(String[] args) 12. { 13. SimpleGraphicsDriver sgt = new SimpleGraphicsDriver(); 14. } 15. }
Exercises
5.1 5.2 5.3 Check and explain what happens if one omits the super-command in paintComponent or if it is put at the end of paintComponent. Add further graphic commands to paintComponent. Test what happens if you draw outside the currently visible area. Resize the window. A repaint of the GUI is initiated by the runtime system quite frequently. We would like to see how often this occurs. To this end, dene an integer variable noOfRepaints in SimpleGraphicsPanel and set it to zero in the constructor. Increment this variable in method paintComponent. Then its current value is the number of calls to paintComponent. Add a command to paintComponent which draws the value of noOfRepaints into the panel with some explanation, for example, The number of redraws is. Move and resize the panel to see how many repaints are actually performed.
In this chapter we learn how the mouse is integrated into an application. We shall see how the mouse motion and the use of buttons can be monitored and used by an application.
In this chapter we describe how to use the mouse1 in a Swing component. In previous chapters we used the mouse indirectly as a means of pressing buttons. As buttons can be pressed in other ways (by a nger on a touch screen), these applications reacted to buttons but they did not react to the mouse itself. In order to directly integrate the mouse into an application we have to add a mouse listener to some component. The listener then keeps an eye on the mouse and starts user-dened actions if certain events occur. Such events can be clicking a mouse button, the mouse leaving or entering the component, a change of the mouse position while inside the component, etc. Note that the listener is assigned to a component not to the mouse itself. It tracks the mouse only while it is inside the component. Often one is interested in the mouse buttons or in the mouse motion but not both. Therefore there are two listeners dened by the interfaces MouseListener and MouseMotionListener in the AWT library java.awt.event. The rst is for handling the mouse buttons while the latter monitors the motion. To add a mouse listener to a Swing component comp use
comp.addMouseListener(MouseListener mouseListener); comp.addMouseMotionListener(MouseMotionListener motionListener);
In Chapters 3 and 4 we introduced the ActionListener and the corresponding ActionEvent. The implementation of an ActionListener requires the implementation of only a single method, actionPerformed. Mouse listeners have more methods which correspond to different mouse actions. There is also a special event, the MouseEvent. This is discussed in the following sections.
We use the term mouse also to denote the reference point (hot spot) of the mouse pointer icon.
54
Basics
when a mouse button is clicked2 . To nd out which button was clicked one has to look at the MouseEvent object mevt, see Section 6.3.
mouseEntered(MouseEvent mevt) is automatically called by the runtime system
if a mouse button is pressed. To nd out which button was pressed one has to look at the MouseEvent object mevt, see Section 6.3.
mouseReleased(MouseEvent mevt) is automatically called by the runtime sys-
tem if a mouse button is released. To nd out which button was released one has to look at the MouseEvent object mevt, see Section 6.3. A click results in calling three methods, mousePressed, mouseReleased and
mouseClicked.
55
informs the listener. Depending on whether the mouse was moved or dragged3 , different methods of the listener are called. The event object is passed as an argument to the method. A MouseMotionListener has two methods; both of which have to be implemented:
void mouseMoved(MouseEvent mevt) void mouseDragged(MouseEvent mevt)
while it is in a component that the listener is associated to. The new mouse position can be found by analysing the event object mevt. The mouse is moved if the runtime system receives a signal from the mouse. Usually this results in a change of the position of the mouse pointer on the screen by at least one pixel.
mouseDragged(MouseEvent mevt) is automatically called if the mouse is moved
To nd out which button was used we can use the following methods from class SwingUtilities. They return true if the button appearing in the method name is used, and false otherwise:
boolean SwingUtilities.isLeftMouseButton(MouseEvent me) boolean SwingUtilities.isMiddleMouseButton(MouseEvent me) boolean SwingUtilities.isRightMouseButton(MouseEvent me)
Java does not (yet) support double clicks. One can use method
int getClickCount()
to determine the number of clicks in a short period. The length of this period depends on the platform and can usually be set using utilities of the operating system. Then a double click can be checked by using if(mevt.getClickCount() == 2).
56
Basics
6.4.2
For the implementation we dene the class MouseEventPanel. The panel (now) has no methods of its own; it serves only as a playground for the mouse. It will be the central component in a frame of type MouseEventFrame which we also dene. The mouse listeners will be assigned to this panel. They will then track the mouse while it is in the panel. Below the mouse event panel (South) we add another panel to the frame. This is of type StatusPanel and will be used to display information on the mouse.
57
A StatusPanel has a 3 3 grid layout, containing nine labels. The rst one in every row contains a xed text, e.g. Position, the second and third are used to display varying information, e.g. the current x- and y-coordinates of the mouse. Two labels are dummies placed at unused positions in the grid. A StatusPanel has methods to set the coordinate values, the click count and the information whether the mouse is in or out. Figure 6.2 shows how the components are arranged. The listings of the classes are given below. In MouseEventFrame instances of MyMouseListener and MyMousePositionsListener are created and are both assigned to the MouseEventPanel. This demonstrates that more than one can be assigned to a single component.
JFrame
JPanel
y x = 13 7
53
er in mb nu is e us mo
po
t si
io
n: of
cl
: nt ne po m co ic
ks
ye
27 s
nine JLabels
Figure 6.2 A blueprint for the view part of the mouse application
58
Basics
File: its/MouseEvents/MouseEventFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. package its.MouseEvents; import its.SimpleFrame.SimpleFrame; import java.awt.BorderLayout; public class MouseEventFrame extends SimpleFrame { MouseEventPanel mePanel = new MouseEventPanel(); StatusPanel stPanel = new StatusPanel(); public MouseEventFrame() { this.setTitle("Mouse application"); this.getContentPane().add(mePanel,BorderLayout.CENTER); this.getContentPane().add(stPanel,BorderLayout.SOUTH); pack(); MyMousePositionsListener mPosAdpt = new MyMousePositionsListener(stPanel); mePanel.addMouseMotionListener(mPosAdpt);
19. 20. 21. MyMouseListener MAdpt = new MyMouseListener(stPanel); 22. mePanel.addMouseListener(MAdpt); 23. 24. } 25. }
File: its/MouseEvents/MouseEventPanel.java
1. 2. 3. 4. 5. package its.MouseEvents; import java.awt.Color; import java.awt.Dimension; import javax.swing.JPanel;
6. 7. public class MouseEventPanel extends JPanel 8. { 9. public MouseEventPanel() 10. { 11. this.setBackground(Color.white); 12. this.setPreferredSize(new Dimension(300,300)); 13. } 14. }
59
File: its/MouseEvents/StatusPanel.java
package its.MouseEvents; import java.awt.GridLayout; import javax.swing.JLabel; import javax.swing.JPanel; public class StatusPanel extends { private JLabel posText = new private JLabel XCoord = new private JLabel YCoord = new private JLabel countText = new private JLabel counts = new private JLabel dummy1 = new private JLabel inOutText = new private JLabel inOut = new private JLabel dummy2 = new private int clickCount = 0; JPanel JLabel("position:"); JLabel("0", JLabel.RIGHT); JLabel("0", JLabel.RIGHT); JLabel("no. of clicks"); JLabel("0", JLabel.RIGHT); JLabel(); JLabel("mouse is in comp."); JLabel("no", JLabel.RIGHT); JLabel(); 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44.
public StatusPanel() { this.setLayout(new GridLayout(3,3)); this.add(posText); this.add(XCoord); this.add(YCoord); this.add(countText); this.add(counts); this.add(dummy1); this.add(inOutText); this.add(inOut); this.add(dummy2); } // Updates the displayed coordinates of the mouse position. public void setCoordinates(int x, int y) { XCoord.setText(Integer.toString(x)); YCoord.setText(Integer.toString(y)); } // Sets the information whether the mouse is in or out. public void setInOut(String str) { inOut.setText(str); } // Increments the click count and displays it.
60
Basics
6.4.3
When a mouse button is clicked the click count is increased by one. Whenever the mouse is moved or a button is clicked, the information in the
displayed. To implement the rst function we derive class MyMousePositionsListener from MouseMotionListener. The listener is assigned to the MouseEventPanel. We override method mouseMoved in such a way that it sets the current coordinates in the status panel. In order for the MyMousePositionsListener to have access to the status panel it receives a reference to the panel in the constructor. As the method mouseMoved is executed every time the mouse is moved (in the panel) we always display the current coordinates. Method mouseDragged is implemented with empty body because we have not specied any reaction to dragging. The second, third and fourth functions are realized by a mouse listener, namely the class MyMouseListener derived from MouseListener. There we implement the methods mouseEntered and mouseExited such that they set the text of the inOut label in the status panel and also the coordinates to (1, 1) when exiting MouseEventPanel. For the fourth function, method mouseClicked in MyMouseListener is implemented to call incrementClickCount() of MouseEventPanel. In order for MyMouseListener to have access to the status panel it receives a reference to the panel in the constructor. Methods MousePressed and MouseReleased are not needed and therefore implemented with no functions, i.e. with empty bodies. This completes the application (see Figure 6.3); the listings of the two listener classes and the driver class MouseEventDriver are given below.
61
File: its/MouseEvents/MyMousePositionsListener.java
package its.MouseEvents; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; public class MyMousePositionsListener implements MouseMotionListener { private StatusPanel statusPane; public MyMousePositionsListener(StatusPanel s) { statusPane = s; } public void mouseMoved(MouseEvent evt) { statusPane.setCoordinates(evt.getX(),evt.getY()); } public void mouseDragged(MouseEvent evt) { // implemented with empty body } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24.
62
Basics
File: its/MouseEvents/MyMouseListener.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. package its.MouseEvents; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; public class MyMouseListener implements MouseListener { private StatusPanel statusPane; public MyMouseListener(StatusPanel s) { statusPane = s; } public void mouseEntered(MouseEvent e) { statusPane.setInOut("yes"); } public void mouseExited(MouseEvent e) { statusPane.setInOut("no"); statusPane.setCoordinates(-1,-1); } public void mouseClicked(MouseEvent e) { statusPane.incrementClickCount(); } public void mousePressed(MouseEvent e) { //implemented with empty body } public void mouseReleased(MouseEvent e) { //implemented with empty body } }
63
File: its/MouseEvents/MouseEventDriver.java
package its.MouseEvents; public class MouseEventDriver { public static void main(String[] args) { MouseEventFrame MEF = new MouseEventFrame(); MEF.showIt(); } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
Exercises
6.1 Change the status panel such that instead of the number of all clicks, the number of clicks of the right and left buttons are displayed separately. Augment the mouse listener such that these functions work. Recall that the class SwingUtilites provides methods to check which button was used. Add the following function to the application described in Section 6.4. If the mouse is in the upper left corner of the MouseEventPanel then the colour of this panel changes to yellow. By the upper left corner we mean the x- and y-coordinates of the click position are both less than 50. If the mouse is inside of the MouseEventPanel but outside the upper left corner then the colour is white. Hint: Find out which listeners have to be modied. If necessary, pass more references to the listeners in their constructors. To change the colour of a panel use the following two commands:
this.setBackground(Color.yellow); this.repaint();
6.2
6.3
Remove the status panel from the application and display the information in the MouseEventPanel. Do not embed any components into that panel, use paintComponent.
Interactive graphics
In Chapter 5 we saw how an application can display predened graphics. In Chapter 6 the mouse was embedded into an application. We shall now see how to combine these two things so that one can interactively generate and manipulate graphics.
We extend the application from Chapter 6 by an interactive part. This will enable you to generate and alter a drawing using the mouse. The drawing consists of circles that are added to or removed by mouse clicks. The resulting application has a model, a view and a control part. The model part consists of a class dening a circle and a class dening a data structure which stores the circles currently in the graphic and which also supplies methods to add a circle or remove one. The view part is mostly like the application in Chapter 6. The class MouseEventPanel of Chapter 6 is augmented by overriding its paintComponent method as described in Chapter 5. The control part is implemented by extending the listeners from Chapter 6.
sition is removed, but only if the distance is less than 30 pixels. Distances are in the Euclidean metric of R2 . If more than one circle has the same distance to the click position, choose an arbitrary one.
The status panel now also displays the current number of circles.
Interactive graphics
65
7.2.1
Circles
Class Circle has three double elds, x, y and radius, which contain the coordinates of the centre of the circle and its radius, respectively. Following the object-oriented paradigm of Java, only the class Circle knows how to draw a circle. Therefore, Circle provides a method draw which contains the graphics commands to draw the circle. These commands need a graphics reference (Graphics) which is given as an argument to draw. Note that the draw-method of class Circle actually is a view part method. Thus, the distinction of view and model part in this case is not only between classes but also inside a single class: some methods handle view aspects and others model aspects. The data on the centre and radius of the circle are stored as double; the draw method, however, needs integers. We convert double to integers by simply rounding to the nearest integer. We shall learn about a better and more exible way of converting to pixel coordinates in Section 13.3. The fillOval-method expects the upper right corner of the bounding box and the width and height of the box as parameters. The coordinates are found by subtracting the radius from the coordinates of the centre of the circle. By multiplying the radius by two we nd the width and height. To keep the code short we do not check whether the values are plausible, e.g. that the radius or the pixel coordinates are non-negative. Class Circle also supplies the method distance(double x1, double y1) which computes the Euclidean distance between the circles centre and the point (x1 , y1 ). This method is used to determine which circle is closest to the location of a mouse click and has to be deleted. File: its/InteractiveGraphic/Circle.java
package its.InteractiveGraphic; import java.awt.Graphics; public class Circle { 1. 2. 3. 4. 5. 6. 7.
66
Basics
8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. }
private double x,y,radius; public Circle(double xx, double yy, double rad) { x = xx; y = yy; radius = rad; } // Draws the circle public void draw(Graphics g) { g.fillOval((int)Math.round(x-radius),(int)Math.round(y-radius), (int)Math.round(2.0*radius),(int)Math.round(2.0*radius)); } // Computes the distance between the circles // centre and the point (x1,y1). public double distanceTo(double x1, double y1) { return(Math.sqrt(Math.pow(x-x1,2)+Math.pow(y-y1,2) )); }
7.2.2
Class CircleAdministration does most of the work. The heart of it is an object of type Vector. This is a class predened in Java; one can think of it as an array with exible length. Vectors can store arbitrary objects. If a vector contains n objects then they are indexed from 0 to n 1. The vector circles used in CircleAdministration stores the Circle objects currently in our graphic. To access the ith object one uses the method get(i). The method returns an Object and thus one has to cast it to the correct data type, a Circle in our case1 . The syntax for this is:
(Circle)(circles.get(i))
Besides the constructor, the class CircleAdministration has the following methods:
void addCircle(Circle circ) void removeNearestCircle(int x1, int y1) void drawAll(Graphics g) int getNoOfCircles()
Version 1.5 of the Java SDK (software development kit) allows typed vectors to be dened. These can only store objects from a single class, e.g. only Circles. This avoids casting.
Interactive graphics addCircle(circ) adds circle circ to vector circles. removeNearestCircle(x1,y1) determines a circle stored in vector circles
67
whose centre has minimum distance to the point (x, y). If the distance is at most 30 (pixel), then the circle is removed from circles.
drawAll(g) draws all circles currently stored in circles. This is done by calling the draw-method of each of them. The Graphics object g is passed on. getNoOfCircles() returns the number of circles currently stored in vector circles.
In method removeNearestCircle(x1,y1) the circle to be removed is found by computing the distances of the point (x1 , y1 ) to the centre of all circles. This is not the most effective way of doing this but we choose it to keep the code short. File: its/InteractiveGraphic/CircleAdministration.java
package its.InteractiveGraphic; import java.awt.Graphics; import java.util.Vector; public class CircleAdministration { private Vector circles; public CircleAdministration() { circles = new Vector(); } public void addCircle(Circle circ) { circles.add(circ); } public void removeNearestCircle(int x1, int y1) { Circle circ; double minDist = Double.MAX_VALUE; int minDistIndex = -1; for (int i=0 ; i < circles.size() ; i++) { circ = (Circle)(circles.get(i)); if(circ.distanceTo(x1,y1) < minDist) 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28.
68
Basics
29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. }
{ minDist = circ.distanceTo(x1,y1); minDistIndex = i; }// if }//for i if ((minDistIndex >= 0) && (minDist < 30)) { circles.removeElementAt(minDistIndex); }//if }//method public void drawAll(Graphics g) { Circle currentCircle; for (int i=0 ; i < circles.size() ; i++) { currentCircle = (Circle)(circles.get(i)); currentCircle.draw(g); }//for i }//method public int getNoOfCircles(){ return(circles.size()); }//method
Interactive graphics
69
File: its/InteractiveGraphic/InteractivePanel.java
package its.InteractiveGraphic; import import import import java.awt.Color; java.awt.Dimension; java.awt.Graphics; javax.swing.JPanel; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43.
public class InteractivePanel extends JPanel { private CircleAdministration circleAdm; private double radius = 6.0; public InteractivePanel() { circleAdm = new CircleAdministration(); this.setBackground(Color.white); this.setPreferredSize(new Dimension(300,300)); } public void paintComponent(Graphics g) { super.paintComponent(g); circleAdm.drawAll(g); } public void addCircle(int x, int y) { circleAdm.addCircle(new Circle(x,y,radius)); repaint(); } public void removeNearestCircle(int x, int y) { circleAdm.removeNearestCircle(x,y); repaint(); } public int getNoOfCircles() { return(circleAdm.getNoOfCircles()); } }
70
Basics
The status panel has to display the number circles. We change class StatusPanel from Chapter 6 as follows. We now use a 4 3 grid layout and three more labels. The additional labels contain the text No. of circles, the number of circles, and a dummy to ll the grid. We add a method setNoOfCircles(int n) to set the number of circles. File: its/InteractiveGraphic/StatusPanel.java
1. package its.InteractiveGraphic; 2. 3. import java.awt.GridLayout; 4. import javax.swing.JLabel; 5. import javax.swing.JPanel; 6. 7. public class StatusPanel extends JPanel 8. { 9. private JLabel posText = new JLabel("position:"); 10. private JLabel XCoord = new JLabel("0", JLabel.RIGHT); 11. private JLabel YCoord = new JLabel("0", JLabel.RIGHT); 12. private JLabel countText = new JLabel("no. of clicks"); 13. private JLabel counts = new JLabel("0", JLabel.RIGHT); 14. private JLabel dummy1 = new JLabel(); 15. private JLabel circleText = new JLabel("no. of circles"); 16. private JLabel noOfCirc = new JLabel("0", JLabel.RIGHT); 17. private JLabel dummy2 = new JLabel(); 18. private JLabel inOutText = new JLabel("mouse is in comp."); 19. private JLabel inOut = new JLabel("no", JLabel.RIGHT); 20. private JLabel dummy3 = new JLabel(); 21. 22. private int clickCount = 0; 23. 24. public StatusPanel() { 25. this.setLayout(new GridLayout(4,3)); 26. this.add(posText); 27. this.add(XCoord); 28. this.add(YCoord); 29. this.add(countText); 30. this.add(counts); 31. this.add(dummy1); 32. this.add(circleText); 33. this.add(noOfCirc); 34. this.add(dummy2); 35. this.add(inOutText); 36. this.add(inOut); 37. this.add(dummy3); 38. } 39.
Interactive graphics
71
40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57.
public void setCoordinates(int x, int y){ XCoord.setText(""+x); YCoord.setText(""+y); } public void setInOut(String str){ inOut.setText(str); } public void incrementClickCount(){ clickCount++; counts.setText(Integer.toString(clickCount)); } public void setNoOfCircles(int n){ noOfCirc.setText(Integer.toString(n)); } }
The texts in brackets have to be replaced by the appropriate variable names without brackets. In order for the listener to have access to that panel the constructor of MyMouseListener now receives a reference to the interactive panel.
File: its/InteractiveGraphic/MyMouseListener.java
package its.InteractiveGraphic; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.SwingUtilities; 1. 2. 3. 4. 5. 6.
72
7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52.
Basics
public class MyMouseListener implements MouseListener { private StatusPanel statusPane; private InteractivePanel interactivePane; public MyMouseListener(StatusPanel s,InteractivePanel i) { statusPane = s; interactivePane = i; } public void mouseEntered(MouseEvent e) { statusPane.setInOut("yes"); } public void mouseExited(MouseEvent e) { statusPane.setInOut("no"); statusPane.setCoordinates(-1,-1); } public void mouseClicked(MouseEvent e) { statusPane.incrementClickCount(); if(SwingUtilities.isLeftMouseButton(e)) //left mouse button { interactivePane.addCircle(e.getX(),e.getY()); } else // any other mouse button { interactivePane.removeNearestCircle(e.getX(),e.getY()); } statusPane.setNoOfCircles(interactivePane.getNoOfCircles()); } public void mousePressed(MouseEvent e) { //implemented with empty body } public void mouseReleased(MouseEvent e) { //implemented with empty body } }
Interactive graphics
73
Figure 7.1 The interactive application. Only 14 circles are shown, rather than the 15 given on the status panel, as 2 occupy the same location
74
Basics
Since version 1.4 of the Java SDK (software development kit) there is a third listener for the mouse: a MouseWheelListener keeps tracks of the mouse wheel and is activated every time the mouse wheel is moved.
7.6.2
Let us again summarize the implementation of the modelviewcontrol concept of this application. In this example the class CircleAdministration does all the work to keep the abstract data up to date. The circles are stored in CircleAdministration and this class deletes or adds circles. Only this class knows how many circles there are. The class InteractivePanel, on the other hand, does the work to display the graphics. It does not know about the circles. In order to draw the graphic it has to consult CircleAdministration. It does so by calling the drawAll method of CircleAdministration. Also, if the listener associated with InteractivePanel detects a click, InteractivePanel only knows whether to add or delete a circle and the position. It then passes the work to CircleAdministration by calling the addCircle (or removeNearestCircle) method. After an update (add or delete) has been completed by CircleAdministration, InteractivePanel updates the display. To this end it calls its method repaint, which causes the runtime system to call paintComponent at an appropriate time.
Exercises
7.1 Change the application as follows. Add a eld color to class Circle as an instance of java.awt.Color. Dene a second constructor for Circle which allows it to pass a colour in addition. Then modify CircleAdministration in such a way that the circles in turn receive the colours red, blue, green, red, etc. Modify the application in the following way. Add a line to the status panel which displays the coordinates of the centre of that circle that is currently closest to the mouse pointer. Note that this might change with every move of the mouse. Make sure that the application can handle the situation where no circle is present. Change the application to display line segments instead of circles. A line is added by two consecutive clicks of the left mouse button. The location of the rst click is one end point of the line, the location of the second click is the other end point. A click of the right button removes a line nearest to the click location. There are various ways to dene nearest, for example one can take the distance to the rst end point, to the middle of the line or the geometrical distance.
7.2
7.3
Menus
In this chapter we learn how to add menus to a frame and how to integrate them into our application.
Menus are most often used as subcomponents of frames. They are hierarchically organized. The topmost component is the menu bar. This is a rectangular area located below the title bar of a frame and above the content pane. The intermediate components are the menus. A menu appears as text in the menu bar. When one clicks on such a text the actual menu will roll out. It contains the menu items, which are lowest in the hierarchy. Figure 8.1 illustrates the structure. The menu items are the components that trigger actions. They can be considered as a special kind of button. In fact, like buttons, one uses an action listener to monitor them. It sometimes is necessary to disable certain menu items for some of the time. A disabled menu item does not trigger any action when clicked on. On most platforms the text of a disabled menu item is shown lighter than that of enabled ones. One situation where disabling a menu item makes sense is the following: assume data are loaded from a le, and a menu item is then clicked on to start extensive calculations on the data. It would make no sense to repeat these extensive calculations on the same data if the result is always the same. Thus one disables the menu item that starts the calculations. It is enabled again only when new data are present. We shall now implement a small GUI that demonstrates the use of menus in Swing. As it does not handle any other data we do not implement a model part.
76
Basics
Menu1
contentPane
Figure 8.1 The structure of an application with menus. The second menu is rolled out and its entries can be selected
8.2.1
Menu bars
From JMenuBar we only need the constructor and the method to add a menu:
JMenuBar() add(JMenu menu)
Menus are added to the menu bar from left to right in the order of the add commands. To add a menu bar to a frame we use the following method from class JFrame:
setJMenuBar(JMenuBar menuBar)
Note that there are distinct methods setJMenuBar and setMenuBar for adding a JMenuBar (Swing) and a MenuBar (AWT).
8.2.2
Menus
From class JMenu we need the constructor which receives the menu title in a string and methods to add menu items or separators (horizontal lines) to the menu:
JMenu(String menuTitle) add(JMenuItem menuItem) addSeparator()
Menus
77
In a menu, the items or separators are added from top to bottom in the order of the add commands.
8.2.3
Menu items
From JMenuItem we need the constructor which receives the item title in a string and a method to assign an action listener to the menu item. Menu items behave much like buttons. In particular, the action listener is automatically informed when the item is clicked on. The listener then starts the desired actions. Finally we need a method to enable or disable a menu item:
JMenuItem(String itemText) addActionListener(ActionListener listener) setEnabled(boolean b)
The call setEnabled(false) disables a menu item. The item appears with a lighter text and any listener assigned to it is no longer informed when the item is clicked on. Hence, a disabled item does not trigger any reaction in the application. Calling setEnabled(true) will enable the item again. Its text becomes darker and listeners assigned to the item are again informed of events.
8.2.4
We are now able to implement the GUI. The listings below show the code for the frame class MenuFrame and the driver class MenuDriver. A MenuFrame has a label centrally placed in its content pane. A menu bar is created and added. Then the two menus are created and added. Finally we generate the menu items and add them to the respective menus. Finally an action listener is created and assigned to all menu items. This listener is an instance of the class MenuListener which we dene later in Section 8.3. The frame class also has three public methods for setting the text in the label, and enabling or disabling the menu item Test in the rst menu:
setText(String text) enableTest() disableTest()
File: its/Menus/MenuFrame.java
package its.Menus; import its.SimpleFrame.SimpleFrame; import javax.swing.*; import java.awt.BorderLayout; public class MenuFrame extends SimpleFrame { 1. 2. 3. 4. 5. 6. 7. 8.
78
9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54.
Basics
private JLabel display; private JMenuItem testItem; public MenuFrame() { display = new JLabel("No menu selected.",JLabel.CENTER); // Create a menu bar and add it to the frame JMenuBar menubar = new JMenuBar(); this.setJMenuBar(menubar); // Create and add the menus JMenu firstMenu = new JMenu("Menu 1"); JMenu secondMenu = new JMenu("Menu 2"); menubar.add(firstMenu); menubar.add(secondMenu); // Create the menu items and add them to the menus JMenuItem firstItem = new JMenuItem("Item 1"); testItem = new JMenuItem("Test"); JMenuItem exitItem = new JMenuItem("Exit"); JMenuItem enableItem = new JMenuItem("Enable Test"); JMenuItem disableItem = new JMenuItem("Disable Test"); firstMenu.add(firstItem); firstMenu.add(testItem); firstMenu.addSeparator(); firstMenu.add(exitItem); secondMenu.add(enableItem); secondMenu.add(disableItem); // Create a listener and add it to the menu items MenuListener menuList = new MenuListener(this); firstItem.addActionListener(menuList); testItem.addActionListener(menuList); exitItem.addActionListener(menuList); enableItem.addActionListener(menuList); disableItem.addActionListener(menuList); // Add the label to the frame this.getContentPane().add(display,BorderLayout.CENTER); } // Method to set the text in the label public void setText(String text){
Menus
79
55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67.
display.setText(text); } // Method to enable the label public void enableTest(){ testItem.setEnabled(true); } // Method to disable the label public void disableTest(){ testItem.setEnabled(false); } }
File: its/Menus/MenuDriver.java
package its.Menus; public class MenuDriver { public static void main(String[] args) { MenuFrame mf = new MenuFrame(); mf.showIt(); } } 1. 2. 3. 4. 5. 6. 7. 8.
80
Basics
Figure 8.2 The menu application. The rst menu is rolled out and its second item, test, has been disabled
File: its/Menus/MenuListener.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. package its.Menus; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class MenuListener implements ActionListener { private MenuFrame menuFrame; public MenuListener(MenuFrame mf) { menuFrame = mf; } public void actionPerformed(ActionEvent evt) { String actionCommand = evt.getActionCommand(); if(actionCommand.equals("Item 1")){ menuFrame.setText("Item 1 selected"); } else if(actionCommand.equals("Test")){ menuFrame.setText("Item Test selected"); } else if(actionCommand.equals("Exit")){ System.exit(0); } else if(actionCommand.equals("Enable Test")){ menuFrame.enableTest(); menuFrame.setText("Item \"Test\" in Menu 1 enabled."); } else if(actionCommand.equals("Disable Test")){
Menus
81
30. 31. 32. 33. 34. 35. 36. 37.
menuFrame.disableTest(); menuFrame.setText("Item \"Test\" in Menu 1 disabled."); } else{ System.out.println("ERROR: unknown action command."); } } }
Exercise
8.1 Construct a GUI consisting of a frame with a menu and an embedded panel. The menu has three items Red, Blue and Yellow. When a menu item is selected, the background colour of the panel is changed accordingly.
More on listeners
In this chapter we discuss how to use listeners in general. We also introduce some more listeners that react to events not considered so far. This chapter can be skipped in a rst reading.
for doing this, where XXXXXX is the type of the listener, e.g. MouseMotion. All listeners are interfaces. They require certain methods to be implemented by the programmer, e.g. mouseMoved. These methods are then automatically called by the operating system if the related event occurs, e.g. the mouse is moved. The code for the applications reaction to an event therefore has to be inside these methods. Some listeners have only one method, such as actionPerformed in ActionListeners, while others have more, like the ve methods of a MouseListener. Every one of them reacts to a different event. If one only wants a reaction to some events, then the other methods are implemented with an empty body. The methods of listener receive an event object as an argument. This event object contains more information about the event. Different listeners have different kinds of event objects (ActionEvent for ActionListeners, MouseEvent for MouseListeners). The event object is automatically generated by the runtime system when an event occurs and the appropriate method of the listener is called. The information in the various types of events varies. A MouseEvent contains the coordinates at which the mouse pointer was when the event occurred. For an
More on listeners ActionEvent this does not make sense. It is not of importance where a button was pressed. An ActionEvent on the other hand contains information on which
83
button had been pressed. The different event types supply methods to access the information contained in the event, e.g. getX() and getY() allow one to get the coordinates of a MouseEvent, and getActionCommand() allows one to nd the source of an action event.
9.2.1
In the counter example we dened the listener in a separate class CounterListener. The listener is assigned to two buttons and has to take actions when one of them is pressed. The reaction was to update the CounterPanel. The counter listener has to know which panel to update. Therefore we pass a reference to a
CounterPanel in the constructor of the listener. Alternatively we can dene the listener as an internal class inside CounterPanel. Then the methods of CounterPanel are known to the listener and can be called directly, i.e. the listener can use decrement() instead of countPane.decrement(). Therefore, no reference to a
panel is passed to the listener. The constructor of the listener has an empty body. In fact, it can be omitted altogether; then the default constructor of ActionListener takes over. This is implemented in the package its.CounterInternalGUI, where the class CounterInternalPanel with an internally dened listener replaces both CounterPanel and CounterListener. We only print the listing of this class. File: its/CounterInternalGUI/CounterInternalPanel.java
package its.CounterInternalGUI; import import import import import import import javax.swing.JPanel; javax.swing.JButton; javax.swing.JLabel; java.awt.BorderLayout; javax.swing.SwingConstants; java.awt.event.ActionListener; java.awt.event.ActionEvent; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13.
84
14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60.
Basics
private JLabel valueLabel; public CounterInternalPanel() { counter = new CounterModel(); BorderLayout bordLay = new BorderLayout(); this.setLayout(bordLay); JButton upButton = new JButton("Up"); JButton downButton = new JButton("Down"); valueLabel = new JLabel(""+counter.getValue(),SwingConstants.CENTER); this.add(upButton,BorderLayout.WEST); this.add(downButton,BorderLayout.EAST); this.add(valueLabel,BorderLayout.CENTER); // Definition of the listener, no reference // to this is passed on. InternalCounterListener countList = new InternalCounterListener(); upButton.addActionListener(countList); downButton.addActionListener(countList); } public void increment(){ counter.increment(); valueLabel.setText(""+counter.getValue()); } public void decrement(){ counter.decrement(); valueLabel.setText(""+counter.getValue()); } // Internal Listener Class class InternalCounterListener implements ActionListener{ //The constructor is empty public InternalCounterListener() { } public void actionPerformed(ActionEvent evt){ String actionCommand = evt.getActionCommand(); if(actionCommand.equals("Up")){ increment(); } else if(actionCommand.equals("Down")){
More on listeners
85
61. 62. 63. 64. 65. 66. 67. 68. 69.
9.2.2
In this section we will learn how to implement a listener as an interface inside another class. For users with no or very limited experience in using interfaces we give a very short survey about the interface mechanism in Java. In Java a class can extend only one other class and inherit its properties. For example, in the itspackage class CounterFrame extends SimpleFrame and thus inherits the property of terminating the application when the frame is closed. Sometimes one would like a class to inherit the properties of more than one other class. The concept of interfaces allows this to a certain extent. A class can extend only one other class but it can implement more than one interface. An interface requires certain methods to be implemented. If a class NewClass is derived from OldClass and implements the interfaces IF1 and IF2 then the syntax for this is:
class NewClass extends OldClass implements IF1, IF2
Inside the class NewClass we then have to implement the methods required by interfaces IF1 and IF2. In our example we dene class CounterInterfacePanel which we would like to inherit the properties of a JPanel and of an ActionListener. To this end we let CounterInterfacePanel extend JPanel and implement ActionListener.
class CounterInterfacePanel extends JPanel implements ActionListener
The interface ActionListener requires method actionPerformed(ActionEvent evt) to be implemented inside CounterPanel (we get an error message from the compiler if we do not do this). After implementing actionPerformed in Line 50, the class CounterInterfacePanel also has the properties of an action listener, it has become an action listener itself. Therefore, an instance of CounterInterfacePanel is assigned as an action listener to the buttons. Recall that the keyword this references the current instance.
upButton.addActionListener(this); downButton.addActionListener(this);
We list the modied code for the counter panel. The class name is CounterInterfacePanel and it is located in the sub-package CounterInterfaceGUI.
86
Basics
File: its/CounterInterfaceGUI/CounterInterfacePanel.Java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. package its.CounterInterfaceGUI; import import import import import import import javax.swing.JPanel; javax.swing.JButton; javax.swing.JLabel; java.awt.BorderLayout; javax.swing.SwingConstants; java.awt.event.ActionListener; java.awt.event.ActionEvent;
public class CounterInterfacePanel extends JPanel implements ActionListener{ private CounterModel counter; private JLabel valueLabel; public CounterInterfacePanel() { counter = new CounterModel(); BorderLayout bordLay = new BorderLayout(); this.setLayout(bordLay); JButton upButton = new JButton("Up"); JButton downButton = new JButton("Down"); valueLabel = new JLabel(""+counter.getValue(),SwingConstants.CENTER); this.add(upButton,BorderLayout.WEST); this.add(downButton,BorderLayout.EAST); this.add(valueLabel,BorderLayout.CENTER); // A CounterInterfacePanel is now // also an ActionListener. Therefore, it // is assigned to the buttons using this. upButton.addActionListener(this); downButton.addActionListener(this); } public void increment(){ counter.increment(); valueLabel.setText(""+counter.getValue()); } public void decrement(){ counter.decrement();
More on listeners
87
45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63.
valueLabel.setText(""+counter.getValue()); } // Method actionPerformed is implemented // inside the class CounterPanel. public void actionPerformed(ActionEvent evt){ String actionCommand = evt.getActionCommand(); if(actionCommand.equals("Up")){ increment(); } else if(actionCommand.equals("Down")){ decrement(); } else{ System.out.println("ERROR: Unexpected ActionCommand"); } }// actionPerformed }
9.2.3
Anonymous listeners
Listeners can also be dened anonymous and on-the-y. The syntax for assigning an anonymous listener to a component comp looks like this:
comp.addXXXXXXListener([Whole definition of XXXXXXListener goes here.]);
In our example, we have to implement one listener for each button. The anonymous implementation of the listener for the Up button then looks like this:
upButton.addActionListener( // Implementation of the listener begins new ActionListener(){ // Implementation of required method begins public void actionPerformed (ActionEvent evt){ increment(); } // End of implementation of actionPerformed }// End of implementation of ActionListener );//End of method addActionListener
The listener is anonymous because it does not receive a name. It is on-the-y because it is placed where the listener is assigned to the button, namely inside the parentheses of method addActionListener. As the listener has no name, it cannot be referenced from anywhere. In particular, it cannot be reused for the Down button. Therefore we must dene another listener for that button, which we again do as an anonymous listener.
88
Basics
As an anonymous listener is responsible for only a single button, it gets informed only when this specic button is pressed. There is no need to check which button it was. Therefore, we can immediately call methods increment and decrement for the Up respectively Down button. For other listeners, however, there still can be different events being triggered by the same component, e.g. for a mouse listener, one might have to check which mouse button has been used. The use of anonymous listeners is demonstrated in class CounterAnonymousPanel.
File: its/CounterAnonymousGUI/CounterAnonymousPanel.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. package its.CounterAnonymousGUI; import import import import java.awt.BorderLayout; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.*;
public class CounterAnonymousPanel extends JPanel{ private CounterModel counter; private JLabel valueLabel; public CounterAnonymousPanel() { counter = new CounterModel(); BorderLayout bordLay = new BorderLayout(); this.setLayout(bordLay); JButton upButton = new JButton("Up"); JButton downButton = new JButton("Down"); valueLabel = new JLabel(""+counter.getValue(),SwingConstants.CENTER); this.add(upButton,BorderLayout.WEST); this.add(downButton,BorderLayout.EAST); this.add(valueLabel,BorderLayout.CENTER); // The listener for the up-button is defined // anonymous and on the fly. upButton.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent evt){ increment(); }// actionPerformed }//ActionListener );//addActionListener // The listener for the down-button is defined
More on listeners
89
37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57.
// anonymous and on the fly. downButton.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent evt){ decrement(); }// actionPerformed }//ActionListener );//addActionListener } public void increment(){ counter.increment(); valueLabel.setText(""+counter.getValue()); } public void decrement(){ counter.decrement(); valueLabel.setText(""+counter.getValue()); }
9.2.4
We have seen four ways to implement a listener: in a separate class (Section 3.4), in an internal class (Section 9.2.1), as an interface (Section 9.2.2) or as an anonymous object (Section 9.2.3). The rst method is especially suited to beginners with little experience of using interfaces. It also makes sense if the listener has to manipulate objects from many classes. Then references to all these classes are passed to the listener in the constructor, so the listener can access them. The second method allows the listener to directly access methods and variables of the class it is dened in. This is suited for applications where the listener has to manipulate objects from only one class. The third method completely avoids having to dene a listener class of its own. This is considered a more elegant and efcient way by many authors. For beginners in graphics programming, however, it is often confusing, and a more structured approach with separate classes is preferred. It also limits the listeners range to the class that implements it. The last method, anonymous listeners, assigns an individual listener to every component. This saves the trouble of checking which component triggered the listener. Also this method is not easy for beginners to understand. Let us remark that philosophical wars are fought about what is the best way to implement listeners and to introduce them to beginners in graphics programming. The authors opinion on teaching listeners clearly is to use separate classes in the beginning. This is based on experience. Starting out with the method described in Section 9.2.2 or 9.2.3 only makes sense if students are experienced with using
90
Basics
interfaces or anonymous denitions. If not, this approach causes confusion (But, then where is my listener after all?). In the three modications of CounterPanel presented in this section, one can also avoid dening the methods increment and decrement. Instead one can transfer the respective code to the appropriate places in the listener.
9.2.5
Listeners require the implementation of all their methods. Often one is interested in only one of them and the rest are implemented with empty bodies. To save some work, Java supplies classes that implement all the listener interface methods with empty bodies. The name for such a class is the name of the corresponding listener interface with Listener replaced by Adapter, e.g. the class implementing a MouseListener is called a MouseAdapter. Adapters are present for most listeners that require more than one method to be implemented. Then, instead of implementing all methods of the listener interface one only has to override the desired method of the corresponding adapter class.
to immediately exit the application when the close button is pressed. Often one would like to perform some clean-up or rescue work before termination, such as saving changed data to the disk. We dene class CleanUpFrame which denes a method cleanUp to perform this work. In our example it just writes a message to the console and then exits the application. CleanUpFrame uses a window listener to activate this method. As we use only one of the seven methods of interface WindowListener we actually use the corresponding adapter class WindowAdapter and thus avoid implementing six methods. We implement the class as an internal class CleanUpAdapter. It implements method windowClosing(WindowEvent we) which is automatically called by the runtime system when the close button is pressed. Method windowClosing then calls the cleanUp method of the frame. In the constructor of CleanUpFrame an instance of CleanUpAdapter is created and assigned to the frame.
More on listeners
91
File: its/CleanUpFrame/CleanUpFrame.java
package its.CleanUpFrame; import javax.swing.JFrame; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; public class CleanUpFrame extends JFrame { public CleanUpFrame() { this.setSize(200,200); this.setLocation(200,200); this.setTitle("CleanUpFrame"); CleanUpAdapter cleaner = new CleanUpAdapter(); this.addWindowListener(cleaner); } public void showIt(){ this.setVisible(true); } public void hideIt(){ this.setVisible(false); } // Performs the clean up work before terminating the application. private static void cleanUp(){ // Real code for the clean up goes here. System.out.println("Cleaning up and then terminating the program."); System.exit(0); } // Internal class private class CleanUpAdapter extends WindowAdapter{ public void windowClosing(WindowEvent we) { // Call the cleanUp method of the frame. cleanUp(); } }// internal class } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41.
92
Basics
Exercises
9.1 Modify the text analysis GUI from Chapter 4 by implementing the listener as in internal class as described in Section 9.2.1. Decide into which class it should be implemented. Modify the text analysis GUI from Chapter 4 by implementing the listener as a part of some other class as described in Section 9.2.1. Decide which class that should be.
9.2
10
In this chapter we begin to describe how graphical interfaces are used to display and manipulate text. We introduce the methods for reading and writing text les and for displaying text.
94
Basics
We now present the most important classes for reading and writing ASCII text les and some of their methods.
10.1.1
The class File is the Java abstraction of a le or directory on the hard disk or some other storage medium; we shall use the term hard disk in the following. Below we list a constructor and some methods we need:
File(String filename) boolean boolean boolean boolean boolean String String exists() delete(); renameTo(File newName) createNewFile() isDirectory() getName() getPath()
that neither an existing le with that name is opened nor is a le created on the hard disk.
exists() returns true if the le exists. delete() deletes the le from the hard disk and returns true if successful or false otherwise, e.g. if the le did not exist. renameTo(File newName) renames the le. The new name is that of the le object newName. createNewFile() creates a le on the hard disk. The le name is that specied
in the constructor, but only if the le does not already exist. If successful it returns true and false otherwise.
isDirectory() returns true if the le is a directory and false otherwise. getName() returns just the le name, without the path but with extension. getPath() returns the whole path including le name and extension.
Note that under MS Windows one can use both / or \ as a separator. Because of the special meaning of backslash in Java strings it has to be duplicated.
95
10.1.2
The classes FileReader and FileWriter supply methods for reading and writing ASCII les. These are convenience classes as they make it easy to access les. The programmer does not have to dene a stream explicitly. We start by looking at a constructor and some methods of class FileReader:
FileReader(File aFile) void close() int read() int read(char[] buffer,int start,int length)
FileReader(File aFile) opens the le aFile for reading. All further methods
code).
read(buffer,start,length) reads length and puts the characters into the char-array buffer starting at position start of the buffer. Returns the number
of characters read or 1 if the le end is reached. Most constructors and methods of java.io throw exceptions if errors occur, mostly IOExceptions. Thus they have to be embedded into try-catch blocks or the exceptions have to be thrown further. Class FileWriter supplies methods for writing an ASCII le:
FileWriter(File aFile) void void void void close() write(char[] buffer) write(char[] buffer,int start,int length) write(String str, int start, int length)
the le is already present the old content is lost! All further methods refer to this le.
close() stops the le writer and closes the le. It is important not to forget this
! !
96
Basics write(str,start,length) writes the number of characters equivalent to the current value of length of string str starting at position start of the string.
10.1.3
The program FileReadWrite is given below and demonstrates the methods described above. It reads some characters from the le testtext1.txt and stores them in a buffer (char-array) for potential further use. The buffer is initialized with dashes (-), so that the unused parts are visible when it is printed. The buffer is written to the standard output (screen). After this, a part of the buffer is written to another le testtext2.txt. If this le already exists, it is rst erased. The constant path has to be changed if the directory structure is different from the one in the downloaded les. The path name has to end with a slash (/) or two backslashes. Below we list the content of le testtext1.txt on the left and that of testtext2.txt on the right:
Line 1 Line 2 Line 3 Line 4 Line 5 Line 6 Line 7 Line 8 Line 9 The last line ne 1 Line 2 Line 3 Line 4 Line
When the buffer is printed, one can see that the line breaks appear as in the le because a FileReader reads the line break characters (\n).
File: its/TextIO/FileReadWrite.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. package its.TextIO; import java.io.File; import java.io.FileReader; import java.io.FileWriter; public class FileReadWrite { // This variable has to be set according to your system public static String path = "./its/TestData/"; public FileReadWrite(String ReadFileName,String WriteFileName) {
97
14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60.
//Buffer for the text char[] buffer = new char[128]; for (int i = 0; i < buffer.length; i++) { buffer[i] = '-'; } // Create two File-variables File readfile = new File(path+ReadFileName); File writefile = new File(path+WriteFileName); //READING: if (readfile.exists()) { try { // Create a reader. FileReader fr = new FileReader(readfile); // Read the file and store 100 characters in the // buffer starting at position 5 of the buffer. fr.read(buffer,5, 100); // close file fr.close(); } catch (Exception ex) { System.out.println("Problem opening file "+readfile.getName()); } }//if else { System.out.println("File not found "+readfile.getName()); } System.out.println("Buffer>"+buffer+"<Buffer"); //WRITING: try { if (writefile.exists()) { writefile.delete(); } if (writefile.createNewFile()) { // create a writer FileWriter fw = new FileWriter(writefile);
98
Basics
61. 62. // writes 40 characters from the buffer 63. // starting at position 7 of the buffer 64. 65. fw.write(buffer,7,40); 66. 67. // close file 68. fw.close(); 69. 70. }//if 71. else 72. { 73. System.out.println("File not created "+writefile.getName()); 74. } 75. } 76. catch (Exception ex) 77. { 78. System.out.println("Problem opening file "+writefile.getName()); 79. } 80. } 81. 82. public static void main(String[] args) 83. { 84. FileReadWrite RWF = new FileReadWrite("testtext1.txt","testtext2.txt"); 85. } 86. }
10.1.4
Sometimes one would like to access a text le line by line. To do this another reader is used, a BufferedReader. We list the constructor and some methods:
BufferedReader(FilerReader fReader) close() String readLine()
reached. The line-end characters are not returned. The following program LineRead reads a le line-wise. The while loop is terminated when the string read is null, i.e., when the le end is reached.
99
File: its/TextIO/LineRead.java
package its.TextIO; import java.io.*; public class LineRead { // This variable has to be set according to your system public static String path = "./its/TestData/"; public LineRead(String ReadFileName) { File readfile = new File(path+ReadFileName); String line; try { BufferedReader bfr = new BufferedReader(new FileReader(readfile)); while((line = bfr.readLine()) != null) { System.out.println("READ>"+line+"<"); } } catch (Exception ex) { System.out.println("Problem opening file "+readfile.getName()); } } public static void main(String[] args) { LineRead LR = new LineRead("testtext1.txt"); } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31.
100
Basics JEditorPane() constructs an editor pane. read(Reader myReader, Object description) reads the text supplied by myReader and displays it in the editor area. In the second argument description further information on the type of text can be given; we do not use this here, i.e. we set description = null. getText() returns the text currently displayed in the editor area as one string,
including the line-end characters. The following listing TextDisplayFrame implements a text display. We derive a TextDisplayFrame from SimpleFrame and glue a JEditorPane into it as a central component. The constructor gets a le name as a string. It constructs a le
readfile with this name. As in Section 10.1.3 a FileReader for le readfile is created. This reader is then passed to the read-method of JEditorPane. The pane then starts the reader and the text is displayed in the JEditorPane. Class TextDisplayDriver is just the start class.
File: its/TextDisplay/TextDisplayFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. package its.TextDisplay; import import import import import import its.SimpleFrame.SimpleFrame; java.io.File; java.io.FileReader; java.io.IOException; javax.swing.JEditorPane; java.awt.BorderLayout;
public class TextDisplayFrame extends SimpleFrame { private JEditorPane textDisplayPane; public TextDisplayFrame(String filename) { textDisplayPane = new JEditorPane(); this.getContentPane().add(textDisplayPane,BorderLayout.CENTER); this.setSize(200,160); File readfile = new File(filename);
try{ FileReader fr = new FileReader(readfile); textDisplayPane.read(fr,null); }catch(IOException e){ System.out.println("Problems opening or reading " +readfile.getPath());
101
27. 28. 29. 30.
} } }
File: its/TextDisplay/TextDisplayDriver.java
package its.TextDisplay; public class TextDisplayDriver { // Adjust paths if necessary!! private static String path = "./its/TestData/"; private static String fileName = "testtext1.txt"; public static void main(String[] args) { TextDisplayFrame TAF = new TextDisplayFrame(path+fileName); TAF.showIt("Text Display"); } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
The result is shown in Figure 10.1. Obviously the frame is too small to display all the text. In the next section we shall see how to display long large texts using scrolling. A JEditorPane supplies the basic functions of a text editor automatically. We can place the cursor, insert or delete text and also copy (using CNTR-C and CNTRV). The reader is advised to try this. Other, more complex, functions such as searching are not provided.
Figure 10.1 Display of TextDisplayDriver. Not all lines of the text are visible
Scrolling
11
In this chapter we see how to display long texts or large drawings that do not t into the window. Only a part of them can be made visible. So-called scroll panes allow navigation in the text or the drawing and thus display different parts of the document.
JScrollPane(JComponent comp) this constructor generates a JScrollPane, the viewport of which displays part of comp. If the whole component ts into
the viewport the scroll bars disappear. This behaviour can be changed using the next method.
setHorizontalScrollBarPolicy(int policy) determines when the scroll bars are visible. Class JScrollPane denes constant values for policy: HORIZONTAL_SCROLLBAR_ALWAYS, HORIZONTAL_SCROLLBAR_AS_NEEDED and HORIZONTAL_SCROLLBAR_NEVER and have obvious meanings. setVerticalScrollBarPolicy(int policy) is the same as above, but for the
Scrolling
Frame Document (text or picture)
103
Let us augment the application from Section 10.2 so that the text can be scrolled. Our text is still loaded into a JEditorPane. We then generate a scroll pane to display parts of it. The vertical scroll bar is visible at once. The horizontal one does not appear even though the text contains a very long line. The reason is that the JEditorPanel breaks long lines when reading them. One has to rst make the panel wide (using the mouse) and then narrow to make the horizontal scroll bar appear. The functions of the scrollbars are automatically supplied. Moving the horizontal or vertical slider moves the viewport inside the document. In the following we list the code for TextDisplayScrollFrame which displays scrollable text. The driver class TextDisplayScrollDriver is not listed. Figure 11.2 shows the result.
104
Basics
File: its/Scrolling/TextDisplayScrollFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. package its.Scrolling; import import import import import import import its.SimpleFrame.SimpleFrame; java.io.File; java.io.FileReader; java.io.IOException; java.awt.BorderLayout; javax.swing.JEditorPane; javax.swing.JScrollPane;
public class TextDisplayScrollFrame extends SimpleFrame { private JEditorPane TextDisplayPanel; public TextDisplayScrollFrame(String filename) { TextDisplayPanel = new JEditorPane(); JScrollPane scrollPane = new JScrollPane(TextDisplayPanel); this.getContentPane().add(scrollPane,BorderLayout.CENTER); File readfile = new File(filename);
try{ FileReader fr = new FileReader(readfile); TextDisplayPanel.read(fr,null); }catch(IOException e){ System.out.println("Problems opening or reading " +readfile.getName()); }
Scrolling
105
Figure 11.3 The drawing display application. The vertical scroll bar is visible because the height of the viewport is less than the preferred height of the panel to display. The horizontal scroll bar is not visible because the viewport is wider than the preferred width of the panel. The vertical line is the right edge of the preferred area. The drawing outside the preferred area is not considered by the runtime system when determining whether to show the scroll bar
As an example we construct a panel and add some drawing commands to its method paintComponent. The resulting class is called DrawingDisplayScrollPanel. The size of the panel is set to 250 250 pixels. We draw a red rectangle to indicate the border of this area and a text to denote it. We then draw a circle and a text outside the area specied in the setPreferredSize command. We then create a scroll pane that displays the panel in its viewport. The scroll pane is embedded into a frame. We list class DrawingDisplayScrollPanel below but omit the listing of the frame class DrawingDisplayScrollFrame and the driver DrawingDisplayScrollDriver; both can be downloaded from the books home page. Try resizing the frame in different ways to see when scroll bars appear or vanish. Running this application and resizing the frame will show that the scroll bars become visible if the preferred size area of the panel does not t into the viewport. The result can be seen in Figure 11.3. The runtime system does not recognize that there is some drawing outside this area. The programmer must take care of that by monitoring the current size of the drawing and adjusting the preferred size. In Chapter 21 we shall learn an elegant way to do this. File: its/Scrolling/DrawingDisplayScrollPanel.java
package its.Scrolling; import import import import java.awt.Color; java.awt.Dimension; java.awt.Graphics; javax.swing.JPanel; 1. 2. 3. 4. 5. 6.
106
Basics
7. 8. public class DrawingDisplayScrollPanel extends JPanel { 9. 10. public DrawingDisplayScrollPanel() { 11. this.setBackground(Color.white); 12. this.setPreferredSize(new Dimension(250,250)); 13. } 14. 15. public void paintComponent(Graphics g){ 16. super.paintComponent(g); 17. Color oldColor = g.getColor(); 18. g.setColor(Color.red); 19. g.drawRect(0,0,249,249); 20. g.drawString("Border of preferred size.",10,240); 21. g.setColor(Color.blue); 22. g.fillOval(300,150,20,20); 23. g.drawString("This is outside the preferred size",260,180); 24. g.setColor(oldColor); 25. } 26. }
Dialogues
12
In this chapter we introduce dialogues as a further means for userprogram interaction. They are used when the application needs information from the user in order to proceed. First, we look at dialogues that are predened in Swing and then turn to user-dened dialogues. We shall do this by extending the examples from previous chapters into a small editor. We will also learn how to transfer data between dialogues and the application.
In Chapter 10 we read a xed le into our editor. In Section 11.1 we saw how to display the text so that it can be scrolled. The use of menus was presented in Chapter 8. Here, we combine these three things into an editor application. In addition to that, we add some dialogues for choosing a le, searching and showing a warning message. We begin by developing the skeleton of the application to which we then add more and more functions. No model is used for the actual data the text because our focus is on dialogues. Text models will be considered in Chapter 18.
108
Basics
The real actions are implemented later. We implement a listener in class EditorListener which monitors the menu items. As a reaction to selecting a menu item, the appropriate method of the class EditorSkeletonFrame is called. Class EditorListener will not be changed later on, when we complete the editor.
File: its/Dialogs/EditorSkeletonFrame.java
1. package its.Dialogs; 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. import import import import its.SimpleFrame.SimpleFrame; java.awt.*; javax.swing.*; java.io.*;
public class EditorSkeletonFrame extends SimpleFrame { private JEditorPane textDisplayPane; public EditorSkeletonFrame() { textDisplayPane = new JEditorPane(); JScrollPane scrollPane = new JScrollPane(textDisplayPane); this.getContentPane().add(scrollPane,BorderLayout.CENTER); // Create menu bar, menus and menu items JMenuBar menubar = new JMenuBar(); this.setJMenuBar(menubar); JMenu fileMenu = new JMenu("File"); JMenu toolMenu = new JMenu("Tools"); menubar.add(fileMenu); menubar.add(toolMenu); JMenuItem loadItem = new JMenuItem("Load"); JMenuItem saveItem = new JMenuItem("Save"); JMenuItem exitItem = new JMenuItem("Exit"); JMenuItem searchItem = new JMenuItem("Search"); fileMenu.add(loadItem); fileMenu.add(saveItem); fileMenu.addSeparator(); fileMenu.add(exitItem); toolMenu.add(searchItem); // Create a listener and add it to the menu items EditorListener editorListener = new EditorListener(this); loadItem.addActionListener(editorListener); saveItem.addActionListener(editorListener); exitItem.addActionListener(editorListener);
Dialogues
109
40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63.
searchItem.addActionListener(editorListener); } // The next four methods do not yet do anything // useful. The real code is supplied later. public void openFile(){ //Code for loading a file has to go here. System.out.println("Menu item Load selected."); } public void saveFile(){ //Code for saving a file has to go here. System.out.println("Menu item Save selected."); } public void search(){ //Code for searching has to go here. System.out.println("Menu item Search selected."); } public void exitEditor(){ //Code for leaving the editor has to go here. System.out.println("Menu item Exit selected."); } }
File: its/Dialogs/EditorListener.java
package its.Dialogs; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import javax.swing.JFrame; public class EditorListener implements ActionListener { private EditorSkeletonFrame editor; public EditorListener(EditorSkeletonFrame edi) { editor = edi; } public void actionPerformed(ActionEvent evt) { String actionCommand = evt.getActionCommand(); if(actionCommand.equals("Load")){ editor.openFile(); } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.
110
20.
Basics
else if(actionCommand.equals("Save")){
21. editor.saveFile(); 22. } 23. else if(actionCommand.equals("Exit")){ 24. editor.exitEditor(); 25. } 26. else if(actionCommand.equals("Search")){ 27. editor.search(); 28. } 29. else { 30. System.out.println("Error: Unexpected action command."); 31. } 32. } 33. }
12.2.1
In Swing, le selection dialogues are predened in the class JFileChooser. We present only a few of its methods; the class offers a lot more possibilities. The navigation in JFileChooser is (mostly) done as in the le selection dialogues of the operating system. One can change directories, to choose a le from a list, or by typing its name into a text eld. There are buttons labelled Open and Cancel. We do not have to implement any listener for these buttons. All the functions one expects from a le selection dialogue are supplied by JFileChooser.
JFileChooser(String startDirectory); int showOpenDialog(Component parent); int showSaveDialog(Component parent); File getSelectedFile();
JFileChooser(String startDirectory) generates a le selection dialogue which is not yet visible. When it becomes visible the les in startDirectory
is modal; see explanations below. On closing, a le selection dialogue returns an integer value which is one of the following integer constants: APPROVE_OPTION
Dialogues
111
indicates that the Open (or Save, in case of a save dialogue) button of the dialogue has been clicked. CANCEL_OPTION indicates that the Cancel button of the dialogue has been clicked.
showSaveDialog(Component parent) displays the le selection dialogue for saving les; the rest is as for showOpenDialog. getSelectedFile() returns the selected le in a variable of type File if APPROVE_OPTION has been selected.
Methods that display a le selection dialogue receive a Component parent as an argument. Class Component from the AWT library is the mother class of all graphical components in Java. The reference to the parent is needed because a JFileChooser is modal. The parent component is blocked as long as it is visible itself. Only after the dialogue is closed can the parent component be used again. This is to avoid unwanted interactions such as modifying a le in the parent frame while it is being saved. Figure 12.1 shows the result of creating a JFileChooser with the TestData directory of the ITS package, a start directory, and then making it visible by showOpenDialog.
12.2.2
We declare a variable chooser of type JFileChooser and a variable selectedFile of type File in class EditorFrame by
private JFileChooser chooser; private File selectedFile;
112
Basics
These variables are not yet instantiated using new; this will happen in method openFile which is described below. We then add the code for the dialogue at the position marked //Code for opening a file has to go here. in class EditorSkeletonFrame. The code for the resulting method openFile is listed below. The complete listing for the class EditorFrame can be downloaded from the books home page.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. public void openFile(){ if(chooser == null){ chooser = new JFileChooser(startPath); } int returnVal = chooser.showOpenDialog(this); if(returnVal == JFileChooser.APPROVE_OPTION) { File selectedFile = try{ chooser.getSelectedFile();
FileReader reader = new FileReader(selectedFile); textDisplayPane.read(reader,null); reader.close(); }catch(IOException e){ System.out.println("Problems opening or reading " +selectedFile.getName());
The rst lines create a le selection dialogue if this has not been done before. This is because we would like to avoid unnecessarily creating new instances of it. The condition in the if-statement in Line 2 is true only at its rst execution. Then an instance of the le chooser is created and assigned to the variable chooser in Line 3. From that point on chooser is not null and hence the statement in Line 3 is not executed again. We then make the le chooser visible in Line 5 as an Open-dialogue using method showOpenDialog(this). The dialogue is modal and thus blocks its parent component as long as it is visible. In our case the parent component is the EditorFrame which is passed to the dialogue as this. The execution of the code of the EditorFrame is stopped at Line 5 until either the Open or the Cancel button of the dialogue is selected. Then the variable returnVal receives the value APPROVE_OPTION or CANCEL_OPTION respectively, the dialogue is made invisible and the execution of the code of EditorFrame is continued. If APPROVE_OPTION is selected we nd out which le has been selected using method getSelectedFile. Then a le reader for this le is created in Line 10. This reader is passed to the read method of JTextPanel which loads the le and displays the text. As explained in Section 10.1, readers have to be placed inside a try-catch block. This concludes the implementation of the le opening procedure of our editor.
Dialogues
113
The methods saveFile and exitEditor are implemented in a very simple way; exercises to extend the implementation are given at the end of the chapter. The saveFile method writes the current content of the editor area to a le with the same name as the one it loaded from. The exitEditor method exits the application.
public void saveFile(){ try{ FileWriter fw = new FileWriter(selectedFile); fw.write(textDisplayPane.getText()); fw.close(); }catch(IOException e){ System.out.println("Problems writing "+selectedFile.getName()); } textDisplayPane.setText(""); } public void exitEditor(){ System.exit(0); }
114
Basics JDialog we only need the constructor and some methods to add components. The semantics of the methods is the same as that for JFrames.
JDialog(Frame parent, String title, boolean modal) getContentPane.add(Component comp) setVisible(boolean b) pack()
In order to have a proper display we call the default constructor of JDialog in the constructor of SearchDialog using the super-method.
JRadioButton(String buttonName) creates a button labelled buttonName. setSelected(boolean pressed) determines whether the button is selected (pressed = true) or not (pressed = false). In the rst case the black dot
is visible inside the circular area of the button. This method is used initially to set which buttons are pressed before the rst user action.
setActionCommand(String command) assigns the string command as the action
command to the button. Radio buttons are not automatically assigned an action command. This is because their label is often not a text but a picture. We set the action command ourselves using method setActionCommand.
getActionCommand() returns the action command assigned to the button.
We still have to make sure that only one button is pressed at a time. This is done by grouping the buttons. We use the class ButtonGroup from the AWT library. Below we list the constructor and some methods:
JButtonGroup(); add(JRadioButton button) String getSelection().getActionCommand()
Dialogues JButtonGroup() constructs a button group not yet containing any button. add(JRadioButton button) adds the radio button button to the group. getSelection().getActionCommand() returns the action command of the but-
115
ton which is currently selected in the group. This statement combines methods from ButtonGroup and ButtonModel, a class we do not discuss here. Once the buttons are in a group, only one at a time can be pressed. Pressing an unpressed one releases the one previously pressed. The listing of the whole dialogue can be found in Section 12.5.2.
12.5.1
Depending on how much information has to be exchanged between a dialogue and the program that created it, different methods of information exchange can be used. Return values may be used to transfer a single variable from the dialogue back to the program. This is, for example, done by JFileChooser. Further information can then be accessed using get-methods of the dialogue such as getSelectedFile and JFileChooser. This approach is appropriate if the data format is xed and not too much data have to be transferred. A more exible way of realizing information transfer is to dene a special object for this purpose. The data transfer object has to be dened by the programmer to store all the information to be transferred between the program and the dialogue. It should also have get and set methods to access the data; see also Section B.3. A data transfer object can be used to transfer data both ways, from the program to the dialogue and back. It might contain some initial information which is displayed by the dialogue. This is then altered by the user in the dialogue and the modied data are returned to the program. We describe the framework for this approach below and incorporate it into our editor after that. The basic steps for a data transfer using an object are: 1. Dene a class for the data transfer object. Let us call it DataTransferObject here. A DataTransferObject contains the information that is to be exchanged between the dialogue and the application that created the dialogue. Dene the get and set methods to access the data. 2. Dene a dialogue class, say MyDialog, with the desired layout. The dialogue should also contain two buttons labelled, for example, OK and Cancel.
116
Basics
3. Dene a method showIt(DataTransferObject dto) in class MyDialog. This method receives a data transfer object dto as an argument. It (possibly) extracts some data from dto which is to be displayed in the dialogue. It also makes the dialogue visible. 4. Dene a listener for the dialogue (preferably as an internal class). This listener monitors the OK and Cancel buttons. When one of them is clicked the listener updates the data transfer object. Changes made by the user while the dialogue was visible, e.g. text inputs, are put into the data transfer object. In addition the data transfer object has to contain the information whether OK or Cancel was clicked. Finally the listener makes the dialogue invisible. 5. The information transfer then proceeds as follows: (a) The application creates an instance myDialog of MyDialog and an instance dto of DataTransferObject. (b) If desired, the application initializes some values of dto using the set methods. (c) The application makes the dialogue visible by calling myDialog.showIt (dto). As a dialogue is modal, the application pauses until the dialogue is invisible again. During this time the user can change the information in the data transfer object through the dialogue. (d) Once the dialogue is invisible the application extracts the modied data from dto and uses it.
12.5.2
We dene the class DataTransferObject to exchange data. An object of type DataTransferObject contains three elds: one string and two boolean variables. The string searchWord contains the text to search for. To indicate whether the search has to be case sensitive we use the boolean eld caseSensitive. Finally the boolean eld search indicates whether the dialogue was closed by clicking Find (true) or Cancel (false). The class also denes the get- and set-methods to read and write these elds. For convenience there is a setAll method to set all information. The application EditorFrame can now check whether it has to search (getSearch() == true), which word to search for (getSearchWord()) and whether to obey cases (getCaseSensitive() == true). Class SearchDialog has SearchListener as an internal class1 . The listener keeps track of the two JButtons Find and Cancel (it does not monitor the radio buttons). On closing the dialogue, the current information of the dialogue is written into the DataTransferObject. Then the program can extract the information from it. In our example the search itself is done by just counting the number of occurrences of the word. The respective method countWords of EditorFrame is not listed here. In order to avoid the generation of unnecessary objects from within the EditorFrame class, we generate the dialogue only once. After that, it is made visible or
1
Dialogues
117
invisible. We check whether the dialogue is already dened by checking whether the reference to it is null or not.
if (searchDialog == null) { searchDialog = new SearchDialog(this); dataTransfer } = new DataTransferObject();
but not instantiated using new. We now list the classes SearchDialog and DataTransferObject.
File: its/Dialogs/SearchDialog.java
package its.Dialogs; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class SearchDialog extends JDialog { private private private private private private private private JPanel mainPanel = new JPanel(); JTextField searchTextField = new JTextField(); JRadioButton yesButton = new JRadioButton("Yes"); JRadioButton noButton = new JRadioButton("No"); JButton searchButton = new JButton("Find"); JButton cancelButton = new JButton("Cancel"); DataTransferObject dataTransfer; ButtonGroup group = new ButtonGroup(); 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29.
public SearchDialog(Frame frame) { super(frame,"Search dialog",true); JLabel questionS JLabel questionCS JLabel filler = new JLabel("search word:"); = new JLabel("case-sensitive?"); = new JLabel();
118
30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55.
Basics
mainPanel.setLayout(new GridLayout(4,2,10,0)); mainPanel.add(questionS); mainPanel.add(questionCS); mainPanel.add(searchTextField); mainPanel.add(yesButton); mainPanel.add(filler); mainPanel.add(noButton); mainPanel.add(searchButton); mainPanel.add(cancelButton); group.add(yesButton); group.add(noButton); yesButton.setActionCommand("yesActionCommand"); noButton.setActionCommand("noActionCommand"); yesButton.setSelected(true); noButton.setSelected(false); SearchListener SLis = new SearchListener(); searchButton.addActionListener(SLis); cancelButton.addActionListener(SLis); this.pack(); } public void showIt(DataTransferObject dto) {
56. dataTransfer =dto; 57. this.setVisible(true); 58. } 59. 60. // Internal class 61. class SearchListener implements ActionListener 62. { 63. // No constructor defined, default constructor is used 64. 65. public void actionPerformed(ActionEvent evt) 66. { 67. String searchText = searchTextField.getText(); 68. boolean caseSensitive = 69. (group.getSelection().getActionCommand().equals("yesActionCommand")); 70. String command = evt.getActionCommand(); 71. if(command.equals("Cancel")) 72. { 73. dataTransfer.setAll(searchText,caseSensitive,false); 74. //Note that setVisible is a method of class SearchDialog,
Dialogues
119
75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85.
//not of the internal class SearchListener! setVisible(false); } else if (command.equals("Find")) { dataTransfer.setAll(searchText,caseSensitive,true); setVisible(false); } } }// internal class }
File: its/Dialogs/DataTransferObject.java
package its.Dialogs; public class DataTransferObject { private String searchWord; private boolean caseSensitive; private boolean search; public DataTransferObject() { searchWord = ""; caseSensitive = true; search = true; } public void setAll(String w, boolean cs, boolean s) { searchWord = w; caseSensitive = cs; search = s; } public String getSearchWord() { return(searchWord); } public boolean getCaseSensitive() { 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29.
120
30.
Basics
As for other kinds of dialogue, the parent component is blocked while the message is visible. The text title appears in the title bar of the dialogue. The text message is shown in the dialogue. Argument type determines which symbol (e.g. warning sign, exclamation mark) is shown next to the message. There are predened constants in JOptionPane. Figure 12.3 shows the result. This completes our editor. The editor also needs the classes SearchDialog and InfoTransferObject dened in Section 12.5. The whole program is found on the home page of the book.
Dialogues
121
Exercises
12.1 Add a menu item Save as to the rst menu. When clicking it, a le selection dialogue should appear where one can select a le or input a new name. The text is then saved to this le. Augment the Load function as follows. If a le has already been loaded and is displayed, a dialogue box appears. The user is asked whether the displayed text should be saved before loading the new one. Depending on the users choice the old text is simply erased (using setText("")) or saved and erased before loading the new one. Augment the Exit function as follows. If Exit is selected while a text is displayed, a dialogue appears. The user is asked whether the text should be saved before exiting or not. Depending on the users choice the appropriate action is performed. This procedure should also be executed when the close button to the frame is pressed. Add another menu Help to the editor. It should have two items Help and About. The rst should display a help text in a dialogue window and the second some information on the program. Add a status bar under the editor pane which displays some additional information, e.g. the number of lines or the le name.
12.2
12.3
12.4
12.5
More on graphics
13
In this chapter we discuss some techniques to improve drawings. This chapter can be skipped on a rst reading.
More on graphics
123
uses cap = BasicStroke.CAP_ROUND. The class provides more features, such as dashed lines, which we do not discuss here.
BasicStroke(float width) constructs a solid BasicStroke with the specied
line width and with default values for the cap and join styles.
BasicStroke(float width, int cap, int join) constructs a solid BasicStroke with the specied attributes.
The following code snippet shows how to use class Graphics2D and BasicStroke to draw lines 5 and 6 pixels wide and have rounded and square ends respectively:
// define strokes in the preamble BasicStroke stroke5 = new BasicStroke(5.0f,BasicStroke.CAP_ROUND,BasicStroke.JOIN_BEVEL); BasicStroke stroke6 = new BasicStroke(6.0f,BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL);
// using the strokes in paintComponent public void paintComponent(Graphics g){ super.paintComponent(gr); // cast to Graphics2D Graphics2D g2d = (Graphics2D) g; // draw a line with width 5 g2d.setStroke(stroke5); g2d.drawLine(10,10,30,40); // draw a line with width 6 g2d.setStroke(stroke6); g2d.drawLine(10,40,30,10); }
124
Basics
= screenDim.width; = tk.getScreenResolution();
They return the current width and height of the panel in pixels. The left edge of the black rectangle is then one-third the width of the panel. We then place the black rectangle at the desired position by calling drawRect with the appropriate values. The following listings show the code including the start class ResizeDriver. Figure 13.1 shows the result.
More on graphics
125
File: its/ResizeDisplay/ResizeFrame.java
package its.ResizeDisplay; import its.SimpleFrame.SimpleFrame; public class ResizeFrame extends SimpleFrame{ public ResizeFrame(){ ResizePanel rp = new ResizePanel(); this.setSize(500,300); this.getContentPane().add(rp); } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.
File: its/ResizeDisplay/ResizePanel.java
package its.ResizeDisplay; import java.awt.Color; import java.awt.Graphics; import javax.swing.JPanel; public class ResizePanel extends JPanel{ public ResizePanel(){ this.setBackground(Color.yellow); } public void paintComponent(Graphics g) { super.paintComponent(g); // get the current dimensions of the panel int currentWidth = this.getWidth(); int currentHeight = this.getHeight(); // take a third of the current dimensions int wThird = currentWidth/3; int hThird = currentHeight/3; // set color to black 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23.
126
Basics
24. g.setColor(Color.black); 25. // and draw the rectangle 26. g.fillRect(wThird, hThird, wThird, hThird); 27. } 28. 29. 30. }
File: its/ResizeDisplay/ResizeDriver.java
1. 2. 3. 4. 5. 6. 7. 8. 9. package its.ResizeDisplay; public class ResizeDriver { public static void main(String[] args){ ResizeFrame rf = new ResizeFrame(); rf.showIt("ResizeFrame"); } }
Exercise
13.1 Extend the resizeable frame as follows: add four buttons labelled up, down, left and right. When clicking one of them, the black rectangle has to move in that direction (by an amount you can specify). Make sure it is always fully contained in the panel!
An example project
14
In this chapter we present a small project based on the modelviewcontrol approach. The aim is to implement a game that can be interactively played by the user. The design and implementation are described in some detail because this example is also to serve as a template for further projects. The aim is not to design a stylish layout, but a working graphical interface.
14.1 Specication
Our example game is the 15-puzzle. It consists of a 4 4 board with 15 movable blocks and one free space. One can move a block adjacent to the empty place in that direction. See Figure 14.1 for an example. The user interface has to display the board and enable the user to move blocks. The implementation has to ensure that only legal moves are possible. These are moves that can be performed in the real puzzle.
128
Basics
(a)
(b)
(c)
(d)
Figure 14.1 The 15-puzzle: (a) some conguration of the 15 blocks; (b), (c) and (d) are derived from (a) by moving one block at a time. The aim is to arrange the blocks in such a way that the numbers increase when read row-wise
driver to test the functionality of the non-graphical classes. One might argue whether a class for moves or constants is really necessary for this simple game. In fact, the use of these classes here helps to structure the project, speeds up the design, and makes the program easy to maintain. The BoardModel is not restricted to a size of 4 4 but allows an arbitrary n m board. The parameters determining the size are passed in the constructor. The blocks are numbered 0, 1, 2, . . . , n m 1; where 0 stands for the empty place. The board is represented by the two-dimensional n m array board. The array stores at board[i][j] the number of the block in row i column j. Two other arrays rowOfBlock and colOfBlock store for every block the number of the row and column that it is currently in. This way positions and numbers of the blocks can be easily linked. The board is initialized with the blocks consecutively numbered in a row-wise fashion with the empty place at the lower right corner. The class supplies get-methods to access the current conguration and the board size. The dynamics of the game is provided by method moveIt. It receives an instance of MoveModel, checks whether the move is legal and if so moves the block. It returns a boolean value to indicate whether the move has been made or not.
An example project
129
File: its/BlockPuzzle/BoardModel.java
package its.BlockPuzzle; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43.
public class BoardModel{ private int noOfRows, noOfCols; private int[][] board; // The board as an array private int[] rowOfBlock; // The number of the row of every block private int[] colOfBlock; // The number of the column of every block public BoardModel(int nr, int nc){ noOfRows = nr; noOfCols = nc; board = new int[noOfRows][noOfCols]; rowOfBlock = new int[noOfRows * noOfCols]; colOfBlock = new int[noOfRows * noOfCols]; // initialize the board. The blocks are // numbered row-wise 1,2,... int kk = 1; for(int r=0; r < noOfRows; r++){ for(int c=0; c < noOfCols; c++){ if(kk < noOfRows * noOfCols){ board[r][c] = kk; rowOfBlock[kk] = r; colOfBlock[kk] = c; kk++; } }//for c }//for r // ... and the missing block is at the lower right. board [noOfRows-1][noOfCols-1] = 0; rowOfBlock[0] = noOfRows-1; colOfBlock[0] = noOfCols-1; } public boolean moveIt(MoveModel mm){ int dir = mm.getDirection(); int block = mm.getBlockNumber(); int row = rowOfBlock[block]; int col = colOfBlock[block]; boolean ok = true; // an UP move is possible if the missing block
130
44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90.
Basics
// is above the position (r,c), i.e., at // (r-1,c). Esp. r has to be larger than 0. // The tests for the other directions are similar. if(dir == Constants.DIRECTION_UP){ if((row > 0) && (board[row-1][col] == 0)){ board[row-1][col] = board[row][col]; rowOfBlock[block]--; board[row][col] = 0; rowOfBlock[0]++; } else{ ok = false; } } else if(dir == Constants.DIRECTION_DOWN){ if((row < noOfRows-1) && (board[row+1][col] == 0)){ board[row+1][col] = board[row][col]; rowOfBlock[block]++; board[row][col] = 0; rowOfBlock[0]--; } else{ ok = false; } }else if(dir == Constants.DIRECTION_LEFT){ if((col > 0) && (board[row][col-1] == 0)){ board[row][col-1] = board[row][col]; colOfBlock[block]--; board[row][col] = 0; colOfBlock[0]++; } else{ ok = false; } }else if(dir == Constants.DIRECTION_RIGHT){ if((col < noOfCols-1) && (board[row][col+1] == 0)){ board[row][col+1] = board[row][col]; colOfBlock[block]++; board[row][col] = 0; colOfBlock[0]--; } else{ ok = false; } } return(ok); } public ConfigurationModel getCurrentConfiguration(){
An example project
131
91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101.
return(new ConfigurationModel(board)); } public int getNoOfCols(){ return(noOfCols); } public int getNoOfRows(){ return(noOfRows); } }
The class MoveModel provides the abstract description of a move. It species the number of the block to be moved and its direction. The direction is one of the constants dened in class Constants. File: its/BlockPuzzle/MoveModel.java
package its.BlockPuzzle; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.
public class MoveModel { private int direction; private int blockNumber; public MoveModel(int dir, int bn) { direction = dir; blockNumber = bn; } public int getBlockNumber(){ return(blockNumber); } public int getDirection(){ return(direction); } }
Instances of class ConfigurationModel represent a snapshot of the board. This can be used to protocol the course of a game, or to store the current state of the game. Method equals checks whether two congurations are identical. This can be used for test purposes. The class provides a method toString which returns the conguration in a format that can also be printed on the console.
132
Basics
File: its/BlockPuzzle/ConfigurationModel.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. package its.BlockPuzzle;
public class ConfigurationModel { private int[][] board; int noOfRows, noOfCols; public ConfigurationModel(int[][] b) { noOfRows = b.length; noOfCols = b[0].length; board = new int[noOfRows][noOfCols]; for (int r=0;r < noOfRows ; r++ ) { for (int c=0;c < noOfCols ; c++ ) { board[r][c] = b[r][c]; }//for }//for } public int getBlockNo(int r, int c){ return(board[r][c]); } public String toString(){ String confAsString = ""; for(int r=0; r < noOfRows; r++){ for(int c=0; c < noOfCols; c++){ if(board[r][c] < 10) { confAsString += " "+board[r][c]; } else { confAsString += " "+board[r][c]; }//ifelse }//for c confAsString += "\n"; }//for r confAsString += "\n"; return(confAsString); } public boolean equals(ConfigurationModel conf){ boolean result = true;
An example project
133
45. 46. 47. 48. 49. 50. 51. 52. 53. 54.
for(int r=0; r < noOfRows; r++){ for(int c=0; c < noOfCols; c++){ if(this.board[r][c] != conf.board[r][c]){ result = false; }//if }//for c }//for r return(result); } }
Class constants dene constants for the possible directions of the moves. File: its/BlockPuzzle/Constants.java
package its.BlockPuzzle; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
public class Constants { public public public public } static static static static final final final final int int int int DIRECTION_UP DIRECTION_DOWN DIRECTION_RIGHT DIRECTION_LEFT = = = = 1; 2; 3; 4;
The test of the model is done by class BlockPuzzleTest. It constructs a 4 4 board, fetches the initial conguration of the board and prints it. Then two moves are generated and executed. The rst one is legal but the second one is not. A thorough test should of course check more situations, such as attempting to move a block off the board or whether there are two blocks with the same number. The test is performed by comparing the true and expected congurations. File: its/BlockPuzzle/BlockPuzzleTest.java
package its.BlockPuzzle; 1. 2. 3. 4. 5. 6. 7. 8.
134
Basics
9. passed = true; 10. //Generate a model and print it (also as string) 11. BoardModel bm = new BoardModel(4,4); 12. ConfigurationModel trueConf, expectedConf; 13. trueConf = bm.getCurrentConfiguration(); 14. expectedConf = new ConfigurationModel(new int[][] 15. {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,0}}); 16. check(expectedConf,expectedConf); 17. 18. //Make a move 19. System.out.println("Move 15 right"); 20. MoveModel move1 = new MoveModel(Constants.DIRECTION_RIGHT,15); 21. if(!bm.moveIt(move1)){ 22. System.out.println("Illegal Move!"); 23. } 24. trueConf = bm.getCurrentConfiguration(); 25. expectedConf = new ConfigurationModel(new int[][] 26. {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,0,15}}); 27. check(expectedConf,expectedConf); 28. 29. //Make another move 30. System.out.println("Move 5 up"); 31. MoveModel move2 = new MoveModel(Constants.DIRECTION_UP,5); 32. if(!bm.moveIt(move2)){ 33. System.out.println("Illegal Move!"); 34. } 35. trueConf = bm.getCurrentConfiguration(); 36. expectedConf = new ConfigurationModel(new int[][] 37. {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,0,15}}); 38. check(expectedConf,expectedConf); 39. // display the test result 40. if (passed) { 41. System.out.println("Test passed"); 42. } 43. else { 44. System.out.println("Test NOT passed"); 45. } 46. } 47. private static void check(ConfigurationModel conf1, 48. ConfigurationModel conf2){ 49. if(conf1.equals(conf2)){ 50. System.out.println("Configuration ok:"); 51. System.out.println(conf1.toString()); 52. } 53. else{ 54. System.out.println("Expected and true configurations" 55. + " do NOT match");
An example project
135
56. 57. 58. 59. 60. 61.
The listing below shows the test result as it is displayed on the console. It is correctly detected that the second move is illegal.
Configuration ok: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 Move 15 right Configuration ok: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 15 Move 5 up Illegal Move! Configuration ok: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 15 Test passed
136
Basics
conguration on all positions (r, c), (r is the row number, c is the column number) to nd the block number at this position. The blocks are drawn as rectangles with a number near the middle. The missing block is shown as a lled black rectangle. The code for drawing a block and the empty place is put into two private methods. Putting it into paintComponent is of course possible, but would make the structure hard to follow. The drawing is scalable (resizeable). We use the technique described in Section 13.3 for this purpose. The panel does not know anything about the rules of the game, it can merely display the current state. Therefore, method makeMove(move) makes a move by calling the method moveIt(move) of the board model. The panels display has to be updated if the conguration is changed. This update is made by calling the panels repaint method. If the move has not been made a warning message is displayed. It uses a predened message dialogue from class JOptionPane. The class has a public method getBlockNoAtPixels(x,y) which will be used in the control part. It determines the number of the block that contains the pixel coordinates (x, y). In order to nd out which row of blocks contains the given pixel y-coordinate we rst divide the height of the panel by the number of rows. This is the height of a single row in pixels. Then we divide y by this height to get the row number. Note that all numbers involved are integers; then rounding is downwards and the correct row number is computed. The column number is computed analogously.
File: its/BlockPuzzle/BlockPuzzlePanel.java
1. 2. 3. 4. 5. 6. package its.BlockPuzzle; import import import import java.awt.Color; java.awt.Dimension; java.awt.Graphics; javax.swing.JPanel;
An example project
137
7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52.
import javax.swing.JOptionPane; public class BlockPuzzlePanel extends JPanel{ private private private private BoardModel boardMod; ConfigurationModel currentConf; int noOfRows, noOfCols; int columnWidth,rowHeight;
public BlockPuzzlePanel( BoardModel bm){ boardMod = bm; noOfRows = bm.getNoOfRows(); noOfCols = bm.getNoOfCols(); this.setPreferredSize(new Dimension(300,300)); this.setBackground(Color.white); } public void paintComponent(Graphics g){ super.paintComponent(g); int w = getWidth(); int h = getHeight(); columnWidth = w/noOfCols; rowHeight = h/noOfRows; currentConf = boardMod.getCurrentConfiguration(); for(int r=0; r < noOfRows; r++){ for(int c=0; c < noOfCols; c++){ if(currentConf.getBlockNo(r,c) != 0){ drawBlock(r,c,currentConf.getBlockNo(r,c),g); } else { drawMissingBlock(r,c,g); } }//for c }//for r } public void makeMove(MoveModel move){ if(boardMod.moveIt(move)){ this.repaint(); } else{ JOptionPane.showMessageDialog(this, "Illegal Move","ITS BlockPuzzle", JOptionPane.WARNING_MESSAGE); }//ifelse
138
Basics
53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. }
} private void drawBlock(int r, int c, int n, Graphics g){ g.drawRect(c*columnWidth+2,r*rowHeight+2,columnWidth-4, rowHeight-4); g.drawString(""+n,c*columnWidth+(columnWidth/2), r*rowHeight+rowHeight/2); } private void drawMissingBlock(int r, int c, Graphics g){ g.fillRect(c*columnWidth,r*rowHeight,columnWidth,rowHeight); } public int getBlockNoAtPixels(int x,int y){ int c = x/(this.getWidth()/noOfCols); int r = y/(this.getHeight()/noOfRows); return(currentConf.getBlockNo(r,c)); }
Class DirectionPanel is quite simple. It receives a 4 1 grid layout, into which four radio buttons are placed. They are labelled with the four directions and receive action commands. The action commands will be used to determine which button is selected. The radio buttons are grouped as described in Section 12.4. The upbutton is selected. The class has only one method, getDirection. This returns the direction specied by the currently selected button. The return value is the corresponding direction-constant dened in class Constants. File: its/BlockPuzzle/DirectionPanel.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. package its.BlockPuzzle; import import import import java.awt.GridLayout; javax.swing.ButtonGroup; javax.swing.JPanel; javax.swing.JRadioButton;
public class DirectionPanel extends JPanel { private JRadioButton upBut, downBut, rBut, lBut; private ButtonGroup group; public DirectionPanel(){ this.setLayout(new GridLayout(4,1));
An example project
139
15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52.
= = = =
upBut.setActionCommand("u"); downBut.setActionCommand("d"); lBut.setActionCommand("l"); rBut.setActionCommand("r"); group = new ButtonGroup(); group.add(upBut); group.add(downBut); group.add(lBut); group.add(rBut); upBut.setSelected(true); this.add(upBut); this.add(downBut); this.add(lBut); this.add(rBut); } public int getDirection(){ String actionCommand = group.getSelection().getActionCommand(); int result = 0; if(actionCommand.equals("u")){ result = Constants.DIRECTION_UP; } else if(actionCommand.equals("d")){ result = Constants.DIRECTION_DOWN; } else if(actionCommand.equals("r")){ result = Constants.DIRECTION_RIGHT; } else if(actionCommand.equals("l")){ result = Constants.DIRECTION_LEFT; } return( result ); } }
The class BlockPuzzleFrame is not derived from SimpleFrame but from JFrame to make this project independent of the its-package. In order to use it on its own, one has to replace the package name its.BlockPuzzle by BlockPuzzle. The frame implements a window listener, which terminates the application when the frame is closed; see Section 9.3.
140
Basics
File: its/BlockPuzzle/BlockPuzzleFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. package its.BlockPuzzle;
public class BlockPuzzleFrame extends JFrame { private DirectionPanel dirPanel; private BlockPuzzlePanel bpPanel; // Constructor public BlockPuzzleFrame(int rows, int cols)
this.setLocation(200,200); this.setTitle("ITS Block Puzzle"); BoardModel boardMod = new BoardModel(rows,cols); bpPanel = new BlockPuzzlePanel(boardMod); dirPanel = new DirectionPanel(); getContentPane().add(bpPanel,BorderLayout.CENTER); getContentPane().add(dirPanel,BorderLayout.EAST); BlockPuzzleListener bpList = new BlockPuzzleListener(bpPanel,dirPanel); bpPanel.addMouseListener(bpList); // Correct termination: // Otherwise only the frame disappears when clicking // on the close symbol but the process keeps running. addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); pack(); }
An example project
141
45. 46. 47. 48. 49. 50. 51. 52. 53.
public
142
Basics
8. private BlockPuzzlePanel bpPanel; 9. 10. BlockPuzzleListener (BlockPuzzlePanel bp, DirectionPanel m){ 11. dirPanel = m; 12. bpPanel = bp; 13. } 14. 15. public void mouseClicked(MouseEvent evt){ 16. // 17. int x = evt.getX(); 18. int y = evt.getY(); 19. int blockNo = bpPanel.getBlockNoAtPixels(x,y); 20. // Constructs the move 21. MoveModel move = 22. new MoveModel(dirPanel.getDirection(),blockNo); 23. // Request a move to made be in the BlockPuzzlePanel. 24. // Note that the BlockPuzzleListener does not know HOW to make 25. // a move. 26. bpPanel.makeMove(move); 27. } 28. 29. }
The driver class BlockPuzzle creates a BlockPuzzleFrame and makes it visible. A screen shot of the program is shown in Figure 14.3.
Figure 14.3 The block puzzle application after some moves have been made
An example project
143
File: its/BlockPuzzle/BlockPuzzle.java
package its.BlockPuzzle; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
class BlockPuzzle{ public static void main(String[] args){ BlockPuzzleFrame g1f = new BlockPuzzleFrame(4,4); g1f.showIt(); } }
14.5 Summary
The example in this chapter is to illustrate the design process of a graphical application. The non-graphical part is specied, implemented and tested separately from the graphical one. Both graphical and non-graphical classes supply the methods needed by the control part. For example BoardModel implements the move in moveIt and BlockPuzzelPanel supplies a pixel-to-block-number transformation in getBlockNoAtPixels. The approach of separating data administration, graphics and user interaction is the key to a rapid save and easily maintainable implementation. An often-heard claim is that this slows down the program execution because, for example, the use of get- and set-methods is slower than direct access to public variables. This is almost always incorrect. One can achieve between 0 and 2 per cent speed-up for most applications by using a badly structured unsave and hard-to-maintain implementation. The time to debug such an implementation is, however, considerably larger. In our block puzzle example we have been able to implement the model, view and control parts one after the other. Though a conceptional separation of these parts is always possible, a temporal separation is not. One will often have to develop the three parts separately, but parallel. The block puzzle example is kept very short. It lacks a number of important tests, such as checking whether the numbers of rows and columns are positive in the constructor of BoardModel. Also the determination of the block clicked by method getBlockNoAtPixels is precise only to within a few pixels.
Exercises
14.1 Add a reset button to the GUI. On pressing it the blocks are arranged in the initial conguration.
144
Basics
14.2
Add a text area to the GUI which displays the history of the game by displaying the moves made, one per line. It should be scrollable and cleared if the game is reset. Add a menu to the GUI. There should be menu items Save and Load. On selecting them the current state of the game (the current conguration) is saved or a saved game is loaded and displayed, respectively. The les to save to or load from should be user selectable. Make sure that boards of different size can be handled.
14.3
An example project
145
14.4
Add a menu item Select board size to one of the menus. When selecting it, a dialogue appears, prompting the user for the numbers of rows and columns. On closing the dialogue, the board size is reset to the new values and the blocks are arranged in the original conguration. Change the way the user selects a move as follows: the direction panel is removed. To make a move the user clicks on a block B. If the empty place is in the same row or column as B, then all blocks between the empty place and B are moved, including B. The empty place then is at the former position of B. Note that this requires changes in or re-implementation of many classes. Implement the following GUI. There is a rectangular grid with n m cells. Initially the cells are empty. A red chip is placed into one of the cells. Then the chip can be moved to another, different, cell. A yellow chip is put into the cell of the previous position of the red one. There are always at most two chips on the grid. The game can be restarted with an empty grid. See Figure 14.4. Implement the following game (tic-tac-toe light). There is a rectangular grid with n n cells, n 3. Initially the cells are empty. Two players, called RED and BLACK, take turns to place chips of their colour into empty cells. RED begins. The game is over if one player has three chips next to one another in one row or all cells are occupied. The game can be restarted with an empty grid. See Figure 14.5.
14.5
14.6
14.7
PART
II
Pixel graphics
15
Pixel images are an essential ingredient in most graphical interfaces. They occur as small symbols on buttons or in messages or they are the main data object, for example in an application that displays photos. Such images are stored pixel by pixel. We shall not create such images ourselves. Instead we focus on displaying and manipulating existing ones.
We begin by discussing different types of pixel graphics and the Java classes for these kinds of image. We then show how images can be displayed and manipulated.
150
contrast-intense images in order to achieve the same visual quality. Some compressed image formats are loss-free, i.e. the original image can be precisely recovered. The Java libraries support the following image formats. When loading such an image it is automatically decompressed and can be displayed.
Graphics Interchange Format, le extension GIF. This format stores images in
256-bit colours, one byte per pixel. The pictures are compressed without loss of information. It is often used for small icons or graphics on the Internet. Using this format might cause legal problems, because the CompuServe company holds patents for the compression method.
Joint Photographic Experts Group, le extensions JPG, JPEG. This format
uses a compression method in which image information is lost. For photos a reduction to 20 per cent of the original image le size can be achieved while still maintaining a good quality. JPEG is probably the most widely used format to store larger photos or drawings.
Portable Network Graphic, le extension PNG. A newer image format with
growing importance. It uses a loss-free compression and might replace the GIF format in the long run. Most Internet browsers can render images in any of these three formats. In this book we shall mostly use PNG images; the examples work also when using JPEG or GIF. Using compressed image formats is especially advisable if the pictures have to be sent over a network: sending or loading times are drastically reduced. Loading a large image into an application takes some time, even on a modern computer. To save the user from waiting for the image to be displayed, images are often loaded asynchronously. This means that a second process is started which runs in parallel to the main program and loads the image. This way the main program is not blocked and the user can go on working while the image is loaded. If, however, the image has to be processed right away by the application then it should be loaded synchronously and the main program has to wait until the image is available.
Pixel graphics
int getImageHeight() int getImageWidth()
151
ImageIcon() default constructor. ImageIcon(Image picture) creates an ImageIcon out of an instance of the AWT class Image. ImageIcon(String filename) creates an ImageIcon from an image le, the name of which is supplied in the argument filename. ImageIcon(URL webaddress) creates an ImageIcon from a web address which is supplied in the argument webaddress. Class URL is from the java.net library. getImageHeight() returns the image height in pixels. getImageWidth() returns the image width in pixels.
which we shall use now. We create an ImageIcon by passing the le name of the image in the constructor. Note that you might have to adjust the path to match your directory structure. To keep the program short, we do not check whether the specied le really exists. The image is then displayed in the label. The size of the label is adjusted so that it matches the image size, provided the layout manager of the parent component allows that size. The label is centrally embedded into the frame. We use the pack method of the parent frame to guarantee this. The result is shown in Figure 15.1. File: its/Images/ImageFrame.java
package its.Images; import its.SimpleFrame.SimpleFrame; import javax.swing.*; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
152
The picture to be displayed does not have to be on the local hard disk, it might just as well be on some place on the Internet. Internet locations are specied by their uniform resource locator, URL for short. In Java these are realized by
Pixel graphics
153
class URL from the java.net library. One passes the address as a string in the constructor. More on accessing the net may be found in Chapter 22. The following application WebImageFrame fetches the picture of a pear pear.png from the specied net address and displays it. The le size is 190 kilobytes. Depending on the bandwidth of the network connection, it can take up to 10 seconds to load. The application works only if the computer has access to the Internet. Attempts to access net locations can fail for many reasons, which are beyond the control of the user. Therefore, the corresponding Java statements are embedded into try-catch blocks. This allows the program to continue in case network access fails.
File: its/Images/WebImageFrame.java
package its.Images; import import import import its.SimpleFrame.SimpleFrame; java.net.URL; javax.swing.ImageIcon; javax.swing.JLabel; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33.
public class WebImageFrame extends SimpleFrame { public WebImageFrame() { URL picURL = null; JLabel pictureLabel = null; try { picURL = new URL("http://www.imm.dtu.dk/swingbook/"+ "+HTMLTest/pear.png"); } catch (Exception ex) {System.out.println("Problems in creating URL"+picURL.getPath());} if(picURL != null){ ImageIcon picture = new ImageIcon(picURL); pictureLabel = new JLabel(picture); } else{ pictureLabel = new JLabel("Image not loaded"); } this.getContentPane().add(pictureLabel); pack(); } public static void main(String[] args)
154
34. { 35. WebImageFrame wifr = new WebImageFrame(); 36. wifr.showIt("WebImageFrame: loading Image from the net"); 37. } 38. }
15.4.1
As mentioned above, loading a picture might take some time, especially if the image has to be transferred over a network. To avoid blocking the main application while the image is fetched, the loading process can be run asynchronously. Class Toolkit from the AWT library supplies methods for this purpose. We shall use the getImage-method of the default toolkit to load images. One can specify the name of the image le or a web location (URL) where the image is stored. Method getImage returns an object of class Image:
Image picture = Toolkit.getDefaultToolkit.getImage(String filename); Image picture = Toolkit.getDefaultToolkit.getImage(String url);
Once the picture is loaded into an object of class Image, it can be manipulated by methods of this class. As a rst application we show how a number of images can be arranged. We derive class ImagePanel from JPanel. An image panel will automatically arrange pictures and display them. The background colour is set to be yellow to emphasize the contrast to the pictures. The images are displayed in method paintComponent of the panel. We use the following method of class Graphics. The arguments are explained afterwards.
drawImage(Image picture,int left, int top, ImageObserver imObs)
The rst argument (picture) contains the image to display. The two integer arguments left and top specify the pixel position of the upper left corner of the image inside the panel. The last argument is of type ImageObserver. This is an interface from the AWT library which is of great help when loading or displaying images. Fortunately, we do not have to implement this interface ourselves. Every graphical component, regardless whether it is AWT or Swing, implements this interface. We therefore can use this as image observer.
Pixel graphics
155
To determine the width or height of an image we use the following two methods of class Image which return, respectively, these two values in pixels. Note that these values refer to the size of the picture in the Image variable. The actual display might be enlarged or shrunk with techniques described later on.
int getWidth(ImageObserver imo) int getHeight(ImageObserver imo)
An ImagePanel uses a vector images to store the images to be displayed. In method paintComponent this vector is traversed. For every image in the vector its width and height are determined for an adequate placement. This is done every time the panel is repainted. Thus the pictures are always arranged to t the width of the panel, unless the panel is too small to t a single picture in which case the picture is partly invisible. In ImagePanel we dene three methods to add a picture:
addImage(Image picture) addImage(String filename) addImageAndTrack(String filename)
The rst adds a picture that is already stored as an instance of class Image. The second one loads a picture from a le. The third one also loads a picture from a le but in a slightly different way that we shall explain later. In any case a re-drawing of the panel is initiated after a picture is added by calling repaint. The following listing contains the classes ImagePanel and ImagePanelFrame. The latter one creates an ImagePanel and adds some pictures to it. The result is shown in Figure 15.2.
Figure 15.2 ImagePanelFrame. Resizing the frame might change the arrangement of the pictures
156
File: its/Images/ImagePanel.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. package its.Images; import java.awt.*; import javax.swing.JPanel; import java.util.Vector; public class ImagePanel extends JPanel { private private private private Vector images; int gap = 10; MediaTracker mediTracker; int imageID;
public ImagePanel() { this.setLayout(new BorderLayout()); this.setBackground(Color.yellow); images = new Vector(); mediTracker = new MediaTracker(this); imageID = 0; this.setPreferredSize(new Dimension(1000,1400)); } public void paintComponent(Graphics g) { super.paintComponent(g); int currentYPosition = gap; int currentXPosition = gap; int imageWidth, imageHeight; int maxHeight = -1; int panelWidth = this.getWidth(); for (int i = 0; i < images.size(); i++) { Image currentImage = (Image)(images.get(i)); imageWidth = currentImage.getWidth(this); imageHeight = currentImage.getHeight(this); // Check whether to start a new row. if((gap+imageWidth+currentXPosition) > panelWidth) { currentYPosition += maxHeight + gap; maxHeight = -1; currentXPosition = gap; } if (imageHeight > maxHeight)
Pixel graphics
157
45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83.
{ maxHeight = imageHeight; } g.drawImage(currentImage,currentXPosition,currentYPosition,this); currentXPosition += gap + imageWidth; }// for i } public void addImage(String filename) { Image im = Toolkit.getDefaultToolkit().getImage(filename); images.add(im); repaint(); } public void addImageAndTrack(String filename) { Image im = Toolkit.getDefaultToolkit().getImage(filename); imageID++; mediTracker.addImage(im,imageID); try { // Wait for the image to be completely loaded. mediTracker.waitForID(imageID); } catch (InterruptedException ex){ System.out.println("Error loading image "+filename+"."); } images.add(im); repaint(); } public void addImage(Image picture) { images.add(picture); repaint(); } }
File: its/Images/ImagePanelFrame.java
package its.Images; import its.SimpleFrame.SimpleFrame; import java.awt.BorderLayout; 1. 2. 3. 4.
158
5. 6. public class ImagePanelFrame extends SimpleFrame 7. { 8. // Adjust the following path if necessary 9. private final String picturePath = "./its/TestData/"; 10. 11. public ImagePanelFrame() 12. { 13. this.setSize(900,600); 14. ImagePanel ip = new ImagePanel(); 15. this.getContentPane().add(ip,BorderLayout.CENTER); 16. ip.addImage(picturePath+"strawberry.png"); 17. ip.addImage(picturePath+"banana.png"); 18. ip.addImage(picturePath+"lime.png"); 19. ip.addImage(picturePath+"plum.png"); 20. ip.addImage(picturePath+"lemon.png"); 21. ip.addImage(picturePath+"apple.png"); 22. ip.addImage(picturePath+"grapes.png"); 23. } 24. public static void main(String[] args) 25. { 26. ImagePanelFrame ipf = new ImagePanelFrame(); 27. ipf.showIt("Loading Images"); 28. } 29. 30. 31. }
Depending on the version of the SDK, the operation system and the hardware, one might observe slightly different behaviour. It might happen that only the yellow background of the panel is visible or only some of the images are displayed. The reason for this is the asynchronous loading. Let us look at three lines of code used in the addImage method of ImagePanel:
Image im = Toolkit.getDefaultToolkit().getImage(filename); images.add(im); repaint();
They suggest that the image stored in filename is rst loaded, then added to the vector images and nally a re-drawing is initiated. As the image is in the vector it should be displayed. The real time line is, however, different. The rst command initiates the loading process, which then runs in parallel to the main application. This means that the two other statements of the above code fragment might be executed while the image is being loaded. Thus the image added to the vector might be incomplete or even empty and repaint would display it only partly or not at all. If one resizes the frame later, when all images are loaded, the automatic repainting will make them appear. The time needed to load a picture depends on
Pixel graphics
159
its size and the hardware used. Therefore the aforementioned problems might not occur on some systems. For more on the mechanism of asynchronous loading consult Chapter 20. To solve this problem one uses the concept of a media tracker. This can be used to monitor the loading process, wait for the picture to be fully loaded and only then display it. Class MediaTracker can be found in the AWT library. A media tracker can monitor the loading process of many images. To distinguish the different images, every image receives an identication (ID) number. We use the following constructor and methods:
MediaTracker(Component comp) addImage(Image picture,int id) waitForID(int id) waitForID(int id,int msec)
MediaTracker(Component comp) creates a media tracker which monitors images loaded for component comp. addImage(Image picture,int id) adds picture to those images monitored by the media tracker. The picture gets identication number id. waitForID(int id) waits for the loading process of the image with identication number id to nish. The application waits at this point until the picture is fully loaded. The statement has to be embedded into a try-catch block because it throws an InterruptedException if the loading process fails (e.g. due a
network failure). The programmer can implement appropriate reactions for this case.
waitForID(int id,int msec) waits for the loading process of the image with identication number id to nish or for msec milliseconds to pass, whichever happens rst. Also this statement has to be embedded into a try-catch block.
In method addImageAndTrack of class ImagePanel we implement this concept. The identication numbers are increased from one picture to the next to guarantee uniqueness. The repaint method is called only when the image is fully loaded. Class ImagePanelTrackerFrame is like ImagePanelFrame except that it uses addImageAndTrack instead of addImage. It can be downloaded from the books home page.
15.4.2
Modifying images
Up to now, we displayed the images as they are, i.e. every pixel in the image le was shown on the screen. Pictures loaded into Image variables can also be rescaled or reected or only a part of them can be displayed. To this end it sufces to use method drawImage from class Graphics with an appropriate set of arguments. Recall that only part of an image might be displayed if drawImage is used while the image is not yet fully loaded.
160
The various ways of calling drawImage are described below. They all expect an instance of ImageObserver as one of the arguments. As discussed in Section 15.4.1 we can use this here. In the following, we assume that we draw into a panel, the procedure remains the same for other components which can display images.
drawImage(Image picture, int left, int top, ImageObserver imo) drawImage(Image picture,int left,int top, int width, int height, ImageObserver imo) drawImage(Image picture, int tl, int tt, int tr, int tb, int sl, int st, int sr, int sb, ImageObserver imo)
drawImage(Image picture, int left, int top, ImageObserver imo) has already been described in Section 15.4.1. It displays image picture such that its upper left corner is at pixel coordinates left and top of the panel. drawImage(Image picture,int left,int top, int width, int height, ImageObserver imo) draws image picture, such that its upper left corner is at pixel coordinates left and top in the panel. The displayed image is width pixels wide and height high. The image is shrunk or stretched to match these
constraints. It is important to note that the image itself, i.e. the content of variable picture, is not changed. The resizing is only in the display.
drawImage(Image picture, int tl, int tt, int tr, int tb, int sl, int st, int sr, int sb, ImageObserver imo) draws a rectangular clip of the image picture into a rectangular target area of the panel. The integer values tl, tt, tr and tb specify, respectively, the pixel coordinates of the left, top, right and bottom edges of the target rectangle. The values sl, st, sr and sb specify, respectively, the pixel coordinates of the left, top, right and bottom edges of the source rectangle in picture. The width and height of
the source rectangle do not have to match the corresponding values of the target rectangle; the clip will be resized to t into the target area. See also Figure 15.3. These commands can also be used to reect an image. If one chooses a negative value for argument width in the second drawImage method then the image will be vertically reected. The displayed image has a width that is the absolute value of argument width but left and right are interchanged. Using a negative value for height results in a horizontal reection. This also affects the meaning of the parameters left or top. These always reference the position of the upper left corner of the original picture. After a vertical reection this becomes the upper right corner of the displayed image. Also the last drawImage method can be used to reect the clipped part. Here, (sl, st) (the upper left corner of the source rectangle) is always mapped onto (tl, tt) (the upper left corner of the target rectangle). Similar for (sr, sb) and (tr, tb). In an unreected display we have sl < sr, st < sb, tl < tr, tt < tb. To vertically reect the clipped area one only has to swap left and right by choosing tr < tl, i.e. by choosing the right edge of the target left of its left edge. For a horizontal reection tt > tb is set.
Pixel graphics
161
(0,0)
sl
sr
(0,0)
tl
tr
st
tt tb
Source rectangle
Figure 15.3 How to use method drawImage to display part of a picture. (a) A source rectangle is specied by its left, right, top and bottom coordinates. (b) A target rectangle is specied in the same way. The content of the source rectangle is stretched or shrunk to t into the target rectangle and displayed there
Rotating an image is not supported by the Graphics class. As the pixels of images are normally organized in a row-wise fashion, rotations require substantial computational effort. We illustrate the use of drawImage in application ImageCutAndMirrorFrame. The frame contains CutAndMirrorPanel as an internal class. In the constructor of the frame, the digitalized photo orange.png is loaded and passed to the panel. The photo is 400 400 pixels. The paintComponent method of the panel contains six drawImage commands which are described below. The left and top parameters are chosen such that there are always 10 pixels between any two images. The different drawImage commands are labelled AG in the listing.
(A) draws the original image of size 400 400 at position (10, 10) of the panel. (B) draws an image to the right of the original one at position (420, 10). The height
is the original one (400) but the width is only 50, i.e. the image is horizontally shrunk to one-eighth.
(C) draws the image even further right in original height (400) but only 100 pixels wide and vertically reected. The reection is achieved by letting width be neg-
ative (100). As mentioned above, this swaps left and right. Position (580, 10) therefore denotes the right upper corner of the displayed image which is the left upper corner of the original.
(D) draws an image below the original one at position (10, 420). The width is the
original one (400) but the height is only 50, i.e. the image is vertically shrunk to one-eighth.
(E) draws the image even further down in original width (400) but only 100 pixels high and horizontally reected. The reection is achieved by letting height
162
be negative (100). Similar to (C) this swaps up and down. Position (10, 580) therefore denotes the lower left corner of the displayed image which is the upper left corner of the original.
(F) Draws an enlarged copy of a part of the original image. The rst four numerical
parameters 420, 420, 580, 580 specify the upper left and lower right corners of the target rectangle in the panel. This area is 160 160 pixels. The following four parameters 250, 130, 290, 170 determine a 40 20 source rectangle in the original image. Note that these coordinates refer to the original 400 400 picture in the Image variable, not to the displayed one in the panel. The source has to be horizontally stretched by a factor of four and vertically by a factor of eight to t into the target area.
(G) To indicate the source for (F) area, a white rectangle is drawn in the panel.
Figure 15.4 Application ImageCutAndMirrorFrame. Top left: original size picture. Lower right: enlarged images of the area in the white rectangle. Lower left and top right: the original image shrunk and reected
Pixel graphics
163
File: its/Images/ImageCutAndMirrorFrame.java
package its.Images; import its.SimpleFrame.SimpleFrame; import java.awt.*; import javax.swing.JPanel; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44.
public class ImageCutAndMirrorFrame extends SimpleFrame { // Adjust the following path if necessary private static final String picturePath ="./its/TestData/orange.png"; private MediaTracker mediTracker; public ImageCutAndMirrorFrame() { this.setSize(600,610); Image im = Toolkit.getDefaultToolkit().getImage(picturePath); CutAndMirrorPanel capp = new CutAndMirrorPanel(im); this.getContentPane().add(capp); int imageID = 1; mediTracker = new MediaTracker(this); mediTracker.addImage(im,imageID); try{ mediTracker.waitForID(imageID); } catch (InterruptedException ex){ System.out.println("Error loading "+picturePath+"."); } } public static void main(String[] args) { ImageCutAndMirrorFrame icamp = new ImageCutAndMirrorFrame(); icamp.showIt("ImageCutAndMirrorFrame"); }
// internal class private class CutAndMirrorPanel extends JPanel{ private Image im; CutAndMirrorPanel(Image i){ im = i; } public void paintComponent(Graphics g){ super.paintComponent(g);
164
45. g.drawImage(im,10,10,this); 46. g.drawImage(im,420,10,50,400,this); 47. g.drawImage(im,580,10,-100,400,this); 48. g.drawImage(im,10,420,400,50,this); 49. g.drawImage(im,10,580,400,-100,this); 50. g.drawImage(im,420,420,580,580,250,130,290,150,this); 51. g.setColor(Color.white); 52. g.drawRect(260,140,40,20); 53. } 54. }// internal class 55. }// class
// // // // // // //
The resizing done in the previous paragraphs affected only the displayed image, not the one contained in the Image variable. We now describe how an image can be physically resized. Such a resizing can, for example, be used to generate thumbnails for digitized photos. Think of an application that has to display an overview of a number of digitized photos. Such photos need several megabytes of memory each when stored as an instance of class Image. Using the appropriate drawImage method to display a miniaturized version does not solve the memory problem because only the display is resized while the picture in the Image variable maintains its full size. Then a few photos ll the main memory and the application is slowed down owing to swapping. We now describe how smaller (or larger) copies of pictures can be generated. To overcome the aforementioned memory problem one can load the photos one at a time, generate small copies and keep just those. Only the copies are maintained in the memory. The programmer has to make sure that references to the original big image are destroyed (e.g. by use of local variables) so that the automatic garbage collection of Java releases the memory. We use the following method of class Image to create a resized copy of an image:
Image getScaledInstance( int width, int height, int hints)
Arguments width and height specify the width and height of the resulting new image. The third argument hints determines which technique is used to enlarge or shrink the image. For an enlargement additional pixels have to be generated, for a miniaturization several pixels have to be combined. The quality of the resulting picture depends on the way this is done. A better quality usually requires a longer conversion time. We list the constants for hints which are dened in class Image together with the qualities achieved by the corresponding conversion techniques. Good trade-off between speed and quality Fast conversion and moderate quality Reasonably good quality Good quality Good quality
Pixel graphics
165
Figure 15.5 Application ImageScaleFrame. Top: original size image and a copy that is linearly scaled by 0.5. Bottom: upper part of the copy scaled by 2.0. The scroll pane can be used to show the hidden parts
Scaling is done asynchronously. To ensure that the resulting image is only displayed after it is completed one can again use a media tracker. The following application ImageScaleFrame demonstrates scaling a picture. The main program loads a photo into the Image object original. Then two copies small and large are generated. They are copies of the original scaled by a factor of 0.5 and 2.0, respectively. We dened method scaleImage(image,factor) which determines the size of the resulting picture from the dimensions of image and the scale factor factor. Horizontal and vertical scaling factors are equal; it is left as an exercise to extend the method to different factors. ImagePanel is used to display the three images. It is embedded into a scroll pane to be able to access all of the pictures. See Figure 15.5. File: its/Images/ImageScaleFrame.java
package its.Images; import its.SimpleFrame.SimpleFrame; import java.awt.*; import javax.swing.*; 1. 2. 3. 4. 5. 6. 7. 8.
166
9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53.
// Adjust the following path if necessary private static final String picturePath ="./its/TestData/orange.png"; private MediaTracker mediTracker; private int imageID = 0; public ImageScaleFrame() { this.setSize(800,600); ImagePanel ip = new ImagePanel(); JScrollPane sp = new JScrollPane(ip); sp.setHorizontalScrollBarPolicy (JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); sp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); this.getContentPane().add(sp); mediTracker = new MediaTracker(this); Image original = loadImageAndTrack(picturePath); ip.addImage(original); Image klein = scaleImage(original,0.5); ip.addImage(klein); Image gross = scaleImage(original,2.0); ip.addImage(gross); repaint(); } private Image scaleImage(Image im, double factor){ imageID++; int newWidth = (int)(im.getWidth(this)*factor); int newHeight = (int)(im.getWidth(this)*factor); Image scaledIm = im.getScaledInstance(newWidth,newHeight,Image.SCALE_FAST) mediTracker.addImage(scaledIm,imageID); try { mediTracker.waitForID(imageID); } catch (Exception ex) { ex.printStackTrace(); } return(scaledIm); } private Image loadImageAndTrack(String filename) { Image im = Toolkit.getDefaultToolkit().getImage(filename); imageID++;
Pixel graphics
167
54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69.
mediTracker.addImage(im,imageID); try { mediTracker.waitForID(imageID); } catch (InterruptedException ex){ System.out.println("Error loading "+filename+"."); } return(im); } public static void main(String[] args) { ImageScaleFrame isf = new ImageScaleFrame(); isf.showIt("ImageScaleFrame: Scaled images"); } }
16
In this chapter we introduce more graphical components from the Swing library. Some of them, such as lists and tables, are used to display data in a structured way. Others, such as split panes or tabbed panes, are used to embed other components so that one can easily switch between them.
16.1 Borders
One can draw a line a border around most Swing components in order to separate them from one another. Swing offers class Border and the utility class BorderFactory for this purpose. There are various different types of borders. We show only how to get an etched border; for others see the documentation. Let comp be a Swing component. To get a border, use the appropriate methods of the class BorderFactory namely:
Border bdr = BorderFactory.createEtchedBorder(); comp.setBorder(bdr);
To create a border with a text, one rst creates a border bdr and then adds the text:
Border bdr = BorderFactory.createEtchedBorder(); Border textbdr = BorderFactory.createTitledBorder(Border bdr, Stringtext);
Here is an example consisting of four panels with borders, two have a text. The result is shown in Figure 16.1. Note that the border is inside the panel, so leave a margin if you draw in the panel. File: its/Borders/BorderPanel.java
1. 2. 3. 4. package its.Borders; import java.awt.*; import javax.swing.JPanel;
169
File: its/Borders/BorderFrame.java
package its.Borders; import java.awt.*; import its.SimpleFrame.SimpleFrame; 1. 2. 3. 4. 5. 6. 7. 8. 9.
170
10. 11. public BorderFrame() 12. { 13. this.getContentPane().setLayout(new GridLayout(2,2)); 14. BorderPanel borderPanel1 = new BorderPanel(); 15. BorderPanel borderPanel2 = new BorderPanel(); 16. BorderPanel borderPanel3 = new BorderPanel("Panel 3"); 17. BorderPanel borderPanel4 = new BorderPanel("Panel 4"); 18. this.getContentPane().add(borderPanel1); 19. this.getContentPane().add(borderPanel2); 20. this.getContentPane().add(borderPanel3); 21. this.getContentPane().add(borderPanel4); 22. } 23. 24. public static void main(String[] args){ 25. BorderFrame borderFrame = new BorderFrame(); 26. borderFrame.showIt("Borders"); 27. 28. } 29. }
16.2 Lists
Lists serve two purposes. They can be used to display text information in rows and they enable the user to select rows and use the selected information in the program. The graphical concept of a list should not be confused with the data structure with the same name. Java contains an implementation of the latter in the class LinkedList. The graphical component for lists is the Swing class JList. The list entries are shown as rows. Instances of JList allow arbitrary Objects as list entries. The data are internally stored in a ListModel, a class we describe in Section 16.2.3. An explicit use of list models is advisable if the data displayed in a list change as the program is running. If the data in the list are xed, it is not really necessary to use the model explicitly. An example for this can be found in Section 16.2.1. JLists also allow the user to select one or more entries. One can display an image or augment a text with an icon but the programmer has to specify how the display should be drawn by implementing a ListCellRenderer. We shall use Strings only as list entries, which can be displayed without implementing a user-dened renderer. We describe the constructor and some methods of JList in the following:
JList() JList(String[] entries) void setListData(String[] entries) void setSelectionMode(int selectionMode)
171
void getSelectionMode() void setVisibleRowCount(int rowNo) int getSelectedIndex() int[] getSelectedIndices() Object getSelectedValue() Object[] getSelectedValues()
JList() creates an empty list. JList(String[] entries) creates a list which contains the strings of array entries. The rst string is the rst, i.e. top-most, list entry. setListData(String[] entries) sets the list entries to the strings in array entries. The rst string is the rst, i.e. top-most, list entry. setSelectionMode(int) sets the way in which the user is allowed to select list entries. One can choose between the following: SINGLE_SELECTION only one
list entry can be selected, if a new one is selected the old selection is dropped;
SINGLE_INTERVAL_SELECTION one group of adjacent entries can be selected; MULTIPLE_INTERVAL_SELECTION arbitrary combinations of entries can be selected. The three constants are dened in class ListSelectionModel. getSelectionMode() determines which selection mode is currently used. The returned integer value is one of those described for method setSelectionMode
above.
setVisibleRowCount(int rowNo) sets the number of rows visible of the list. This adjusts the height of the graphical component such that rowNo shows how many
entry is selected.
getSelectedIndices() returns the indices of the selected entries in an integer
array.
getSelectedValue() returns the (rst) selected entry (not the index but the entry itself) or null if nothing is selected. getSelectedValues() returns the selected entries (not the indices but the entries themselves), or null if nothing is selected.
16.2.1
To get text entries into a list one can pass an array of strings in the constructor. The following program ListDemo creates a list with the names of the German provinces. The visible length of the list is set to 8. To make all entries accessible we place the list into the viewport of a scroll pane. The scroll pane is then embedded into the frame. We do not set the selection mode, so the list has the default selection mode which is MULTIPLE_INTERVAL_SELECTION. The main-method is placed in the frame class so no driver class is needed.
172
File: its/Lists/ListDemoFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. package its.Lists; import import import import its.SimpleFrame.SimpleFrame; java.awt.BorderLayout; javax.swing.JList; javax.swing.JScrollPane;
public class ListDemoFrame extends SimpleFrame { private String[] entries = {"Schleswig-Holstein", "Niedersachsen", "Hamburg", "Bremen", "Mecklenburg-Vorpommern", "Brandenburg", "Berlin", "Nordrhein-Westfalen","Hessen","Sachsen-Anhalt", "Rheinland-Pfalz","Thringen","Sachsen","Saarland", "Bayern", u "Baden-Wrttemberg"}; u public ListDemoFrame() { JList provinces = new JList(entries); JScrollPane scrollPane = new JScrollPane(provinces); this.getContentPane().add(scrollPane,BorderLayout.CENTER); this.pack(); } public static void main(String[] args) { ListDemoFrame ldf = new ListDemoFrame(); ldf.showIt("List demo"); } }
16.2.2
We now consider lists where entries are added or removed and the user can select entries and trigger an action as a response to that. We want to design a GUI with two lists side by side. Initially only the left one contains some items. The user is allowed to select single entries in the left list. Whenever an item is selected in the left list which is not in the right list, it is copied to the right list. If the item selected in the left list is already in the right list, it is deleted from the latter. The left list is not changed during this process; only the right list changes. We use two components to implement the desired functions: list models to handle the data and listeners, which react to selecting list entries. We introduce these components in the following sections.
16.2.3
List models
When lists are dynamic, i.e. when list entries are added or deleted in the course of the program, the explicit use of a model for the list entries is recommended. The
173
user can implement the interface ListModel for this purpose. However, there is a predened implementation of list models in class DefaultListModel which is sufcient for many purposes. In a list with n entries, the list entries are numbered 0, 1, . . . , n 1, where 0 denotes the rst (topmost) entry. When a list element is added or removed then the numbering is updated. In the following we describe class DefaultListModel which is located in the javax.swing library.
DefaultListModel() int size() boolean contains(Object entry) Object get(int position) void insertElementAt(Object entry, int position) void addElement(Object entry) void removeElementAt(int position) boolean removeElement(Object entry) void removeAllElements()
entries.
size() returns the number of entries currently in the list model. contains(Object entry) returns true if object entry is in the list model, and false otherwise. get(int position) returns the list entry at position position. Throws an exception if position is negative or greater than or equal to the current number of list entries. As the return type is Object, an explicit cast to the correct type
might be needed.
insertElementAt(Object entry, int position) adds entry entry at position position to the list model. Throws an exception if position is an invalid
index.
addElement(Object entry) adds an entry at the end of the list. removeElementAt(int position) removes the entry at position position. Throws an exception if position is an invalid index. The remaining list entries
Once the list model is dened, one passes it to a JList in the constructor or by using the set-method:
JList(ListModel lModel) setModel(ListModel lModel)
174
Changes in the list model (additions or deletions of entries) are automatically displayed by the corresponding list. No call to repaint is necessary.
16.2.4
As specied above, our application has to react to a list entry being selected. The appropriate listener for this purpose is a ListSelectionListener. It is assigned to a JList, say myjlist, by
myjlist.addListSelectionListener(listener)
This method is automatically called by the runtime system every time the selection is changed. It is not executed if the selection is unchanged, for example if the user clicks in an item that is already selected. The body of this method has to contain the code that should be executed in response to the selection. Method valueChanged receives an object of type ListSelectionEvent as an argument. This is automatically generated by the runtime system and contains further information on the selection event that has occurred. We shall use the following methods of ListSelectionEvent:
int getFirstIndex() int getLastIndex() boolean getIsAdjusting()
getFirstIndex() returns the position of the rst (top-most) entry, the selection
mode of which might have changed (from selected to non-selected or vice versa). Might refers to the fact that the section state might be the same as before. For example if entries 5 to 8 were selected and the user now selects 5 to 10 then the rst selected index remains unchanged. Which entries are selected can be found by using method getSelectedIndices of class JList.
getLastIndex() returns the position of the last (bottom-most) entry, the selection mode of which might have changed. See also getFirstIndex().
of which generates a list selection event. For example previously selected entries become unselected before new ones become selected. Usually, one would prefer this series of events to be completed before the application reacts to the change. For such a rapid series of events all but the last one return true as a result of getIsAdjusting. Only the last one returns false as a result of getIsAdjusting. This can be used to wait for the end of the series of adjustments before taking further action. At the moment, ListSelectionEvent supports only the selection modes SINGLE_SELECTION and SINGLE_INTERVAL_SELECTION.
175
16.2.5
The following program ListTransferFrame demonstrates the use of dynamic lists. The view part consists of two lists. Both are embedded into scroll panes which are then glued into a panel. The panel is glued centrally into a frame. The panel has a 1 2 grid layout so that the lists are side by side. We do not derive classes for these lists, because we do not add any additional functionality to them. The list on the left contains the names of the German provinces. As mentioned in the specication this list does not change. Therefore, do we not explicitly dene a model for it. The selection mode is set such that only a single entry can be selected. As the right list is dynamic, we dene our own list model. Entries are inserted into or deleted from the model. The display of the right list is automatically updated as a response to this. For the control part we use class TransferListener which implements the interface ListSelectionListener. It is implemented as an internal class of the frame so that it has direct access to elds of the frame. When the listeners valueChanged-method is informed, we rst make sure that we are not in the middle of a sequence of events that change the selection. If that is not the case, we nd out which entry is selected in the left list. This is stored in the string variable sel. We then check whether sel is already an entry in the right list and if so delete it there. Otherwise sel is added as an entry to the right list. The program is listed below. When using it, note the following. If an entry A is selected on the left it is copied to the right list (assuming it was not already there). If this entry A is clicked again the corresponding entry A is not removed from the right list. One rst has to select a different entry B on the left and select A again to remove it from the right list. The reason is, as explained above, that the selection has to change in order for valueChanged to be activated. Figure 16.2 shows the result.
176
File: its/Lists/ListTransferFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. package its.Lists; import import import import import its.SimpleFrame.SimpleFrame; java.awt.GridLayout; java.awt.BorderLayout; javax.swing.*; javax.swing.event.*;
public class ListTransferFrame extends SimpleFrame { private JList leftList, rightList; private DefaultListModel rightListModel; JButton transferButton; String[] entries = {"Schleswig-Holstein","Niedersachsen","Hamburg", "Bremen","Mecklenburg-Vorpommern","Brandenburg", "Berlin","Nordrhein-Westfalen","Hessen", "Sachsen-Anhalt","Rheinland-Pfalz","Thringen", u "Sachsen","Saarland","Bayern","Baden-Wrttemberg"}; u public ListTransferFrame() { this.setSize(400,300); leftList = new JList(entries); rightListModel = new DefaultListModel(); rightList = new JList(rightListModel); leftList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); TransferListener selLis = new TransferListener(); leftList.addListSelectionListener(selLis); JPanel listPanel = new JPanel(); listPanel.setLayout(new GridLayout(1,2)); JScrollPane leftScrollPane = new JScrollPane(leftList); JScrollPane rightScrollPane = new JScrollPane(rightList); listPanel.add(leftScrollPane); listPanel.add(rightScrollPane); this.getContentPane().add(listPanel,BorderLayout.CENTER); } public static void main(String[] args) { ListTransferFrame LLF = new ListTransferFrame(); LLF.showIt("List Transfer Frame"); }
177
46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68.
//Listener as internal class class TransferListener implements ListSelectionListener{ public TransferListener(){} public void valueChanged(ListSelectionEvent evt){ if(!evt.getValueIsAdjusting()) { String sel = (String)leftList.getSelectedValue(); if(rightListModel.contains(sel)) { rightListModel.removeElement(sel); } else { rightListModel.addElement(sel); }//ifelse }//if }//valueChanged }//internal class }
16.3 Tables
Tables are used to display data with a two-dimensional structure. Examples are price lists, distance tables on maps, address books, etc. In Swing class JTable is used to visualize tables. As for lists, a model is internally used to handle the data. If the data are complex or change while the program is running, it advisable to explicitly dene such a table model. A table consists of cells arranged in a rectangular grid. It is possible to add headings to every column. JTables are column-oriented. This means that there is support for the manipulation of columns (for example swapping two columns or rendering the column content) but only limited support for row manipulation. When designing a table one should therefore try to arrange the data in such a way that a column contains data of the same type (strings, integers, images, etc.). In the following we show how tables can be used to display static data, how table models work and how they can be used to dynamically or interactively change data in a table.
16.3.1
If a table displays data that do not change, then the explicit use of a table model is not necessary. Instead the data can be passed directly to the table in the constructor. The data are organized in a two-dimensional array content[][].
178
It is interpreted as an array consisting of the rows, i.e. content[i][] is the onedimensional array containing the entries of the ith row of the table. In a second, one-dimensional array columnNames the headers of the columns are passed. The rows and the array with the column names all have to have the same length! The constructor then looks like this:
JTable(Object[][] content,Object[] columnNames)
Note that the arrays are of type Object. If non-standard types are used, one has to ensure that the program knows how to render them by supplying a userdened renderer. This is done by implementing the interface TableCellRenderer. By default, JTable uses a DefaultTableCellRenderer which can display the standard types such as strings, images and boolean values. For most purposes this is sufcient. In order to make the column names visible the table should be embedded into a scroll pane. The heads remain visible even if the table is vertically scrolled. By default, the widths of the columns are all the same and they are automatically adjusted such that all columns t into the visible area. This is done even though a scroll pane is used to display the table. If the visible area is small then the columns become too narrow to properly display the content of some or all the cells. In those cells some dots (...) are shown instead. To switch the automatic width adjustment off one uses the command:
setAutoResizeMode(JTable.AUTO_RESIZE_OFF)
The following listing StaticTableFrame is an example for creating and displaying a table. The resulting display is shown in Figure 16.3. File: its/Tables/StaticTableFrame.java
1. 2. 3. 4. 5. package its.Tables; import its.SimpleFrame.SimpleFrame; import javax.swing.JScrollPane; import javax.swing.JTable;
179
6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27.
public class StaticTableFrame extends SimpleFrame { = {{"Cell 0.0","Cell 0.1","Cell 0.2"}, {"Cell 1.0","Cell 1.1","Cell 1.2"}}; private String[] columnNames = {"Column 0","Column 1","Column 2"}; private String[][] entries
public StaticTableFrame() { JTable table = new JTable(entries,columnNames); JScrollPane scrollpane = new JScrollPane(table); this.getContentPane().add(scrollpane); } public static void main(String[] args) { StaticTableFrame STF = new StaticTableFrame(); STF.showIt("Static Table Frame"); } }
16.3.2
Table models
The method of explicitly passing the table data in the constructor is not suitable when using tables with complex content or very large tables. So-called table models provide an elegant method in this situation. Changing the table content at runtime is also easy when using table models. Table models implicitly describe the content to be rendered into a cell. The whole table does not have to exist in the form of a two-dimensional array. The table model has to be able to provide the content of every cell on demand. The following application is an example of this. We construct a multiplication table. The cell in row r and column c displays the product r c of the row and column number. As rows and columns are indexed beginning with zero, the rst row and column consist entirely of zeros. The model then just species the following rule: The content of cell (r, c) is r c. The model does not have to construct a two-dimensional array with these values1 . We derive our own table model from the class AbstractTableModel which is dened in the library javax.swing.table. Class AbstractTableModel requires the implementation of the following abstract methods:
int getRowCount() int getColumnCount() Object getValueAt(int r, int c)
1
The current implementation of the Java runtime system seems to explicitly construct the table as a two-dimensional array.
180
More components and techniques getRowCount() has to be implemented to return the number of rows in the table. getColCount() has to be implemented to return the number of columns in the
table.
getValueAt(r,c) has to be implemented to return the content of the table cell
in column c of row r. Besides these three abstract methods AbstractTableModel contains some nonabstract methods one would often like to override:
String getColumnName(int c) Class getColumnClass(int c) boolean isCellEditable(int r, int c) void setValueAt(Object val, int r, int c)
getColumnName(c) returns the name of column c as a string. By default, an AbstractTableModel assigns the names A, B, C, . . . , Z, A A, AB, AC, . . . to the columns. Overriding getColumnName by your own implementation allows you
ensures that the correct renderer is used to display the entries. As mentioned above, tables are column-oriented and in every column the objects should be of the same class. To determine the appropriate class for column c one can look at the entry in the rst row: getValueAt(0,c).getClass().
isCellEditable(r, c) the return value is true if the cell is editable and false otherwise. The default implementation returns false, i.e. the user cannot
change the content of the cell. The programmer can override this method to make some cells editable by the user.
setValueAt(Object val, r, c) can be used to set the value of the cell in row r and column c to val. The default implementation has an empty body, i.e.
it does not change the value of the cell. The programmer can override this method to change the values of editable cells. In the following listing we provide an implementation of MultiplicationTableModel. The numbers of rows and columns are given as an argument in the constructor. Method getValueAt(r,c) is implemented to return the product of r and c. Method getColumnName(c) is overridden to return the string Col followed by the number c as a string. In Figure 16.4 the results are shown when using automatic resizing of the column width and without using it. We also list the class MultiplicationTableFrame. Such a frame generates a MultiplicationTableModel and a JTable using this model. The table is embedded into a scroll pane which in turn is embedded into the frame.
181
(a)
(b)
Figure 16.4 MultiplicationTableFrame. (a) The column widths are reduced to t the width of the display. As there is a minimum column width (15 pixels on most platforms) this is not always possible. (b) The automatic resizing is switched off and all columns have their default width (75 pixels on most platforms)
File: its/Tables/MultiplicationTableModel.java
package its.Tables; import javax.swing.table.AbstractTableModel; public class MultiplicationTableModel extends AbstractTableModel{ private int noOfRows, noOfCols; public MultiplicationTableModel(int r, int c) { noOfRows = r; noOfCols = c; } // Implementing the tree abstract methods: public int getColumnCount() { return(noOfRows); } public Object getValueAt(int r,int c) { return(new Integer(r*c)); } public int getRowCount() { return(noOfRows); } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25.
182
26. // Overriding some methods: 27. public Class getColumnClass(int c){ 28. return( getValueAt(0,c).getClass() ); 29. } 30. 31. public String getColumnName(int c){ 32. return("Col "+c); 33. } 34. }
File: its/Tables/MultiplicationTableFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. package its.Tables; import its.SimpleFrame.SimpleFrame; import javax.swing.JScrollPane; import javax.swing.JTable; public class MultiplicationTableFrame extends SimpleFrame { public MultiplicationTableFrame(int r, int c) { MultiplicationTableModel multModel = new MultiplicationTableModel(r,c); JTable multTable = new JTable(multModel); multTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); JScrollPane scrollPane = new JScrollPane(multTable); this.setSize(350,250); this.getContentPane().add(scrollPane); } public static void main(String[] args) { MultiplicationTableFrame mtf30 = new MultiplicationTableFrame(30,30); mtf30.showIt("Multiplication Table"); } }
16.3.3
Editable tables
In this section we show how different objects are used in different columns and how the content of table cells can be interactively changed by the user. The table is an order form where the user can order some products. In our example three products are available: circles, triangles and rectangles. The table has ve columns which contain the products name, a picture of the product,
183
the price per piece, the quantity ordered and the price for the ordered quantity. Initially, no products are ordered, i.e. the quantities are 0. The user can edit the quantities. As a result the price for the ordered quantity in the last column is updated. Our order form has an additional fourth row where the total price for all ordered products is displayed and updated with every change of the order. The table is implemented by our table model OrderTableModel. This model is then passed to a JTable which displays it. We organize the data as follows: the rst three rows contain the product information. The rst column (column number 0) contains the product names as Strings. The next column contains the pictures ImageIcons. The following column contains the price per piece as double. The fourth column (number 3) contains the number of pieces ordered as int. This column is editable: the user can change the quantities. In the last column the total price for every item is listed as double. The values of these cells are computed as (price per piece) times (quantity ordered). The last cell of the fourth row contains the total amount of the order. The values are computed in method getValueAt which determines the values by a large case distinction (switch statement). In particular, the rst three rows containing the three products are treated differently from the last row containing the total amount. One has to make sure that a re-computation is performed when the user changes the quantity ordered. This is achieved by using method fireTableDataChanged() of class AbstractTableModel. It not only initiates a re-computation but also an update of the display of the associated JTable. In Java the so-called re method of a class informs the listeners which are associated to that class. We have not explicitly dened a listener in our application. However, using a table with table model automatically implements listeners for the basic functions. These are responsible for the re-computation of the cell values and the update of the display. We now list the classes OrderTableModel and OrderTableFrame and show a screen shot of the application in Figure 16.5.
184
File: its/Tables/OrderTableModel.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. package its.Tables; import javax.swing.ImageIcon; import javax.swing.table.AbstractTableModel; public class OrderTableModel extends AbstractTableModel { private String[] columnNames = {"Product","Picture","Price", "Quantity","Total"}; private String[] products = {"Circle","Triangle","Rectangle"}; private String[] imageName = {"circ.png","tria.png","rect.png"}; private int[] quantities = {0,0,0}; private double[] prices = {10.00, 12.00, 12.50}; private static final String Path = "./its/TestData/"; public boolean[] bv= {true,false,true}; public OrderTableModel() { } public int getColumnCount() { return(columnNames.length); } public int getRowCount() { return(products.length+1); } public String getColumnName(int c) { return(columnNames[c]); } public Class getColumnClass(int c) { return getValueAt(0, c).getClass(); } public Object getValueAt(int r, int c) { Object result = new Object(); if( r < products.length) { switch(c){ case 0: result = products[r]; break; case 1: result = new ImageIcon(Path+imageName[r]);
break;
185
45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82.
case 2: result = new case 3: result = new case 4: int quantity double price result = new }//switch
} else{ switch(c){ case 0: result = new String("SUM"); break; case 1: result = new Object(); break; case 2: result = new Double(0.0); break; case 3: result = new Integer(0); break; case 4: double sum = 0.0; double ee; for (int i = 0; i < products.length; i++) { sum += ((Double)getValueAt(i,4)).doubleValue(); } result = new Double(sum); break; } } return(result); } //cells in column 3 can be edited public boolean isCellEditable(int r, int c) { return(c == 3); } public void setValueAt(Object obj, int r, int c) { if(c == 3){ quantities[r] = ((Integer)obj).intValue(); } this.fireTableDataChanged(); } }
File: its/Tables/OrderTableFrame.java
package its.Tables; import its.SimpleFrame.SimpleFrame; import javax.swing.JScrollPane; 1. 2. 3. 4.
186
5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27.
import javax.swing.JTable; public class OrderTableFrame extends SimpleFrame { public OrderTableFrame() { this.setSize(300,250); OrderTableModel otmodel = new OrderTableModel(); JTable JTab = new JTable(otmodel); JTab.setRowHeight(50); JScrollPane SP = new JScrollPane(JTab); this.getContentPane().add(SP); } public static void main(String[] args) { OrderTableFrame otframe = new OrderTableFrame(); otframe.showIt(); } }
16.4 Trees
Hierarchical structures can often be represented by trees. Examples of hierarchical structures are family trees, the organization structure of a company, the directory structure on a computer hard disk, or the embedding structure of Swing components as explained in Section 4.5. The notion of a tree comes from the area of combinatorics and graph theory, a eld of mathematics that considers discrete structures. A hierarchy is reected in a tree as follows: there is a most important component, the root. The tree then branches into (less important) subcomponents that, in turn, branch into sub-subcomponents. The bottom-most components of the hierarchy form the leaves of the tree, they do not branch any further. The parts of the tree that correspond to the components are called nodes and the connecting links are called edges. Figure 16.6 shows an example of this concept. The sub-nodes of a node are called its children. The children are numbered 0, 1, 2, . . . . In graphical representations the numbering is usually from left to right or from top to bottom. Trees are often drawn to grow downwards. The root is on top and the leaves (lowest in hierarchy) are at the bottom. In graphical interfaces trees are often drawn to grow from left to right. Swing offers predened components to represent and display trees. For the abstract representation of the tree structure we use the non-graphical classes DefaultTreeModel and DefaultMutableTreeNode. The classes are located in javax.swing.tree. The graphical component is
187
Figure 16.6 Two graphical representations of the same tree: (a) mathematical and (b) used in graphical interfaces
dened in class JTree which displays the abstract information of the tree model. The individual nodes of the tree are dened as DefaultMutableTreeNode. These are then inserted into the tree model together with the structural information, i.e. the information that tells you which node is a child of which other node. We describe these three classes in the following sections and then show two examples of applications using trees.
16.4.1
Class DefaultMutableTreeNode is a convenience implementation of the interface TreeNode. For our applications, the following methods are sufcient:
DefaultMutableTreeNode(Object label) add(DefaultMutableTreeNode node)
DefaultMutableTreeNode(Object label) denes a mutable tree node. The node receives object label as label. When a node is displayed one sees a sym-
bol and the label to the right of the symbol. The label is often a string. The term mutable refers to the fact that clicking on such a node in the display makes the subtree under this node appear or disappear. This function is automatically supplied by DefaultMutableTreeNode.
add(DefaultMutableTreeNode node) adds node node as the right-most child to this node. We shall not use this method in our examples but it can be helpful
in some situations. When nodes are displayed, they receive a default symbol. For DefaultMutableTreeNode this is the folder symbol of Java if the node is not a leaf. If the node is a leaf the le symbol of Java is displayed.
188
16.4.2
Class TreeModel
Instances of this class dene the abstract structure of the tree. This is done by specifying the root and, for every node, specifying its children and their order. We shall use the following methods.
DefaultTreeModel(TreeNode root) int getChildCount(TreeNode parent) boolean isLeaf(TreeNode node) insertNodeInto(MutableTreeNode newChild, MutableTreeNode parent, int k) Object getChild(TreeNode parent, int k) removeNodeFromParent(TreeNode child)
DefaultTreeModel(TreeNode root) creates a tree model with root node root. We may use a DefaultMutableTreeNode instead of TreeNode. There is no
k has to be one of 0, 1, 2, . . . , n otherwise a runtime error will occur. If k is strictly less than n then the former children k through n 1 become children k + 1 through n and newChild is squeezed in between children k 1 and k + 1.
Object getChild(TreeNode parent, int k) returns the kth child of parent as an Object and null if the parent does not have a kth child. removeNodeFromParent(TreeNode child) removes child from its parent. Note that the whole subtree with root child is also removed.
16.4.3
Class JTree
The graphical component for displaying trees in Swing is dened in the class JTree. We only need the following methods:
JTree(TreeModel treeModel) setTreeModel(TreeModel treeModel) putClientProperty("JTree.lineStyle","Angled");
as parameter.
setTreeModel(TreeModel treeModel) is used to set the tree model.
189
gled lines corresponding to trees edges. Alternatives to Angled are Horizontal, where horizontal lines between the nodes indicate the structure, and None where only indentation is used.
16.4.4
Sample applications
In the rst sample application we dene a tree manually. The structure is like the one in Figure 16.6. The labels are to reect a simple taxonomy of plants. Class BiologyTree is derived from JTree. It contains an instance biologyTreeModel of class DefaultTreeModel. The tree model is constructed by dening the root node and adding other nodes as children of existing ones. The classes TreeFrame and TreeDemoDriver are also listed.
File: its/Trees/BiologyTree.java
package its.Trees; import javax.swing.JTree; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; public class BiologyTree extends JTree { private DefaultTreeModel biologyTreeModel; public BiologyTree(){ makeModel(); this.setModel(biologyTreeModel); this.putClientProperty("JTree.lineStyle","Angled"); } private void makeModel(){ DefaultMutableTreeNode root = new DefaultMutableTreeNode("Trees"); DefaultMutableTreeNode leaved = new DefaultMutableTreeNode("Leaved Trees"); DefaultMutableTreeNode conifer = new DefaultMutableTreeNode("Conifers"); DefaultMutableTreeNode beech = new DefaultMutableTreeNode("Beech"); DefaultMutableTreeNode oak = new DefaultMutableTreeNode("Oak"); DefaultMutableTreeNode birch = new DefaultMutableTreeNode("Birch"); DefaultMutableTreeNode pine = new DefaultMutableTreeNode("Pine"); DefaultMutableTreeNode fir = new DefaultMutableTreeNode("Fir"); DefaultMutableTreeNode beechR = new DefaultMutableTreeNode("Red Leaved"); 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26.
190
27.
DefaultMutableTreeNode beechG = new DefaultMutableTreeNode("Green Leaved"); biologyTreeModel = new DefaultTreeModel(root); biologyTreeModel.insertNodeInto(leaved ,root ,0); biologyTreeModel.insertNodeInto(conifer,root ,1); biologyTreeModel.insertNodeInto(beech ,leaved ,0); biologyTreeModel.insertNodeInto(oak ,leaved ,1); biologyTreeModel.insertNodeInto(birch ,leaved ,1); biologyTreeModel.insertNodeInto(pine ,conifer,0); biologyTreeModel.insertNodeInto(fir ,conifer,1); biologyTreeModel.insertNodeInto(beechR ,beech ,0); biologyTreeModel.insertNodeInto(beechG ,beech ,1);
28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. } 40. }
File: its/Trees/TreeFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. package its.Trees; import its.SimpleFrame.SimpleFrame; import java.awt.*; public class TreeFrame extends SimpleFrame { public TreeFrame() { this.setSize(300,500); BiologyTree bioTree = new BiologyTree(); this.getContentPane().add(bioTree,BorderLayout.CENTER); } public static void main(String[] args) { TreeFrame treeFrame = new TreeFrame(); treeFrame.showIt("Tree Frame"); } }
In the second application we generate a directory tree. The tree is constructed recursively by method recursion. We only list the class DirectoryTree. The tree displays the directory structure of the its-package. The frame class DirectoryFrame and the driver class DirectoryDriver can be downloaded from the books home page. The results of both applications are shown in Figure 16.7.
191
File: its/Trees/DirectoryTree.java
package its.Trees; import import import import javax.swing.JTree; javax.swing.tree.DefaultMutableTreeNode; javax.swing.tree.DefaultTreeModel; java.io.File; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.
public class DirectoryTree extends JTree { private DefaultTreeModel directoryTreeModel; //The following path might have to be changed. private String startDir = "./its"; private File startFile; public DirectoryTree() { startFile = new File(startDir); recursion(startFile); this.setModel(directoryTreeModel); } public DefaultMutableTreeNode recursion(File currentFile)
192
22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. }
{ DefaultMutableTreeNode curNode = new DefaultMutableTreeNode(currentFile.getName()); if(currentFile == startFile) { directoryTreeModel = new DefaultTreeModel(curNode); } if(currentFile.isDirectory()) { File[] files = currentFile.listFiles(); for (int i = 0; i < files.length; i++) { directoryTreeModel.insertNodeInto(recursion(files[i]),curNode,0); }//for i } return(curNode); }
16.5.1
Class JComboBox
A combo box looks like a text eld with a small button attached at its right side. The button shows an arrow pointing downwards. If the arrow is clicked, a list rolls down out of the text eld. Items can be selected from the list or text can be typed into the text eld. After selecting an item or hitting the return key the list is rolled up again. Note that text written by the user is not permanently added to the list. We describe some features of class JComboBox which implements combo boxes in Swing and then present an application using this graphic component.
JComboBox() JComboBox(String[] listEntries) JComboBox(Vector[] listEntries) additem(String listEntry)
193
insertItemAt(String listEntry, int pos) int getSelectedIndex() Object getSelectedItem() int getItemCount() setEditable(boolean b) setMaximumRowCount(int rowCount)
JComboBox() generates a combo box with an empty list. JComboBox(String[] listEntries) generates a combo box with a list containing the items of array listEntries. The rst entry is the rst string. JComboBox(Vector[] listEntries) generates a combo box with a list containing the items of vector listEntries. The rst list entry is the object at position 0
of the vector. If necessary, one has to implement a renderer to draw the objects.
additem(String listEntry) adds item listEntry at the end of the list. insertItemAt(String listEntry, int pos) inserts listEntry at position pos into the list. The elements previously at pos and further down the list are moved
selected or the user typed in new text then the return value is 1.
getSelectedItem() returns either the selected list item or the text entered by the user. As the return type is Object one might have to cast it to the desired
type.
getItemCount() returns the number of items currently in the list. setEditable(boolean b) if b is false then the user cannot enter a text into the combo box but is only allowed to select an item from the list. If b is true the
user may also enter a text. The appearance of the combo box depends on this property. Non-editable combo boxes have a grey background, editable ones are white.
setMaximumRowCount(int rowCount) sets the length of the visible part of the list to the current value of rowCount. If the list contains more items than rowCount,
a scroll bar automatically appears on the right. As for lists, there is a data model available for combo boxes: interface ComboBoxModel or a class DefaultComboBoxModel which implements enough features for most applications. We do not describe the models here; their use is similar to the list models in Section 16.2.3.
16.5.2
The main part of our application is a panel CalendarPanel where the user can select a date. The panel provides only one public method getDate which returns the selected date as a string. The date is selected using three combo boxes. One box
194
contains the day numbers. It cannot be edited but its content changes depending on the setting of month and year. The next combo box contains the month names. It is not editable; we do not allow the user to invent new months. The third combo box contains a list of years from 2004 to 2007. It is editable so the user can select another year by entering its number. The month box is lled by passing an array with the months names to the box in the constructor. The day and year boxes are lled in method initBoxes by adding the items in for-loops. Also in this method the visible length of boxes are set. For the day box the value is 10 so that a scroll bar will appear in the list. A listener class CalendarListener is dened as an internal class. A CalendarListener is associated with the combo boxes for year and month. If an entry is selected in one of these boxes (or a new year is entered by the user) then the listeners actionPerformed method is engaged. It has to adjust the days displayed in the day combo box depending on the currently selected year and month. It does so by rst determining the year and month. From this the length of the month is computed as one of 28, 29, 30 or 31. The actual adjustment of the day combo box is done by method setDayListTo. It compares the current length of the list of the day combo box with the desired one. If the list is too short the appropriate entries are added; if it is too long the last entries are removed. The method to adjust the length of February is only correct from 1901 to 2099. If the user enters a number outside this range, then the listener sets the year to 2000. The code for CalendarPanel is listed below. The test program consists of a frame (ComboBoxFrame) and a panel (DisplayDatePanel). A CalendarPanel is centrally embedded into the frame and a DisplayDatePanel is embedded to the south. The frame also has a button labelled OK on the right (east). If clicked, the date currently selected in the CalendarPanel is displayed in the DisplayDatePanel. We do not list the latter classes; they can be downloaded from the books home page. A screen shot is shown in Figure 16.8. File: its/ComboBox/CalendarPanel.java
1. package its.ComboBox; 2. 3. import java.awt.GridLayout; 4. import java.awt.event.ActionEvent;
195
5. 6. 7. 8. 9. public class CalendarPanel extends JPanel { 10. private String[] months = {"January","February","March","April", 11. "May","June","July","August", 12. "September","October","November","December"}; 13. private JComboBox yearBox = new JComboBox(); 14. private JComboBox monthBox = new JComboBox(months); 15. private JComboBox dayBox = new JComboBox(); 16. private JLabel yearLabel = new JLabel("Year "); 17. private JLabel monthLabel = new JLabel("Month "); 18. private JLabel dayLabel = new JLabel("Day "); 19. private JLabel dummy = new JLabel(""); 20. private int startyear; 21. private int endyear; 22. 23. public CalendarPanel() { 24. this.setLayout(new GridLayout(4,2)); 25. startyear = 2004; 26. endyear = 2007; 27. initBoxes(); 28. this.add(dayLabel); 29. this.add(dayBox); 30. this.add(monthLabel); 31. this.add(monthBox); 32. this.add(yearLabel); 33. this.add(yearBox); 34. this.add(dummy); 35. 36. yearBox.setEditable(true); 37. CalenderListener cList = new CalenderListener(); 38. yearBox.setActionCommand("YearChanged"); 39. yearBox.addActionListener(cList); 40. monthBox.setActionCommand("MonthChanged"); 41. monthBox.addActionListener(cList); 42. } 43. 44. private void initBoxes() 45. { 46. for (int y=startyear;y <= endyear;y++ ) 47. { 48. yearBox.addItem(Integer.toString(y)); 49. }//for 50. for (int d=1;d<=31 ;d++ ) 51. import import import import java.awt.event.ActionListener; javax.swing.JComboBox; javax.swing.JLabel; javax.swing.JPanel;
196
52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97.
{ dayBox.addItem(Integer.toString(d)); }//for monthBox.setMaximumRowCount(12); dayBox.setMaximumRowCount(10); yearBox.setMaximumRowCount(4); } private void setDayListTo(int desiredLength){ int currentLength = dayBox.getItemCount(); if(currentLength < desiredLength){ for (int i = currentLength+1; i <= desiredLength; i++) { dayBox.addItem(Integer.toString(i)); }//for i } else if (currentLength > desiredLength) { for (int i = currentLength-1; i >= desiredLength; i--) { dayBox.removeItemAt(i); }//for i } } public String getDate(){ return((String)dayBox.getSelectedItem()+"."+ (String)monthBox.getSelectedItem()+" "+ (String)yearBox.getSelectedItem()); } class CalenderListener implements ActionListener { public void actionPerformed(ActionEvent evt){ String actionCommand = evt.getActionCommand(); int year = Integer.parseInt(((String)yearBox.getSelectedItem()).trim()); int month = monthBox.getSelectedIndex(); if((year > 2099) || (year < 1901)){ year = 2000; yearBox.setSelectedItem("2000"); } if(actionCommand.equals("YearChanged")) { if((year % 4 == 0) && (month == 1)){ setDayListTo(29); } } else if(actionCommand.equals("MonthChanged"))
197
98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111. 112. 113. 114. 115. 116. 117. 118.
{ if((month == 0) || (month == 2) || (month == 4) || (month == 6) || (month == 7) || (month == 9) || (month == 11)){ setDayListTo(31); } else if(month == 1){ if(year % 4 == 0){ setDayListTo(29); } else { setDayListTo(28); }//ifelse }//ifelse else{ setDayListTo(30); }//ifelse }//ifelse }//actionPerformed }//internal class }
198
HORIZONTAL_SPLIT and VERTICAL_SPLIT in class JSplitPane. Observe that choosing HORIZONTAL_SPLIT determines that the two components are placed horizontally next to each other; the divider bar is the vertical. Similarly VERTICAL_SPLIT results in placing one component on top of the other and a hori-
fraction refers to the current width of the split-pane. If this method is called before the component is visible then the width is zero and the divider will be at the left (or top) margin.
setOneTouchExpandable(boolean b) if parameter b is true then two small
arrows appear on the divider bar. They point towards the two areas. When clicking on one arrow the divider bar jumps in that direction. If the divider bar is not at a margin of the split pane, it jumps to that margin to which the arrow points. If it is at the margin it jumps back to the previous position. If parameter b is false the arrows are not shown and the described functions are not available. In our demonstration SplitPaneFrame we use nested split panes. At rst a split pane splitPane1 with a vertical divider (HORIZONTAL_SPLIT) is centrally embedded into a frame. The width of the divider is set to 20 pixels and it receives an arrow to enable jumping. Into its right area another split pane splitPane2 is embedded. This has a horizontal divider. This arrangement results in three areas. Into the left area of splitPane1 we embed a JList which lists the le names of some directory. Into each of the two areas of splitPane2 we embed a JLabel which displays an ImageIcon. All three aforementioned components are embedded using scroll panes so that their whole content can be accessed by scrolling.
199
The control part of the application allows the user to select an image from the list on the left and display it in the lower right area. To this end he or she has to select a le name in the list and press the button that is embedded into the frames South position. The control part is here implemented as an anonymous listener. The program checks whether the selected le has an extension which indicates an image le. Only les with extensions .jpg or .png are loaded; other selections are ignored. One of the images, the one of the orange, is available as an uncompressed PNG-format and a strongly compressed JPG-format. Loading the latter into the lower right component and comparing it with the constantly displayed PNG-image at the upper right makes the loss of quality visible. We list the program below and a screen shot is shown in Figure 16.9. File: its/SplitPanes/SplitPaneFrame.java
package its.SplitPanes; import import import import import import its.SimpleFrame.SimpleFrame; java.awt.*; java.awt.event.ActionEvent; java.awt.event.ActionListener; java.io.File; javax.swing.*; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.
public class SplitPaneFrame extends SimpleFrame { private JButton okButton; private JList list; private JLabel picLabel1, picLabel2; private ImageIcon picture1, picture2;
200
17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63.
private static String picturePath = "./its/TestData/"; public SplitPaneFrame() { this.setSize(800,400); JSplitPane splitPane1 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); JSplitPane splitPane2 = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane1.setOneTouchExpandable(true); splitPane1.setDividerSize(20); this.getContentPane().add(splitPane1,BorderLayout.CENTER); splitPane1.setRightComponent(splitPane2); picture1 = new ImageIcon(picturePath+"/Orange.png"); picture2 = new ImageIcon(picturePath+"/Banana.png"); picLabel1 = new JLabel(picture1); JScrollPane sp2a = new JScrollPane(picLabel1); splitPane2.setTopComponent(sp2a); picLabel2 = new JLabel(picture2); JScrollPane sp2b = new JScrollPane(picLabel2); splitPane2.setBottomComponent(sp2b); splitPane2.setDividerLocation(150); splitPane1.setDividerLocation(100); File startFile = new File(picturePath); list = new JList(startFile.listFiles()); JScrollPane sp = new JScrollPane(list); splitPane1.setLeftComponent(sp); okButton = new JButton("OK"); okButton.setBackground(Color.cyan); this.getContentPane().add(okButton,BorderLayout.SOUTH); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String filename = ((File)(list.getSelectedValue())).getPath(); if((filename.endsWith(".png")) || (filename.endsWith(".jpg")) || (filename.endsWith(".PNG")) || (filename.endsWith(".JPG"))) { picLabel2.setIcon(new ImageIcon(filename)); } } }); }
201
64. 65. 66. 67. 68. 69.
public static void main(String[] args) { SplitPaneFrame spf = new SplitPaneFrame(); spf.showIt("Split-Panes",200,200); } }
Figure 16.10 The third page of the example application for tabbed panes
202
TabbedPane(); TabbedPane(int tabPosition); add(Component comp); add(String tabText, Component comp); add(Component comp, int index); setTitleAt(int pos, String tabTitle); setSelectedComponent(Component comp); setSelectedIndex(int pos)
TabbedPane() constructor. The tabs are placed on the upper edge. TabbedPane(int tabPosition) constructor in which one can specify the position of the tabs. The four values for parameter tabPosition are predened in class JTabbedPane: TOP, BOTTOM, LEFT, RIGHT. add(Component comp) adds comp as last page. Last means that the tab is the
right-most (or bottom-most) one. The tab does not have any text.
add(String tabText, Component comp) adds comp as last page. Last means that the tab is the right-most (or bottom-most) one. The tab is labelled by tabTitle. add(Component comp, int index) adds comp such that the corresponding tab is at position index. The tab does not have any text. setTitleAt(int pos, String tabTitle) assigns a new text tabTitle to the tab at position pos. setSelectedComponent(Component comp) selects the page containing component comp, i.e. this page is made visible. setSelectedIndex(int pos) selects the page whose tab is at position pos, i.e.
this page is made visible. The listing TabbedPaneFrame below shows how to use a tabbed pane. As we do not add any new functions we use a JTabbedPane and do not extend it into a new class. The tabbed pane is glued into a frame. Then four components are added to to the tabbed pane. At rst we add two JLabels, each displaying an image. Then we add red ColorPanel. This is dened in the package its.SimpleFrameWithPanels. Finally we add a JTextArea which contains some text describing what the four components are. The JTextArea is added at position 0 so that its tab is the left-most. We then set the selected index to 1 which makes the image of the plum visible. The result is shown in Figure 16.10. File: its/TabbedPane/TabbedPaneFrame.java
1. package its.TabbedPane; 2. 3. import its.SimpleFrame.SimpleFrame; 4. import its.SimpleFrameWithPanels.ColorPanel;
203
5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 30. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51.
import java.awt.Color; import javax.swing.*; public class TabbedPaneFrame extends SimpleFrame { private static String picturePath = "./its/TestData/"; public TabbedPaneFrame() { // Create a tabbed pane and glue into the frame JTabbedPane tabbedPane = new JTabbedPane(); this.getContentPane().add(tabbedPane); // Generate two labels each of which contains an image // and add them to the tabbed pane ImageIcon plum = new ImageIcon(picturePath+"plum.png"); JLabel plumLabel = new JLabel(plum); ImageIcon lime = new ImageIcon(picturePath+"lime.png"); JLabel limeLabel = new JLabel(lime); tabbedPane.add("Plum",plumLabel); tabbedPane.add("Lime",limeLabel); // Generate a red panel from its.SimpleFrameWithPanels.ColorPanel // and add it. ColorPanel redPane = new ColorPanel(Color.red); tabbedPane.add("Red Pane", redPane); // Generate a text area and add it as the first (0-th) // component. JTextArea legend = new JTextArea(); legend.setText("Legend in a JTextArea\n"+ "JLabel showing a plum\n"+ "JLabel showing a lime\n"+ "A red Panel"); tabbedPane.add(legend,0); tabbedPane.setTitleAt(0,"Legend"); tabbedPane.setSelectedIndex(1); } public static void main(String[] args) { TabbedPaneFrame tpf = new TabbedPaneFrame(); tpf.setSize(300,200); tpf.showIt("Tabbed Panes",200,200); } }
204
Class JTabbedPane offers a large number of ways to change the appearance. One can, for example, use icons as labels for the tabs or change the background colour of the pages. If a tabbed pane contains a large number of pages and the tabs do not t into a single line then they are arranged in rows. This can have the effect that the space for displaying the selected page is very small. Since version 1.4 of the SDK it is possible to use scrollable tabs. The tabs are then arranged in a single row which can be scrolled left or right.
Exercises
16.1 Implement a GUI with the same layout as ListTransferFrame and the following functions. When an entry in the left list is selected it is moved to the right one, i.e. removed from the left and added to the right list. When an entry in the right list is selected it is moved to the left one. Implement a GUI with a similar layout as ListTransferFrame and the following functions. Add two buttons labelled Move to right and Move to left to the GUI and allow arbitrary selections in both lists. Selecting entries in one of the lists does not trigger any action. Pressing one of the buttons causes the appropriate selection to be moved to the other list.
16.2
Grid-bag layout
17
We present the most powerful but also the most complicated layout manager of Java, the grid-bag layout. This layout manager allows us to design complex arrangements of components without using stacks of nested intermediate components.
The grid layout described in Chapter 2 allows a component to be embedded in one cell of the grid and all cells have the same size. The grid-bag layout discussed here allows different widths or heights for different columns and rows. In addition, a component can be embedded to cover more than one cell. These cells then form a bag, i.e. a rectangular sub-grid. When using grid-bag layout, the components are not placed according to the order of the add commands. The placement is done by passing to the layout manager information on the position of every component and on the size of the bag it has to be embedded into. The grid-bag layout is a bit tricky to use. Many beginners give up using this very powerful tool after a few tries because it does not do what it is told to do. We rst discuss and analyse these problems and then show how to solve them. Only then do we turn to describing the Java classes involved in realizing a grid bag layout. The example layout is specied in Figure 17.1. The layout consists of six panels that are embedded into a parent component, e.g. the content pane of frame. As a rst step we determine the size of the grid, i.e. the number of rows and columns. Here a 4 4 grid seems appropriate. The grid is indicated by dashed lines in Figure 17.1. The columns and rows are numbered from left to right and from top to bottom, both starting with zero. The grid structure, i.e. the number of rows and columns, is not explicitly specied by the programmer. Instead the programmer species the size and location of the embedded components. The layout manager then computes the necessary grid structure. This computation is, however, not always done in the way we expect, as we shall shortly see. The size of a component is specied as the number of columns and rows it spans. In Figure 17.1 panel A spans two columns and three rows and panel D spans one column and two rows. The location of a component is specied by the leftmost column and top-most row it covers. In Figure 17.1 panel A is located at (0, 0) and B is at (2, 0). Table 17.1 summarizes the parameters for the six panels. In the next section we describe how these values are passed to the layout manager.
206
0 0
B
1
A E D C F
Figure 17.1 Sketch of the desired layout. The tick marks at the upper and left margins indicate the dimensions of the panels. For example, panels A and C should be half as wide as the parent component and panel A should be three-quarters the height of the parent component. The underlying 4 4 grid is shown as dashed lines. With respect to this grid, panel C has a width of 2 (columns) and a height of 1 (row). Its position is column 0 and row 3
Table 17.1 The size and location parameters for the six panels in the layout of Figure 17.1
Panel Width Height Left Top
A B C D E F
2 2 2 1 1 1
3 2 1 2 1 1
0 2 0 2 3 3
0 0 3 2 2 3
Using these values would give the result shown in Figure 17.2. The corresponding program GridBagFrameBad is not listed here but can be downloaded from the books home page. The gure shows two deciencies: the panels A and C are only one-third the width of the frame (not a half) and panel B is only one-third the height of the frame, not a half. This is because when the layout manager computes the grid size from the values in Table 17.1 it deletes unnecessary column and row boundaries. These are boundaries that do not coincide with the boundary of some component. In our example the boundary between columns 0 and 1 in Figure 17.1 is unnecessary. At no place does it coincide with a component boundary. On the other hand the boundary between columns 2 and 3 is necessary; it coincides with the right boundary of panel D and the left boundaries of panels E and F. Inspecting the other row and column boundaries shows that the one between row 0 and row 1 is unnecessary and is thus deleted by the layout manager. All other bounds are necessary. The layout used by the layout manager is then a 3 3 grid. All columns are equally wide and all rows are equally high. This explains why the result shown in Figure 17.2 differs from the specication in Figure 17.1. The way to adjust the width and height of components is thus not to introduce additional columns and rows but to assign weights to the columns and rows. We
Grid-bag layout
207
sketch the desired layout in Figure 17.3. Only the necessary columns and rows are introduced. Recall that the columns and rows are not explicitly dened by the user. They are computed by the layout manager from dimensions and locations of the embedded components. The gure also shows the weights given to the columns and rows. The rst column gets weight 2.0, the second and third ones both have
2.0 0 1.0 1 1.0 2
4.0
2.0
B A
1.0
E D C F
1.0
4.0
Figure 17.3 A sketch of the desired layout that reects the columnrow structure implicitly used by the layout manager. The resulting 3 3 grid is shown by dashed lines. The weights are shown at the top and left. The sums of the column and row weights are shown in bold
208
Table 17.2 The size, location and weight parameters for the six panels in the layout of Figure 17.3
Panel Width Height Left Top Weightx Weighty
A B C D E F
1 2 1 1 1 1
2 1 1 2 1 1
0 1 0 1 2 2
0 0 2 1 1 2
weight 1.0. We still have the problem that we cannot directly set the weights for the rows or columns because rows and columns are internal concepts of the layout manager. We can, however, assign two weights to the embedded components. The horizontal weight (called weightx) affects the components width and the vertical one (called weighty) affects the components height. The weight of a column is then the maximum weight of a component in that column. Thus, by setting the horizontal weight of panel A to 2.0 and that of panel C to 1.0, column 0 receives weight 2.0. The weight of the topmost row (row 0) is set by using a vertical weight of 2.0 for panel B. Using the vertical weight of panel A for this purpose would have side-effects on row 1 because panel A spans rows 0 and 1. All other horizontal and vertical weights are set to 1.0. Then row 1, row 2, column 1 and column 2 all receive weight 1. The parameters are listed in Table 17.2. These settings will result in the desired layout. The column widths (or row heights) are then computed as follows. Let w0 , . . . , wn be the n + 1 column weights. Let W = w0 + + wn be the sum of the weights. Then the width of column i is a fraction of wi /W of the width of the parent component. In our case w0 = 2.0, w1 = 1.0, w2 = 1.0 and W = 4.0. Hence column 0 gets 2.0/4.0 = 1/2 of the width of the parent component while columns 1 and 2 each get 1.0/4.0 = 1/4. This shows that the absolute values of the weights are irrelevant; only their ratio is important. We might have used the set w0 = 0.2, w1 = 0.1, w2 = 0.1 or w0 = 6.0, w1 = 3.0, w2 = 3.0 without changing the result.
Grid-bag layout
209
A GridBagConstraints object has a number of elds to store the layout parameters of a component. The class does not have any set or get methods to access these parameters. Instead the elds are public and can be directly accessed from the outside quite a non-object-oriented approach. In the following we list some of the elds and their interpretation.
int int int int double double int gridwidth gridheight gridx gridy weightx weighty fill
the number of columns spanned the number of rows spanned the left-most column the top-most row the horizontal weight the vertical weight determines whether the component is resized to t the bag.
The layout parameter fill determines whether an embedded component should be resized such that it lls the bag. The possible values NONE, BOTH, HORIZONTAL and VERTICAL are dened as constants in GridBagConstraints. We always use fill = BOTH here, which resizes the component to precisely t its bag. The reader is encouraged to try other settings for the various components. Class GridBagConstraints has more elds which we do not discuss here; the user is referred to the Java documentation. The following code snippet shows how to set the constraints for panel A of our application. First an instance of GridBagConstraints is generated, then the elds are set to the desired values. The values are those of the corresponding line in Table 17.2.
GridBagConstraints constraintsA = new constraintsA.gridwidth constraintsA.gridheight constraintsA.gridx constraintsA.gridy constraintsA.weightx constraintsA.weighty constraintsA.fill = = = = = = = GridBagConstraints();
1; // The width of panel A. 2; // The height of panel A. 0; // Left column of panel A. 0; // Top row of panel A. 2.0; // The horizontal weight of panel A. 1.0; // The vertical weight of panel A. GridBagConstraints.BOTH; // resize panel A // to fit its bag.
We still have to link the constraints to the panel. This is done by using method:
setConstraints(Component embeddedComp,GridBagConstraints constraints);
of class GridBagLayout. In our case the panel is called panelA and the constraints constraintsA:
setConstraints(panelA,constraintsA);
210
Summarizing, the following steps are necessary to use a grid-bag layout for embedding components comp0, . . . ,compK, . . . compN into parent component parent:
Sketch the desired layout on paper and determine how many rows and columns
rameters. (b) Link the constraints constraintsK to compK using gbl.setConstraints(c) Embed
(compK,constraintsK). compK into parent by parent.add(compK) (or .getContentPane().add(compK) in case parent is a frame). parent
Creating and setting the constraints for every component would result in many lines of nearly identical code. In the example application GridBagFrame we dened a private method easyConstraints that does all this work. It receives the constraint values, the component and the layout as arguments. It then creates an instance of GridBagConstraints, sets the values, and links constraints and components. The eld fill is always set to BOTH. The GUI created by this program is shown in Figure 17.4.
Grid-bag layout
211
File: its/GridBag/GridBagFrame.java
package its.GridBag; import import import import import its.Borders.BorderPanel; its.SimpleFrame.SimpleFrame; java.awt.GridBagConstraints; java.awt.GridBagLayout; javax.swing.JComponent; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44.
public class GridBagFrame extends SimpleFrame { private GridBagLayout gbl = new GridBagLayout(); public GridBagFrame() { this.getContentPane().setLayout(gbl); BorderPanel panelA = new BorderPanel("Panel A"); BorderPanel panelB = new BorderPanel("Panel B"); BorderPanel panelC = new BorderPanel("Panel C"); BorderPanel panelD = new BorderPanel("Panel D"); BorderPanel panelE = new BorderPanel("Panel E"); BorderPanel panelF = new BorderPanel("Panel F"); this.getContentPane().add(panelA); this.getContentPane().add(panelB); this.getContentPane().add(panelC); this.getContentPane().add(panelD); this.getContentPane().add(panelE); this.getContentPane().add(panelF); easyConstraints(gbl,panelA,1,2,0,0,2.0,1.0); easyConstraints(gbl,panelB,2,1,1,0,1.0,2.0); easyConstraints(gbl,panelC,1,1,0,2,1.0,1.0); easyConstraints(gbl,panelD,1,2,1,1,1.0,1.0); easyConstraints(gbl,panelE,1,1,2,1,1.0,1.0); easyConstraints(gbl,panelF,1,1,2,2,1.0,1.0); } private void easyConstraints(GridBagLayout GLB,JComponent Comp, int w, int h, int x, int y,double wx, double wy){ GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.BOTH; constraints.gridwidth = w; constraints.gridheight = h; constraints.gridx = x; constraints.gridy = y;
212
45. constraints.weightx = wx; 46. constraints.weighty = wy; 47. gbl.setConstraints(Comp,constraints); 48. } 49. 50. public static void main(String[] args){ 51. GridBagFrame gridbagFrame = new GridBagFrame(); 52. gridbagFrame.showIt("Grid-Bag Layout"); 53. 54. } 55. }
Exercises
17.1 17.2 17.3 Change the constraint parameters for some of the panels in application GridBagFrame. Look at the result and explain why it looks the way it does. Change method easyConstraints so that the fill value can also be set. Try different settings and explain the result. Design a grid-bag layout for the GUI in Figure 17.5.
C B A E F G D
PART
III
Advanced topics
Styling text
18
When text is displayed, there is often a need to use different font styles, sizes or colours to emphasize parts of the text. We now show how this can be done by using a so-called document model for the text. The model allows style attributes to be assigned to parts of the text that are automatically moved when text is inserted or deleted.
Swing provides non-graphical classes that model text documents. They allow manipulations of the text such as insertion and deletions and the assignment of different styles to different parts of the text. When such a text model is displayed in a text component, the styles become visible. By using a text model the style and formatting are done abstractly and are independent of the display used. Listeners can be attributed to documents to trigger actions in response to changes in the text. The javax.swing.text sub-library contains the interface Document and some classes implementing this interface. We will use class DefaultStyledDocument which supplies many methods to modify the layout of normal (ASCII) text. There is another predened class, HTMLDocument, which provides a method for styling HTML formatted text. If these classes do not provide the layout features for the text under consideration, then one has to dene ones own document class by implementing interface Document or by extending an existing class. We proceed by introducing a number of helper classes for formatting text documents and then show how they are used.
18.1 Positions
Most operations on documents rely on the concept of a position marker. These are dened in class Position1 . A text is a sequence of letters or symbols. We prefer the word symbols because spaces and punctuation signs are also considered. A position designates a place between two consecutive symbols. The offset of a position is the number of symbols between it and the beginning of the text. The position before the rst symbol (symbol number zero) has offset 0, the one after
1
The name Position is misleading. An instance of Position does not specify a position in the document that is xed, but one that moves if text is deleted or inserted.
216
Advanced topics
0 1 2 3 4 5 6 7 8 9 10 11 12 13
S w i n g
i s
g o o d .
P 1
P 2
P 3
Figure 18.1 A text of 14 symbols. The numbering of the symbols begins with 0 and is shown above the text. Three positions markers, P1 , P2 and P3 , are shown. P1 has offset 0 as there is no symbol between it and the start of the text, P2 has offset 6 because it is immediately before the 6th symbol and P3 has offset 9
the rst symbol has offset 1, etc. In the next section we use position markers to specify the beginning and end of text parts that receive specic formatting. Figure 18.1 illustrates the concept of position markers. Let us now see what happens to position markers when the text is changed. We begin by inserting a single symbol, an m say, between the symbols 5 and 6 of the text in Figure 18.1. Then all symbols of the original text beginning with the one numbered 6 are moved one place to the right and the new symbol is inserted at place 6. Position marker P3 is moved with the text; its offset is increased from 9 to 10. The new letter is inserted precisely at the position occupied by marker P2 . The convention is that the insertion happens before the marker. Hence the offset of P2 is increased to 7. The result is shown in Figure 18.2. Let us now see what happens to position markers when text is deleted. Let us rst assume that the deleted text does not contain a position marker. In the original text of Figure 18.1 let us delete the symbols 2 to 5 (ing_). All position markers to the right of the deleted text will move left, i.e. their offsets are decreased. The result is shown in Figure 18.3. Let us now delete the symbols 4 to 10 (g_is_go) in Figure 18.1, including both spaces. This range includes position markers P2 and P3 . Though the symbols are deleted, the two markers are not. They are now at the position formerly occupied by the deleted text. Figure 18.4 shows the result.
0 1 2 3 4 5 6 7 8 9 10 11 12 13
S w i n g
m i s
g o o d .
P 1
P 2
P 3
S w i s
g o od .
P 1
P 2
P 3
Figure 18.3 The result of deleting symbols ing_ in Figure 18.1. P2 and P3 move left to offsets 2 and 5
Styling text
0 1 2 3 4 5 6
217
S w i n o d .
{
PP 2 3
P 1
Figure 18.4 As a result of deleting symbols g_is_go the two position markers P2 and P3 now occupy the same place. Both have offset 4
To set the attribute values of an attribute set attrSet one uses methods from another class, namely StyleConstants. We list and explain some of them:
setForeground(SimpleAttributeSet attrSet, Color c) setBackground(SimpleAttributeSet attrSet, Color c) setFontSize(SimpleAttributeSet attrSet, int size) setBold(SimpleAttributeSet attrSet, boolean b) setItalic(SimpleAttributeSet attrSet, boolean b) setUnderline(SimpleAttributeSet attrSet, boolean b) setSuperscript(SimpleAttributeSet attrSet, boolean b) setSubscript(SimpleAttributeSet attrSet, boolean b)
setForeground(SimpleAttributeSet attrSet, Color c) sets the text colour in attrSet to c. The default is the text colour of the graphical component
218
Advanced topics setBold(SimpleAttributeSet attrSet, boolean b) determines whether the text formatted according to attrSet should be bold (b = true) or not (b = false). The default value is b = false. setItalic(SimpleAttributeSet attrSet, boolean b) determines whether the text formatted according to attrSet should be in italics (b = true) or not (b = false). The default value is b = false. setUnderline(SimpleAttributeSet attrSet, boolean b) determines whether the text formatted according to attrSet should be underlined (b = true) or not (b = false). The default value is b = false. setSuperscript(SimpleAttributeSet attrSet, boolean b) determines whether the text formatted according to attrSet should appear as superscript (b = true) or not (b = false). The default value is b = false. setSubscript(SimpleAttributeSet attrSet, boolean b) determines whether the text formatted according to attrSet should appear as subscript (b = true) or not (b = false). The default value is b = false.
Once an attribute set is dened, it can be assigned to parts of the text. The assignment is done using the following method from class Document:
setCharacterAttributes(int offset, int length, AttributeSet attrSet, boolean replace)
This formats the text part consisting of as many symbols as the current value of length and beginning with the symbol at place offset as specied by the attribute values in attrSet. The parameter replace determines whether the attributes previously assigned to that text part should be removed rst. In the following example we dene an attribute set ugly for formatting a text part in yellow, italic font on red background. Then the text in the document doc from symbols 203 to 234 (32 symbols) is formatted in this way. Another attribute set greenText just denes the font colour to be green. This is then used to format symbols 200 to 214. By setting the parameter replace to false the attributes of ugly other than the colour are maintained where the ranges overlap (symbols 203 to 214). In this range we have green, italic text on a red background. The reader is encouraged to try this and other examples of overlapping text attributes.
// Define attribute set ugly and ... SimpleAttributeSet ugly = new SimpleAttributeSet(); // ... set its attributes to yellow italics on red StyleConstants.setForeground(ugly, Color.yellow); StyleConstants.setBackground(ugly, Color.red); StyleConstants.setItalic(ugly, true); // Format 32 symbols beginning with 203 using ugly. // Any previously assigned attributes are erased. styledDoc.setCharacterAttributes(203,32,ugly,true);
Styling text
// Define attribute set greenText and ... SimpleAttributeSet greenText = new SimpleAttributeSet(); // ... set its attributes to green font StyleConstants.setForeground(greenText, Color.green); // Format 15 symbols beginning with 200 using greenText. // Any previously assigned attributes except the text colour // are maintained. styledDoc.setCharacterAttributes(203,15,greenText,false);
219
When a text range is formatted, position markers are automatically assigned to its beginning and end. These markers not the values for offset and length specied in setCharacterAttributes determine the range of the formatting henceforth. Thus, the formatted range will move left or right when text is deleted or inserted before it. Symbols inserted into the formatted range receive the same formatting, thus extending the range.
The instance devt of class DocumentEvent contains information on the event. The class provides the following methods to access this information:
Document getDocument() int getLength() int getOffset() DocumentEvent.EventType getType() DocumentEvent.Element getChange(Element elem)
220
Advanced topics
getDocument() returns a reference to the Document that triggered the event. This
attributes that changed. A number of further classes are involved in this. We do not discuss the mechanism here; refer to the Java documentation.
DefaultStyledDocument() is the constructor. createPosition(int offset) generates a position marker, i.e. an instance of Position, before the symbol currently is at position offset. If the parameters are outside the current size of the document then a BadLocationException
will occur.
addDocumentListener(DocumentListener docList) assigns docList as docu-
the document. If the parameters are outside the current size of the document then a BadLocationException will occur.
Styling text insertString(int offset, String text, AttributeSet attrSet) inserts the symbols in string text before the symbol currently at position offset in the document. The new text is formatted as specied in attrSet. If the parameters are outside the current size of the document then a BadLocationException will occur. remove(int offset,int length) deletes as many symbols as the current value of length, starting with the one currently at position offset. If the parameters are outside the current size of the document then a BadLocationException
221
will occur. The text of a document is often read from some le. This is easily done using the utility class EditorKit, which is also found in the javax.swing.text package. Every Swing component textComp that can display documents has an editor kit. It can be accessed by textComp.getEditorKit(). We need the following method from EditorKit:
read(FileReader fReader,Document doc, int offset);
The le reader should be assigned to the le whose content we want to insert into the document doc. Parameter offset species that position in the document before which the le content is inserted. For a new document offset = 0 is used. The following code snippet demonstrates the use. We want to read a le filename into DefaultStyledDocument called doc and then display it in a Swing component textComp:
DefaultStyledDocument doc = new DefaultStyledDocument(); textComp.setDocument(doc); EditorKit ediKit = textComp.getEditorKit(); FileReader fReader = new FileReader(filename); ediKit.read(fReader,doc,0);
222
Advanced topics
Figure 18.5 The formatting achieved by program DocumentFrame. The text NEW! has been added. The status panel displays a protocol of the operations that have been performed
File: its/Document/DocumentFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. package its.Document;
Styling text
223
12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58.
public class DocumentFrame extends SimpleFrame { private JTextPane textPane; private DocumentStatusPanel statusBar; private DefaultStyledDocument styledDoc; private EditorKit ediKit; private Position p1,p2; private final String filename = "./its/TestData/DocTest.txt";
public DocumentFrame() { textPane = new JTextPane(); statusBar = new DocumentStatusPanel(); styledDoc = new DefaultStyledDocument(); textPane.setDocument(styledDoc); ediKit = textPane.getEditorKit(); JScrollPane sp = new JScrollPane(textPane); this.getContentPane().add(sp,BorderLayout.CENTER); this.getContentPane().add(statusBar,BorderLayout.SOUTH); this.setSize(500,500); readFile(filename); DocAtt.createAttributes(); formatText(); try { p1 = styledDoc.createPosition(228); p2 = styledDoc.createPosition(248); } catch (Exception ex) { System.out.println("Problem creating Positions."); } styledDoc.addDocumentListener(new DocListener(statusBar,p1,p2)); statusBar.update("",p1.getOffset(),p2.getOffset()); } private void readFile(String filename){ try { ediKit.read(new FileReader(filename),styledDoc,0); } catch (Exception ex)
224
59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92.
Advanced topics
// Formats some text ranges using attribute sets defined in // class DocAtt private void formatText(){ styledDoc.setCharacterAttributes ( 73,10,DocAtt.underlinedText,false); styledDoc.setCharacterAttributes(85, 3,DocAtt.bigText,false); styledDoc.setCharacterAttributes(90, 4,DocAtt.boldText,false); styledDoc.setCharacterAttributes(98, 7,DocAtt.italicText,false); styledDoc.setCharacterAttributes(110, 7,DocAtt.greenText,false); styledDoc.setCharacterAttributes (131,13,DocAtt.superscriptText,false); styledDoc.setCharacterAttributes (147,11,DocAtt.subscriptText,false); styledDoc.setCharacterAttributes (171,14,DocAtt.whiteOnBlackText,false); styledDoc.setCharacterAttributes(190,18,DocAtt.ugly,true); styledDoc.setCharacterAttributes(228,20,DocAtt.redText,false); }
Exercises
18.1 Use program DocumentFrame to check the results of inserting or deleting text before, in and after formatted text ranges. Also check what happens to the position markers when text is inserted or deleted at different places.
Styling text
225
18.2
Add a menu bar similar to the one in class Editor of Chapter 12. Implement the functions for the menu items. Modify the Search function such that all occurrences of the search text are highlighted. Make sure that this also works if the search text is right at the beginning or the end of the text. Add a menu item Show positions. When selecting it, all user-added position markers are shown by inserting a blue letter M on yellow background at the markers position. Selecting the menu entry again makes them disappear.
18.3
Printing in Java
19
In this chapter we briey discuss the printing mechanism of Java. We present an easy way to print graphical components. Finally we implement a generic class that can be used to add print capabilities to other applications.
This library provides classes and methods for adding printing capabilities to graphical interfaces. We do not present all classes nor do we consider all methods. We focus on those necessary to implement an easy way to access a printer. More can be found in Java documentation. Our example application consists of a frame PrintFrame with a menu. The menu has only one item: Print. We dene a panel PrintPanel with preferred size 300 200. It shows a rectangle, a lled oval and a text. Another rectangle is drawn at the edge of the panel to indicate its margin. This panel is then glued into the content pane of the frame. Figure 19.1 shows how the application looks. The functions of the application are simple: when the menu item Print is selected, the content of the embedded PrintPanel is printed. The general approach to printing a graphical component printableComp is as follows:
The component printableComp has to implement the interface Printable by dening the method print. One has to create an instance printJob of class PrinterJob and pass component printableComp to it. This is done by calling the method printJob.setPrintable(printableComp). The PrinterJob is then started by printJob.print().
One can specify that a print dialogue appears before the printing is started. This dialogue is the standard print dialogue of the operating system. It normally allows you to select the printer and the pages to be printed and possibly more. The programmer does not have to bother how the graphics is converted into data that the printer understands or how the data are sent to the printer.
Printing in Java
227
Figure 19.1 A picture of the print application. Note the rectangle at the margins of the panel. This is added to control whether the whole panel is printed
This method is called by the runtime system when the component has to be printed. The runtime system also supplies the parameters graphics, pageFormat and pageIndex. This method plays the same role for printing as paintComponent does for drawing. In fact, one can think of printing as drawing to another device. The actual printing is started by calling paint inside method print of the component to be printed. As with paintComponent we should not call print ourselves. The parameters have the following meaning:
Graphics graphics links the application to the platform-specic printer drivers. This is similar to the Graphics object in the method paintComponent which links to the rendering routines of the operating system. The Graphics object
formation on the page size and layout of the selected printer (or the default printer). The class provides get-methods to access these parameters.
int pageIndex is the number of the page to be printed. The runtime system calls print with varying values of pageIndex, namely 0, 1, 2, 3, . . . . If the print command is successful it returns the integer constant PAGE_EXISTS (dened in interface Printable). Otherwise print returns NO_SUCH_PAGE and
228
Advanced topics
the runtime system will not call print again. The programmer has to make sure that the correct value is returned. This way multi-page documents can be printed. Let us now look at a simple implementation of the interface Printable. We implement method print such that it prints only one page; our panel is a one-page document. We rst check whether the parameter pageIndex is 0 (the rst page). If so we call the paint-method of the panel and return PAGE_EXISTS. If pageIndex is greater than 0 we return NO_SUCH_PAGE. The runtime system will then stop calling print. Let us analyse what is happening in the rst case, i.e. when pageIndex is 0. By calling the paint-method of the panel, a repainting is initiated. The painting is done on some device. Which device this is (the screen or the printer) is determined by the Graphics object. In our example we pass the Graphics object g which we receive as an argument of print on to the paint method. This object is generated by the runtime system and refers to some printer. As a result, paint draws to the printer and not to the screen. We cannot use repaint here because it does not allow a Graphics object to be passed as an argument; repaint initiates a redrawing of the screen. The skeleton of the implementation then looks like this:
public int print(Graphics g, PageFormat pageFormat, int pageIndex) { if (pageIndex > 0) { return(NO_SUCH_PAGE); } else { this.paint(g); return(PAGE_EXISTS); } }
There are, however, some technical precautions to be taken. First of all one should switch off the double buffering of the graphics. This feature is important for a nice display when the screen is redrawn but might spoil the printing. After the printing, the double buffering should be switched on again. We therefore surround the paint command by some commands as shown below. We do not elaborate on the class RepaintManager used here:
RepaintManager currentManager = RepaintManager.currentManager(this); currentManager.setDoubleBufferingEnabled(false); paint(g); currentManager.setDoubleBufferingEnabled(true);
Another topic to consider when printing is the placement and scaling of the printed image. The origin of the printer coordinate system is normally the upper left corner of the paper. Most printers, however, cannot print on some small margin of the paper for technical reasons. Therefore the upper left corner of the printable area of the paper is below and to the right of its upper left corner. The Graphics
Printing in Java
229
object of the print-method identies the upper left corner of the component to be printed with the upper left corner of the paper. Without any correction the image of the component would not show parts outside the printable area. We therefore have to shift the image such that it lies completely in the printable area. Class Graphics provides the method translate for this purpose. The command
g.translate(int xNew, int yNew)
moves the origin of the coordinate system of the Graphics object g to the point (xnew , ynew ) (in the coordinates before the translation). If xnew > 0 and ynew > 0 then the image is shifted to the right and downwards. It remains to determine the amount of the translation. This information is contained in the PageFormatobject. The commands
double pageFormat.getImageableX() double pageFormat.getImageableY()
return the x- and y-coordinates of the upper left corner of the printable area in the printer coordinates. As these are double values, they should be rounded to the next larger integer. Altogether we have the following implementation of the interface Printable:
public int print(Graphics g, PageFormat pageFormat, int pageIndex) { if (pageIndex > 0) { return(NO_SUCH_PAGE); } else { int x = (int)pageFormat.getImageableX() + 1; int y = (int)pageFormat.getImageableY() + 1; g.translate(x,y); RepaintManager currentManager = RepaintManager.currentManager(this); currentManager.setDoubleBufferingEnabled(false); this.paint(g); currentManager.setDoubleBufferingEnabled(true); return(PAGE_EXISTS); } }
230
Advanced topics
This method throws a PrinterException and is therefore embedded into a trycatch block. To tell the print job which component it has to print we use setPrintable. The argument of this method has to be a Printable object, i.e. an object that implements the Printable interface:
setPrintable(Printable printableObject)
The print job is sent to the default printer specied on the computer. If one wishes to select another printer, one calls method
boolean printDialog()
Then a printer selection dialogue similar to the one shown in Figure 19.2 appears. There one can select a number of options, e.g. the printer, the number of copies to print or the paper orientation. On closing the dialogue a boolean value is returned. If the value is true, then the print job is processed, otherwise it is cancelled. The following code snippet shows the application of class PrinterJob to print printableComp:
PrinterJob printJob = PrinterJob.getPrinterJob(); printJob.setPrintable(printableComp); if (printJob.printDialog()){ try { printJob.print();
Printing in Java
231
public class PrintPanel extends JPanel implements Printable { public PrintPanel() { this.setBackground(Color.white); this.setPreferredSize(new Dimension(300, 200)); } public void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(Color.black); g.drawRect(20,20,100,50); g.fillOval(80,80,60,30); g.drawString("Printing in JAVA is easy!",100,150); g.setColor(Color.red); g.drawRect(0,0,299,199); } public int print(Graphics g, PageFormat pageFormat, int pageIndex) { if (pageIndex > 0) { return(NO_SUCH_PAGE); } else { int x = (int)pageFormat.getImageableX() + 1;
232
Advanced topics
33. 34. 35. 36. 37. 38. 39. 40. } 41. } 42. 43. }
int y = (int)pageFormat.getImageableY() + 1; g.translate(x,y); RepaintManager currentManager = RepaintManager.currentManager(this); currentManager.setDoubleBufferingEnabled(false); this.paint(g); currentManager.setDoubleBufferingEnabled(true); return(PAGE_EXISTS);
Class PrintFrame generates a frame that has a PrintPanel embedded into its content pane. The frame has a menu with only one item Print. The listener for this item is implemented by dening actionPerformed. When the item is selected, a print job for the embedded panel is generated and started as described in Section 19.2. The frame also has main method which launches the application. File: its/Printing/PrintFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. package its.Printing; import import import import import import import its.SimpleFrame.SimpleFrame; java.awt.event.ActionEvent; java.awt.event.ActionListener; java.awt.print.PrinterException; java.awt.print.PrinterJob; java.awt.BorderLayout; javax.swing.*;
public class PrintFrame extends SimpleFrame implements ActionListener{ private PrintPanel pp; public PrintFrame() { this.setTitle("Printing Application"); pp = new PrintPanel(); this.getContentPane().add(pp,BorderLayout.CENTER); JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("File"); JMenuItem druckItem = new JMenuItem("Print"); menuBar.add(menu); menu.add(druckItem); druckItem.addActionListener(this);
Printing in Java
233
27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53.
this.setJMenuBar(menuBar); pack(); } public void actionPerformed(ActionEvent evt){ String command = evt.getActionCommand(); if(command.equals("Print")) { PrinterJob printJob = PrinterJob.getPrinterJob(); printJob.setPrintable(pp); if (printJob.printDialog()){ try { printJob.print(); } catch(PrinterException pe) { System.out.println("Error printing: " + pe); } } } } public static void main(String[] args) { PrintFrame prfr = new PrintFrame(); prfr.showIt(); } }
The argument comp is a graphical component which does not necessarily implement Printable. This component is printed as a result of this call. Components embedded into comp are also printed. There is one special case when printing frames: Only the embedded components of a frame are printed, not the frame itself. The code of PrintSuit combines the implementation of interface Printable and the generation of the PrinterJob object. Application PrintSuitTestFrame
234
Advanced topics
demonstrates the use of PrintSuit. As a result the embedded text area and button are printed; the frame itself however, is, not printed.
File: its/Printing/PrintSuit.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. package its.Printing; import import import import import import import java.awt.Component; java.awt.Graphics; java.awt.print.PageFormat; java.awt.print.Printable; java.awt.print.PrinterException; java.awt.print.PrinterJob; javax.swing.RepaintManager;
public class PrintSuit implements Printable { private Component compToPrint; public static void printComponent(Component comp) { new PrintSuit(comp).print(); } private PrintSuit(Component comp) { this.compToPrint = comp; } public void print() { PrinterJob printJob = PrinterJob.getPrinterJob(); printJob.setPrintable(this); if (printJob.printDialog()){ try { printJob.print(); } catch(PrinterException pex) { pex.printStackTrace(); } } } public int print(Graphics g, PageFormat pageFormat, int pageIndex) { if (pageIndex > 0) { return(NO_SUCH_PAGE); } else { int x = (int)pageFormat.getImageableX() + 1; int y = (int)pageFormat.getImageableY() + 1; g.translate(x,y); RepaintManager currentManager = RepaintManager.currentManager(compToPrint)
Printing in Java
235
42. 43. 44. 45. 46. 47. 48. 49.
File: its/Printing/PrintSuitTestFrame.java
package its.Printing; import import import import import import its.SimpleFrame.SimpleFrame; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JButton; javax.swing.JTextArea; java.awt.BorderLayout; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30.
public class PrintSuitTestFrame extends SimpleFrame implements ActionListener { public PrintSuitTestFrame() { JButton button = new JButton("Print"); button.addActionListener(this); this.getContentPane().add(button,BorderLayout.SOUTH); JTextArea textPane = new JTextArea(); textPane.setText("Test test\n test test\n test test\n test test"); this.getContentPane().add(textPane,BorderLayout.CENTER); } public void actionPerformed(ActionEvent evt){ PrintSuit.printComponent(this); } static public void main(String[] args) { PrintSuitTestFrame pstf = new PrintSuitTestFrame(); pstf.showIt(); } }
20
Threads are parts of an application which run in parallel. Although most applications in this book seem to be single-threaded, they are not. Any application involving a graphical surface is automatically multi-threaded. A number of threads are generated by the runtime system, and these run in parallel to the program code of the actual application. These threads usually run unnoticed by the user. In some cases, however, this concurrency can cause problems. We want to address the most frequent of these problems in this chapter.
To understand the role of threads in Java applications with graphical user interfaces, let us remember the life cycle of such applications. As mentioned in the introduction, the GUI for the application is created in the start-up phase. Once the GUI is made visible the application is event driven. That is, it is steered by events triggered by the user (or by other applications). Such an event is, for example, pressing a button. Events are detected by the runtime system, which informs the appropriate listener of the application. For a button it would be an action listener. This listener then executes a method that contains the code for reacting to the event. For action listeners this is method actionPerformed. The administration of the events is done by a thread that is started unknown to the user, the so-called event thread, sometimes also called event dispatching thread. We describe this mechanism in the following and then address some problems it might cause. We would like to emphasize that this chapter is not meant as an introduction to concurrent programming with threads in Java. The reader is assumed to know the basics on threads or to look them up in the Java documentation or in tutorials on the web.
237
temporarily store the events. This is a data structure that handles the events in a rst-in-rst-out manner. Any newly incoming event is added at the end of the queue and then gradually moves to the head where older events are taken out and processed. Processing an event means to call a method (e.g. actionPerformed) of the appropriate listener. This method then carries out some code as a reaction to the event. The latter happens in the event thread, not the main thread. Besides events that are processed by those listeners dened by the programmer, the queue contains events coming from listeners that are automatically assigned to Swing components. These include requests to repaint a component as a reaction to resizing it or clicking on a menu.
The internal names given to threads by the runtime system might be different on different platforms or in different versions of the SDK.
238
Advanced topics
Running Working Running Working Running Working Running Working Running Working Running Working Running Working Running Working Running Working Running Working Running
in 0 in 1 in 2 in 3 in 4 in 5 in 6 in 7 in 8 in 9 in
thread main thread AWT-EventQueue-0 thread AWT-EventQueue-0 thread AWT-EventQueue-0 thread AWT-EventQueue-0 thread AWT-EventQueue-0 thread AWT-EventQueue-0 thread AWT-EventQueue-0 thread AWT-EventQueue-0 thread AWT-EventQueue-0 thread AWT-EventQueue-0
Let us analyse the observed behaviour. The following list shows what happens in chronological order. 1. When the menu is clicked, a command to repaint the GUI with the rolled-out menu is added at the end of the event queue. Some time after the command has been executed the menu becomes visible. The repaint command is removed from the queue. 2. When the menu item Start is selected, a call to the method actionPerformed is added to the event queue. 3. A command to redraw the GUI with the retracted menu is added to the queue, behind the call to actionPerformed. 4. In every iteration of the for loop a command to redraw the label with the updated text is added to the queue, behind the repaint command. By the rst-in-rst-out property of the queue the events in 3 and 4 can be processed only after the event in 2 by the event thread. In particular, the lengthy actionPerformed method is run in the event thread and hinders it from processing the following events. Until actionPerformed terminates, the GUI is not updated and shows the rolled-out menu. Also the label and menu bar are not repainted when the frame is resized. The repaint requests are added at the end of the event queue where they wait to come to the head. Further events from the GUI occurring during this time, such as mouse clicks, are added to the event queue. They all have to wait until the previously added requests have been processed, especially the lengthy actionPerformed in 2. As no reaction to these events happens during that time,
239
(a)
(b)
Figure 20.1 The results of applications (a) BlockingFrame and (b) NonBlockingFrame
the GUI appears to be blocked. The console output commands are operations that do not require any graphical components and are not put into the event queue. Therefore they are performed right away and appear as expected. The code for BlockingFrame is listed below and the resulting display is shown in Figure 20.1. File: its/Blocking/BlockingFrame.java
package its.Blocking; import import import import import import import its.SimpleFrame.SimpleFrame; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JLabel; javax.swing.JMenu; javax.swing.JMenuBar; javax.swing.JMenuItem; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24.
public class BlockingFrame extends SimpleFrame { private JLabel display; public BlockingFrame(){ display = new JLabel("Nothing happened"); this.getContentPane().add(display); JMenuBar menuBar = new JMenuBar(); this.setJMenuBar(menuBar); JMenu menu = new JMenu("Menu1"); menuBar.add(menu); JMenuItem startItem = new JMenuItem("Start");
240
25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41.
Advanced topics
menu.add(startItem); MenuListener mListener = new MenuListener(); startItem.addActionListener(mListener); } public static void main(String[] args){ System.out.println("Running in thread "+ Thread.currentThread().getName()); BlockingFrame bf = new BlockingFrame(); bf.showIt("BlockingFrame"); } private void lengthyWork(){ //This is the lengthy work for(int i = 0; i < 10; i++){ System.out.println("Working "+i); System.out.println("Running in thread "+ Thread.currentThread().getName()); display.setText("Working "+i); try{ Thread.sleep(500); }catch(Exception ex){ ex.printStackTrace(); } }//for i display.setText("Done"); }
42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. //Internal class 53. class MenuListener implements ActionListener{ 54. 55. public void actionPerformed(ActionEvent evt){ 56. 57. String command = evt.getActionCommand(); 58. if(command.equals("Start")){ 59. lengthyWork(); 60. } 61. else 62. { 63. System.out.println("Unknown ActionCommand"); 64. System.exit(0); 65. } 66. } 67. }//internal class 68. }
241
20.2.2
The solution to the above problem is not to run the lengthy lengthyWork method in the event thread but in a separate thread. The event thread merely starts this other thread. It then immediately processes the next event in the queue, while the lengthy job is running in the other thread in parallel. In our example we dene an internal class WorkerThread which is derived from Thread. When a thread is started then its method run is executed. Therefore, all the instructions the thread should execute should be placed here2 . In our example the lengthy job is placed there. Now, method actionPerfomed does not call lengthyWork but it creates an instance worker of WorkerThread and starts it by calling worker.start(). After this, actionPerfomed nishes. Even though actionPerfomed is run in the event thread, it does so only for the time needed to start the worker thread. It does not block the processing of further events. The menu rolls back immediately after we selected the menu item Start. Also the text of the label changes every half second. On the console we can now see that the lengthy job is performed in neither the main nor the event thread. Instead it runs in a thread called Thread-1 which is the internal name for our worker thread. As the lengthy operation now runs in a separate thread, the event thread can process the requests to repaint the frame or label as soon as they come in. Below, a listing of the console output is shown:
Running in thread main WorkerThread1: started as Thread-1 Working 0 Running in thread Thread-1 Working 1 Running in thread Thread-1 Working 2 Running in thread Thread-1 Working 3 Running in thread Thread-1 Working 4 Running in thread Thread-1 Working 5 Running in thread Thread-1 Working 6 Running in thread Thread-1 Working 7 Running in thread Thread-1 Working 8 Running in thread Thread-1 Working 9
2
As for listener methods such as actionPerformed, this formulation has to be taken with a grain of salt. Some of the code might actually be in other methods, in lengthyWork in our case, which are then called from inside run.
242
Advanced topics
The code for NonBlockingFrame is listed below and the resulting display is shown in Figure 20.1.
File: its/Blocking/NonBlockingFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. package its.Blocking; import import import import import import import its.SimpleFrame.SimpleFrame; java.awt.event.ActionEvent; java.awt.event.ActionListener; javax.swing.JLabel; javax.swing.JMenu; javax.swing.JMenuBar; javax.swing.JMenuItem;
public class NonBlockingFrame extends SimpleFrame { private JLabel display; private int nummer; public NonBlockingFrame(){ nummer = 0; this.setTitle("Non blocking frame"); display = new JLabel("Nothing happened"); this.getContentPane().add(display); JMenuBar menuBar = new JMenuBar(); this.setJMenuBar(menuBar); JMenu menu = new JMenu("Menu1"); menuBar.add(menu); JMenuItem startItem = new JMenuItem("Start"); menu.add(startItem); MenuListener mListener = new MenuListener(); startItem.addActionListener(mListener); } private void lengthyWork(){ //This is the lengthy work for(int i = 0; i < 10; i++){ System.out.println("Working "+i); System.out.println("Running in thread "+ Thread.currentThread().getName());
243
38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83.
display.setText("Working "+i); try{ Thread.sleep(500); }catch(Exception ex){ ex.printStackTrace(); } }//for i display.setText("Done"); } public static void main(String[] args){ System.out.println("Running in thread "+ Thread.currentThread().getName()); NonBlockingFrame nbf = new NonBlockingFrame(); nbf.showIt(); } //Internal class class MenuListener implements ActionListener{
public void actionPerformed(ActionEvent evt){ String command = evt.getActionCommand(); if(command.equals("Start")){ // The worker thread is created and started Thread worker = new WorkerThread(); worker.start(); } else { System.out.println("Unknown ActionCommand"); System.exit(0); } } }//internal class //Internal class class WorkerThread extends Thread{ int num; public WorkerThread(){ nummer++; num = nummer; }
244
Advanced topics
84. 85. 86. 87. 88. 89. 90. 91. 92. 93. }
public void run(){ System.out.println("WorkerThread"+num+": started as "+ Thread.currentThread().getName()); lengthyWork(); System.out.println("WorkerThread"+num+": Done"); } }
20.3 Side-effects
and started all these threads run in parallel. What happens can best be seen in the consoles output. If this is unwanted, one has to check whether there already is a worker thread running before starting the next one. This can be done by setting a ag (e.g. a boolean variable) before starting the thread and have the thread reset it just before it exits (last command in the run method). There are other ways of achieving this which require a deeper knowledge of threads.
Sometimes the blocking of the GUI is not that unwanted. If, for example, a large
le is read, then one might not want further user commands to be processed before the whole le has been loaded. Here, so-called progress meters might be a more elegant way of solving the problem.
If, in our example, a number of threads for the lengthy work are started, all of
them are slowed down. They do not nish after ve seconds each. The reason is that on one-processor systems and most computers just have one main processor the different threads do not really run concurrently. Instead, they have to share the single processor. Each one runs only for a short time and is then interrupted while another thread continues to run for a short time, etc. The different threads receive time slices.
245
analysed and displayed. One might experience the problem that the drawing is incompletely displayed. We use a simple example to demonstrate this phenomenon. We dene a class Drawing as an abstract representation of the drawing. The drawing shows three parallel lines. Initially the lines are horizontal. Method flipLines will change the drawing to three vertical lines. Calling flipLines again will make the lines horizontal again. To simulate a complex modication of a drawing, the lines are ipped one after the other with a one second pause after each ip. Class UpdatePanel is dened to display a drawing. To this end it has access to an instance of class Drawing. We do not list these classes; they can be downloaded from the books home page. Class BadUpdateFrame is the main class of the application. It has a variable drawing of class Drawing. It also has an UpdatePanel to display this drawing. Initially the lines are horizontal. Then the BadUpdateFrame calls the flipLines method of the drawing, which ips the lines one after the other in roughly three seconds. This cannot be seen in the display because no event is created to trigger a repaint. Therefore we add a repaint command for the panel after the lines have been ipped. Then flipLines and repaint are called again to ip the lines to horizontal and display this. We only list the program BadUpdateFrame; the actual demonstration is placed in method badDemo.
File: its/GraphicsUpdate/BadUpdateFrame.java
package its.GraphicsUpdate; import javax.swing.JFrame; import its.SimpleFrame.SimpleFrame; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24.
public class BadUpdateFrame extends SimpleFrame{ private UpdatePanel uppane; private Drawing drawing; public BadUpdateFrame(){ drawing = new Drawing(); uppane = new UpdatePanel(drawing); getContentPane().add(uppane); pack(); } public void badDemo(){ uppane.repaint(); drawing.flipLines(); uppane.repaint(); drawing.flipLines(); uppane.repaint ();
// // // // //
paint the flip them paint the flip them paint the
246
Advanced topics
25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. }
drawing.flipLines(); // flip them to vertical } public static void main(String[] args){ BadUpdateFrame badFrame = new BadUpdateFrame(); badFrame.showIt("Bad Update"); badFrame.badDemo(); }
This should display a sequence of three drawings. The rst has three horizontal lines, the second one three vertical ones and the third one again consists of three horizontal lines:
What is really displayed depends on many things: the operation system, the version of the SDK, the current load (number of processes) on the machine and many more things. Also the behaviour might vary from one execution of the application to the next. Most of the time one observes the following sequence of images:
For an explanation let us look at the following three lines of code in BadUpdateFrame:
drawing.flipLines(); // flip them to vertical uppane.repaint(); // paint the vertical lines drawing.flipLines(); // flip them back to horizontal
Their order suggests that after all three lines are ipped, the new drawing is displayed and only then are they ipped again. The problem comes from the fact that
247
Event thread
{
Data used to repaint
repaint added to event queue repaint is taken out of the event queue The panel is repainted using the modified data
Figure 20.2 The main thread, on the left, modies the drawing while the repaint event proceeds to the head of the event queue. When the repainting is nally performed, the modied drawing is shown
the flipLine instructions run in the main thread, while the repaint instruction is performed in the event thread. The main thread just adds repaint to the event queue to indicate that the GUI should be repainted at some time. In this case, the order of the code lines in the listing does no longer reect the order of their execution. It might take some time for the event thread to process the request to repaint. By that time, the main thread has already ipped the rst line. The display then shows part of the old and part of the new picture. The temporal order in which things happen is listed below and is also shown in Figure 20.2: 1. All lines are horizontal. 2. The repaint command is added to the event queue. 3. The rst line is ipped to vertical. 4. The repaint command reaches the head of the queue and the panel is repainted to show the current drawing with the rst line already vertical. 5. The other two lines are ipped but the display is not updated.
20.4.2
A solution by buffering
One way of solving the problem is to use a buffer for the drawing. This means that we have two abstract drawings, one of which is displayed and not changed and the other one that is not displayed and modied. When the modications of the second one are completed, the drawings change their role. To this end the reference to the drawing is changed in the panel. Class UpdatePanel provides a method for this purpose (setDrawing). Then the new drawing is displayed by calling the repaint method of the panel. The content of the currently displayed drawing is copied to
248
Advanced topics
the other one. It is important to copy the drawing and not to use an assignment of references such as drawing1 = drawing2. In that case the two drawings would be identied; only one drawing would exist with two references to it. See Section B.1 for a more detailed explanation. File: its/GraphicsUpdate/GoodUpdateFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. package its.GraphicsUpdate; import javax.swing.JFrame; import its.SimpleFrame.SimpleFrame;
public class GoodUpdateFrame extends SimpleFrame{ private UpdatePanel uppane; private Drawing drawing1, drawing2; public GoodUpdateFrame(){ drawing1 = new Drawing(); drawing2 = new Drawing(); uppane = new UpdatePanel(drawing1); getContentPane().add(uppane); pack(); } public void goodDemo(){ uppane.repaint(); drawing2.flipLines(); uppane.setDrawing(drawing2); uppane.repaint(); drawing1.copy(drawing2); drawing1.flipLines(); uppane.setDrawing(drawing1); uppane.repaint (); } public static void main(String[] args){ GoodUpdateFrame goodFrame = new GoodUpdateFrame(); goodFrame.showIt("Good Update"); // The drawing contains goodFrame.goodDemo(); }
21
In this chapter we combine many techniques from the previous parts of the book to design a generic graphics package for displaying and manipulating drawings. The graphical objects to be displayed are specied in an abstract way and the package may be extended by the user. The package also supports adding and deleting graphical objects through user interaction.
such as lines, circles or triangles but may include much more complex shapes such as polygons or curves. Some of them are dened in the package.
The programmer can add new graphical objects. The graphical objects are specied by coordinates that are real numbers. No
visible.
The programmer can choose whether the scaling is proportional (both x- and
y-axes have the same scale) or not (the drawing is extended horizontally and vertically to ll the panel).
The colour of every graphical object can be set and changed. The display automatically scales when the panel is re-scaled. The package provides methods to add or delete graphical objects from the
drawing.
250
Advanced topics
21.2.1
Class Drawing
A drawing is composed of graphical objects. Therefore we choose Drawing to do the administrative work. This class maintains a data structure that contains all graphical objects currently in the drawing. The data structure allows new graphical objects to be added or existing ones deleted. The class provides a method draw which displays the whole drawing. To this end it asks all graphical objects to draw themselves. Some more administrative features will be described later on.
21.2.2
Class GraphicalObject
A graphical object is an abstract description of a geometrical shape. As there are innitely many kinds of such objects, one cannot dene one single class to cover all the possibilities. We therefore dene GraphicalObject to be an abstract class. All features that are common to all graphical objects (and that are relevant for our project) are specied in this class. If the programmer needs a new specic shape, then he or she has to derive a class from GraphicalObject. In this class those features are implemented that are specic to the shape. Let us think which features are common to all graphical objects and are also relevant for the task we have in mind. First of all we should know the place and size of a graphical object. These are specied by the left-most, right-most, top-most, and bottom-most points of the object. This information is important for the project because we have to scale the drawing. Another feature that all graphical objects have is colour. While these features of a graphical object are quite obvious, there are some more that will prove useful. Each graphical object has to be uniquely identiable. Even if two circles have the same centre, radius and colour they are two different objects. To make graphical objects unique, each one has an integer eld called uid for unique identication number. If we think of interactively changing a drawing with the mouse, another concept proves useful. Think of a drawing with many graphical objects and suppose we want to select one of them with the mouse. Then one has to link the current mouse position to one of the objects according to some rule. There are many ways to specify which object is closest to the mouse. The methods for nding out which object that is can be quite involved. We therefore dene an anchor point for every graphical object. Then one can select an object by clicking on its anchor point. Even so, two objects might have the same anchor point, in which case we select one at random. The anchor points are shown in the drawing as little dots.
251
Another thing we have to consider is the rendering of the graphical objects. As the programmer can add new kinds of shapes, one cannot set up a drawing method in advance that works for all of them. Only the programmer knows what the graphical object has to look like. Therefore, we require that a graphical shape knows how to draw itself. This concept is implemented by an abstract method draw in class GraphicalObject. Every (concrete) class derived from it has to implement this method. There is, however, a problem here. To actually render the shape, the graphical object has to know the pixel coordinates in the panel where the drawing is displayed. These depend on the panels current size and the size of the drawing which, in turn, depend on other graphical objects in the drawing. The necessary information is provided by instances of classes DimensionObject and ScaleObject. These classes are discussed later. As an example, the package has three graphical objects that are implemented, Circle, Line and OpenPolygon. The listing of OpenPolygon is shown below. The constructor receives the corner points of the polygon as an array. It is checked whether the array contains at least two points. If this is the case, the array is traversed to nd the extremal coordinates. A DimensionObject with these coordinates is created. The reference point for the object the anchor is dened to be the rst point. The draw method receives a reference to a Graphics object and a ScaleObject. The latter contains the information on the current size of the display. This information is used to transfer the abstract real coordinates of the polygon into screen (pixel) coordinates. Conversion routines are provided by methods of the utility class Conversions. The pixel coordinates are then used to draw the polygon as a sequence of line segments. Class OpenPolygon provides a method textString which returns a textual description of the polygon.
File: its/GenericDraw/OpenPolygon.java
package its.GenericDraw; import java.awt.Graphics; public class OpenPolygon extends GraphicalObject { private RealPoint[] points; public OpenPolygon(RealPoint[] pts) { points = pts; if(points.length < 2){ System.out.println("ERROR in OpenPolygon: less than two points."); } else { double xmin = points[0].getX(); double xmax = points[0].getX(); 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17.
252
18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36.
Advanced topics
double ymin = points[0].getY(); double ymax = points[0].getY(); for (int i = 1; i < points.length; i++) { xmin = Math.min(xmin,points[i].getX()); xmax = Math.max(xmax,points[i].getX()); ymin = Math.min(ymin,points[i].getY()); ymax = Math.max(ymax,points[i].getY()); } dimObj = new DimensionObject(xmin,xmax,ymin,ymax); anchor = points[0]; } } public void draw(Graphics g, ScaleObject scale) { PixelPoint startPix = Conversions.realToPixelPoint(points[0],scale); g.setColor(color); for (int i = 0; i < points.length; i++) { PixelPoint endPix = Conversions.realToPixelPoint(points[i],scale); g.drawLine(startPix.getX(),startPix.getY(), endPix.getX(),endPix.getY()); startPix = endPix; }
37. 38. 39. } 40. 41. public String textString(){ 42. String result = "Open Polygon: "+points[0].toString(); 43. for (int i = 1; i < points.length; i++) { 44. result += ";"+points[i].toString(); 45. } 46. return(result); 47. } 48. }
21.2.3
Class GenericDrawPanel
Class GenericDrawPanel is derived from JPanel and displays the drawing. A programmer who wants to use the GenericDrawing package has to know about this class. GenericDrawPanel keeps the abstract representation of the drawing in an instance of class Drawing. GenericDrawPanel also provides methods to add and delete graphical objects. These methods only call the corresponding methods of class Drawing. The paintComponent method of GenericDrawPanel calls the draw method of class Drawing. A mouse adapter is implemented as internal class GenericDrawMouseAdapter. It supports two operations: if the right mouse button is clicked inside the panel, then the graphical object whose anchor is closest to the mouse position is removed from the drawing. If the left mouse button is clicked once, the mouse position is
253
remembered. If it is clicked a second time, a line between the mouse positions of the two clicks is added to the drawing. The functions of the adapter can be changed or extended by the programmer.
21.3.1
Constants
We dene a class Constants which contains the denition of some xed values that are used by one or more classes of the package. These include the width of the margins around the drawing. The class also denes some boolean variables which determine the appearance of the drawing, e.g. whether the anchor points are drawn or whether the drawing is displayed in proportional model.
21.3.2
The classes RealPoint and PixelPoint each store elds x and y. These are the x- and y-coordinates of the point. The rst class uses double and second one int. The constructor of the pixel points checks that the coordinates are non-negative. Both classes provide get-methods to access the elds. File: its/GenericDraw/PixelPoint.java
package its.GenericDraw; public class PixelPoint{ private int x, y; public PixelPoint(int xx, int yy){ if((xx >= 0) && (yy >= 0)){ x = xx; y = yy; } else { System.out.println("ERROR: Illegal pixel coordinates."); } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.
254
Advanced topics
17. public int getX(){ 18. return(x); 19. } 20. 21. public int getY(){ 22. return(y); 23. } 24. 25. public String toString(){ 26. return("["+x+";"+y+"]"); 27. } 28. }//class
File: its/GenericDraw/RealPoint.java
1. package its.GenericDraw; 2. 3. public class RealPoint{ 4. private double x, y; 5. 6. public RealPoint(double xx, double yy){ 7. x = xx; 8. y = yy; 9. } 10. 11. public double getX(){ 12. return(x); 13. } 14. 15. public double getY(){ 16. return(y); 17. } 18. 19. public double distanceTo(RealPoint p){ 20. return(Math.sqrt(Math.pow((this.x-p.x),2)+Math.pow((this.y-p.y),2))); 21. } 22. 23. public String toString(){ 24. return("("+x+";"+y+")"); 25. } 26. }//class
We shall use real points for the abstract specication of geometric objects. Whenever such an object has to be rendered, the real points are converted into pixel coordinates. The pixel coordinates are computed in such a way that the whole
255
drawing ts into the panel. Actually we want to have a small margin around the drawing in the panel. We allow the margin to be different at the four edges. The following data are needed to compute this conversion:
minimal x-coordinate of the drawing maximal x-coordinate of the drawing minimal y-coordinate of the drawing maximal y-coordinate of the drawing width of the panel in pixels height of the panel in pixels width of the left margin around the drawing in pixels width of the right margin around the drawing in pixels width of the top margin around the drawing in pixels width of the bottom margin around the drawing in pixels
double double double double int int int int int int
Then the usable width and height of the panel without the margins are wu = w l r and hu = h t b. We now compute the factors f x and f y by which the drawing has to be scaled horizontally and vertically to t into the margins.
f x = wu/(xmax xmin) f y = wh /(ymax ymin) f = min{ f x, f y }
factor for horizontal scaling factor for vertical scaling factor for proportional scaling
Then, to convert a real point Preal = (xreal , yreal ) into the pixel point P pix = (xpix, ypix), one uses the following formulas. Note that we have to reect the ycoordinate because the screen coordinate system is upside down. The L-shaped brackets stand for downward rounding to the nearest integer. xpix = (xreal xmin) f x + l ypix = h ((yreal ymin) f y + t) In case of a proportional scaling, f is used instead of both f x and f y .
21.3.3
Dimension objects
Every GraphicalObject contains an instance of a DimensionObject. The dimension object contains the extremal coordinates of the graphical object. These are the minimal and maximal x- and y-coordinates of the object. For every class derived from GraphicalObject, the programmer has to make sure that these values are correctly set. See the listing of class OpenPolygon for an example. The instances of Drawing also contain a DimensionObject called extremalDimensions. It contains the extremal dimension of the whole drawing, e.g. the minimum x-coordinate of any graphical object in the drawing. The values of extremalDimensions have to be updated when a new graphical object is added or deleted. When a new shape is added, one only has to check whether one of its dimensions gives a new extremum. Class DimensionObject provides a method,
256
Advanced topics combineWith, for doing this. This method compares the minima of this and the DimensionObject given as an argument, and sets the minima to the smaller of
the values. The update of the maxima is done in an analogous way. If a graphical object is deleted from the drawing the update of extremalDimensions is more difcult. If one of the values of extremalDimensions, say the minimal x-coordinate, comes from the deleted object, then one has to nd that object of the drawing that now has minimal x-coordinate. We do this by re-computing the values of extremalDimensions from scratch. That is, we screen all graphical objects in the drawing to determine the new extremal values. There are more efcient ways of updating the values after a deletion but they require elaborate data structures, the implementation of which would occult the structure of the package.
(a)
(b)
Figure 21.1 The test application GenericDrawingDemo. (a) In proportional mode, i.e. both coordinate axes have the same scale circles appear as circles. (b) In non-proportional mode, i.e. sized to ll the panel circles might appear as ellipses
257
21.3.4
The class Conversions provides methods to convert between pixel and real coordinates. To this end the methods have to know the relevant parameters, e.g. the dimensions of the drawing and the size of the panel. These values are supplied in an object of class ScaleObject. The conversion methods expect a pixel or real point and a scale object as arguments. They compute the coordinates of the real or pixel point, respectively, from the point they received and return this. Whenever a re-drawing of the panel is initiated, the parameters of the scale object are set to the current values by class Drawing. The draw-methods of the classes derived from GraphicalObject receive the scale object as a parameter. The coordinates of the graphical objects are real coordinates. With the scale object at hand, the conversions can be used to convert those to the pixel coordinates needed for drawing into the panel. Two screen shots of a test application GenericDrawingDemo are shown in Figure 21.1.
22
We now discuss how HTML-formatted text is displayed in Swing components. We shall rst show how les containing HTML text can be displayed. Then we show how to make a simple browser which can access and display HTML-formatted web pages.
In Chapters 12 and 18 we saw how ASCII texts can be displayed and edited. Another commonly used text type is HTML (HyperText Mark-up Language). Practically all web pages are HTML-formatted and so are many software manuals that are supplied on a CD. Files with HTML-formatted text are ASCII les that contain the text to be displayed and additional formatting instructions. These instructions, called HTML-tags, are enclosed in less-than and greater-than signs. For example, the sequence
<center>HTML and Swing</center>
means that the text HTML and Swing is displayed in a horizontally centred fashion. When loading an HTML le into a Swing component for ASCII text, no formatting is done; the HTML-tags are shown as they appear in the text. The programs that display HTML text in a formatted fashion have to interpret the HTML-tags, and display the remaining text as required by these formatting instructions. Such programs are, for example, web browsers. We do not have to write an HTML interpreter ourselves to format HTML text. The Swing library contains components with a built-in HTML interpreter, e.g. JEditorPane. These components can display most HTML documents in a formatted fashion. One has to be aware that the HTML specication has changed a number of times, in particular new HTML-tags were added which allow more sophisticated formatting. The HTML interpreter of Java knows the tags of a specic version of HTML; newer or non-standardized formatting instructions are ignored. Thus the Swing components might display some new, stylish web pages somewhat differently from a standard web browser.
259
constructor. A URL is a web address. One can use class URL for this purpose, or one can just pass on the URL as a string. The constructors have the following form:
JEditorPane(URL webAddress) JEditorPane(String webAddressAsString)
These constructors have to be embedded into a try-catch block, because generating a URL can throw an exception. To access a local le, the URL has to start with file:/// instead of http://. We use a local le in our example because then the program works without a connection to the Internet. If the computer is connected to the Internet, one can use the following URL:
http://www.imm.dtu.dk/swingbook/HTMLTest/test1.html
We are only interested in displaying HTML and do not allow the text to be edited using method setEditable(false) of JEditorPane. File: its/HTML/SimpleHTMLFrame.java
package its.HTML; import its.SimpleFrame.SimpleFrame; import javax.swing.JEditorPane; public class SimpleHTMLFrame extends SimpleFrame { private JEditorPane ediPane; private static String htmlSource = "/its/TestData/test1.html"; private static String workingDir; public SimpleHTMLFrame(String URLname) { this.setSize(400,400); workingDir = System.getProperty("user.dir"); // in the following we assume that the program // is run from the parent directory of its. // Use an absolute path if necessary: try{ ediPane = new JEditorPane("file:///"+workingDir+htmlSource); // ediPane = new JEditorPane (file:///C:/absolute/path//its/TestData/ ediPane.setEditable(false); } catch(Exception e){} // Later, the code for the link listener will be added here. this.getContentPane().add(ediPane); } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27.
260
Advanced topics
28. public static void main(String[] args){ 29. SimpleHTMLFrame shf = new SimpleHTMLFrame(htmlSource); 30. shf.showIt("Simple HTML-Display"); 31. } 32. // Later, the link listener class will be defined here. 33. }
Program SimpleHTMLFrame displays a single HTML document but it does not use the most important HTML feature: links (HTML hyperlinks). When clicking on a link, nothing happens. In the next section we see how links can be followed.
The listener is assigned to that component which displays the HTML document, in our case the JEditorPanel ediPane. The listener is assigned using the following command:
ediPane.addHyperlinkListener(HyperlinkListener hyLis)
After the listener is assigned to the panel, it is notied by the runtime system if a link is clicked on, i.e. its hyperlinkUpdate method is called. The runtime system also generates a HyperlinkEvent object and passes it to hyperlinkUpdate as an argument. The programmer has to insert the code into hyperlinkUpdate which is to be executed as a reaction to selecting the link. In our example we want to display that page to which the link refers. We rst check whether the event type is ACTIVATED, i.e. whether the link has been selected. Other event types are ENTERED and EXITED. This test is done by inspecting the hyperlink event object hylevt
if(hylevt.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
The new web page is displayed in the editor pane. Methods that access web pages can throw exceptions, therefore calls of such methods have to be embedded into try-catch blocks. In our example, we write a message to the console if an
261
exception occurs. In a serious application, an error diagnosis should be performed and a repair should be started. To enable links, we augment class SimpleHTMLFrame to HTMLViewer by adding code at two positions indicated by comments in SimpleHTMLFrame. At the rst position we add
LinkLis lili = new LinkLis(); ediPane.addHyperlinkListener(lili);
At the second position we add the implementation of the hyperlink listener as an internal class:
private class LinkLis implements HyperlinkListener { public void hyperlinkUpdate(HyperlinkEvent hylevt) { try { if (hylevt.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { URL newPage = hylevt.getURL(); ediPane.setPage(newPage); } } catch (Exception ex) { System.out.println("Problems with hyperlink-listener"); } }// method }// internal class
We do not list the resulting program HTMLViewer; it can be downloaded from the books home page. It can, for example, be used to display HTML-formatted help or manual pages. Still, we miss some features when using the viewer. One of them is a Back-function which re-displays the previous HTML page. In the next section we shall extend the viewer to a simple web browser which contains this feature.
262
Advanced topics
elements which are arranged in a panel called ToolPanel. There is a text eld for entering a URL and two buttons labelled GO! and Back. When pressing the rst one, the browser loads the web page specied in the text eld. One can navigate through the web following links. The browser keeps a record of the previous pages. Pressing the Back-button makes the browser return to the web page from which the currently displayed page had been reached. Going back stops when we get back to the page whose URL had been directly entered into the text eld. The history of recently visited web pages is stored in a stack, a data structure which stores data like a stack of paper. New data can be placed on the top of the stack and only the topmost data can be taken from the stack. Assume we rst look at web page W1 which contains a link to page W2 . We use this link to get to W2 . Then the address (URL) of W1 is put on top of the stack. If W2 contains a link to page W3 and we use this link, then the address W2 is put on the stack. Using the Back-button of our browser will cause the application to remove the top-most address (W2 ) from the stack and display the corresponding page. After that, W1 is the top-most address on the stack. Using the Back-button again will therefore cause the application to remove the address of W1 from the stack and display it. The GO!- and Back-buttons of our application are monitored by a ButtonListener which implements the interface ActionListener and performs the desired actions if one of the buttons is pressed. The hyperlinks are monitored by class LinkLis which implements a HyperlinkListener like in the previous section. The application is listed below. It can be tested by using the following URL:
http://www.imm.dtu.dk/swingbook/HTMLTest/test1.html
File: its/HTML/Browser.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. package its.HTML; import import import import import import import import import its.SimpleFrame.SimpleFrame; java.awt.*; java.awt.event.ActionEvent; java.awt.event.ActionListener; java.net.URL; java.util.Stack; javax.swing.*; javax.swing.event.HyperlinkEvent; javax.swing.event.HyperlinkListener;
public class Browser extends SimpleFrame { private JEditorPane ediPane; private static String startURL = "http://www.imm.dtu.dk/swingbook/HTMLTest/test1.html"; private ToolPanel tools; private Stack urlStack;
263
20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65.
public Browser(String URLname) { this.setSize(600,600); urlStack = new Stack(); ediPane = new JEditorPane(); ediPane.setEditable(false); ediPane.setMinimumSize(new Dimension(600,600)); LinkLis lili = new LinkLis(); tools = new ToolPanel(URLname); ediPane.addHyperlinkListener(lili); this.getContentPane().add(tools,BorderLayout.NORTH); this.getContentPane(). add(new JScrollPane(ediPane),BorderLayout.CENTER); } public static void main(String[] args) { Browser brow = new Browser(startURL); brow.showIt("ITS-Browser"); } private class LinkLis implements HyperlinkListener { public void hyperlinkUpdate(HyperlinkEvent hyevt) { try { if (hyevt.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { URL t = ediPane.getPage(); urlStack.push(t); ediPane.setPage(hyevt.getURL()); } } catch (Exception ex) { System.out.println("Problems with hyperlink listener"); } }// method }// internal class private class ToolPanel extends JPanel{ private JTextField urlField; private JButton backButton, goButton; public ToolPanel(String URLname){ this.setLayout(new FlowLayout());
264
66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82. 83. 84. 85. 86. 87. 88. 89. 90. 91. 92. 93. 94. 95. 96. 97. 98. 99. 100. 101. 102. 103. 104. 105. 106. 107. 108. 109. 110. 111.
Advanced topics
urlField = new JTextField(); urlField.setText(URLname); backButton = new JButton("Back"); goButton = new JButton("GO!"); JLabel ulab = new JLabel(" URL:"); urlField.setPreferredSize(new Dimension(300,30)); this.add(backButton); this.add(ulab); this.add(urlField); this.add(goButton); ButtonListener buli = new ButtonListener(); backButton.addActionListener(buli); goButton.addActionListener(buli); }//constructor public String getURL(){ return(urlField.getText().trim()); } }// internal class private class ButtonListener implements ActionListener{ public void actionPerformed(ActionEvent actevt){ String command = actevt.getActionCommand(); if (command.equals("Back")) { if(urlStack.size() > 0) { URL url = (URL)urlStack.pop(); try { ediPane.setPage(url); } catch (Exception ex) { System.out.println("Problem in Back: URL not found."); } } } else if (command.equals("GO!")) { try { URL url = new URL(tools.getURL()); urlStack.removeAllElements(); ediPane.setPage(url); }
265
112. 113. 114. 115. 116. 117. 118. 119.
catch (Exception ex) { System.out.println("Problem in GO!: URL not found."); } }//ifelse }//method }// internal class }
In our application we create a URL-object with this address. Then an InputStream for reading from that URL is created. This is done by using method openStream() of class URL. We then use method read() of class InputStream to read the source of the HTML page character by character. Each character is immediately written to the console. The code of ReadURL and the resulting console output are listed below. File: its/OnlineMonitor/ReadURL.java
package its.OnlineMonitor; import java.io.InputStream; import java.net.URL; public class ReadURL { private static String immURL = "http://www.imm.dtu.dk/swingbook/HTMLTest/test1.html"; public static void main(String[] args) { readAndPrintTheURL(immURL); } public static void readAndPrintTheURL(String urlName){ // Create a URL 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.
266
16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37.
Advanced topics
URL urlToRead = null; try { urlToRead = new URL(urlName); } catch (Exception ex) { ex.printStackTrace(); } // Read and the URL characterwise // and print it to the console. if(urlToRead != null){ try { // Open the streams InputStream inputStream = urlToRead.openStream(); int c = inputStream.read(); while (c != -1) { System.out.print((char)c); c = inputStream.read(); } inputStream.close(); }catch(Exception e){System.out.println("Problem reading from URL");} } } }
The source code of the web page that appears on the console looks like this
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <HTML> <HEAD> <TITLE></TITLE> <META NAME="Generator" CONTENT="Winedit 2000"> <META NAME="Author" CONTENT="Paul Fischer"> </HEAD> <BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#FF0000" VLINK="#800000" ALINK="#FF00FF" BACKGROUND="?"> <H1> TEST HTML-Document</H1> This is the first page. <p> <A HREF="test2.html">Link to the next page</A>. </BODY> </HTML>
267
It contains the quotes of the DAX and the 30 stocks included in this index. The web address might change, so one has to check this and change it if necessary. The page is formatted as so-called comma-separated values (csv). The actual separator, however, is a semicolon. This format is suited to be imported into spreadsheet programs. We use this page because it is easier to analyse than HTML. Every line contains the information on one stock. A line on this page looks like this:
GDAXI;4016,52;1/13/2004;15:58;+20,61;4006,85;4033,67;4003,19;78199616
It contains separated by semicolons the name of the index, its recent value, date and time of the value and information on the change, highest and lowest values, etc. The page is regularly updated, approximately every 30 seconds. Our application extracts the recent value from the line; in the above example this is 4016.52. In our application we use a so-called web query which allows the line containing the DAX information to be extracted.1 We do not discuss the HTTP protocol or web queries here. The reader is referred to corresponding manuals and tutorials which are available on the web. The application reads this web page every ve seconds, extracts the current DAX value and updates the display. The quotes are displayed as a graph that is extended to the right. We do not want the display to be blocked while the application waits ve seconds before fetching the next value. Therefore, we dene a thread for accessing the web. This runs in parallel to the main thread which handles the GUI. Only the thread class MonitorThread is listed below. The run-method of MonitorThread consists of a while-loop. The condition for the while-loop is a boolean variable goOn. This can be set to false by calling method stopThread. Then, the next time the while condition is checked the loop is terminated, which also terminates the thread. In the loop, the method getOneQuote is called and then the thread pauses ve seconds. Method getOneQuote is a modication of readAndPrintTheURL from the previous section. It uses a BufferedReader to read the line with the information on the DAX. The private method getQuoteFromString parses this line and extracts the recent value of the DAX index as a double. This value is then passed to the display panel. The application consists of three more classes MonitorData, MonitorPanel and OnlineMonitor. The rst one implements the data model. It provides methods
1
If the information is contained in an HTML page, we would have to analyse the page and write an appropriate parser to extract the desired information.
268
Advanced topics
Figure 22.1 The online monitor for the DAX stock index
to add new data, access the present data, and to get information necessary for scaling the data so it ts into the display. The display is implemented in class MonitorPanel which is derived from JPanel. Class OnlineMonitor is derived from JFrame. It has a MonitorPanel embedded and also contains the main-method for starting the application. A screen shot is shown in Figure 22.1. File: its/OnlineMonitor/MonitorThread.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. package its.OnlineMonitor;
public class MonitorThread extends Thread{ private static final long delay = 5000; private String yahooDax = "http://de.finance.yahoo.com/d/quotes.csv?s=@GDAXI&"+ "f=sl1d1t1c1ohgv&e=.csv"; private MonitorPanel moniPane; private boolean goOn; public MonitorThread(MonitorPanel mp){ moniPane = mp; goOn = true; }
269
22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49.
public void run(){ while(goOn){ getOneQuote(yahooDax); try { Thread.sleep(delay); } catch (Exception ex) { ex.printStackTrace(); }//try }//while System.out.println("Thread stopped"); } public void stopThread(){ goOn = false; } private double getOneQuote(String urlName) { URL urlToRead = null; try { urlToRead = new URL(urlName); } catch (Exception ex) { ex.printStackTrace(); } try { // Open the streams InputStreamReader inputReader = new InputStreamReader(urlToRead.openStream()); BufferedReader urlReader = new BufferedReader(inputReader); String line = urlReader.readLine(); System.out.println(">"+line+"<"); double quote = getQuoteFromString(line); moniPane.addData(quote); }catch(Exception e){System.out.println("Problem in URLReader");} return(1.0); }
50. 51. 52. 53. 54. 55. 56. 57. 58. private static double getQuoteFromString(String str){ 59. String quoteString; 60. String euro,cent; 61. StringTokenizer stok = new StringTokenizer(str,";"); 62. stok.nextToken(); // skip GDAXI 63. quoteString = stok.nextToken(); // get quote as eeee,cc 64. StringTokenizer stok2 = new StringTokenizer(quoteString,",\""); 65. euro = stok2.nextToken(); 66.
270
Advanced topics
Applets
23
Applets are used when a program is designed to run in a browser. Applets replace frames in web applications. The applet is linked to an HTML page. Whenever this HTML page is made visible in a browser, the applet is loaded over the net and runs in the browser.
All the examples we have looked at so far have been based on frames, which are shown on the screen. These programs, running on the local machine, are called applications. Applets allow programs to run in a browser. Users everywhere on the web can fetch the applet and run it on their machines. Applets are like frames in that one can embed other graphical components into them. On the other hand one cannot make them visible and run by themselves. They need to be linked to an HTML page. Then if that page is displayed in a browser, the applet is started and can be seen in the browser. Another difference between applications and applets is that applets are not allowed to do certain things. For example an applet may not write or read les on the machine it is running on. This is to protect users who run an applet found on some web page on their computers. We only present a very simple way to use applets. There are more issues the programmer should consider when writing an applet for a serious application.
There is a difference between frames and applets. While the embedding is usually done in the constructor of a frame, it is done in method init of the applet. Also, an applet is not directly started by the user. Instead it is externally started by the browser, when the corresponding web page is loaded. An applet has to be linked to the HTML page that displays it. We shall call this page the master page of the
272
Advanced topics
applet. When the master page is loaded, the browser fetches the code of the applet (its class le) and starts the Java runtime system to execute the code. Applets are driven by four methods, which we explain below. Even though applets can have a constructor, it should not be used.
public public public public void void void void init() start() stop() destroy()
init() is automatically called by the browser when the master page is loaded
into the browser for the rst time after the browser has started. This method replaces the constructor. All the code for embedding other components initializing variables, etc., should go here. This method is only executed once in the applets life.
start() is automatically called by the browser after the init has nished. It is
also called when the user returns to the master page after having looked at other pages without shutting the browser down. The code for resuming interrupted work should go here.
stop() is automatically called by the browser when the user leaves the master
page without shutting the browser down. As the applet is paused, all code for interrupting the computations should go here. Some browsers also call destroy at this point. Then the applet is terminated. It is restarted using init when the user returns to this page.
destroy() is automatically called by the browser just before the applet is termi-
nated. All code for the nal clean-up should go here. In our examples we only put real code into the init method. The other three methods contain only print commands. The result of these commands will appear on the Java console. The Java console is a program that comes with the Java plugins for browsers. How it is activated depends on the browser and the operating system. In recent versions of Windows one can nd it in Start -> Settings -> Java Plug-in.
23.1.1
A counter applet
In our rst example we reuse the CounterPanel from Chapter 3. Such a panel is glued centrally into the applet. No further code is needed, because a CounterPanel supplies all functions of a counter. We list the program below. File: its/Applet/CounterApplet.java
1. package its.Applet; 2. 3. import javax.swing.JApplet;
Applets
273
4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27.
import its.CounterGUI.CounterPanel; import java.awt.BorderLayout; public class CounterApplet extends JApplet { public void init(){ CounterPanel cPane = new CounterPanel(); this.getContentPane().add(cPane,BorderLayout.CENTER); } public void start(){ System.out.println("Start"); } public void stop(){ System.out.println("Stop"); } public void destroy(){ System.out.println("Destroy"); }
To make the applet run, it must be embedded in an HTML page. This is done by inserting an applet tag (<APPLET>) into the page. In the tag one has to specify where the applets class les are found (CODE), its width (WIDTH) and height (HEIGHT). The value of the CODE parameter is the path to the class les. If packages are used the path has to reect their structure. Also all classes used by the applet have to be there in our example; this includes some classes from the its.CounterGUI package. Here is the directory structure needed for the example:
its/Applet/CounterApplet.class its/CounterGUI/CounterModel.class its/CounterGUI/CounterPanel.class its/CounterGUI/CounterListener.class
Our HTML page has the minimum structure; HTML-tags have many more formatting capabilities. The page looks like this:
<html> <head> <title> Counter Applet </title> </head>
274
Advanced topics
The page is placed in the directory right above the its directory which contains the class les. This page can also be reached from the books home page. You can follow the link to the applet from there or type the following line in your browser to load the page and run the applet. The result is shown in Figure 23.1.
http://www.imm.dtu.dk/swingbook/AppletTest/CounterDemo.html
23.1.2
The second example is an applet displaying a timer which is incremented every half second. The timer is run in a thread of type TimerThread in order to avoid blocking the applet. A panel (TimerPanel) is used to display the current value of the timer. The thread updates the panel every half second. The applet class is called TimerApplet. The listings are shown below. File: its/Applet/TimerThread.java
1. package its.Applet; 2. 3. public class TimerThread extends Thread { 4.
Applets
275
5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25.
private int time; private TimerPanel timerPane; public TimerThread(TimerPanel tp) { time = 0; timerPane = tp; } public void run(){ while(true){ try { Thread.sleep(500L); time += 500; System.out.println("Running "+time); timerPane.setTime(time); } catch (InterruptedException ex) { } } } }
File: its/Applet/TimerPanel.java
package its.Applet; import javax.swing.JPanel; import java.awt.Color; import java.awt.Graphics; public class TimerPanel extends JPanel { private int time; public TimerPanel() { this.setBackground(Color.yellow); time = 0; } public void paintComponent(Graphics g){ super.paintComponent(g); g.drawString(Integer.toString(time), 50,50); } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21.
276
Advanced topics
22. public void setTime(int t){ 23. time = t; 24. this.repaint(); 25. } 26. 27. 28. 29. }
File: its/Applet/TimerApplet.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. package its.Applet; import javax.swing.JApplet; import java.awt.BorderLayout; public class TimerApplet extends JApplet { private TimerThread timer; public void init(){ TimerPanel timerPane = new TimerPanel(); timer = new TimerThread(timerPane); this.getContentPane().add(timerPane,BorderLayout.CENTER); timer.start(); } public void start(){ System.out.println("Start"); } public void stop(){ System.out.println("Stop"); } public void destroy(){ System.out.println("Destroy"); } }
23.1.3
Remarks
The Java SDK contains an applet viewer, a program that can display an applet without using a browser. This is helpful when developing and testing an applet.
Applets
277
Sometimes one is making a project which should be used both as an application and as an applet. It is then advisable to use a panel into which all components of the GUI are embedded. One just has to embed this single panel into a frame or an applet to get the desired type of program. The CounterPanel is an example of such a modular design.
A.1 Chapter 2
A.1.1 2.1
Both windows close. The reason is that clicking the close button of a SimpleFrame results in the command System.exit(0). This terminates the application, i.e. the program that contains the main-method. In our case this is SimpleFrameDriver. As both frames are constructed there, both are terminated.
A.1.2
2.2
A.2 Chapter 4
A.2.1 4.2
The following listings of the package its.Light contain a trafc light simulation in a modelview implementation. File: its/Light/Constants.java
package its.Light; 1. 2. 3. 4. 5. 6. 7. 8. 9.
public class Constants public static final public static final public static final public static final }
= = = =
1; 2; 3; 4;
280
Appendices
File: its/Light/LightModel.java
1. package its.Light; 2. 3. public class LightModel { 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. public void printColor(){ switch (currentColors) { case Constants.LIGHT_RED: System.out.println("RED"); break; case Constants.LIGHT_RED_ORANGE: System.out.println("RED&ORANGE"); break; case Constants.LIGHT_GREEN: } public int getCurrentColors(){ return(currentColors); } } public void nextColor(){ switch (currentColors) { case Constants.LIGHT_RED: currentColors = Constants.LIGHT_RED_ORANGE; break; case Constants.LIGHT_RED_ORANGE: currentColors = Constants.LIGHT_GREEN; break; case Constants.LIGHT_GREEN: currentColors = Constants.LIGHT_ORANGE; break; case Constants.LIGHT_ORANGE: currentColors = Constants.LIGHT_RED; break; default: System.out.println("ERROR: ILLEGAL COLOR COMBINATION!"); break; } public LightModel() { currentColors = Constants.LIGHT_RED; private int currentColors;
281
45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55.
System.out.println("GREEN"); break; case Constants.LIGHT_ORANGE: System.out.println("ORANGE"); break; default: System.out.println("ERROR: ILLEGAL COLOR COMBINATION!"); break; } } }
File: its/Light/LightTest.java
package its.Light; public class LightTest { public static void main(String[] args) { LightModel light = new LightModel(); for (int i = 0;i < 10 ;i++ ) { light.printColor(); light.nextColor(); }//for } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12.
File: its/Light/LightFrame.java
package its.Light; import its.SimpleFrame.SimpleFrame; import java.awt.Color; import java.awt.GridLayout; import javax.swing.JButton; import javax.swing.JPanel; public class LightFrame extends SimpleFrame { private JPanel redPanel,orangePanel,greenPanel; private LightModel light; public LightFrame(LightModel lm) { light = lm; 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.
282
16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. 57. 58. 59. 60. 61.
Appendices
this.getContentPane().setLayout(new GridLayout(4,1)); redPanel = new JPanel(); orangePanel = new JPanel(); greenPanel = new JPanel(); JButton nextButton = new JButton("Next"); LightListener lightList = new LightListener(this); nextButton.addActionListener(lightList);
private void setColor(int color){ switch (color) { case Constants.LIGHT_RED: redPanel.setBackground(Color.red); orangePanel.setBackground(Color.lightGray); greenPanel.setBackground(Color.lightGray); break; case Constants.LIGHT_RED_ORANGE: redPanel.setBackground(Color.red); orangePanel.setBackground(Color.orange); greenPanel.setBackground(Color.lightGray); break; case Constants.LIGHT_GREEN: redPanel.setBackground(Color.lightGray); orangePanel.setBackground(Color.lightGray); greenPanel.setBackground(Color.green); break; case Constants.LIGHT_ORANGE: redPanel.setBackground(Color.lightGray); orangePanel.setBackground(Color.orange); greenPanel.setBackground(Color.lightGray); break; default: System.out.println("ERROR: ILLEGAL COLOR COMBINATION!"); break; }//Switch }
283
62. 63. 64. 65. 66. 67. 68.
File: its/Light/LightListener.java
package its.Light; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class LightListener implements ActionListener { private LightFrame parentFrame; public LightListener(LightFrame pf) { parentFrame = pf; } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26.
public void actionPerformed(ActionEvent evt) { String actComm = evt.getActionCommand(); if(actComm.equals("Next")){ parentFrame.showNextColors(); } else { System.out.println("ILLEGAL COMMAND SOURCE"); } }
File: its/Light/LightDriver.java
package its.Light; public class LightDriver { public static void main(String[] args) { LightModel lm = new LightModel(); 1. 2. 3. 4. 5.
284
Appendices
6. LightFrame lf = new LightFrame(lm); 7. lf.showIt(); 8. } 9. }
A.2.2
4.3
Here is the code in a non-modelview implementation. It is also in one le and not documented. It works, but one should not program Java like this. File: its/ColorSelection/ColorSelectionFrame.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. package its.ColorSelection; import import import import import javax.swing.*; java.awt.Color; java.awt.GridLayout; its.SimpleFrame.SimpleFrame; java.awt.event.*;
public class ColorSelectionFrame extends SimpleFrame { private JPanel colPanel; private JButton redBut, blueBut, yellowBut;
public ColorSelectionFrame() { blueBut = new JButton("blue"); redBut = new JButton("red"); yellowBut = new JButton("yellow"); ColorListener cList = new ColorListener(); blueBut.addActionListener(cList); redBut.addActionListener(cList); yellowBut.addActionListener(cList); colPanel = new JPanel(); colPanel.setBackground(Color.gray); GridLayout gLayout = new GridLayout(2,2); this.getContentPane().setLayout(gLayout);
285
34. 35. 36. 37. 38. 39. this.setVisible(true); 40. 41. 42. } 43. 44. // internal class 45. class ColorListener implements ActionListener{ 46. public void actionPerformed (ActionEvent evt) 47. { 48. 49. String actComm = evt.getActionCommand(); 50. System.out.println(""+actComm); 51. if(actComm.equals("blue")){ 52. colPanel.setBackground(Color.blue); 53. } else if(actComm.equals("red")){ 54. colPanel.setBackground(Color.red); 55. } else if(actComm.equals("yellow")){ 56. colPanel.setBackground(Color.yellow); 57. } 58. }//method 59. } 60. public static void main(String[] args) { 61. ColorSelectionFrame colorSelectionFrame1 = new ColorSelectionFrame(); 62. } 63. } 64. this.getContentPane().add(blueBut); this.getContentPane().add(redBut); this.getContentPane().add(yellowBut); this.getContentPane().add(colPanel);
A.3 Chapter 13
A.3.1 13.1
The following listings of the package its.ResizeJumpDisplay contain the code of a modelview implementation. File: its/ResizeJumpDisplay/PositionModel.java
package its.ResizeJumpDisplay; 1. 2. 3. 4. 5.
286
6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51.
Appendices
// // // // // //
left corner of the black rectangle to be only at: 0/stepNo, 1/stepNo,...,allowedMax/stepNo of the current width or height of the panel. With the choice below this is 0/9, 1/9, 2/9, 3/9, 4/9, 5/9, and 6/9.
private static final int stepNo = 9; private static final int allowedMax = 6; // // // The next variable specifies the length and height of the black rectangle as a number of steps. Here we take 3.
private static final int blackRectSteps = 3; // // // // The next two variables contain the current position of the black rectangle (in fractions of the current width and height of the panel).
public int getXInSteps(){ return(upperLeftX); } public int getYInSteps(){ return(upperLeftY); } public int getNoOfSteps(){ return(stepNo); } public int getBlackSizeInSteps(){ return(blackRectSteps); }
287
52. 53. 54. 55. 56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78.
public void moveDown(){ if(upperLeftY < allowedMax){ upperLeftY++; } } public void moveUP(){ if(upperLeftY > 0){ upperLeftY--; } } public void moveRight(){ if(upperLeftX < allowedMax){ upperLeftX++; } } public void moveLeft(){ if(upperLeftX > 0){ upperLeftX--; } }
File: its/ResizeJumpDisplay/ResizeJumpFrame.java
package its.ResizeJumpDisplay; import its.SimpleFrame.SimpleFrame; import java.awt.BorderLayout; public class ResizeJumpFrame extends SimpleFrame{ public ResizeJumpFrame(){ PositionModel posModel = new PositionModel(3,5); ResizeJumpPanel resizePanel = new ResizeJumpPanel(posModel); this.setSize(500,300); this.getContentPane().add(resizePanel,BorderLayout.CENTER); 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
288
Appendices
15. DirectionPanel dirPanel = new DirectionPanel(posModel,this); 16. this.getContentPane().add(dirPanel,BorderLayout.SOUTH); 17. } 18. 19. }
File: its/ResizeJumpDisplay/ResizeJumpPanel.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. package its.ResizeJumpDisplay; import java.awt.Color; import java.awt.Graphics; import javax.swing.JPanel; public class ResizeJumpPanel extends JPanel{
private PositionModel posModel; public ResizeJumpPanel(PositionModel pm){ posModel = pm; this.setBackground(Color.yellow); } public void paintComponent(Graphics g) { super.paintComponent(g); // get the current dimensions of the panel in pixels int currentWidth = this.getWidth(); int currentHeight = this.getHeight(); // compute the current size of a step in pixels int hStepInPixels = currentWidth/posModel.getNoOfSteps(); int vStepInPixels = currentHeight/posModel.getNoOfSteps(); // compute the pixel positions of the // upper left corner of the black rectangle // and its width and height. int upperLeftX = posModel.getXInSteps() * hStepInPixels; int upperLeftY = posModel.getYInSteps() * vStepInPixels; int blackWidth = hStepInPixels * posModel.getBlackSizeInSteps(); int blackHeight= vStepInPixels * posModel.getBlackSizeInSteps(); //set colour to black g.setColor(Color.black);
289
39. 40. 41. 42. 43. 44. 45. 46.
//
g.fillRect(upperLeftX,upperLeftY,blackWidth,blackHeight); }
File: its/ResizeJumpDisplay/DirectionPanel.java
package its.ResizeJumpDisplay; import java.awt.GridLayout; import javax.swing.JButton; import javax.swing.JPanel; public class DirectionPanel extends JPanel { public DirectionPanel(PositionModel posMod, ResizeJumpFrame parent) { GridLayout gLayout = new GridLayout(1,4); this.setLayout(gLayout); JButton upBut = new JButton("Up"); JButton downBut = new JButton("Down"); JButton rightBut = new JButton("Right"); JButton leftBut = new JButton("Left"); this.add(upBut); this.add(leftBut); this.add(rightBut); this.add(downBut); DirectionListener dirList = new DirectionListener(posMod,parent); upBut.addActionListener(dirList); downBut.addActionListener(dirList); rightBut.addActionListener(dirList); leftBut.addActionListener(dirList); } } 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29.
290
Appendices
File: its/ResizeJumpDisplay/DirectionListener.java
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. package its.ResizeJumpDisplay; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; public class DirectionListener implements ActionListener{ private PositionModel posModel; private ResizeJumpFrame parentFrame; public DirectionListener(PositionModel pm,ResizeJumpFrame rjf) { posModel = pm; parentFrame = rjf; } public void actionPerformed(ActionEvent evt) { String actionComm = evt.getActionCommand(); if(actionComm.equals("Up")){ posModel.moveUP(); } else if(actionComm.equals("Down")){ posModel.moveDown(); } else if(actionComm.equals("Left")){ posModel.moveLeft(); } else if(actionComm.equals("Right")){ posModel.moveRight(); } parentFrame.repaint(); } }
File: its/ResizeJumpDisplay/ResizeJumpDriver.java
1. 2. 3. 4. 5. 6. 7. 8. 9. package its.ResizeJumpDisplay; public class ResizeJumpDriver { public static void main(String[] args){ ResizeJumpFrame rf = new ResizeJumpFrame(); rf.showIt("ResizeJumpFrame"); } }
This appendix addresses some problems that frequently appear when one begins to program larger applications in Java.
Listing of output:
Value a = 3 Value b = 4
292
Appendices
a = b 4 4 b = 7 4 7
executed.
Let us now look at what happens if we use objects. The following code snippet uses arrays of integers instead of integers. Arrays are objects in Java. We use arrays of length two and apply the same operations as above to the elements at array position 1. Below is a listing of the code and output. Code snippet:
int[] A = {1,3}; int[] B = {1,4}; A = B; B[1] = 7;
Listing of output:
Value A[1] = 3 Value B[1] = 4 Statement A = B executed. Value A[1] = 4 Value B[1] = 4 Statement B[1] = 7 executed (new value for B[1]). Value A[1] = 7 Value B[1] = 7
The behaviour is not as one might have expected. After we set the entry at position 1 in array B to 7, the entry at position 1 in array A is also 7. We would expect A[1] to be 4. The reason for this is that the statements a = b in the rst code snippet and A = B in the second one have different semantics and thus different results. Figure B.1 shows what happens if we use integers. The names a and b always refer to the integer value. Therefore the statement a = b means a is assigned the value of b.
int a = 3 int b = 4 a = b b = 7 a a a a 3 3 4 4 b b b 4 4 7
Figure B.1 A visualization of the rst code snippet. The Java statements are listed on the left and the results are on the right. A rectangle symbolizes a memory location and the letter inside is the variable name
293
1 3
0 1 0 1
1 3
0 1
B B
1
0
4
1
A = B
1 3
0 1
1
0
4
1
B[1] = 7
1 3
Figure B.2 A visualization of the second code snippet. The Java statements are listed on the left and the results are displayed on the right. A rectangle symbolizes the memory area of an array of length 2. The array indices 0 and 1 are shown above. The arrow from the variable name to the rectangle indicates which array is referenced by the variable name. Here the statement A = B means that A refers to the same array as B. The array to which A originally pointed cannot be accessed any more and is therefore shown in grey
Let us now look at the second program snippet. The statement int[] A = {1,3} creates an integer array of length 2. The variable name A is now a reference to the memory location where the array is stored. It does not refer to any integer value. It cannot refer to a value because the array holds two values. Thus the statement A = B means A now references the same memory location as B. Now, changing B[1] to 7 has the effect that also A[1] is 7. The originally created array with entries 1 and 3 is lost. It cannot be accessed any more because there is no reference pointing to it. Javas automatic garbage collection detects such objects and removes them from the memory. See also Figure B.2. The full listing of the program can be found in package its.ReferenceDemo.
public class FlexArray { // Here the variable data is DECLARED // NO array is created! Thus data are null // at this point. We cannot DEFINE data // here because we do not know how long the
294
9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41. 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55.
Appendices
// array should be. // Variable data stores the data. private int[] data; // these variables store the start and end index and the // length of the array private int startindex, endindex, length; public FlexArray(int s, int e) { if(s > e){ System.out.println("ERROR in FlexArray: Start index > end index"); } else { startindex = s; endindex = e; length = endindex - startindex + 1; // In the next command the variable data // is defined. We now know how long the // array has to be. Then data is no longer // null but it references an integer array. data = new int[length]; }//if }//constructor
private int indexingFunction(int c){ int result = -1; if((c < startindex) || (c > endindex)){ System.out.println("ERROR in FlexArray: Illegal index: "+c +" not in ["+startindex+","+endindex+"]"); } else { result = c - startindex; }//if return(result); }
295
56. 57. 58. 59. 60. 61. 62. 63. 64. 65. 66. 67. 68. 69. 70. 71. 72. 73. 74. 75. 76. 77. 78. 79. 80. 81. 82.
}//method
public int size(){ return(length); } private boolean indexOK(int i){ if((i >= startindex) && ( i <= endindex)){ return(true); } else{ System.out.println("ERROR in FlexArray: Index out of bounds."); return(false); } } }
File: its/General/FlexArrayDriver.java
package its.General; import javax.swing.JFrame; public class FlexArrayDriver { public static void main(String[] args) { // Define a FlexArray with indexing -3,-2,..,2. // and fill it with i3 at position i. FlexArray myArray = new FlexArray(-3,2); for(int i=-3; i <= 2; i++){ myArray.setValue(i,i*i*i); } //Read a certain value System.out.println("Value at -2 is "+myArray.getValue(-2)); //Read a certain value System.out.println("Value at 2 is "+myArray.getValue(2)); 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.
296
Appendices
Now let us look at a frequently made mistake. We only add one word in the constructor of FlexArray. We replace the line
data = new int[length];
by
int[] data = new int[length];
The resulting class is called WrongFlexArray and the driver is WrongFlexArrayDriver. We do not print the listing, as it differs only in that one line and the fact that FlexArray is replaced by WrongFlexArray everywhere. Here is the result of the test run:
java.lang.NullPointerException at its.General.WrongFlexArray.setValue(WrongFlexArray.java:70) at its.General.WrongFlexArrayDriver.main(WrongFlexArrayDriver.java:18) Exception in thread "main"
What has happened? Why is there a NullPointerException when we want to set a value in the data array? Well, the data array is not dened. At least not the data array we want to use in method setValue. The problem lies in the constructor of WrongFlexArray. In the line
int[] data = new int[length];
another local integer array by the name data is dened. This has nothing to do with the integer array by the name data which is declared before the constructor. The local array ceases to exist when the constructor is nished. Then the data array which is declared before the constructor is still there, but not created by using new and thus null. When method setValue tries to access it a NullPointerException is triggered.
297
298
Appendices
Three classes are dened, AClass, BClass and CClass. AClass has (non-static) method methodA1. Now BClass wants to use that method. CClass is the master class which uses both AClass and BClass. The structure is listed below.
1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. class AClass{ public AClass(){ } public int methodA1(){ // commands } } ----------------------------------class BClass{ public BClass(){ } public void methodB1(){ int n = methodA1(); //methodA1 unknown }
----------------------------------class CClass{ public CClass(){ } public static void main(String[] a){ AClass a = new AClass(); BClass b = new BClass(); } }
299
because inside BClass, the methods of AClass are unknown. In order to access the non-static methods of AClass, BClass has to have an instance of AClass. In the listing below we change the constructor of BClass to have an instance of AClass as an argument. Then that can be used to call the methods of AClass. In CClass an instance of AClass is created and then given to BClass.
class AClass{ public AClass(){ } public int methodA1(){ // commands } } ----------------------------------class BClass{ private AClass aClass; public BClass(AClass ac){ aClass = ac; // now an AClass is known } public void methodB1(){ ... int n = aClass.methodA1(); //methodA1() known as a //method of aClass } } ----------------------------------class CClass{ public CClass{ } public static void main(String[] a){ AClass ac = new AClass(); // ac is instance of AClass ){ 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. 35. 36. 37. 38. 39. 40. 41.
300
Appendices
42. AClass b = new BClass(ac); 43. // ac is given to BClass in the 44. // constructor. 45. } 46. 47. }
In the its-programs, there are examples of this. AClass is like StatusPanel which provides a method to set the mouse coordinates. BClass is like the listener that knows the coordinates and wants to set them in the status panel. The listener gets a reference to a status panel in the constructor, so it can access the method to set the coordinates.
The paths are separated by semicolons and backslashes are used inside the paths. This is correct for Windows systems. On UNIX/Linux systems, colons and slashes are used instead. As a general remark, one should always include the current working directory (dot) in the classpath. When using packages one should include the directory one above the package in the classpath. As mentioned in the introduction, Chapter 1, one should always run the javac and java commands from the directory immediately above the root of the package structure used. For the programs of this book that means the directory containing the directory its. The syntax then is (for Windows):
javac its\[packageName]\[sourceFileName].java java its.[packageName].[sourceFileName]
Still it might happen that the compiler complains that it cannot nd some class les. This usually happens if an environment variable is set for the classpath. These are variables that determine the classpath for the whole computer. They might point to directories not containing the classes of the project. Then the compiler looks in the wrong places. One can solve this problem either by including the correct dictionaries into the environment variable or by specifying an explicit class path in the javac and java commands. For the latter try
javac -classpath . its\[packageName]\[sourceFileName].java java -classpath . its.[packageName].[sourceFileName]
301
To set the environment variable one uses commands of the operating system, not of the Java development system. Setting these variables differs from one operating system to the next and even from one version to the next of the same operating system. We therefore refer to the manual or online help of the operating system.
GIF, 150 JPEG, 150 JPG, 150
15-puzzle, 127
Index
containers, 15 content pane, 9 cycle-free, 45 cycle-free embedding, 45 data encapsulation, 297 data stream, 93
AbstractTableModel, 179
access, 297 action command, 33 ActionEvent, 32
ActionListener, 32 actionPerformed, 32
anonymous listener, 87 applet, 271 applet tag, 273 applet viewer, 276 ASCII, 93 asynchronous loading, 150 AttributeSet, 217
DefaultMutableTreeNode, 186, 187 DefaultStyledDocument, 215, 220 DefaultTableCellRenderer, 178 DefaultTreeModel, 186 Dimension, 15
divider, 197
EditorKit, 221
embedding cycle-free, 45 hierarchical, 44 empirical tests, 25 encapsulation, 297 event dispatching thread, 236 event driven, 3, 236 event queue, 236 event thread, 236 events, 3, 28, 31 example programs BadUpdateFrame, 245 BiologyTree, 189 BlockingFrame, 237, 239 BlockPuzzle, 143 BlockPuzzleFrame, 140
304
Index
example programs (Continued) BlockPuzzleListener, 141 BlockPuzzlePanel, 135, 136 BlockPuzzleTest, 127, 133 BoardModel, 127, 129 BorderFrame, 169 BorderPanel, 168 Browser, 262 CalendarListener, 194 CalendarPanel, 193, 194 Circle, 65 CircleAdministration, 6567 CleanUpAdapter, 90 CleanUpFrame, 90, 91 ColorPanel, 15 ColorSelectionFrame, 284 ConfigurationModel, 127, 132 Constants, 127, 133, 279 CounterAnonymousPanel, 88 CounterApplet, 272 CounterDriver, 29, 31 CounterFrame, 29, 30 CounterInterfacePanel, 86 CounterInternalPanel, 83 CounterListener, 32 CounterModel, 24 CounterModelTest, 25 CounterPanel, 28, 29 DataTransferObject, 115, 116, 119 DirectionListener, 290 DirectionPanel, 138, 289 DirectoryTree, 191 DocAtt, 221 DocumentFrame, 221, 222 Drawing, 245 DrawingDisplayScrollDriver, 105 DrawingDisplayScrollFrame, 105 DrawingDisplayScrollPanel, 105 EditorFrame, 112 EditorListener, 108, 109 EditorSkeletonFrame, 107, 108 FileReadWrite, 96 FlexArray, 293 FlexArrayDriver, 293, 295 GenericDrawMouseAdapter, 252 GenericDrawPanel, 252 GoodUpdateFrame, 248 GridBagFrame, 210, 211 ImageCutAndMirrorFrame, 161, 163 ImageFrame, 151 ImagePanel, 154, 156
ImagePanelFrame, 157 ImagePanelTrackerFrame, 159 ImageScaleFrame, 165 InteractiveFrame, 68 InteractivePanel, 68, 69 ITSBrowser, 261 LayoutDriver, 20, 21 LayoutFrame, 20 LightDriver, 283 LightFrame, 281 LightListener, 283 LightModel, 280 LightTest, 281 LineRead, 98, 99 ListDemo, 171 ListDemoFrame, 172 ListTransferFrame, 175, 176 MenuDriver, 77, 79 MenuFrame, 77 MenuListener, 79, 80 MonitorThread, 267, 268 MouseEventDriver, 60, 63 MouseEventFrame, 56, 58 MouseEventPanel, 56, 58 MoveModel, 127, 131 MultiplicationTableFrame, 180,
182
MultiplicationTableModel, 180,
181
MyMouseListener, 60, 62, 71 MyMousePositionsListener, 60, 61 NonBlockingFrame, 242 OpenPolygon, 251 OrderTableFrame, 185 OrderTableModel, 183, 184 PixelPoint, 253 PositionModel, 285 PrintFrame, 226, 232 PrintPanel, 226, 231 PrintSuit, 233, 234 PrintSuitTestFrame, 233, 235 ReadURL, 265 RealPoint, 253, 254 ResizeDriver, 126 ResizeFrame, 125 ResizeJumpDriver, 290 ResizeJumpFrame, 287 ResizeJumpPanel, 288 ResizePanel, 125 SearchDialog, 113, 117 SearchListener, 116
Index SimpleFrame, 12 SimpleFrameDriver, 13, 14 SimpleGraphicsDriver, 50, 51 SimpleGraphicsFrame, 50, 51 SimpleGraphicsPanel, 49, 50 SimpleHTMLFrame, 259 SimplePanelFrame, 16, 17 SimplePanelFrameDriver, 17, 18 SplitPaneFrame, 197, 199 StaticTableFrame, 178 StatusPanel, 56, 59, 70 TabbedPaneFrame, 202 TextAnalysisDriver, 44 TextAnalysisFrame, 40, 41 TextAnalysisListener, 43, 44 TextAnalysisModel, 38 TextAnalysisPanel, 40, 41 TextDisplayDriver, 100, 101 TextDisplayFrame, 100 TextDisplayScrollDriver, 103 TextDisplayScrollFrame, 103, 104 TimerApplet, 274, 276 TimerPanel, 274, 275 TimerThread, 274 ToolPanel, 262 TransferListener, 175 TreeDemoDriver, 189 TreeFrame, 189, 190 UpdatePanel, 245 WebImageFrame, 153 WorkerThread, 241 WrongFlexArray, 296 WrongFlexArrayDriver, 296 File, 94
le selection dialog, 110 FileReader, 95 FileWriter, 95 llOval, 49 llRect, 49 lters, 93 re method, 183 ow layout manager, 18 FlowLayout, 18 frame, 9 getContentPane, 16 graphical user interface, 23 Graphics, 47 Graphics Interchange Format, 150 Graphics2D, 122 grid layout manager, 18 grid-bag layout, 205
305
JApplet, 271 JButton, 28 JComboBox, 192 JDialog, 113 JEditorPane, 99 JFileChooser, 110 JFrame, 9, 11 JLabel, 27 JList, 170 JMenuBar, 76 JMenuItem, 76
Joint Photographic Experts Group, 150
JOptionPane, 120 JPanel, 15 JRadioButton, 114 JScrollPane, 102 JSplitPane, 197 JTabbedPane, 201 JTable, 177 JTextArea, 99 JTextField, 39 JTextPane, 99 JTree, 187, 188 KeyListener, 90
label, 27 labels, 27
306
Index
Layout-Manager, 16 leafs, 186 listener, 28 anonymous, 87 listeners, 3, 31 ListModel, 170, 173 repaint, 68
repaint(), 47
scroll bars, 102 Scrolling, 102 separators, 76 setColor, 49 setVisible, 10
MouseMotionListener, 53, 54
nodes, 186 offset, 215 of a position, 215 on-the-y, 87 output stream, 93 override, 47
TableCellRenderer, 178
test, 25 test plan, 25 text, 93, 215 Text elds, 39 title bar, 9 Toolkit, 123, 154 tree, 186 TreeModel, 188 UML, 33 Unicode, 93 Uniform Resource Locator, 258 URL, 152, 258 URL, 153 viewport, 102 visibility, 297
paintComponent, 47 panel, 14 pixel, 149 PNG, 150 Portable Network Graphic, 150 Position, 215, 217 position marker, 215 Printable, 227 printable area, 228 private, 297 protected, 297 public, 297
radio button, 114 radio buttons, 113 Reader, 93 reference, 291 reference point, 48, 49
WindowListener, 90
windows, 9 Writer, 93
JDK 5.0 compliant Fully comprehensive Great reference for life Accompanied by a wide range of interactive online resources for students and lecturers Student-friendly, full-colour, easy to follow design and layout Hundreds of lines of real code and examples to show you how its done
Bestselling, application-oriented,
introductory graphics book with OpenGL revised for 2005! Uses a proven top-down, programmingoriented approach to teach core concepts Covers all topics required for a fundamental course, including shading, modelling and texture mapping Additional student resources include online Source Code Lecturer resources include online Solutions, PowerPoint figures, and PowerPoint lecture notes