16 May 2001
Simon Gronlund is earning his Master of Science in Computer Science at the Royal Institute of Technology, Sweden, as an adult student. He also teaches Java and computer-related courses at the college. When he isn't tampering with his Warp 4 PC, he spends his spare time with his two boys and his wife.
If you have a comment about the content of this article, please feel free to vent in the OS/2 eZine discussion forums
Into Java, Part 17
Last time we added the ability to save your paintings to a file. We
used the very convenient object stream facility that Java provides. Simply letting a class implement the
JFileChooserfrom Swing is pretty straightforward. Anyone familiar with the Windows user interface will recognize it immediately, whether good or bad. But as with everything done in Java, you are free to change the look and feel and of course most other details you might like to. But admittedly, this time I think that doing that will be tricky and way out of the scope of this column.
Nevertheless, you do not need to construct anything at all yourself,
it comes with the box. But how do we use it? And how do we interact with it?
Let us start with the declaration of
JFileChooser. It will be located in the
PaintPanel class since it is there
we chose to have our file opening and file saving methods. Since we have already imported
javax.swing.* to the class we only need declare a
JFileChooser. At the same time it seems
convenient to declare a variable for the file we are currently working with, so we will use the class
File that we have seen before. I chose
to name them
JFileChooser is still not instantiated, and we do not do that in the constructor either.
Why? It is a matter of using your own judgment. Sometimes we do not need a file chooser, why bother Java with creating
such an instance then? This time we know we certainly will use one, but we can still wait a while, and thus save
some application loading time. This is very polite programming. Do not instantiate things at once as you save
time and the user finds your application quicker. On the other hand they will be waiting for these objects later
on, but we will see that this is perhaps better. You decide.
openedFile, let us make a little detour to some changes we need to do with the method
saveFilethat we wrote in the last column. It still works, but let us use the currently used file name. Later we will be able to change that name. (For a while now, we will not be able to save anything with PaintBox since there is no file name given yet.)
We do a test to make sure that no file name is yet chosen by the user.
Starting from scratch you must use the "Save as..." alternative rather than "Save", right?
Naturally, from here we could have chosen to jump to the method that will take care of that, but we still aren't in
But if there is a currently used file and anyone picks the menu
item "Save" we will continue as before, with only one difference, we instantiate a
as argument. (The menu is located in the PaintBox class,
where we have yet not added the "Save as..." menu item.)
saveFile we have to make the same change to
readFile, but there is no need to check for a sane
openedFile variable since we will never come to
readFile unless there is one. We will
soon see to that.
FileInputStream is now constructed
openedFile as argument,
the remainder is still untouched. Expect more changes.
Clicking "Open..." you would like to see the file
chooser open before your eyes. Let us do the implementation.
Recall that we did not instantiate the file chooser from the start,
but now we have to. Since it is null we are sent to
initFChooser, another new method. Otherwise we would continue with the last line, but let
us wait on that for a moment.
This method will only be called once, the first time anyone
uses the file chooser. And this method will only be called from inside this class, hence it will be
private. From now on the file chooser will always be
present, though most of the time it will be invisible and only brought to visibility upon requests from the user. It is
also used for both open and save actions, differing only in how you call it when
you need it.
We simply instantiate a new
JFileChooser. That will take a while for the Java Virtual Machine, though it is
much quicker when you use Java 1.3. In the future, any call to it will be handled without delay.
This time we set a current directory to the directory we are executing the application from, as we construct a
File object with a single dot. But
of course, you can use any directory of your choice, just create another path with the
The last thing we do is to set a file filter. Since we are not
interested in files not recognized by PaintBox, which is too simple to show GIF or JPEG and such stuff, we would
like a filter. This is the way. But hey, how is
PaintFileFilter, the filter object used?
PaintFileFilteris a class, but since we will only use it within the scope of
PaintPanelwe can simply add it to the
PaintPanel.javafile, as an add-on after the
An abstract class lacks implementation of at least one method,
but may have no method at all implemented. It is solely intended to be inherited, as an ancestor to some family
An abstract class can never be instantiated in itself, it needs
to be extended and the abstract methods must be implemented.
The difference between an abstract class and an interface is only
that an abstract class can have methods and variables that may be inherited, interfaces do not.
FileFilterclass has two methods that we need to implement.
We start with the
accept method that returns a
boolean. It is simply two clauses that may return
false. Both clauses make calls to methods of other Java classes.
The first clause asks the
File object argument for its name, a
String that is changed to lower case and finally tested against our own new file
format "pbo" (PaintBox). If the file tried does end in ".pbo"
String class will return
The second clause simply asks the
File object if it is a directory, every directory should
of course show up in any decent file chooser.
If any one of these two clauses gives a
true is returned.
The second method is used to set the description at the "Files
of type" at the bottom of the file chooser. See the file chooser
This is a most basic file filter class, but it serves its purpose,
we have only one file type, and it instructs us how to do it. There are several pages on the Internet on how to
make more complex filters.
Now, how is our filter used? Our file chooser, for each file and
directory, simply asks this filter if the object at hand is of the correct type, otherwise it will not show up on the
pane. And the file chooser sets the file type.
When this is done we have a file chooser set up that is
ready to use.
the java.awtpackage. It is used to locate the file chooser near the place you called for it. We use
thisas parameter, that is a reference to the
PaintPanelobject we are using. Maybe there are other uses for it as well, I have not dug that deep into this topic.
The file chooser opens and we can highlight a file and click
"Open". The file chooser turns invisible and we are ready to see the outcome, how do we do that?
We notice that we get an
int back as a result. Now let us look at the Java API, at
JFileChooser, and we will find quite
a few constant variables of the
We are interested in the
If you clicked OK, or double clicked a file, you approved the file selection and this constant
is returned. We are not interested in any other possibility in our application.
Next we test if the return value is okay and if so, then we continue.
If anything else is returned we have no backup, we simply leave this method and have to start over. Our file chooser
was turned invisible anyway. Please note that the
if-block induces one extra closing brace at the end of this method, the
if-block we get the selected file from the now invisible file chooser. Now we
see how convenient it is using the
openedFile variable of the
it will be preserved until the next time you open a file or save the file under a new name. It contains
both the file name and in fact, the entire path if you searched for the new file in another directory, partition
or even file system, and it is quietly used by
saveFile anytime you click "Save". Still
File only holds a handle to the file and we have to make a file reader around
it, as before.
Now we are done with the
method, as well as
saveFile. So you may certainly compile here and at least open any file of the
"pbo" type you find. I think you can rename the old
paint.dat and open it to try all this.
PaintPanelwill be today's most-to-do implementation. And there will be some diversions from the plain file chooser topic on the tour. But let us start. As with
readFile, the file chooser may not be instantiated. If you started to paint from scratch there is no file saved yet, thus a call is made to the
After we are certain we really have a file chooser we
declare a temporary
why? Let us wait on the answer for a moment, but ponder over what will happen if you somewhat later cancel
your actions. A temporary holder never hurts. The next variable, the
boolean doSave, comes from similar reasoning. Initially no file is allowed to be saved,
right? It will in a moment be much clearer how to use both these variables.
An observation I have made is that some programmers try really
hard to nest their flow statements, the if-else blocks, to avoid extra variables. At the other extreme we find programmers
that spread variables as seed, but they do not harvest anything good. Moderate use of variables, especially boolean
variables, only makes the application easier to implement, to read, and to improve upon later on. Too heavily nested
flow statements are hard to follow and amend when needed. Also such programming is more often prone to errors.
As with the open file dialog we call a similar method,
showSaveDialog, that in most aspects
works the same way and looks the same way. Also this method returns a result held in an
int, a static constant of the
Continuing with that result, hopefully an
APPROVE_OPTION constant, we ask the file chooser for
the selected file. That may be a new file, if you entered a new file name, or a file you selected from the pane.
Now check yourself; consider if you by mistake wrote a file name of an existing file you did not notice, wouldn't
it be nice having a dialog pane warn you? Most people think so.
Using a file object of the
File class, that is a simple task, merely ask the object if such a file
exists, it will say
false. If such a file already exists then we dive into a new
Here we will make use of a convenience class in the Swing package,
JOptionPane is capable of dozens of
different appearances. We are using the "confirm dialog", a dialog that most of the time has two or
three buttons. I will set aside the next IntoJava to make a small app that can show the different styles of
JOptionPane and also some other GUI
tools. However, there are a zoo of options to use and you really have to make a journey with the
javax.swing package and try to use the different things.
I understand that the next few lines are confusing and will explain
thisis a reference to the parent, int, in this case a reference to the
PaintPanelobject. This reference can be any object of the
Stringthat contains the message. Notice that it is possible to use the new-line character
\n. Please observe that the Java API asks for an
Object, that means a
String, an icon, a
Componentor an array of such objects.
WARNING_MESSAGE, next month we will see four more.
OK_CANCEL_OPTIONthat gives two buttons.
Icontype, but we sent
JOptionPanereally looks nice. Simply send the proper parameters and there you are.
When the user clicks one of the buttons, the
int answer will encapsulate the result in a
JOptionPane static constant. We are
only interested in the
any other answer says we do not want to overwrite the file. If we get an approval, we set the
doSave variable to true.
Finally the outer
if-block is finished, there was no file to overwrite and it seems to
be okay to save the file. So far there are two ways to get an approval; there is no file to overwrite,
but if there was one the user may approve the overwrite himself.
But there is still an obstacle to avoid, we have introduced our
own file type and a filter for it. What will happen if we now try to save with another file type? Let us take
care of that.
It is only when we have an approved
doSave from the former part of the code that this test
is necessary. If
false, it does not matter what ending
the file has, does it? So that is the first clause.
To dive into this
if-block the next clause also has to indicate a possible erroneous file ending.
Hence we ask the file for its name and again ask the
String object that was returned from the file object if it ends with ".pbo".
If so, everything is fine and we do not want to visit this block. Thus we switch a
false with the exclamation mark. The result will be that the
false that is returned from all other file endings is
switched into a
both clauses are true. Voila!
Now we know we have another file ending, but that is still not
an error, only a possible error. So, we must ask the user what his intention is. Another dialog pane is used,
in every essential part it looks the same, only the message and title differ. Recall that
doSave at this point is
true, but any
answer different from
OK_OPTION will set
So far the outcome may be a
meaning to save the file, or not to save.
If the result so far is to save the file, then we simply will replace
the new file name. And now we clearly see why it is good to have such a temporary variable, if we any time change
our mind and we do not want to erase the old
openedFile. Now, why not let
saveFile take care of the rest, avoiding duplicating that code?
On the other hand, what happens if
doSave is set to
false? Remember that we entered the outermost
if-block because we approved a file in the file chooser. Probably that indicates
that we would like to save a file anyway, but perhaps we made a mistake or did not notice a file we do not want to
overwrite. The only natural thing to do is to give the user another chance with the file chooser, a call
back to this very method will do. Note, the outermost
if-block does not have an else-statement, a cancel in the file chooser cancels the entire
Such a thing as a call-back to the method we are in is somewhat
dangerous. If you remember how recursion works, you know that the thread first saves away the context of this
method. Then the thread takes another trip into the method, but is not aware of the former tour. And when finished
the thread jumps back to the very spot it made the recursive call from, that is the
saveAsFile statement in the code above. What will happen
if there are more statements to execute in this first tour in the
The best way to avoid such dangers is to make sure there is nothing
more to do after such a recursive call. That is, put these kind of recursive call as the last statement, then
nothing more is done after the return from the call.
Now we are done with this new method. It contains the necessary
calls to the file chooser and the dialogs helping the user around.
Immediately after the "Save" menu item we simply add
this "Save as..." menu item. As with the other menu items, we include an action listener that calls
a method, in this case the
I have also changed the other item titles to "Save" and "Open..." though that isn't shown here.
Now we are finished with the entire PaintBox example
application. Though it maybe is not the most fancy paint application, it serves as an example of how to build
applications in Java. Naturally there are other ways to make exactly the same outcome, but this way was useful
with its evolutionary development within the IntoJava installments, spanning over a long period.
In Sweden we are facing the spring and hope to experience a lovely
summer, so I give you this naive painting as the closing gift from the PaintBox era.
JFileChooser, a handy tool that comes with the box. There are a few things I did not mention about it; it uses the language you have set your system for, it is capable of opening files in whatever file system your operating system supports, the file filters can be really complex and filter for almost anything, and you can of course create new folders from it.
We have also looked into the
and used only one of its appearances. Multiplying every
option we have we get 240 combinations, but not all of them are meaningful to us. Still, there are several combinations
to pick from and we only need to give the class a different set of options to get them. I love this one, especially
considering the amount of work needed to do the dialog myself from scratch.
Finally we briefly went through how to take care of different situations
that may arise when we try to make an application more powerful. An estimate would say that every powerful feature
you add will cause you twice as many situations to consider; what happens if this is so and that is that?, or
if that is so?, and so on. Try to make it simple, your application will be less error prone and will work snappier.
Next month we will look into some more of the GUI stuff hidden
in Swing. But do not think I will have the space to look into all of it, quite the contrary. The best way to get
the hang of it is to play around and try and cry.
A little hint if you would like to extend PaintBox further, look
found in the
With that class you may make a lot better paintings, clicking around and drawing free hand. It will be tricky,
getting the mouse clicks, adding these to the polygon and getting it painted with the help of the
in java.awt.Graphics. How do you know when you are clicking
around and when you are finished? What happens if you cross the lines, is that shape correctly filled later? Tricky,
but in the end it will be worth the struggle.
Finally, do you have any ideas for a little app we can play with--I
am soon going to discuss threads and networking with Java--please, let me know.
Have a nice month until we see each other again.