Showing posts with label frame. Show all posts
Showing posts with label frame. Show all posts

Friday, 24 June 2011

Swing : Creating 2-Dimensional Shapes

Starting with Java 1.0, the Graphics class had methods to draw lines, rectangles, ellipses, and so on. But those drawing operations are very limited. For example, you cannot vary the line thickness and you cannot rotate the shapes.

Java SE 1.2 introduced the Java 2D library, which implements a powerful set of graphical operations. In this chapter, we will only look at the basics of the Java 2D library. The advanced features will be covered subsequently in one of the future chapters.
To draw shapes in the Java 2D library, you need to obtain an object of the Graphics2D class. This class is a subclass of the Graphics class. Ever since Java SE 2, methods such as paintComponent automatically receive an object of the Graphics2D class. Simply use a cast, as follows:

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

The Java 2D library organizes geometric shapes in an object-oriented fashion. In particular, there are classes to represent lines, rectangles, and ellipses:

Line2D
Rectangle2D
Ellipse2D

These classes all implement the Shape interface.

How to Draw a 2D Shape?

To draw a shape, you first create an object of a class that implements the Shape interface and then call the draw method of the Graphics2D class. For example:

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

Using the Java 2D shape classes introduces some complexity. Unlike the 1.0 draw methods, which used integer pixel coordinates, the Java 2D shapes use floating-point coordinates. In many cases, that is a great convenience because it allows you to specify your shapes in coordinates that are meaningful to you (such as millimeters or inches) and then translate them to pixels. The Java 2D library uses single-precision float quantities for many of its internal floating-point calculations. Single precision is sufficient in most cases; after all, the ultimate purpose of the geometric computations is to set pixels on the screen or printer. As long as any roundoff errors stay within one pixel, the visual outcome is not affected. To add on, float computations are faster on some platforms, and float values require half the storage of double values.

However, manipulating float values is sometimes inconvenient for the programmer because the Java programming language is adamant about requiring casts when converting double values into float values. For example, consider the following statement:

float f = 1.2; // Error

This statement does not compile because the constant 1.2 has type double, and the compiler becomes conscious about loss of precision and starts to complain. The solution is to add an F suffix to the floating-point constant:

float f = 1.2F; // Ok

This will tell the complier something like “Hey I know what I am doing, I have explicitly casted this number to a float, so don't complain and carry on with the compilation”

Lets say you do something like below:
Rectangle2D r = . . .
float f = r.getWidth(); // Error

This statement does not compile either and you receive an error. This is because, the getWidth method returns a double and the compiler is having the same problem it had a few lines before. This time, the solution is to provide an explicit cast:
float f = (float) r.getWidth();

Because the suffixes and casts are a bit of a pain, the designers of the 2D library decided to supply two versions of each shape class: one with float coordinates for conscious programmers, and one with double coordinates for the lazy ones (like you and me).

The Rectangle2D Class:
The Rectangle2D class is an abstract class with two concrete subclasses, which are also static inner classes:
Rectangle2D.Float
Rectangle2D.Double

The below image shows the inheritance diagram of this class.

It is best to try to ignore the fact that the two concrete classes are static inner classes. That is just done to avoid names such as FloatRectangle2D and DoubleRectangle2D.
When you construct a Rectangle2D.Float object, you supply the coordinates as float numbers. For a Rectangle2D.Double object, you supply them as double numbers.

For Ex:

Rectangle2D.Float floatRect = new Rectangle2D.Float(10.0F, 25.0F, 22.5F, 20.0F);
Rectangle2D.Double doubleRect = new Rectangle2D.Double(10.0, 25.0, 22.5, 20.0);


Actually, because both Rectangle2D.Float and Rectangle2D.Double extend the common Rectangle2D class and the methods in the subclasses simply override methods in the Rectangle2D superclass. Therefore, there is no benefit in remembering the exact shape type. You can simply use Rectangle2D variables to hold the rectangle references like below instead of our previous example.

