Core Java 2 - Volume I - Fundamentals, 5th Ed.

Graphics Programming

Under the Hood | Creating a Frame | Frame Positioning | Displaying Info in a Panel | 2D Shapes
Colors | Text & Fonts | Images

I know I say this on almost every page, but with this topic and chapter you are definitely better served by getting the book and following along.  They show all examples and list a great deal more methods, show all possible parameters, and give clear down to earth explanations of a lot of things that I am just going to gloss over quickly.  Getting the book is your best course of action if you want to learn this stuff cold.  I'm going to do my best to lay down the basics though...

Introduction to Swing

When was the last time you used a DOS based program with ASCII text as the only output?  Can you even think of one that's used today?  That's because the guys at Xerox PARC came up with GUIs (Graphical User Interfaces).  Apple stole them from PARC, then Microsoft stole them from Apple.  They were so popular that even UNIX, the last bastion of the command line, had to deal with "Gooeys" eventually.  There's no way around it, logic doesn't butter the biscuit anymore.  You have to make it look pretty and be intuitive and easy to use.  No problem.  If you never wrote GUI stuff before or freaked the first time you saw the source code for a GUI program (what's WinMain?) and said forget it, then this will be a relief.   This section goes over the basics of GUI programming and is a great way to get your feet wet, get comfy, and then dive right in.

Actually, this is the beginning of writing programs that are just like the GUI programs you use everyday.  You'll be able to create a resizable frame just like a Windows or X program and it's not that hard. (While it might turn off the UNIX folk and sure won't make the SUN faithful happy, I'm going to compare what we do here to MS Windows because almost all UNIX folk know, or at least have to deal with, Windows and a good many Windows people wouldn't know what to make of a reference to X Windows or Open Look.)

Some of the following is a little confusing for people who have never thought about event driven or graphical programming, but trust me when I tell you, if you got through the previous six sections then this is going to be easy.

Now, what's Swing?  Well, it helps to back up and drink in a little history first.  When Java was first introduced, Ver 1.0, you used a library called the AWT (Abstract Windows Toolkit) to do all your basic GUI programming with.  The AWT did things in a unique way.  Because Java's claim to fame was "write once, run anywhere" the designers of Java wanted the AWT to maintain the "look and feel" of the operating system the program was running on.  To accomplish this they developed the AWT to work as a "peer" that would hand off the actual work (creating a frame, making a dialog box, etc) to the underlying operating system, maintaining the look and feel in the process.  This worked well for simple tasks but, as things got a little more complicated, people creating graphics libraries were pulling their hair out having to deal with so many different operating systems.  One would act one way, one another, and this lack of consistent behavior made it nearly impossible to write code that was bug free on all platforms. 

Netscape to the rescue.  The boys at Netscape were running into this problem as well so they came up with the IFC (Internet Foundation Classes).  The IFC routines did all the work themselves, all they needed from the underlying OS was a way to put up a Window and paint on it.  With these two simple tasks as the only "peer" based functionality, they wrote code that did all the other dirty work of a GUI library.  Now, this does defeat the original "look and feel" argument, because all your graphical elements will look the same no matter the platform, but Netscape didn't care.

Enter SUN.  They worked with the guys at Netscape to create a similar library of graphical functions to serve as a library for Java.  When they were done they named it "Swing".  So now we have Swing, the official name for the non peer based GUI toolkit that is a part of the Java Foundation Classes (JFC).  (As you may have guessed MS VC++ people, the JFC is similar to the MFCs, but does many things in a different fashion with a very different class hierarchy.)

Note: The JFC is much more than just Swing.  There's a 2D graphics API and a Drag and Drop API plus tons more.  When we're done dig in!

Now, just to avoid confusion, you have to remember a few things.  The AWT hasn't gone anywhere.  It's still there in the same form it was under Java 1.1, but after you learn how to use Swing you won't want to go back to the peer based methods of doing things.  Also, things may run a little slower with Swing because of the added layer of complexity.  With a decent processor and a healthy amount of RAM you won't notice the difference though, and if you use a SUN workstation you're on the same platform as the Java developers so you're in real good shape.  The Book's authors give three reasons to stick with Swing...

A much richer and more convenient set of user interface elements

Because it isn't peer based you're less likely to have platform-specific bugs

Swing gives a consistent user experience across platforms

After reading that last selection you may be saying "Didn't you just say that Java wanted the look and feel of the underlying OS?"  Yep.  The Swing tries to solve this by offering the programmer a few choices.  You can set your graphics to have the look and feel of major GUI OSes.  You can make a Java app look like MS Windows, Motif, or you can define your own.  The last option, the one we'll go with here, is to go with Swing's own look and feel called "Metal".  We will be Metalheads from here on out.

Under the Hood

One last note before we dive in.  If you have programmed for Windows or UNIX GUIs in the past you are used to a set of tools usually called "Visual Development".  This is when you use an IDE to set up what you want the layout of your program to be using painting and layout tools, sort of like using building blocks.  You put a frame here, a dialog box here, a button here, you define some properties and the package creates all the underlying code for you.  You just find the comment line that says..

// insert your code here

add some functionality and you're done.  Users of MS Visual Basic and Visual C++ don't know the first thing about GUI programming because it's all done for them.  (Don't get crazy if you use either of the above.  You're the special breed that picks things apart or you wouldn't be reading this.  You know who I'm talking about.  Even I use VC++6 because the compiler is fast and it's the only way to write code that works on the Windows platform.  Try porting GUI code someday, it doesn't work.  Plus, after you know the basics of GUI programming it gets a bit tedious so it's nice to have the package create a shell that you can customize to your liking.) 

Unfortunately, or fortunately depending on your outlook, Java doesn't have the kind of "Visual" tools that you may be used to.  There are some, Forte headed in this direction and morphed into Netbeans.  The Netbeans IDE now offers a great deal of "Visual" tools that let you do a layout and have the code automatically generated for you, but, as the book's authors say, they aren't as "mature" as the ones for Windows development.   I think this is a good thing because it forces programmers to develop their GUI's from the ground up and actually learn how to do the actual coding for these things.  It forces the programmer to go "under the hood" and learn what's actually going on when you hit that button or click that box.  When you look behind the curtain and see that the great and mighty OZ is not all that complicated you lose a layer of trepidation and realize there's nothing you can't code.  You learn all the dirty secrets that the Gurus know and when you're done learning you're part of their ranks.

The knowledge you get from doing it yourself is priceless and once you make your bones you can set yourself up with what I call "shells" that are basic GUIs waiting to be used.  I have a Menu shell, a dialog shell, and a host of other things that are pre-written and waiting to be customized for use in a program.  Don't get bogged down with the untrue notion that you'll have to spend hours and hours on each project writing a GUI from scratch.  (if you're a comp sci student ask yourself if you ever wrote every single line of code for a project from scratch?  Of course not, you cut and pasted everything useful from your previous projects.  It's the same way here.)

Once you're done learning the basics of GUI and Event Driven programming you will have added weapons to your arsenal that separate the men from the boys (Sorry ladies, it's just a saying), so hunker down and drink it all in.

