Java GUI Made Easy part 1

Confusion

So you finally went through “The Java Tutorial” and want to apply what you learned. You want to make a great application to show how much you know. But now you don’t know where to start. The task seems daunting. All the contrived examples in the tutorials showed the basics of the GUI functionallity but how do you put it all together. That is what I am hoping to do here.
One of the biggest problems is to come up with a project that you can finish and is not too complicated. Aim too high and you will easily get frustrated and give up. Aim too low and you won’t learn anything. One of the easiest choices is to copy an existing application that will fit the bill. As a Windows user the decision is pretty straight forward. I picked the venerable Notepad application supplied with Windows since the beginning of time.
Notepad Screen Shot
I know, I know, you are saying (or yelling), “Notepad??!! Why would I want to copy that crappy app!” That is just the reason. Being crappy, really just means it has a limited feature set. But it has enough features to need a variety of programming constructs.

Where to begin

To build any piece of software you should begin with a list of features. Now just writing “Copy Notepad” is not enough. What makes up Notepad? Here’s a list.

  • Text area – contains an area for the user to type basic fixed format text with a single font.
  • File menu – contains options New, Open, Save, Save As, Page Setup, Print, Exit
  • Edit menu – contains options Undo, Cut, Copy, Paste, Delete, Find, Find Next, Replace, Go To, Select All, Time/Date
  • Format menu – contains options Word Warp, Font
  • View menu – contains options Status Bar
  • Help menu – contains options Help Topics, About Notepad
  • Title bar – Contains document name and application name

Those are the most obvious features. Some features that might get overlooked but are important for a good user experience are, when exiting the application: save current window position, save current window size, and save whether the application is maximized or not. I’ll also add a feature that is not there right now. Most applications have a recent documents list so ours will too. It’s useful to the user and also allows me to show dynamic menus.

The Code

Alright. Let’s get into some code. The complete code listing can be found in the files JNotepad.java and Utils.java. Let’s look at the class declaration first.

public class JNotepad extends JPanel implements WindowListener {

We extend JPanel here because it is easier to construct your GUI using a panel as the base class instead of a Jframe. One example of this is if you want to make a game and you want your play area a specific size, such as, 800×600. It is easier to control that if you use your panel as your game area instead of a frame.
We also want to implement the WindowListener interface so we can properly exit the application. You can also do it with Jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE). The only problem with doing it that way is that you can’t perform any cleanup operations before the applciation closes. One such cleanup operation would be to check the current document being edited to see if it has been changed but not saved yet.
Now we can look at the constructor.

  public JNotepad(JFrame parent) {
    this.parent = parent;
    Utils.loadPrefs(parent);
    parent.addWindowListener(this);
  }

We need access to the parent frame later when we start adding menus. We also need it for the feature that we want to make sure the window on our app will maintain it’s state when you exit. The Utils class will be used to load and save state information. Well look at this class shortly. The next line sets up our listener so we can tell what the user does to our window.
Next is handling exiting the application.

  public void exit() {
    Utils.savePrefs(parent);
    System.exit(0);
  }

There’s our Utils class again. We are now saving the state before we exit. Then we just exit. Calling System.exit(0) will end the main user thread. Since our application is simple this will also exit the VM.

  public void windowClosing(WindowEvent e) {
    exit();
  }

Here windowClosing is called when the user clicks the ‘X’ to close the window. This is set up using the WindowListener interface. When we register our listener with the JFrame we are saying we want all to be told when any of the following events occur: closed, closing, opening, minimizing, restoring, deactivate and activate. For now we are only interested in the closing event since we can use it to interrupt the window before it disappears. One thing to note hear is that if you don’t set the default close or use the listener then when the user clicks the ‘X’ to close the window, that is all that will happen. The window will disappear from view but the application will remain running.

  public static void main(String... args) {
    try {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }
    catch(Exception e) {
        e.printStackTrace();
    }
    JFrame f = new JFrame("JNotepad");

    JNotepad p = new JNotepad(f);
    f.add(p, BorderLayout.CENTER);
    f.setVisible(true);
  }