Rectangle2D floatRect = new Rectangle2D.Float(10.0F, 25.0F, 22.5F, 20.0F);
Rectangle2D doubleRect = new Rectangle2D.Double(10.0, 25.0, 22.5, 20.0);

The construction parameters denote the top-left corner, width, and height of the rectangle.
The Rectangle2D methods use double parameters and return values. For example, the getWidth method returns a double value, even if the width is stored as a float in a Rectangle2D.Float object.

What we just discussed for the Rectangle2D classes holds for the other shape classes as well.

Creating a Point (Dot):
There is a Point2D class with subclasses Point2D.Float and Point2D.Double. (Just like the Rectangle class)

Below is how you can make a point object:
Point2D p = new Point2D.Double(10, 20);

Drawing Ellipses

The classes Rectangle2D and Ellipse2D both inherit from the common superclass RectangularShape. Even though, ellipses are not rectangular, they do have a bounding rectangle. Look at the image below:

As you can see, the ellipse would fit in properly inside a rectangle.

The RectangularShape class defines over 20 methods that are common to these shapes, among them such useful methods as getWidth, getHeight, getCenterX, and getCenterY.

Rectangle2D and Ellipse2D objects are simple to construct. You need to specify
  • The x- and y-coordinates of the top-left corner; and
  • The width and height.
For ellipses, these refer to the bounding rectangle. For example,

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

The line of code above constructs an ellipse that is bounded by a rectangle with the top-left corner at (150, 200), width 100, and height 50.

However, sometimes you don’t have the top-left corner readily available. It is quite common to have two diagonal corner points of a rectangle, but perhaps they aren’t the top-left and bottom-right corners. You can’t simply construct a rectangle as

Rectangle2D rect = new Rectangle2D.Double(px, py, qx - px, qy - py); // Error


If p isn’t the top-left corner, one or both of the coordinate differences will be negative and the rectangle will come out empty. In that case, first create a blank rectangle and use the setFrameFromDiagonal method, as follows:

Rectangle2D rect = new Rectangle2D.Double();
rect.setFrameFromDiagonal(px, py, qx, qy);

Or, even better, if you know the corner points as Point2D objects p and q, then
rect.setFrameFromDiagonal(p, q);

When constructing an ellipse, you usually know the center, width, and height, and not the corner points of the bounding rectangle (which don’t even lie on the ellipse). The setFrameFromCenter method uses the center point, but it still requires one of the four corner points. Thus, you will usually end up constructing an ellipse as follows:

Ellipse2D ellipse = new Ellipse2D.Double(centerX - width / 2, centerY - height / 2, width, height);


Drawing Lines:

To construct a line, you supply the start and end points, either as Point2D objects or as pairs of numbers:

Line2D line = new Line2D.Double(start, end);

or

Line2D line = new Line2D.Double(startX, startY, endX, endY);


A Sample Program incorporating all the shapes we learnt:

Let us wrap up this chapter, with a sample program that will draw all the shapes we just saw in the preceding paragraphs.

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

public class TestShapes
{
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
CreateFrame frame = new CreateFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}

/**
 * A frame that contains a panel with drawings
 */
class CreateFrame extends JFrame
{
public CreateFrame()
{
setTitle("DrawTest");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

// add panel to frame

CreateComponent component = new CreateComponent();
add(component);
}

public static final int DEFAULT_WIDTH = 400;
public static final int DEFAULT_HEIGHT = 400;
}

/**
 * A component that displays rectangles and ellipses.
 */
class CreateComponent extends JComponent
{
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;

// 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);
}
}

If you run the code above, you will see a frame that looks like below:

Swing : Displaying Text in a Component

we are going to learn how to add a component into a Frame. This component is going to be pretty straight forward that writes out some text onto the frame.

As a first step, we need to know how to add a component to the frame. The code to add a component to a frame looks like below:

Container contentPane = frame.getContentPane();
Component c = . . .;
contentPane.add(c);

When you add a component to a frame, you are actually adding the component to the frame’s content pane.

Or, you can use a short cut. As of Java SE 5.0, you can simply use the call

frame.add(c);

This will in-turn call the content pane’s add method.