Creating a Frame

Alright, enough theoretical blah blah, lets do this.  In Java a top-level window is called a frame.  It's just a window that's not contained in any other window.  This top-level version has a peer based class called Frame from the AWT, but the Swing version has been named JFrame. It extends Frame and is one of the few Swing components not painted on a canvas (remember the IFC's?).

A Frame is an example of a container and, as you'll see real soon, we can add other components to it.  But first we'll put up a frame all by itself.  The code listed below is as self explanatory as it gets.  We get a JFrame object (from a class that we created extending JFrame and setting the size of the window), tell it how to exit properly, and then run a method to display it to the screen.  Here goes nothing...

package chap7;

import javax.swing.*;

public class SimpleFrameTest
{
    public static void main(String[] args)
    {
        SimpleFrame frame = new SimpleFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.show();
    }// eom main
}// eoc SimpleFrameTest
    
class SimpleFrame extends JFrame
{
    public SimpleFrame()
    {
        setSize(WIDTH, HEIGHT);
    }
    
   public static final int WIDTH = 200;
   public static final int HEIGHT = 150;
}// eoc SimpleFrame
    

The Output?

(The purists who are following with the book can see that I shrunk it down to make it easier to display on the website)

Nothing to write home about, but now you can create your own window.  What you may have thought was a lot of obscure code is really just the simple prog you see above. Because the Frame class is peer based so you can see that I'm using Windows (W2K Pro) by the "look and feel".  All the components, the buttons, the title bar, are drawn in that "Windows" kind of way.  As you'll see later, Windows and Java don't like each other (About like how Mr. McNealy and Mr. Gates get along) and Windows will not make it easy on us Java people, but Java code does run on any platform as advertised.

As you can see, and I neglected to mention until now, the Swing classes are in a package called javax.swing.  Most of the rewritten AWT classes just have a J thrown in front of them so if you know an AWT class called "foo" it's a pretty good bet the matching swing class is called "jfoo".  That sounds like a hip hop name.  JFoo and 50 Cent live tonight!

ALSO, remember that a JFrame class has a default size of 0 and is positioned in the 0,0 coordinate of your screen.  Sounds like minutia but I know for a fact it was a question on a Java Cert Exam, so I figured I'd mention it.  The 0,0 corresponds to the upper left hand side of the window, and is relative to the window containing it, in this case the entire desktop.  

Get the book to learn the gory details of the default Close Operation method and a little preview of how threads work (The method main ends but the Frame is still on the screen?).  For now, we're moving on to positioning these frames where we need them to be and giving them titles and icons (well, titles at least, keep reading...)

Frame Positioning

Now we can make a frame with the JFrame class.  The next section points out that JFrame is a subclass of the Window class which is a subclass of the ancestor of all GUI objects, Component. It's in these classes that you'll find methods for resizing and moving around Frames.  Always a good idea to hit the API Docs and see what your classes inherit from and what you can do with the object you have.  

Now, to play around a little lets create a frame that's one quarter the size of your screen and centered in the middle of it.  In order to do this we need some system dependant info.  In Java you usually get system dependant info with something called a toolkit.  The Toolkit class has a method called getScreenSize() that returns an object of type Dimension.  The Dimension object has public fields that hold the width and height of the screen.  Here's how to grab the screen size...

Toolkit kit = Toolkit.getDefaultToolkit;
Dimension screenSize = kit.getScreenSize();
int screenWidth = screenSize.width;
int screenHeight = screenSize.height;

Bingo.  Now we can use this information to make a frame 1/4 of the screen and center it....

package chap7;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class CenteredFrameTest
{
    public static void main(String[] args)
    {
        CenteredFrame frame = new CenteredFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.show();
    }// eom main
    
}// eoc CenteredFrameTest

class CenteredFrame extends JFrame
{
    public CenteredFrame()
    {
        
        // get the screen dimensions
        Toolkit kit = Toolkit.getDefaultToolkit();
        Dimension screenSize = kit.getScreenSize();
        int screenHeight = screenSize.height;
        int screenWidth = screenSize.width;
        
        // center the frame in the screen
        
        setSize( (screenWidth/2) , (screenHeight/2) );
        setLocation((screenWidth/4),(screenHeight/4));
        
        // set frame icon and title
        Image img = kit.getImage("icon.gif");
        setIconImage(img);
        setTitle("CenteredFrame");
    }
}// eoc CenteredFrame

The Output

I didn't make it smaller, that's a quarter of my screen.  We use the setTitle() and setIconImage() methods to pretty things up a bit.  Anyway, the point of the code, after we got the screen size, was to show you the setLocation() and setSize() methods of the JFrame class.  By using these you can size your window and put it anywhere on the screen you'd like.

Make sure to get the book and get clued in on a bunch of other methods for moving windows around, or just go to the online API and start exploring...

Displaying Information in a Panel

These Frames are nice, but we'd better start putting something in them.  You could write content directly to the frame, but that's considered bad form in Java.  A frame is meant to be more of a container for other objects.  We're going to create one of those other objects, a panel, and write something on that.  

Now, a frame is a little more complicated than it looks.  It actually has four different parts or "panes" that we can deal with.  The first three are the root pane, the layered pane and the glass pane.  These guys are more for the menus and implementing the look and feel.  The last pane, the one we're concerned with here, is the content pane.  There's a method within the JFrame class called getContentPane().  It returns type Container.  Then, after you have access to the pane you use the add() method of the Container class to add your panel to it.  A little complicated to just write to a panel?  Maybe.  But a panel is a container itself and could hold buttons and scroll bars and all kinds of stuff, so in the end the added complexity is worth it.   You get a content pane like this...

Container contentPane = new getContentPane();
Component c = ...{in our case a panel}...
contentPane.add(c);

Now, how do we whip up a Panel.  Easy.  Swing has a class called JPanel for creating Panels.  But a plain Panel doesn't do anything.  We have to write our own Panel class, extend JPanel, and overwrite the paintComponent() method in JPanal to write what we want to the screen.  With GUI's whenever you resize a window or run a show() method on an object the program the program automatically runs the repaint() method which calls the paintComponent() method for all objects on the screen.  

So, we grab the content pane, create a panel with our own class, add the panel to the content pane, and show the window.  Sounds easy and is...

import java.awt.*;
import javax.swing.*;

public class NotHelloWorld
{
    public static void main(String[] args)
    {
        NHWFrame frame = new NHWFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.show();
    }//eom main
}// eoc NotHelloWorld

class NHWFrame extends JFrame
{
    public NHWFrame()
    {
        setTitle("Hello Graphics!");
        setSize(WIDTH, HEIGHT);
        
        //add a panel to the frame
        NHWPanel panel = new NHWPanel();
        Container contentPane = getContentPane();
        contentPane.add(panel);
    }
     // set dimens
        public static final int WIDTH = 300;
        public static final int HEIGHT = 200;
}//eoc NHWFrame

class NHWPanel extends JPanel
{
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        
        g.drawString("Howdy World!", MESSAGE_X, MESSAGE_Y);
        g.drawString("The First Graphics Prog!", 80, 90);
    }
    
    public static final int MESSAGE_X = 100;
    public static final int MESSAGE_Y = 75;
    
}// eoc NHWPanel

