Finding Lost Frames

Something that I have encountered in some almost-complete projects, were dialogs that were not bound to frames. When you construct a dialog, you should specify who the owner is, which is either a frame or another dialog. It is, however, also possible to specify "null" as the owner, which causes very irritating problems. In MS Windows, if a modal dialog does not have an owner, and you have moved away from it, for example if you quickly switched to Outlook while waiting for it to start up, the only way to get back to the dialog is by using ALT+Tab. If you click on the frame icon on the toolbar, you will get just the frame, not the dialog. Clicking on the frame will cause a beep and that's all.
A few months ago, I asked myself the question: Is there a way in which we can find a frame that has already been created?
One of my first newsletters showed a GlobalHotkeyManager that allowed you to install your own event queue into the AWT, letting you catch any events that occur, before ANY other components get hold of them. Why don't we use the concept to catch Window events and then use those events to remember which frames are available?
The problem with the GlobalHotkeyManager, was that it only allowed exactly one event catcher to be present at a time. It is therefore only safe to use if none of the libraries you use link in a similar event queue. An alternative, suggested by F.S., was to register ourselves as a listener to the main AWT event system by calling Toolkit.getDefaultToolkit().addAWTEventListener().
Each time a window is activated, we grap the handle and add it to our list of frames that we know about. We can then write a method called "lookupFrame(String title)" which goes through the list and returns the first frame that we find with the specified title.

//: FrameLookup.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class FrameLookup {
  private final Collection frames = new LinkedList();
  // Singleton Pattern
  private static final FrameLookup instance = new FrameLookup();
  public static FrameLookup getInstance() {
    return instance;
  }
  private FrameLookup() {
    Toolkit.getDefaultToolkit().addAWTEventListener(
      new AWTEventListener() {
        public void eventDispatched(AWTEvent event) {
          System.out.println("Event Dispatched : " + event);
          if (event.getID() == WindowEvent.WINDOW_ACTIVATED) {
            if (event.getSource() instanceof Frame) {
              synchronized(frames) {
                frames.add(event.getSource());
              }
            }
          }
        }
      }, AWTEvent.WINDOW_EVENT_MASK);
  }
  public Frame lookupFrame(String title) {
    synchronized(frames) {
      Iterator it = frames.iterator();
      while(it.hasNext()) {
        Frame frame = (Frame)it.next();
        if (frame.getTitle().equals(title)) return frame;
      }
    }
    return null;
  }
}
I can now write a test program that creates a frame, forgets the handle, and then creates a dialog and uses the FrameLookup class to find the correct frame to use as owner.
//: FrameLookupTest.java
import javax.swing.*;
import java.awt.*;

public class FrameLookupTest {
  private static final String SWING_FRAME_TITLE = "Swing Frame";
  private static final String AWT_FRAME_TITLE = "AWT Frame";
  private static final String SWING_DIALOG_TITLE = "Swing Dialog";

  public static void main(String[] args) {
    FrameLookup framer = FrameLookup.getInstance();
    makeFrame();

    // we can find visible frame by using our FrameLookup utility
    System.out.println("Frame is " +
      FrameLookup.getInstance().lookupFrame(SWING_FRAME_TITLE));

    makeDialog();
  }

  private static void makeFrame() {
    JFrame frame = new JFrame(SWING_FRAME_TITLE);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(300, 300);
    frame.show();
  }

  private static void makeDialog() {
    // we can bind our JDialog to the Frame
    JDialog dialog = new JDialog(
      FrameLookup.getInstance().lookupFrame(SWING_FRAME_TITLE),
      SWING_DIALOG_TITLE, true);
    dialog.setSize(400, 100);
    dialog.show();
  }
}
This is great; we can discover the correct frame as owner, just by knowing its title! There are some minor problems, such as the fact that there is a racing condition involved. It can happen that we request to find the frame before it has been activated or shown, thereby our lookupFrame() method would return "null". Big Oooops. Our situation is now worse than before, as such intermittent failings are MUCH harder to find. Let's also not forget that the title of a frame does not have to be unique.
When I was preparing this newsletter, I got distracted in that I tried to reproduce the Swing dealock problem (which I didn't manage :(. While I was looking through the source code of JFrame/Frame/Window/Container/Component, I happened upon the method Frame[] java.awt.Frame.getFrames(), which Sun added in the JDK 1.2, according to the @version tag. Is it just me, or have I been reading the wrong publications, that don't tell me about these features? Please, if you have heard of Frame.getFrames(), send me an email.
Anyway, with my newly acquired knowledge, I ran off and wrote a second version of the FrameLookup class that looks like this, and sorts out the racing condition problem:
//: FrameLookup.java take 2
import java.awt.*;
public class FrameLookup {
  // Singleton Pattern
  private static final FrameLookup instance = new FrameLookup();
  public static FrameLookup getInstance() {
    return instance;
  }
  private FrameLookup() { }
  public Frame lookupFrame(String title) {
    Frame[] frames = Frame.getFrames();
    for (int i=0; i<frames.length; i++) {
      if (frames[i].getTitle().equals(title))
        return frames[i];
    }
    return null;
  }
}
We still have the problem that titles alone could be ambigious, but the getFrames() method is definitely the correct way to find a Frame if you need to.
Until next week, and please remember that the more people read this newsletter each week, the more corporate time I can waste collectively, currently I'm wasting about 2 man-months of your development time each week. i.e. please keep on forwarding these newsletters to friends and colleagues who use Java ;-)

created by Dr. Heinz M. Kabutz

0 komentar:

Posting Komentar

Twitter Delicious Facebook Digg Stumbleupon Favorites More

 
This Theme Modified by Kapten Andre based on Structure Theme from MIT-style License by Jason J. Jaeger