In our case, we want to add a single component to the frame on which we will write out our message. To draw on a component, you define a class that extends JComponent and override the paintComponent method in that class.

The paintComponent method takes one parameter of type Graphics. A Graphics object remembers a collection of settings for drawing images and text, such as the font you set or the current color. All drawing in Java must go through a Graphics object. It has methods that draw patterns, images, and text.

Here’s how to make a component onto which you can draw/write stuff:
class MyComponent extends JComponent {
public void paintComponent(Graphics g) {
//code for drawing
}

}

Each time a window needs to be redrawn, no matter what the reason, the event handler notifies the component. This causes the paintComponent methods of all components to be executed.
Never call the paintComponent method yourself. It is called automatically whenever a part of your application needs to be redrawn, and you should not interfere with this automatic process.

Trivia:
What kind of actions triggers this automatic response? For example, painting occurs because the user increased the size of the window or minimized and then restored the window. If the user popped up another window and it covered an existing window and then made the overlaid window disappear, the application window that was covered is now corrupted and will need to be repainted. (The graphics system does not save the pixels underneath.) And, of course, when the window is displayed for the first time, it needs to process the code that specifies how and where it should draw the initial elements.

As you saw in the code fragment above, the paintComponent method takes a single parameter of type Graphics. Measurement on a Graphics object for screen display is done in pixels. The (0, 0) coordinate denotes the top-left corner of the component on whose surface you are drawing.

Displaying Text in our Component:

Displaying text is considered a special kind of drawing. The Graphics class has a drawString method that has the following syntax:

g.drawString(text, x, y)

In our case, we want to draw the string "Welcome to Java Swings Tutorial!!!" in our original window, roughly one-quarter of the way across and halfway down. Although we don’t yet know how to measure the size of the string, we’ll start the string at coordinates (75, 100). This means the first character in the string will start at a position 75 pixels to the right and 100 pixels down.

Thus, our paintComponent method will look like this:

class TestPaintComponent extends JComponent {

public void paintComponent(Graphics g) {
g.drawString("Welcome to Java Swings Tutorial!!!", MESSAGE_X, MESSAGE_Y);
}

public static final int MESSAGE_X = 75;
public static final int MESSAGE_Y = 100;

}

Let us now take a look at a fully functional class that will write out some text in our frame:
class TestFrame extends JFrame
{
public TestFrame()
{
setTitle("Print Something");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

// add panel to frame

TestPaintComponent comp = new TestPaintComponent();
add(comp);
}

public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
}

/**
 * A Component that displays a message.
 */
class TestPaintComponent extends JComponent
{
public void paintComponent(Graphics g)
{
g.drawString("Welcome to Java Swings Tutorial!!!", MESSAGE_X, MESSAGE_Y);
}

public static final int MESSAGE_X = 50;
public static final int MESSAGE_Y = 75;
}


Swing : Positioning a Frame

The JFrame class itself has only a few methods for changing how frames look. Of course, through the magic of inheritance, most of the methods for working with the size and position of a frame come from the various superclasses of JFrame.

The most important methods related to positioning a Frame are:
• The setLocation and setBounds methods for setting the position of the frame
• The setIconImage method, which tells the windowing system which icon to display in the title bar, task switcher window etc
• The setTitle method for changing the text in the title bar
• The setResizable method, which takes a boolean to determine if a frame will be resizeable by the user

Inheritance Hierarchy of the Frame

In the previous paragraph, I said that because of the inheritance hierarchy of the JFrame class, many of the features of its parent classes are available to it. Before we get any further, if you have any doubts reg. the Java Inheritance concepts, Click Here and refresh your understanding of Inheritance.

The image below explains the inheritance hierarchy of the JFrame and the JPanel classes.

 The Component class (which is the ancestor of all GUI objects) and the Window class (which is the superclass of the Frame class) are the classes that you need to look to find the methods to resize and reshape frames. For example, the setLocation method in the Component class is one way to reposition a component.

setLocation(x, y)

The top-left corner is located x pixels across and y pixels down, where (0, 0) is the top-left corner of the screen.