The Output

I'm sorry.  I used the highlighter again.  Just wanted to point out some stuff.  First, I highlighted where we grab the content pane and add to it.  Second, I wanted to highlight the overridden paint method.  You'll get better acquainted with the Graphics context in about two paragraphs so don't worry about the g parameter for now, I just wanted to point out that as an object that's derived from a superclass you have to remember to paint it too, thus the super call.  

Then I wanted to highlight the actual reason why we wrote this thing.  The drawString() method.  It just takes the string you want to print and the coordinates where you want them to go (relative to the upper left hand point of the container it's in).  As you can see by my doctored up version (ever so slightly different from the books) you can hard code the coords or use constants.  It doesn't matter as long as the coords are ints. (Does drawString() have a constructor that takes (String, Point)?...extra credit...ya think?)

OK.  When I said Graphics at the beginning of this chapter you figured on drawing some stuff, eh?  "What's with all this boring Frame and Panel junk?," you're saying...  You're right.  Lets make some shapes.

2D Shapes

Shapes aren't hard to understand or code so we should breeze by this one.  Since JDK 1.0 the Graphics class had methods to draw lines, shapes, whatever, but the drawing operations were very limited.  You couldn't rotate an object or vary drawing line thickness.  Beginning with version 2.0 the J2SE includes the Java 2D library of graphical functions.  We're looking at the basics here, but in Volume 2 there's an advanced graphics chapter that will really get hardcore.

To draw shapes with the 2D library you need to get an object of the Graphics2D class.  It's a subclass of Graphics (remember the parameter for paintComponent() above?)  and all it is is a context that lets the program know what the color settings are, what font to use when rendering text.  All drawing in Java must go through a Graphics object.  Now, if you use a version of the SDK that's Java 2D enabled (If you use this book you're cool) then all methods that receive a Graphics parameter automatically get a Graphics2D object.  You just have to remember to cast it up to a 2D object when your method parameter called for a regular Graphics object.  Is this all getting too kooky?  It's easy, here look...

public void paintComponent(Graphics g)
{
   Graphics2D g2 = (Graphics2D)g;
   ...
}

That's not so bad, right?  We have to do it this way so the functions maintain backward compatibility because a lot of legacy code out there is going to use Graphics and not Graphics2D.  You're hip to that, eh?

Ok, Java 2D library organizes shapes in an OOP way, so we have classes for some basic shapes, Line2D, Rectangle2D, and Ellipse2D that implement an interface called Shape.  There are a lot more shapes but we'll get to them in V2.  Now all you do is create an object using one of the shape classes then use it as a parameter in the Graphics object draw() method.  Eh?  look...

Rectangle2D rect = {......}
g2.draw(rect);

Again, not so bad.  Now how do we construct a Rectangle?  An Ellipse?  

First we have to deal with a bit of Java insanity.  I was going to tell you to buy the book and skip this, but if I do you'll be real confused.  Whomever put together the 2D library wasn't thinking real clear and decided to use float variables as point coordinates.  The reasoning was that this would mimic the real world (feet, inches, etc) and translating it to pixels was no big deal.  What they forgot was that the return type of every Graphics method and it's mother was double.  Now, Java is very strongly typed and won't even put a double in a float var for fear of lost precision.  The programmers, being system guys, figured no problem, we'll just tell them to use a cast.....


Rectangle2D r = ....
float f = r.getWidth();  // blows up..getWidth returns a double...

// The Answer
float r = (float)r.getWidth();  // correct

Bless their souls, but regular Joe programmers aren't going to do a zillion casts or use an F suffix to denote a float.  Soon the Invalid Type errors started to fly (more from legacy stuff than new code).  They're answer was to create two different classes for every shape, ex. -  Rectangle2D.Float and Rectangle2D.Double (They're both concrete subclasses of their abstract parent Rectangle2D and are, interestingly, both inner classes, hence the .  dereference.  Just a gimmick to avoid two separate classes, and you only need to know this for object creation, they inherit everything else, (well almost everything), from the parent.)

Now, as you can see, this is nuts.  I figured you should know so when we create a Rectangle or Ellipse and the constructor call is Rectangle2D.Double(....) you know what's going on.  We even store the thing in a Parent object because remembering the shape type (F or D) doesn't matter, all functions are overridden in the subclasses.  They dropped the ball with this one.

We are going to use the default, all doubles, so we don't have to cast or use the F suffix.  If you ever need to render 1,000 shapes or more you might want to use the .Float variety to save space.  

Both Rectangle2D and Ellipse2D hail from the same parent class, RectangularShape.  An Ellipse isn't really a rectangle but it's bounded by one and is a very good way to describe an Ellipse.  Anyway that's why Rectangles and Ellipses are created in the same way....

Rectangle2D rect = new Rectangle2D.Double(150, 200, 100, 50);
Ellipse2D e = new Ellipse2D.Double(50, 50, 100, 50);

The first two parameters are the coordinates of the upper left hand corner of the shape (or the bounding rect in the Ellipses case) and the second two parameters are width and height of the shape.  Sometimes you don't have the upper left hand corner and the book shows some methods to overcome this (if I cover it we'll be here all day), but suffice it to say that if you have the coordinates of a diagonal you can make your shape.  

A circle is just an ellipse with equal width and height people, don't look for a Circle class.  

We're drawing on a panel, so everything we learned up to now is needed.  We're just going to draw where we rendered text last time.  All the coordinates are relative to our panel, not the screen.

Lastly, you can create a line as well.  Just use the Line2D.Double() constructor and give it the starting X and Y and then the ending X and Y.  

(You can use objects of type Point2D for all these shape constructions as well.  The book goes into it, I'm not.  A Point2D, as you've probably guessed, is just a wrapper with an X and a Y stored in it.  There are overloaded constructors that take Point2D types where coordinates are excepted in the above class constructors...get the book people... Oh yea, they have .Float and .Double constructors too!)

That was forty miles of bad road, huh?  Let's bang out some code for Pete's sake...

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class DrawTest
{
    public static void main(String[] args)
    {
        // create your frame
        DrawFrame frame = new DrawFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.show();
    } //eom main
}// eoc DrawTest

// class for your frame with a panel for drawing
class DrawFrame extends JFrame
{
    public DrawFrame()
    {
        setTitle("Draw Test");
        setSize(WIDTH, HEIGHT);
        
        // add a panel to the frame
        DrawPanel panel = new DrawPanel();
        Container contentPane = getContentPane();
        contentPane.add(panel);
    }
    
    // define the dimens
    public static final int WIDTH = 400;
    public static final int HEIGHT = 400;
}// eoc DrawFrame

// Now create the panel with all of the shapes in it...
class DrawPanel extends JPanel
{
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;  // cast into a 2D object to use 2D lib
        
        // draw a rectangle
        double leftX = 100;
        double topY = 100;
        double width = 200;
        double height = 150;
        
        Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height);
        g2.draw(rect);
        
        // draw the enclosed ellipse
        Ellipse2D ellipse = new Ellipse2D.Double();
        ellipse.setFrame(rect);
        g2.draw(ellipse);
        
        // draw a diagonal line
        g2.draw(new Line2D.Double(leftX, topY, leftX + width, topY + height));
        
        // draw a circle with the same center...
        double centerX = rect.getCenterX();
        double centerY = rect.getCenterY();
        double radius = 150;
        
        Ellipse2D circle = new Ellipse2D.Double();
        circle.setFrameFromCenter(centerX, centerY, centerX + radius, centerY + radius);
        g2.draw(circle);
            
    }// eom paintComponent
    
}// eoc DrawPanel
// eos
        

