JaVa
   

Losing Focus

In order for a component to deal with keyboard events, it must have focus. A good example of focus is filling in forms using a web browser. Imagine that you're filling out a form that requires your name in one text field and your e-mail address in the next; you click on the Name text field and the cursor (that flashy line thing where the text outputs goes) appears, and you have given the text field focus. By then pressing the Tab key, you would probably transfer the keyboard focus to the E-mail text field. In Java a component can have the focus, such as a window or applet or a button or text field component. We are interested simply in the focus of the main component, the applet or the window for apps. For this, we can attach a focus listener, defined by the interface java.awt.event.FocusListener (or if you prefer using an adapter class instead— java.awt.event.FocusAdapter). Again, these work in the same way as key and mouse listeners. It is important to handle whether the focus is lost for a number of reasons. In the most basic sense, you might want to alert the player that the focus has been lost; this is most notable in an applet where the focus can be transferred quite easily from your applet to other elements of the browser, such as the address bar or another window altogether. When the focus is lost, you may also want to pause the game and need to handle special cases along the way. One notable case is a problem that can occur with the key states in the previous AdvancedKeyboard example. Let's say that you hold down the left key, causing the HotSpot object to move to the left. You then click away from the applet (for example, to elsewhere in the browser), causing the applet to lose focus. Then you let go of the key. Because the applet no longer has focus, the key released event for the left key will not be sent to your applet when you let go of it. This means that the HotSpot object will continue to move leftward because no keyReleased method has come in to tell it to do otherwise—to reset our keyState flag value. So the main loop continues to move the HotSpot because it still has the key state for the left key as being held down. You can try this out by running the previous example, AdvancedKeyboard, and performing the aforementioned routine yourself. You should see the HotSpot continue moving in the given direction even when you release the given key. The following example is a fix of the previous AdvancedKeyboard example, this time using the event processor system and handling the loss of the keyboard focus. For this example, we use five source files. To begin with, you need to grab the HotSpot class again and get the source for EventProcessor and EventProcessable from which we defined the source code earlier. All that is left for us to create is a slightly different Animator class and the main class EventAndFocusHandling. To begin with, let's take a look at the new Animator class. This time, the Animator will not handle key events itself straight from an event list but will provide some methods to be called from the main class EventAndFocusHandling, which will itself handle all events from the event processor pump. Here is the code for the new Animator class:

Code Listing 10-11: Animator.java
import java.awt.*;
import java.util.*;
import java.awt.event.*;
public class Animator
{
 public Animator(Rectangle bounds)
 {
 this.bounds = bounds;
 keyState = new boolean[256];
 createHotSpot();
 speedX = 4;
 speedY = 4;
 }
 public void createHotSpot()
 {
 Random rand = new Random();
 int diameter = 100+rand.nextInt(200);
 Color col = new Color(rand.nextInt(Integer.MAX_VALUE));
 int xPos = (bounds.width-diameter)/2; // center x
 int yPos = (bounds.height-diameter)/2; // center y
 hotSpot = new HotSpot(new Point(xPos, yPos), diameter, col);
 }
 public void animate()
 {
 if(keyState[KeyEvent.VK_LEFT] &&
 !keyState[KeyEvent.VK_RIGHT])
 moveLeft();
 else if(keyState[KeyEvent.VK_RIGHT] &&
 !keyState[KeyEvent.VK_LEFT])
 moveRight();
 if(keyState[KeyEvent.VK_UP] && !keyState[KeyEvent.VK_DOWN])
 moveUp();
 else if(keyState[KeyEvent.VK_DOWN] &&
 !keyState[KeyEvent.VK_UP])
 moveDown();
 }
 public void moveLeft()
 {
 hotSpot.bounds.x-=speedX;
 if(hotSpot.bounds.x<0)
 hotSpot.bounds.x = 0;
 }
 public void moveRight()
 {
 hotSpot.bounds.x+=speedX;
 if(hotSpot.bounds.x+hotSpot.bounds.width > bounds.width)
 hotSpot.bounds.x = bounds.width-hotSpot.bounds.width;
 }
 public void moveUp()
 {
 hotSpot.bounds.y-=speedY;
 if(hotSpot.bounds.y<0)
 hotSpot.bounds.y = 0;
 }
 public void moveDown()
 {
 hotSpot.bounds.y+=speedY;
 if(hotSpot.bounds.y+hotSpot.bounds.height > bounds.height)
 hotSpot.bounds.y = bounds.height-hotSpot.bounds.height;
 }
 public void render(Graphics g)
 {
 g.translate(bounds.x, bounds.y);
 g.setColor(Color.blue);
 g.fillRect(0, 0, bounds.width, bounds.height);
 hotSpot.render(g);
 g.translate(-bounds.x, -bounds.y);
 }
 public void handleKeyPressed(KeyEvent e)
 {
 keyState[((KeyEvent)e).getKeyCode()] = true;
 }
 public void handleKeyReleased(KeyEvent e)
 {
 keyState[((KeyEvent)e).getKeyCode()] = false;
 }
 public void resetAllKeyStates()
 {
 Arrays.fill(keyState, false);
 }
 public int speedX;
 public int speedY;
 public HotSpot hotSpot;
 public Rectangle bounds;
 public boolean[] keyState;
}