Similarly, the setBounds method in Component lets you resize and relocate a component in one step.

setBounds(x, y, width, height)

Alternatively, you can give the windowing system control on window placement. If you call
setLocationByPlatform(true);

Before displaying the window, the windowing system picks the location (but not the size), typically with a slight offset from the last window.

Note:
For a frame, the coordinates of the setLocation and setBounds are taken relative to the whole screen. As you will see in the future chapters, for other components inside a container, the measurements are taken relative to the container

Frame Properties

Many methods of component classes come in getter/setter pairs, such as the following methods of the Frame class:

public String getTitle()
public void setTitle(String title)

Such a getter/setter pair is called a property. A property has a name and a type. The name is obtained by changing the first letter after the get or set to lowercase. For example, the Frame class has a property with name title and type String.

Conceptually, title is a property of the frame. When we set the property, we expect that the title changes on the user’s screen. When we get the property, we expect that we get back the value that we set.

Practically speaking, we do not care how the Frame class implements this property. Perhaps it simply uses its peer frame to store the title. Perhaps it has an instance field

private String title;

Nonetheless, we are only bothered as to whether the value we set is displayed fine and the value we get is what is displayed on screen.

There is one exception to this get/set convention. For properties of type boolean, the getter starts with is. For example, the following two methods define the locationByPlatform property:

public boolean isLocationByPlatform()
public void setLocationByPlatform(boolean b)


Determining a Good Frame Size

If you don’t explicitly set the size for a frame, all frames will default to being 0 by 0 pixels. To keep our example programs simple, we resize the frames to a size that we hope works acceptably on most displays. However, in a professional application, you should check the resolution of the user’s screen and write code that resizes the frames accordingly. A window that looks nice on a laptop screen will look like a postage stamp on a high-resolution large desktop monitor.

To find out the screen size, use the following steps:

1. Call the static getDefaultToolkit method of the Toolkit class to get the Toolkit object. (The Toolkit class is a dumping ground for a variety of methods that interface with the native windowing system.)

2. Then call the getScreenSize method, which returns the screen size as a Dimension object. A Dimension object simultaneously stores a width and a height, in public (!) instance variables width and height.

This is a sample code:
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int screenWidth = screenSize.width;
int screenHeight = screenSize.height;

3. We use 50% of these values for the frame size, and tell the windowing system to position the frame as follows:

setSize(screenWidth / 2, screenHeight / 2);
setLocationByPlatform(true);

Additional Tips for dealing with Frames:

Even though we have covered a lot of details about using frames, there are few more tips that will help you deal with these Frames properly.

  • If your frame contains only standard components such as buttons and text fields, you can simply call the pack method to set the frame size. The frame will be set to the smallest size that can accomodate all components. It is quite common to set the main frame of a program to the maximum size. As of Java SE 1.4, you can simply maximize a frame by calling
    frame.setExtendedState(Frame.MAXIMIZED_BOTH);
  • It is also a good idea to remember how the user positions and sizes the frame of your application and restore those bounds when you start the application again. We will learn how to do this in future.
  • If you write an application that takes advantage of multiple display screens, use the GraphicsEnvironment and GraphicsDevice classes to find the dimensions of the display screens. This will help us position and size our frames to look fine irrespective of the monitor size of your user
  • The GraphicsDevice class also lets you execute your application in full-screen mode.

Sample Code:

Our chapter wouldn't be complete without a code example, or would it?

public class MyTestSizedFrame
{
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
FirstSizedFrame frame = new FirstSizedFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}

class FirstSizedFrame extends JFrame
{
public FirstSizedFrame()
{
// get screen dimensions
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int screenHeight = screenSize.height;
int screenWidth = screenSize.width;

// set frame width, height and let platform pick screen location
setSize(screenWidth / 2, screenHeight / 2);
setLocationByPlatform(true);

// set frame title
setTitle("TestSizedFrame");
}
}

The above is similar to the example in the previous chapter with the following differences:
  1. We have let the frame size to be determined based on the monitor screen size and height
  2. We have set a frame title