Java 1.4 Game Programming (Wordware Game and Graphics Library)

When you create a component that contains a border, such as a window, the size of the component that you define includes the border as part of its size. This means that if, for example, you set the size of your JFrame object to the size 400x400, like we have so far in the TemplateGraphicsApplication class, the window (including its borders) would make up this size. This means that the displayable area between the borders is not 400x400 but a dimension that is 400x400 minus the total border dimensions. Furthermore, the top-left of the coordinate system (0, 0) lies at the very top of the window, and the displayable area inside the window's border is at the position of the left border width and the top border height. You may only want to define the overall size and be done with it, but it is important that you know the internal display area too. It is also important that you can define this, as you need to know the resolution of the screen area you are drawing to. You would not want the window including its border to be 400x400 pixels in dimension but the internal display area to be 400x400 and the window's border spaced around it. What we want to do is find out the size of the window's borders and resize the window to the desired size, plus the border size. We can do this using the method getInsets of the frame object, which returns an object of type Insets from which we can retrieve this information. However, we can only retrieve this information once the JFrame is visible because the size of the border is dependent on the platform on which you are running. The following example is the completion of our TemplateGraphicsApplication class fit with a window resized to our needs and some test graphics added to the paint method. Here is the code for TemplateGraphicsApplication.java.

Code Listing 9-1: TemplateGraphicsApplication.java

import javax.swing.*; import java.awt.*; public class TemplateGraphicsApplication extends JFrame { public TemplateGraphicsApplication() { super("Template Graphics Application"); setDefaultCloseOperation(EXIT_ON_CLOSE); setResizable(false); getContentPane().setLayout(null); setVisible(true); Insets insets = getInsets(); DISPLAY_X = insets.left; DISPLAY_Y = insets.top; resizeToInternalSize(DISPLAY_WIDTH, DISPLAY_HEIGHT); } public void resizeToInternalSize(int internalWidth, int internalHeight) { Insets insets = getInsets(); final int newWidth = internalWidth + insets.left + insets.right; final int newHeight = internalHeight + insets.top + insets.bottom; Runnable resize = new Runnable() { public void run() { setSize(newWidth, newHeight); } }; if(!SwingUtilities.isEventDispatchThread()) { try { SwingUtilities.invokeAndWait(resize); } catch(Exception e) {} } else resize.run(); validate(); } public void paint(Graphics g) { Graphics2D g2D = (Graphics2D)g; g2D.translate(DISPLAY_X, DISPLAY_Y); g2D.setColor(Color.blue); g2D.fillRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); g2D.setColor(Color.white); g2D.fillRect(DISPLAY_WIDTH/4, DISPLAY_HEIGHT/4, DISPLAY_WIDTH/2, DISPLAY_HEIGHT/2); } public static void main(String[] args) { new TemplateGraphicsApplication(); } private final int DISPLAY_X; // value assigned in constructor private final int DISPLAY_Y; // value assigned in constructor private static final int DISPLAY_WIDTH = 400; private static final int DISPLAY_HEIGHT = 400; }

When you compile and run this example, you should get output similar to the following figure.

Figure 9-2:

In order to get the border size from the Insets object using the getInsets method of our JFrame, we must make the window frame visible. We can do this by calling the method setVisible(true). However, changing the state of a Swing component outside the Event Dispatch Thread is unsafe when the component is realized. A component is said to be realized when it is in a visible state, such as when we call setVisible on our JFrame object or add a component to a container that is realized. From then on, calling methods to change the state of the component must be called in a synchronized manner with the Event Dispatch Thread. Otherwise, they are unsafe with code executing in the Event Dispatch Thread. There are exceptions to this rule, such as the setText method of the JTextComponent and the repaint method that we will see later in this chapter. But generally, changes to Swing objects need to be executed in the Event Dispatch Thread. So, in order to change the size of our window, first set the window visible (realize it) and then update our DISPLAY_X and DISPLAY_Y values as follows in the constructor for TemplateGraphicsApplication:

Insets insets = getInsets(); DISPLAY_X = insets.left; DISPLAY_Y = insets.top;

We store the left and top inset values of the window's border in the variables DISPLAY_X and DISPLAY_Y to be used in the paint method. In this method we can translate the top corner of the graphics object to these coordinates so that the top corner that we are drawing from is the top corner of the displayable area inside the window, not the very top corner of the window itself. We will see how this can be resolved automatically using components shortly, but note that this is the method we will be sticking with in general for handling windowed applications. Note also that we set the display coordinates before resizing the window so that once this is done, the window will refresh its appearance on screen with the correct coordinates after it resizes itself.

After we have set the window to visible and saved the top corner internal coordinates of the window, we next need to actually resize the window so that the internal area is of the required size. We do this by calling the method resizeToInternalSize, passing in the required internal width and height. Let's run through this method step by step and see how it works.

public void resizeToInternalSize(int internalWidth, int internalHeight) { Insets insets = getInsets();

First we need to get the insets of the window's border. Note that the correct inset data can only be retrieved if the window is realized, as discussed earlier.

final int newWidth = internalWidth + insets.left + insets.right; final int newHeight = internalHeight + insets.top + insets.bottom;

Next we need to calculate the new overall size of the window, including its borders. This is simply calculated as the desired internal size plus the size of the borders in their respective dimensions. Note also that these values must be declared as final, as they are local variables that will be accessed from the following inner class:

Runnable resize = new Runnable() { public void run() { setSize(newWidth, newHeight); } };

As we saw in Chapter 4, we can define object members on the fly, but we can also define interface objects in a similar way. As you can see with the runnable object, we simply need to define the methods of the Runnable interface, which in this case consists of the method run. By holding a reference to the runnable object, it, in a way, gives a reference to a method, a segment of execution that we can call later using the reference. More importantly in this case, we need a runnable object in order to execute code in the Event Dispatch Thread.

if(!SwingUtilities.isEventDispatchThread()) { try { SwingUtilities.invokeAndWait(resize); } catch(Exception e) {} } else resize.run(); validate(); }

To begin with, check to see if we are already running in the Event Dispatch Thread. If so, then execute the code safely in the Event Dispatch Thread using the static method invokeAndWait of the SwingUtilities class. This method will wait until the Event Dispatch Thread gets around to executing this code. Alternatively, there is the method invokeLater that doesn't wait and exits the method straight away, leaving it up to the Event Dispatch Thread to execute the code when it's ready, which you should note should be quite soon after. For this implementation, we want to wait until the window is the right size before progressing. If we are running in the Event Dispatch Thread, we can simply invoke the run method to resize the window then and there. At the end, make a call to the validate method that will lay out subcomponents of the window. This is important for things such as resetting the size of the content pane after the window has been resized, which needs to be correctly sized for things like mouse handling, as we shall see later in the book. Also, the validate method is one of those methods that is thread-safe for calling outside of the Event Dispatch Thread, so we can call it just from the main thread.

Note that calling the state changing method before the window is visible (realized) is fine, so if you wanted to just define the size of the overall window using setSize, that would be perfectly safe to call before calling setVisible on the window.

It's not fair to go into any more detail on this matter now, as we have hardly looked at areas of the Event Dispatch Thread, such as repainting and using event listeners (see Chapter 10). We will return to this discussion in Chapter 13, "Introduction to GUI."

Категории