The last thing to see in this post is the main entry point. Here we set up our UI and create our window frame. The UIManager.setLookAndFeel will set up our window to look like regular applicaiton windows on the system that the application is running on. Then we create our application and pass our frame into the constructor. Since the entire workarea is for text entry we add our application panel to the center of the frame. Then we show the frame. In some applications you may call pack() on the frame to set up your window. However you would only do that if you have a UI full of widgets. With this application we want to let the user control the window size so we will set it up with some defaults.
Now we can look at the Utils class.

  public class Utils {
    private static final String WINDOW_X = "notepad.window.x";
    private static final String WINDOW_Y = "notepad.window.y";
    private static final String WINDOW_HEIGHT = "notepad.window.width";
    private static final String WINDOW_WIDTH = "notepad.window.height";
    private static final String WINDOW_MAXIMIZED = "notepad.window.maximized";

Here we declare the class and then set up some String constants to use as identifiers in the backing store.

    private static int windowX;
    private static int windowY;
    private static int windowWidth;
    private static int windowHeight;
    private static boolean windowMaximized;

This is our state information that we want to store.

  public static void loadPrefs(JFrame frame) {
    Preferences prefs = Preferences.userNodeForPackage(Utils.class);

    windowX = prefs.getInt(WINDOW_X, 0);
    windowY = prefs.getInt(WINDOW_Y, 0);
    windowWidth = prefs.getInt(WINDOW_HEIGHT, 800);
    windowHeight = prefs.getInt(WINDOW_WIDTH, 600);
    windowMaximized = prefs.getBoolean(WINDOW_MAXIMIZED, false);

    frame.setBounds(windowX, windowY, windowWidth, windowHeight);
    if(windowMaximized) {
      frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
    }
  }

If you haven’t seen the preferences class before, it is very useful. There are 2 ways to initialize it. You can call the static method userNodeForPackage or systemNodeForPackage. If you want to store indiviual settings for every user that uses your application you should call userNodeForPackage. To store settings for the computer that your application is running on no matter which user starts it then call systemNodeForPackage. In general you should use user settings so that multiple users won’t have their settings overwritten. Next we load our applications settings using the preferences class get methods. There is one for each Java primitive as well as java.lang.String. Each getter has 2 arguments. The first argument is the key that you want to load the value for. The second argument is the default that you want to set if the key value has not been set. This would typically happen when the app is used for the first time. So as you can see we are going to set up our app to default to an unmaximized window that is 800×600.

  public static void savePrefs(JFrame frame) {
    Preferences prefs = Preferences.userNodeForPackage(Utils.class);

    windowMaximized = ((frame.getExtendedState() & JFrame.MAXIMIZED_BOTH) == JFrame.MAXIMIZED_BOTH);
    if(!windowMaximized) {
      windowX = frame.getX();
      windowY = frame.getY();
      windowWidth = frame.getWidth();
      windowHeight = frame.getHeight();
    }
    prefs.putInt(WINDOW_X, windowX);
    prefs.putInt(WINDOW_Y, windowY);
    prefs.putInt(WINDOW_HEIGHT, windowWidth);
    prefs.putInt(WINDOW_WIDTH, windowHeight);
    prefs.putBoolean(WINDOW_MAXIMIZED, windowMaximized);
  }

Of course if you want to load state you first have to save it. The first thing we want to check is if the window is maximized or not. If it is we don’t want read the other positional values because they will just reflect the full size of the screen instead of the values to return to when the screen becomes normalized.
Ok. There’s not too much there yet but it is a good start. The most important thing to the user is the experience using the application. It weighs very heavily in choice of products to use. Keeping this in mind through the process of creating your app will go a long way to get people to use your app.

JNotepad.java
Utils.java

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: