I have a java form implemented using swing on which I want to place a number of panels in which I can draw on using the graphics2D package.
To do this, I implement the panels using an extension of JPanel thus:
public class GraphicsPanel extends JPanel
{
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
super.paintComponent(g2d);
// Need to specify a function from the calling class here
MethodFromCallingClass();
}
}
In the calling class I have
public GraphicsPanel Graphics1= new GraphicsPanel() ;
public void Graphics1_Paint()
{
// Code to draw stuff on the Graphics1 panel
}
So question is how do I pass the function (Graphics1_Paint) from the calling class to the GraphicsPanel class?
I've tried reading about interfaces and lambdas but so far they make no sense.
Can anyone enlighten me please.
I think the easiest way is to pass the calling class (or some other interface) to the constructor of your GraphicsPanel like
public class GraphicsPanel extends JPanel {
private CallingClass cc;
public GraphicsPanel(CallingClass cc) {
this.cc = cc;
}
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
super.paintComponent(g2d);
cc.MethodFromCallingClass(); // <-- invoke a call-back.
}
}
So it sounds like you want to define what is drawn outside of the custom JPanel class, where you can pass what you want drawn to the JPanel instance at anytime. This is possible.
First, define an interface that contains one method that draws with a Graphics2D instance:
public interface DrawGraphics()
{
public void draw(Graphics2D g);
}
Next you'll want to extend your GraphicsPanel a bit to have the ability to change an underlying instance of DrawGraphics.
So your constructor should now be:
public GraphicsPanel(DrawGraphics graphicsToDraw) { ...
You should also have a set method for the DrawGraphics stored so you can change it at anytime:
public void setDrawGraphics(DrawGraphics newDrawGraphics) { ...
Make sure you add some synchronization somewhere here, or create all DrawGraphics on the EDT because the paintComponents method will execute on the EDT.
Next the paintComponents method can simply draw the graphics:
public void paintComponent(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
super.paintComponent(g2d);
// Need to specify a function from the calling class here
graphicsToDraw(g2d);
}
And when you need to change the DrawGraphics:
// Calling from the EDT
myGraphicsPanel.setDrawGraphics(g -> g.drawString("Hello World!", 50, 50);
I seem to have solved the problem, but I'd like comments on usability and good practice
public class CallingClass
{
public JPanel Graphics1 ;
public CallingClass()
{
Graphics1 = new Graphics1_Paint();
}
public class Graphics1_Paint extends JPanel
{
public Graphics2D g2d;
public void paintComponent (Graphics g)
{
g2d = (Graphics2D) g;
super.paintComponent(g2d);
g2d.drawString("From Calling Class",10,10);
}
}
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new CallingClass();}
});
}
Seems I don't need my GraphicsPanel class after all.
Related
I know that is poorly worded, but I don't know how to word it better. Essentially I have my own JComponent MyComponent and it paints some stuff onto its graphics. I want it to paint its stuff, then call a method to finish the paint, here is an example:
public class MyComponent extends JComponent{
// etc
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g2 = (Graphics2D)g;
g2.drawSomething(); // etc
// Once it is done, check if that function is exists, and call it.
if(secondaryPaint != null){
secondaryPaint(g2);
}
}
}
Then, in a different class:
// etc
MyComponent mc = new MyComponent()
mc.setSecondaryDrawFunction(paint);
// etc
private void paint(Graphics2D g2){
g2.drawSomething();
}
I'm not sure how lambdas work or if they are applicable in this situation, but maybe that?
No lambdas, but the Function interface will work
You can do :
public class MyComponent extends JComponent{
// etc
Function<Graphics2D, Void> secondaryPaint;
public MyComponent(Function<Graphics2D, Void> myfc){
secondaryPaint = myfc;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
//g2.drawSomething(); // etc
// Once it is done, check if that function is exists, and call it.
if(secondaryPaint != null){
secondaryPaint.apply(g2);
}
}
static class Something {
public static Void compute(Graphics2D g){
return null;
}
public Void computeNotStatic(Graphics2D g){
return null;
}
}
public static void main(String[] args) {
Something smth = new Something();
new MyComponent(Something::compute); // with static
new MyComponent(smth::computeNotStatic); // with non-static
}
}
I've extended the JPanel class to draw a graph. The problem that I've got is that I need a global graphics object in order to call it in multiple methods... As an example, here's what I'm trying to do:
public class Graph extends JPanel {
private Graphics2D g2d;
public void paintComponent(Graphics g){
g2d = (Graphics2D)g;
}
public void drawGridLines(int hor, int vert){
g2d.someLogicToDrawMyGridLines(someparams);
}
}
This returns a null pointer exception - so my question is: how do I create a global graphics object? What's the best practice in this situation?
My suggestion would be this:
public class Graph extends JPanel {
public void paintComponent(Graphics g){
super.paintComponent(g);
g2d = (Graphics2D) g;
drawGridLines(g2d, ......);
}
private void drawGridLines(Graphics2D g2d, int hor, int vert){
g2d.someLogicToDrawMyGridLines(someparams);
}
}
i.e. keep all the uses of your graphics context inside the paintComponent call.
How would I pass in the graphics object externally?
Don't. The graphics context is only valid during the invocation of paintComponent(). Instead, use the MVC pattern, discussed here, to update a model that notifies any listening view to render itself. JFreeChart is a complete example.
I've extended the JPanel class to draw a graph. The problem that I've got is that I need a global graphics object in order to call it in multiple methods... As an example, here's what I'm trying to do:
public class Graph extends JPanel {
private Graphics2D g2d;
public void paintComponent(Graphics g){
g2d = (Graphics2D)g;
}
public void drawGridLines(int hor, int vert){
g2d.someLogicToDrawMyGridLines(someparams);
}
}
This returns a null pointer exception - so my question is: how do I create a global graphics object? What's the best practice in this situation?
My suggestion would be this:
public class Graph extends JPanel {
public void paintComponent(Graphics g){
super.paintComponent(g);
g2d = (Graphics2D) g;
drawGridLines(g2d, ......);
}
private void drawGridLines(Graphics2D g2d, int hor, int vert){
g2d.someLogicToDrawMyGridLines(someparams);
}
}
i.e. keep all the uses of your graphics context inside the paintComponent call.
How would I pass in the graphics object externally?
Don't. The graphics context is only valid during the invocation of paintComponent(). Instead, use the MVC pattern, discussed here, to update a model that notifies any listening view to render itself. JFreeChart is a complete example.
I have a problem making an inner class that extends from JPanel to draw anything on it. I overrided the paintComponent method, and whatever I set to draw from here works fine, but using another method to draw does not work.
Here is my inner class code:
private class Plot extends JPanel {
public Plot() {
this.setBackground(Color.WHITE);
}
#Override
public void paintComponent(Graphics graphic) {
super.paintComponent(graphic);
Graphics2D graphic2d = (Graphics2D) graphic;
graphic2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
graphic2d.drawOval(0, 0, this.getWidth() - 1, this.getHeight() - 1);
}
public void drawTitle(final String title) {
Graphics2D graphic2d = (Graphics2D) this.getGraphics();
graphic2d.setColor(Color.red);
graphic2d.drawString(title, 1, 10);
}
}
Notice the drawTitle method. I just want a custom text to be shown. In my outer class which extends from a JFrame I create an instance of this inner class like this:
private Plot plot;
/** Creates new form GraphicsView */
public GraphicsView() {
initComponents();
plot = new Plot();
this.add(plot, BorderLayout.CENTER);
}
public void drawTitle(final String title) {
this.plot.drawTitle(title);
}
I even create a convenient method to call the inner class drawTitle method (with the same name). I do this because I want this JFrame outer class to be visible on button click, once it is visible (which ensures the init of the graphics) I call the outer class drawTitle which in turn calls the inner class method with the same name and where the string show be drawn... but this does not work, I can't see it on the panel. Here is my button click event:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
GraphicsView view = new GraphicsView();
view.setVisible(true);
view.drawTitle("Hello");
}
Thanks in advance, I will appreciate any help. :)
I overrided the paintComponent method, and whatever I set to draw from here works fine
Well, there is the answer to the question. Do all your drawing from the paintComponent() method.
but using another method to draw does not work.
Don't use the getGraphics() method. You should only ever use the Graphics objects passed to the paintComponent() method.
You can't control when Swing repaints() a component. Therefore every time the component is repainted the paintComponent() method is invoked and your other custom painting code will be lost.
Just call the drawTitle() function in the paintComponent override and pass the graphics as an argument. Something like this:
#Override
public void paintComponent(Graphics graphic) {
super.paintComponent(graphic);
Graphics2D graphic2d = (Graphics2D) graphic;
graphic2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
graphic2d.drawOval(0, 0, this.getWidth() - 1, this.getHeight() - 1);
drawTitle(graphic, title);
}
public void drawTitle(Graphics g, final String title) {
Graphics2D graphic2d = (Graphics2D) g;
graphic2d.setColor(Color.red);
graphic2d.drawString(title, 1, 10);
}
Also try to make the title a data member of the class. This might prove helpful later.
In the main method, I am trying to run this:
public static void main(String[] args)
{
game.paintBlocks(g);
}
And getting a "cannot be resolved to variable" error for the "g" parameter.
Elsewhere I have this, which calls on another method in another class (paint(g)) to paint a grid of blocks:
public void paintBlocks(Graphics g)
{
for (int r = 0; r<7; r++)
{
for (int c = 0; c<5; c++)
{
block[r][c].paint(g);
}
}
Do I need to tell it that "g" is in another class? I'm new to this and any help would be awesome!
Where do you want to paint to? I'm assuming you probably want to paint to a window on the screen, in which case you won't be calling paint* yourself, you'll let the Swing framework call it at the appropriate times. In this case, if game is a JFrame, then you just need to make it visible; or if game is some other type of component then you'll need to add it to a visible window. This is the pattern I normally use when I'm teaching basic graphics in Java:
public class MyGame extends JPanel {
public static void main() {
JFrame window = new JFrame("My Game");
window.add(new MyGame());
window.pack();
window.setLocationRelativeTo(null); // Centers the window on the screen
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible();
}
public Dimension getPreferredSize() {
return new Dimension(800, 600);
}
protected void paintComponent(Graphics g) {
// Do my drawing here
}
}
If you want to paint to an off-screen image, then you'll need to create your own graphics context to pass to the paint* methods:
BufferedImage hello = new BufferedImage(game.getWidth(), game.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics g = hello.getGraphics();
game.paintBlocks( g );
In the case of paintBlocks, g is a parameter that is being passed in to the method. In the case of main, g is referencing a variable that hasn't been created anywhere.
Graphics and Graphics2D are abstract classes, and aren't generally intended to be instantiated except by Swing. What Graphics and Graphics2D give you is a context for drawing on a component (like a JPanel or a BufferedImage).
Based on your description you probably want to draw blocks on a Swing component of some kind. (Though it's a little unclear, that would be a normal kind of thing to do.) What you would normally want to do if you are drawing the blocks on a JPanel, for example, is to create a class that extends JPanel and override the paintComponent() method. One way you might do that:
public class BlocksPanel extends JPanel {
// Normal class fields, etc.
// ...
// I would consider making this private, but this is your method from above:
public void paintBlocks(Graphics g) {
for (int r = 0; r<7; r++) {
for (int c = 0; c<5; c++) {
block[r][c].paint(g);
}
}
}
#Override
public void paintComponent(Graphics g) {
paintBlocks(Graphics g);
}
}
There is another example that might help you on page 9 of this document. The Java Tutorials for the Java 2D API may also help.
The variable g is undefined in the main context because you havent declared/initialised it. If you look at your paintBlocks(Graphics g) method, g is passed as a parameter, however the scope of that variable(g) is within the braces({}) of the method paintBlocks(Graphics g).
If you have a class called MyClass that extends a Component, say JPanel, you can do something like this:
class MyClass extends JPanel
{
public static void main(String[] args)
{
Graphics g = getGraphics(); //would return the graphics object for the JPanel
game.paintBlocks(g);
}
}
Its also good to note that the above method in some cases would be tagged as bad programming style. There is an alternative. You can make use of the paintComponent(Graphics g) method provided by the component.
Your main would then look like this:
public static void main(String[] args)
{
repaint(); //this repaints the component, calling the paintComponent method
}
Its also bad programming style to call paintComponent(Graphics g) yourself. You should allow your system to call that method and thats why you have the repaint() method. The system automatically invokes paintComponent(Graphics g) when you repaint.
From paintComponent(Graphics g) you can then do this:
public void paintComponent(Graphics g)
{
super.paintComponent(g); //repainting the panel,not necessary in some cases
game.paintBlocks(g); //passing the graphics object used by the component
}
Hope that helped!