[...]
Yes sorry again I really didn't give it a thought at the time, what
Knute suggested has been extremely helpful and gives me part of my
answer, and that is that as long as I have a reference to the graphic of
the component I can draw to this and send a repaint call to the
component after?
The way I read the above: you've got a GUI component that only redraws
itself using a referenced "graphic" (there's no such thing as a "graphic"
in Java...do you mean an instance of Image, such as BufferedImage?), while
some other code draws into the "graphic" and then tells the component that
the "graphic" has changed.
Is that a correct understanding of what you wrote? If so, then that seems
fine to me.
No particular reason, I chose JPanel as most of the examples I have seen
use this.
Well, unless you have a specific reason for using JPanel, then don't.
Extend JComponent instead, as it's the actual base class for Swing
components.
Int the graphic object and then call a repaint on the component to
refresh display?
You're answering a question with a question (and one I don't really
understand either...what does the word "int" mean here? The usual meaning
in this context, "integer", doesn't seem to apply).
[...]
I'm not really clear on what the "non gui logic class" is. Anything
that needs to know the size of your custom component and involves
itself in the drawing of the component is, to my perception, a GUI
class.
Ah my idea of a gui class is an extended JPanel stuffed with code
Well, fine. But that's not an explanation as to what a "non gui logic
class" is.
If you have in mind a class that doesn't know anything about the GUI,
then
This is exactly what I had in mind, if my understanding is correct then
passin a reference of tthe components graphic object will allow me to
draw to the graphic and then fire an event for the component to redraw
itself.
See above. If I understand this statement correctly, then it seems like a
fine approach. If I don't, then it might or might not be.
I would imagine methods something like
scale, translate, draw, update, origin, rotate, drawText, to name a few
Are the transformation operations (scale, translate, rotate...and what's
the difference between "translate" and "origin"?) independent of the
similar operations that would be set for the custom Swing component? That
is, from your previous description, I have the impression that you want to
be able to scale and center the image in the custom component, and now
from this most recent description I have the impression that you also want
to be able to do things like that and other operations on the non-GUI
class.
I am guessing that the component class would create an instance of the
logic class and pass it's graphic object and an image in the constructor
and would implement property change listener.
What's the difference between "its graphic object" and "an image"? At the
outset of this message, I made the assumption that your ambiguous term
"graphic" referred to some sort of Image instance. But if you can have
both a "graphic" and an "image", then I'm not so sure.
Any buttons or menu item events would call the relevant method in the
logic class.
The logic class would calculate positions, scales and translations etc
and then using the graphic reference draw to this and then call repaint
on the component, possibly via an event, not 100% sure though
I think it makes more sense to follow the "listener" idiom, where your
"non-GUI" class defines a listener interface that the Swing component can
implement and respond to. Then when changes are made, the listener is
notified. In this case, the Swing component would call repaint() itself,
but this allows for a more general-purpose "non-GUI" class. After all, if
it's "non-GUI" then why should it know about a JComponent that needs to
have repaint() called?
Other than that, your description sounds fine. Of course, that assumes I
understand the rest of the model correctly, which may or may not be the
case.
This sounds interesting, how would this work? this sounds similar to the
Swing Model View pattern?
Well, it's more like being "similar" to the event-driven GUI updating
paradigm that practically all mainstream GUI's use.
Model/View is about separating the data from the model. And it works well
in an event-driven paradigm. But it's not mandatory...you can easily have
views (i.e. Swing components) that incorporate their own model, and that's
not in line with the Model/View pattern. Yet, they would still use this
"change some data, call repaint() to ask to be repainted" paradigm.
In other words, what I'm describing really isn't optional, the way that
Model/View is. A correctly-written GUI application will always use this
event-driven paradigm, whether or not it separates the model from the view.
By using the reference to the components graphic object ?
Again, that depends on what you mean by "graphic object". But sure, if
that "graphic" object keeps track of the transform needed for drawing,
that would be a natural place to get the AffineTransform instance.
Note that setting the Graphics2D transform is not the only approach. You
can also pass a transform to some of the Graphics2D.drawImage() overloads,
and it would have the same effect as changing the Graphics2D's own
transform. This is what I'm talking about here:
Do you mean Graphics2D.DrawImage, DrawLine etc?
Those are not the names of any methods in the Graphics2D class. But if
you mean Graphics2D.drawImage(), yes. For that method, you could just
pass a precomputed transform. For drawLine(), there's no such
overload...you've have to explicitly transform integer coordinates
yourself and then use the Graphics.drawLine() method, or create a Shape
representing the line and either transform the Shape before drawing it
(some Shape implementations include a transform() method) or set the
Graphics2D transform itself.
It all depends on exactly what you want to do.
In the hopes that it might help you out a little, I've attached below an
example of a simple custom Swing component that has image and scale
properties that can be set, and which always draws the image centered in
the component at the scale that's been set. It doesn't demonstrate the
additional layer that I think you've been talking about, where there's an
intermediate class that actually manages things like holding on to the
image, storing settings like scale factor, and implementing notification
for listeners, but hopefully it gives you a better idea of how the Swing
component side of things would work.
The custom Swing class is first, followed by some simple sample code that
demonstrates the use of it. (The demo code just opens image files, but
the custom component can also just be assigned some arbitrary image; it
would be simple enough to add a public method to notify the component that
the image's changed so that the component knows to call repaint(), but as
I mentioned before I think it would be better to implement a full-fledged
"listener" API that the component can subscribe to if you're going to have
anything more elaborate than just a simple reference to an Image instance
as shown here).
Pete
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import javax.imageio.*;
import javax.swing.*;
public class JImageBox extends JComponent
{
private Image _image;
private double _scale = 1.0;
AffineTransform _transform;
public JImageBox()
{
this.addComponentListener(new ComponentAdapter()
{
public void componentResized(ComponentEvent arg0)
{
_UpdateImage();
}
});
}
public void setImage(BufferedImage image)
{
_image = image;
_UpdateImage();
}
public void setImage(File file)
{
try
{
_image = ImageIO.read(file);
}
catch (IOException e)
{
e.printStackTrace();
_image = null;
}
_UpdateImage();
}
public void setScale(double scale)
{
_scale = scale;
_UpdateImage();
}
protected void paintComponent(Graphics gfx)
{
if (_image != null)
{
Graphics2D gfx2 = (Graphics2D)gfx;
gfx2.drawImage(_image, _transform, null);
}
}
private void _UpdateImage()
{
_transform = null;
if (_image != null)
{
Dimension size = getSize();
double cxImage, cyImage;
cxImage = _image.getWidth(null) * _scale;
cyImage = _image.getHeight(null) * _scale;
_transform = AffineTransform.getTranslateInstance(
(size.getWidth() - cxImage) / 2,
(size.getHeight() - cyImage) / 2);
_transform.scale(_scale, _scale);
}
repaint();
}
}
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
public class TestImageComponentFrame extends JFrame
{
private JImageBox _imagebox = new JImageBox();
public TestImageComponentFrame(String arg0)
{
super(arg0);
setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
Box box = Box.createHorizontalBox();
JButton button = new JButton("Open File...");
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
_ChooseImage();
}
});
box.add(button);
final JTextField text = new JTextField("1.0", 8);
text.setMaximumSize(text.getPreferredSize());
button = new JButton("Set Scale");
button.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent arg0)
{
try
{
_imagebox.setScale(Double.parseDouble(text.getText()));
}
catch (NumberFormatException e)
{
e.printStackTrace();
}
}
});
box.add(button);
box.add(text);
add(box);
add(_imagebox);
}
private void _ChooseImage()
{
FileDialog filedlg = new FileDialog(this, "Please choose an image
file to load:", FileDialog.LOAD);
filedlg.setVisible(true);
if (filedlg.getFile() != null)
{
_imagebox.setImage(new File(filedlg.getDirectory()
+ filedlg.getFile()));
}
}
}
import java.awt.*;
public class TestImageComponent
{
/**
* @param args
*/
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
TestImageComponentFrame frame = new
TestImageComponentFrame("TestImageComponentFrame");
frame.setSize(640, 480);
frame.setVisible(true);
}
});
}
}