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 |
JFileChooser
from 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 openedFile
and fCho
.
|
The 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 saveFile
that 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
trouble yet.
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
FileOutputStream
with openedFile
as argument. (The menu is located in the PaintBox class,
where we have yet not added the "Save as..." menu item.)
Like 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
with 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
File
object.
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?
PaintFileFilter
is
a class, but since we will only use it within the scope of PaintPanel
we can simply add it to the PaintPanel.java
file, as an add-on after the PaintPanel
class.
|
Abstract class 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
of classes. 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. |
FileFilter
class
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
true
or 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"
the String
class will return true
, else
false
.
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
, 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
image.
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.
Component
of the java.awt
package.
It is used to locate the file chooser near the place you called for it. We use
this
as parameter, that is a reference to the PaintPanel
object 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 int
type.
We are interested in the APPROVE_OPTION
.
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 readFile
method.
Inside 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 File
type;
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 readFile
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.
PaintPanel
will 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
initFChooser
method.
|
After we are certain we really have a file chooser we
declare a temporary File
variable,
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
JFileChooser
class.
|
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
true
or false
. If such a file already exists then we dive into a new
if
-block.
|
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
them here
this
is a reference to the parent,
int, in this case a reference to the PaintPanel
object.
This reference can be any object of the Component
type. String
that 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 Component
or an array
of such objects. String
. WARNING_MESSAGE
, next month we will see four more.
OK_CANCEL_OPTION
that gives two buttons. Icon
type, but we sent null
as parameter. JOptionPane
really
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 OK_OPTION
since
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 doSave
is 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
true
to false
with the exclamation mark. The result will be that the
false
that is returned from all other file endings is
switched into a true
, and
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 doSave
to false
.
So far the outcome may be a doSave
set to true
or false
,
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 old openedFile
with
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
method.
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 saveAsFile
method?
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 saveAsFile
method.
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 JOptionPane
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
into the Polygon
class
found in the java.awt
package.
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
drawPolygon
and fillPolygon
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.
Previous Article |
|
Next Article |