The Output

In the above code I highlighted some nifty little methods that come with Rectangle2D and Ellipse2D.  First the getCenterX() method and the getCenterY() method give you the center coords for a RectagularShape object and you can use them, as we did here, as parameters in the setFrameFromCenter() method to create your shape.  Remember that the default construction of a shape gives it no size (0 width, 0 height) <-- another 310-025 question waiting to happen.  (Actually, since I wrote this section the SUN Cert has dropped most of the swing stuff, so don't get too crazy...) Also, we used the setFrame() method with a Rectangle2D object as a parameter to dimension our Ellipse.

Closing out, the Java 2D Library is screaming for exploration when you're done with this book, chapter or whatever.  Hit the API docs and you'll begin to see what a rich set of graphics tools it really is.  As I said, Volume II 5th Ed., which as of this writing isn't even out yet, (It is now.. almost ready for the 6th) has an advance graphics chapter that  really gets into this hardcore.

Neat shapes, but why can't we color them?  We can....

Colors

Colors are pretty simple.  You just use the setPaint() method of the Graphics2D class and every drawing after that, text or shapes, will be in that color.  For multiple colors you just setPaint(), draw, setPaint(), draw, etc.  

You can actually define your own color with the Color class, or chose from one of thirteen predefined colors.

g2.setPaint(Color.red);
g2.drawString("Warning!", 100, 100);

You create a custom color by using RGB values in a Color class constructor

Color( int redness, int greenness, int blueness);

The book has the list of standard colors and there are also more advanced settings for hues or images.  You can also use the setBackground() and setForeground() methods of the Component class to define default colors before you show your frame or component for the first time.

There's also a class called SystemColor that hold variables for system setting colors.  This is important to have if you want your app to ride along with the window colors the user has picked out for his/her desktop.  Public variables like SystemColor.window or SystemColor.desktop are available to have your app use the system's settings.  There's a whole slew of settings too, go check out the class def online.

Finally, if you want to fill a shape with color don't use the draw method to render it, use a special method called fill().  This will create the shape and fill it with the color you selected in setPaint().  Here's some code...

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;

public class FillTest
{
    public static void main(String[] args)
    {
        // create your frame
        FillFrame frame = new FillFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.show();
    } //eom main
}// eoc FillTest

// class for your frame with a panel for drawing
class FillFrame extends JFrame
{
    public FillFrame()
    {
        setTitle("Fill Test");
        setSize(WIDTH, HEIGHT);
        
        // add a panel to the frame
        FillPanel panel = new FillPanel();
        Container contentPane = getContentPane();
        contentPane.add(panel);
    }
    
    // define the dimens
    public static final int WIDTH = 400;
    public static final int HEIGHT = 400;
}// eoc FillFrame

// Now create the panel with filled rectangles and ellispes
class FillPanel extends JPanel
{
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;  // cast into a 2D object to use 2D lib
        
        // draw a rectangle
        double leftX = 100;
        double topY = 100;
        double width = 200;
        double height = 150;
        
        Rectangle2D rect = new Rectangle2D.Double(leftX, topY, width, height);
        g2.setPaint(Color.red);
        g2.fill(rect);
        
        // draw the enclosed elipse
        Ellipse2D ellipse = new Ellipse2D.Double();
        ellipse.setFrame(rect);
        g2.setPaint(new Color(0, 128, 128));  // blue-green
        g2.fill(ellipse);
                  
    }// eom paintComponent
    
}// eoc FillPanel
// eos