Java End example

This time, as you can see, the Animator class no longer handles events straight from an event list processor but defines the methods handleKeyPressed, handleKeyReleased, and resetAllKeyStates instead to handle input. The idea is that events will be fed to the Animator object from an all-purpose event pump handled in the main class EventAndFocusHandling, as we will now see. Here is the source code for the main class EventAndFocusHandling.

Code Listing 10-12: EventAndFocusHandling.java
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.awt.event.*;
 public class EventAndFocusHandling extends JApplet implements Runnable, KeyListener, MouseListener, FocusListener, EventProcessable
{ public void init()
 {
 getContentPane().setLayout(null);
 setSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
 setIgnoreRepaint(true);
 animator = new Animator(new Rectangle(0, 0, DISPLAY_WIDTH,
 DISPLAY_HEIGHT));
 backBuffer = new BufferedImage(DISPLAY_WIDTH, DISPLAY_HEIGHT,
 BufferedImage.TYPE_INT_RGB);
 bbGraphics = (Graphics2D)backBuffer.getGraphics();
 eventProcessor = new EventProcessor(this);
 addKeyListener(this);
 addMouseListener(this);
 addFocusListener(this);
 }
 public void start()
 {
 isFocused = isFocusOwner();
 loop = new Thread(this);
 loop.start();
 }
 public void stop()
 {
 loop = null;
 }
 public void run()
 {
 long startTime, waitTime, elapsedTime;
 // 1000/25 Frames Per Second = 40 millisecond delay
 int delayTime = 1000/25;
 Thread thisThread = Thread.currentThread();
 while(loop==thisThread)
 {
 startTime = System.currentTimeMillis();
 // handle any events listened for, in main loop
 eventProcessor.processEventList();
 // handle logic
 animator.animate();
 // render to back buffer
 render(bbGraphics);
 // render to screen
 Graphics g = getGraphics();
 g.drawImage(backBuffer, 0, 0, null);
 g.dispose();
 // handle frame rate
 elapsedTime = System.currentTimeMillis() - startTime;
 waitTime = Math.max(delayTime - elapsedTime, 5);
 try
 { Thread.sleep(waitTime); }
 catch(InterruptedException e) {}
 }
 }
 public void render(Graphics g)
 {
 if(isFocused)
 animator.render(g);
 else
 renderPauseScreen(g);
 }
 public void renderPauseScreen(Graphics g)
 {
 g.setColor(Color.black);
 g.fillRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
 g.setColor(Color.white);
 g.drawString("Game Paused: Click mouse on applet to
 continue", 60, 200);
 }
 public void handleFocusLost()
 {
 isFocused = false;
 animator.resetAllKeyStates();
 }
 public void handleFocusGained()
 {
 isFocused = true;
 }
 public void handleEvent(AWTEvent e)
 {
 switch(e.getID())
 {
 case KeyEvent.KEY_PRESSED:
 System.out.println("Key Pressed");
 animator.handleKeyPressed((KeyEvent)e);
 break;
 case KeyEvent.KEY_RELEASED:
 System.out.println("Key Released");
 animator.handleKeyReleased((KeyEvent)e);
 break;
 case MouseEvent.MOUSE_PRESSED:
 System.out.println("Mouse Pressed");
 break;
 case FocusEvent.FOCUS_LOST:
 System.out.println("Focus Lost");
 handleFocusLost();
 break;
 case FocusEvent.FOCUS_GAINED:
 System.out.println("Focus Gained");
 handleFocusGained();
 break;
 }
 }
 public void keyPressed(KeyEvent e) { eventProcessor
 .addEvent(e); }
 public void keyReleased(KeyEvent e) { eventProcessor
 .addEvent(e); }
 public void keyTyped(KeyEvent e) {} // not used
 public void focusGained(FocusEvent e) { eventProcessor
 .addEvent(e); }
 public void focusLost(FocusEvent e) { eventProcessor
 .addEvent(e); }
 public void mousePressed(MouseEvent e) { eventProcessor
 .addEvent(e); }
 public void mouseReleased(MouseEvent e) {} // not used
 public void mouseClicked(MouseEvent e) {} // not used
 public void mouseEntered(MouseEvent e) {} // not used
 public void mouseExited(MouseEvent e) {} // not used
 private Animator animator;
 private Thread loop;
 private BufferedImage backBuffer;
 private Graphics2D bbGraphics;
 private EventProcessor eventProcessor;
 private boolean isFocused;
 private static final int DISPLAY_WIDTH = 400;
 private static final int DISPLAY_HEIGHT = 400;
}


Java End example

To begin with, we first create the event processor and add the action listeners to the applet, with the following code added to the init method of the applet:

eventProcessor = new EventProcessor(this);
addKeyListener(this);
addMouseListener(this);
addFocusListener(this);


Notice also that our main class implements all of the associated listener interfaces and the EventProcessable interface, so this class will provide methods for both receiving and processing event messages. The next thing to be aware of is in the start method of this class, where we have the following code:

isFocused = isFocusOwner();


We use the variable isFocused to hold the current state of focus—if we have the focus or not. In the start method, this is initialized from the applet component method isFocusOwner. Originally, we used the method requestFocus to ask for the focus, but this method cannot be trusted to transfer the focus to our applet component as it behaves differently on different platforms (where its action is said to be platform-dependent). So with this, we just set the focus to the current state so we know for certain if we have the focus or not, and then rely on focus listeners from there. If the applet is not focused to begin with, the user will be alerted of this fact as we show them a different screen (telling them to click on the applet to get focus back to the HotSpot screen). Many applets will start up with an introduction screen that requires the user to click on the applet to begin, which is really a hidden way of getting them to give the applet the focus. The next thing to look at is the event listener methods and how they are implemented; take the following lines of code that can be found in the main applet class:

public void keyPressed(KeyEvent e) { eventProcessor.addEvent(e); }
public void focusGained(FocusEvent e) { eventProcessor.addEvent(e); }


This is where our events come in from the Event Dispatch Thread, and we simply add them to the new "all-purpose" event processor. We could make the EventProcessor implement all of the event listeners itself and just add events straight into the event list itself, but that's only if we want all events of a given type (e.g., all mouse events, all mouse motion events, all key events, etc.) to be added and not just a selection of types. For example, here we only add key pressed and key released events but don't bother with key typed events, so we can select what needs to be processed ourselves, but there would be no harm in adding such functionality to the EventProcessor class also. When the events are processed in the main loop, each event is handled in the handleEvent method defined by the EventProcessable interface that the main applet implements, adding itself to the EventProcessor object, eventProcessor, when it was creating in the applet's init method. Here is the handleEvent method:

public void handleEvent(AWTEvent e)
 {
 switch(e.getID())
 {
 case KeyEvent.KEY_PRESSED:
 System.out.println("Key Pressed");
 animator.handleKeyPressed((KeyEvent)e);
 break;
 case KeyEvent.KEY_RELEASED:
 System.out.println("Key Released");
 animator.handleKeyReleased((KeyEvent)e);
 break;
 case MouseEvent.MOUSE_PRESSED:
 System.out.println("Mouse Pressed");
 break;
 case FocusEvent.FOCUS_LOST:
 System.out.println("Focus Lost");
 handleFocusLost();
 break;
 case FocusEvent.FOCUS_GAINED:
 System.out.println("Focus Gained");
 handleFocusGained();
 break;
 }
 }


As you can see, the key handling methods of the animator object are called appropriately. The methods handleFocusLost and handleFocusGained are called when their respective focus events are sent to us. The method handleFocusGained simply sets the isFocused variable to true. However, we also need to handle the keyStates array of the animator object for the handleFocusLost method, as well as set the isFocused variable to false.

public void handleFocusLost()
{
 isFocused = false;
 animator.resetAllKeyStates();
}


This method, as seen in the Animator class, sets all elements of the keyStates array to false, so now we don't have the problem of the HotSpot object moving along its given vector when the applet loses focus and the key is released.

JaVa
   
Comments