Images
[Agent practical 8 of 9]
We now have a perfectly decent model/application, however, there are a few of things we could do to improve it.
Firstly, we could resize the frame so that it showed the whole of the image whatever its size.
The code to
do this isn't especially hard; you just setSize()
to something appropriate
in the Model
's actionPerformed
method when the data is loaded. As our setData()
method from part 3 updates the height and the width of the Environment, we can get hold of these to set the size.
Remember, you may also want to use getInsets().left
etc., that we saw in the lecture
(docs).
Secondly, we might want to wait to display an image until we've read a proper one in.
At the moment the application shows a black rectangle because we set the data
array in
Environment
with nothing it in, and it fills with zeros. Even if we used
buildWorld()
(which we removed from the constructor), it would fill it with 255s, which
our algorithm would then rerange to zeros.
At the top of our Environment, we currently have the line:
private double[][] data = new double[height][width];
Which makes it an array full of zeros. But if we change it to:
private double[][] data = null;
We keep the label but have a default condition we can check for at different stages.
For example, in
This fills it with zeros (primitive types have a default value of zero; objects have a default value of null
, i.e. "not set up").
This was because in earlier practicals we weren't reading data in, and we didn't want it throwing a whole bunch of "not set up" exceptions if your code
went wrong. However, now we're reading data in, we could do this:
double data[][] = null;
leaving the array not set up until the user uses the "Open..." MenuItem, thereby calling setData
with
a suitable data array. If we did this, we could then do the following to our
getDataAsImage
method:
public Image getDataAsImage() {
if (data == null) return null;
// Our Image making code.
return image;
}
And in our Analyst
's paint
method, this:
public void paint (Graphics g) {
Image image = world.getDataAsImage();
Graphics cg = c.getGraphics(); // Needed out here to draw agents later.
if (image != null) {
cg.drawImage(image, 0, 0, this);
}
// Draw agents etc.
}
The only problem with this is it enables us to run the agent model with no data read in. One solution is to stop the user clicking the menu (see below). Another is to check there's data before using it. The simplest way to do this is to implement something like this in Model:
private void runAgents() {
boolean dataThere = (world.getData() != null) ? true : false;
for (int i = 0; i < numberOfIterations; i++) {
Collections.shuffle(agents);
for (int j = 0; j < agents.size(); j++) {
agents.get(j).run(dataThere);
}
update(getGraphics());
if (dataThere && (stoppingCriteriaMet() == true)) break;
}
}
and then pick up the boolean in run()
to change the behaviour (for example, so agents can move but not eat). Note that we could call
world.getData()
inside run() to check for null , but that would mean a new method call
*every* step of an agent. The way above only calls the method once, which is much more efficient.
Anyhow, we'll leave sorting that out as a little test for you, if you want to do it that way -- we're going to take the
easier root of disabling the run button...
One thing to think through is guiding your users along a path that prevents them using the system in unexpected ways. In the case of our model, for example,
we might not want the users to run the model until they've loaded some environmental data, and we might not want them to save the results until they've
run the model. We can achieve this with setEnabled()
, a method associated with GUI Components. All we need to do is set
any components we want off as disabled in buildGui()
, for example:
runMenuItem.setEnabled(false);
and then setting them enabled in the relevant actionPerformed()
area, so, for example:
if (clickedMenuItem.getLabel().equals("Run")) {
runAgents();
saveMenuItem.setEnabled(true);
}
To do this, you'll need to cut and paste the lines creating those MenuItems into the Model's instance variable area so they
can be seen in both buildGui()
and actionPerformed()
. Give it a go.
This is pretty neat, no? As we said in the lectures, users don't make mistakes -- coders make the mistakes; it's our job to help the users use.
If it annoys you that the model always starts off tucked into the corner of the screen, you can set its location using this statement:
setLocation(
(int)(Toolkit.getDefaultToolkit().getScreenSize().getWidth() / 2) - (getWidth() / 2),
(int)(Toolkit.getDefaultToolkit().getScreenSize().getHeight() / 2) - (getHeight() / 2)
);
Note that the getWidth() and getHeight() on their own are finding the width and height of the frame. Toolkit is a useful class for dealing with the screen and windows.
Finally, here's a version of the IO
class (IO.java) that reads
in image files like gifs
and jpgs
and writes these formats as well, along with a satellite image of an
important and mysterious continent to run your model on
(sat.jpg), should you be interested.