The Output

So, as you can see, colors aren't hard at all....

Text & Fonts

Only two topics to go, hang in there.  Fonts can be looked at in two ways.  Technical and Non Technical.  If you just want to change the font, make it bold or italic, and change the size it's easy and non technical.  If you want to get technical then Java will make you real happy because there are a seemingly endless bunch of methods for measuring and manipulating Fonts. 

I'm going to go the easy route.  If you want to get hyper technical I'll give you some basics, but you better go out and grab the book for the gory details and typesetting terms...

Non Technical

Want to change the font?  

Font helvb14 = new Font("Helvetica", Font.BOLD, 14 );
g2.setFont(helvb14);

Easy.  The Font class is used to define your font and then you use the setFont() method of your Graphics context to use it.  The constructor takes three parameters, 1. The Family Type of the Font, 2. The optional suffix type or style (In this case Bold...The Font class also has Font.PLAIN, Font.ITALIC, or a combo of the two Font.BOLD + Font.ITALIC), and 3. The size of the Font.  Not so bad, right?

Now, how do you know what fonts a particular machine has?  Write once run anywhere, right?  What if the person running the code doesn't have Copperplate Gothic?  You have two choices.  First, you can grab an object of the class GraphicsEnvironment and use the getAvailableFontFamilyNames() method to stick all of the available fonts in an array you can search to see if your font is there.  Tres messy though.  Second, you can use one of five pre-defined font names that Java maps to a font that WILL ALWAYS BE THERE regardless of the platform.  They are SansSerif, Serif, Monospaced, Dialog, and DialogInput.  Java always maps a font (In Windows case SansSerif is mapped to Arial) to these five choices, so you never have to do a "Font Hunt" if you use these fonts.  Tough to get creative with only five choices, so if you want to use a wide variety of fonts you have to bite the bullet and do it the hard way.

Note:  Starting with J2SE 1.3 you can use True Type fonts, but I don't want to get into it here because you have to read them into a createFont() method using input streams and we're not covering that here.....  For you C++ folks you just open a file, read the TTF file  into a buffer and then give the createFont() method the Font type (TTF)  and the buffer... It ain't bad..)

Technical

OK campers, get ready for some fun.  What if you want to center your string in the middle of a window?  How do you know how big your message will be? It depends on three things, your font, your message, and the device you're writing to (in this case the screen).  

First, we need to grab the font characteristics of the screen device.  We call a method of the Graphics2D class called getFontRenderContext().  Then we pass the results into a method from the Font class called getStringBounds().  This guy returns type Rectangle2D....Voila!!  We know how to center a rectangle.... A little convoluted, but we're getting there...

Now, unless you're a typesetter or publisher the next section in the book may as well be written in Latin.   It speaks of things like ascents, descents, baselines, leading and gets a little crazy, well not that crazy, but I'm not going into it here.  I just want to mention it because the code that follows includes a small section to draw the baseline for the font.  Don't let it confuse you or make you nuts.

Well, here goes nothing...

import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import javax.swing.*;

public class FontTest
{
    public static void main(String[] args)
    {
        // create your frame
        FontFrame frame = new FontFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.show();
    } //eom main
}// eoc FontTest

// class for your frame with a panel for drawing
class FontFrame extends JFrame
{
    public FontFrame()
    {
        setTitle("Font Test");
        setSize(WIDTH, HEIGHT);
        
        // add a panel to the frame
        FontPanel panel = new FontPanel();
        Container contentPane = getContentPane();
        contentPane.add(panel);
    }
    
    // define the dimens
    public static final int WIDTH = 300;
    public static final int HEIGHT = 200;
}// eoc FontFrame

// Now create the panel with filled rectangles and ellispes
class FontPanel extends JPanel
{
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;  // cast into a 2D object to use 2D lib
        
        String message = "Hello World!";
        
        Font f = new Font("Serif", Font.BOLD, 36);
        g2.setFont(f);
        
        // Measure the size of the message
        FontRenderContext context = g2.getFontRenderContext();
        Rectangle2D bounds = f.getStringBounds(message, context);
        
        // set (x,y) = top left corner of the text
        double x = (getWidth() - bounds.getWidth()) / 2;
        double y = (getHeight() - bounds.getHeight()) / 2;
        
        // add ascent to y to reach the baseline
        double ascent = -bounds.getY();                // don't worry about this
        double baseY = y + ascent;                     // for now...
        
        // draw the message
        g2.drawString(message, (int)x, (int)(baseY) );
        g2.setPaint(Color.gray);
              
        // draw the baseline
        g2.draw(new Line2D.Double(x, baseY, x + bounds.getWidth(), baseY ));
        
        // draw the enclosing rectangle
        Rectangle2D rect = new Rectangle2D.Double(x, y, bounds.getWidth(),
                                                  bounds.getHeight());
        g2.draw(rect);
 
    }// eom paintComponent
    
}// eoc FontPanel
// eos

The Output

Then the book goes into really technical subjects;  adding more fonts to the logical list by altering the font.properties file in your jre/lib directory, adding your own font maps, and creating a converter class for your own font maps.  These subjects will be left for the brave soul who buys the book and desperately needs to present text in a Swahili Sanskrit font.

Images 

Whew.  This is a long section but we're almost finished.  Just one topic left and it's easy, fun, and very useful.  We are going to read an image from a file and then display it on the screen.  Seems easy and is.  Lets go.

First we grab a Toolkit object, you'll remember them from above when we loaded an image for an icon or got screen size dimensions, and use it's getDefaultToolkit() to get the Toolkit and call the getImage() method.

String name = "blue-ball.gif";
Image image = Toolkit.getDefaultToolkit.getImage(name);

(As you can see this is all done in one line without variables.  The syntax is confusing at this stage, but just realize that "Toolkit.getDefaultToolkit()" returns a Toolkit object when the program runs and you call the getImage() method on that returned object. ;->)

Now you have an image loaded into the variable.  To draw it on the screen you just call the drawImage() method that comes with your Graphics2D object. (actually Graphics2D inherits this from Graphics so if you use it, as the book did, you'll be fine.  I decided to use the Graphics2D just to see if it worked properly.  It did.)

Graphics2D g2;
g2.drawImage(image, 0, 0, null);

Easy, right?  We'll get to that null parameter in a minute.  As you suspected, the second two parameters are the X Y coordinates of the upper left hand side of the image.  Now, because just displaying an image wouldn't be too challenging, we're going to tile it across the panel using the copyArea() method of the Graphics2D class.  

g2.copyArea(0,0,imageWidth, imageHeight, (imageWidth*2), 0 );

The parameters of the call are as follows.  Upper left hand corner X and Y, the width and height of the image, then the distance from X (dx) <- (not calculus, calm down), and the distance from Y(dy).  In the above example I just stuck a copy of the area right next to it on the right.

OK, one last piece of craziness to tackle.  Because this is Java something has to complicate easy tasks, not because Java is implemented poorly, but because to do things right you have to get complex sometimes.  Here's what I'm talking about.  When you use the getImage() method you may have to wait until the image loads.  Java creators anticipated slow network connections for moving big images, so when your drawImage() method is called the image may not be fully loaded yet.  If this is the case, what do you think happens?

Not what you think.  The drawImage() spawns a thread to load the image it couldn't get (this doesn't repeat the work of getImage(), it gets caught....) and returns to the caller (the object that called the method) before it's task is complete!  Very peculiar.  "Spawns a thread" may be making your head spin right now don't sweat it, we get to threads in Volume 2.  Just think of them as subprograms for the time being.  So, as the image loads our program continues to run and starts tiling our screen with blank images!  This is not good.

We need a way to control the program so it waits until the image is loaded before we call drawImage(). (When we get to threads in Vol 2 we'll see how to do this using an ImageObserver class, remember where we put the NULL in the parameter list?  This is where you'd put your ImageObserver object.  It's more for incremental rendering but can tell us if the loading operation is complete as well)  What we're going to do is use a special class called MediaTracker.  By creating an object of this type and "adding" our image to it we can basically halt the program until the image is fully loaded (We do this with a trick.  You learn more about Try and Catch when you learn about exception handling, but all it's about is catching and handling errors or special conditions.  We "Try" a method called waitForID() that waits for the completion of the load for that image's ID.  When the call comes back the image is loaded, and if no errors are reported the exception handle {the catch} is ignored.)  Now that we have a fully loaded image we can move onto our drawImage() method without fear of blank tiles.

Pheeww.  Enough talking.  Here's the code...

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ImageTest
{
    public static void main(String[] args)
    {
        // create your frame
        ImageFrame frame = new ImageFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.show();
    } //eom main
}// eoc ImageTest

// class for your frame with a panel for drawing
class ImageFrame extends JFrame
{
    public ImageFrame()
    {
        setTitle("Image Test");
        setSize(WIDTH, HEIGHT);
        
        // add a panel to the frame
        ImagePanel panel = new ImagePanel();
        Container contentPane = getContentPane();
        contentPane.add(panel);
    }
    
    // define the dimens
    public static final int WIDTH = 300;
    public static final int HEIGHT = 200;
}// eoc ImageFrame

// Now create the panel that displays a tiled image
class ImagePanel extends JPanel
{
    public ImagePanel()
    {
        // aquires the image
        image = Toolkit.getDefaultToolkit().getImage("nix.gif");
        MediaTracker tracker = new MediaTracker(this);
        tracker.addImage(image, 0);
        try { tracker.waitForID(0); }
        catch (InterruptedException exception) {}
    }   
    
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D)g;  // cast into a 2D object to use 2D lib
        
        int imageWidth = image.getWidth(this);
        int imageHeight = image.getHeight(this);
        
        // draw the image in the upper left hand corner
        g.drawImage(image,0 , 0, null);
        
        // now tile the image across the panel
        for (int i = 0; i * imageWidth <= getWidth(); i++ )
         for (int j= 0; j * imageHeight <= getHeight(); j++)
        
            // the code insode the nested loops
               if (i + j > 0 )
                     g2.copyArea(0,0,imageWidth,imageHeight,i*imageWidth,j*imageHeight);
     }// eom paintComponent
    
     private Image image;
    
}// eoc ImagePanel
// eos

The Output

As people with the book can see, one is not forced to use the blue ball gif file if one doesn't want to....

Man, that section was a beast.  But think of all the good stuff you can do; Frames, panels, shapes, colors, images, fonts.  All part of your growing Java brain.  

The next section wraps up with a guide on continuing your Java journey and becoming a true Java masher!

Go back to the top

Send mail to lars@sorcon.com with questions or comments about this web site.
Copyright © 2004 Sorensen Consulting