MaxAxeHax
MaxAxeHax

Reputation: 444

Delegate painting of custom Swing component to parent

My goal

I want my custom Swing component CellLabel extends JLabel to paint certain things (X) always, and certain other elements (Y) only if its parent - (a JPanel holding a grid of CellLabels) - tells it to. The painting of (Y) depends on the state of the neighbouring cells in the grid, and only the JPanel can get this information and decide if and how (Y) should be painted for every child CellLabel.

My question

How can I externalize (into a method with parameters that allow me to describe how to paint (Y) exactly) the painting behavior so that the parent can decide whether it wants a CellLabel child to paint (Y) or not?

My problem

Every Swing tutorial out there tells me I should overwrite the paintComponent method when creating a custom component; however, in my application, the component can't decide for itself if and how (Y) should be painted since it lacks the necessary information.

I tried to write a CellLabel::paintY(int offset) method that I could call from the parent once it has decided how and if to paint (Y):

class CellLabel extends JLabel {
    void paintComponent(Graphics g) {
        super.paintComponent(g);
        // Paint elements (X) which should always be painted, independent
        // of external state
    }        

    void paintY(int[] params){
        Graphics2D g2 = (Graphics2D) this.getGraphics();

        // Perform some calculation with params that decide how painting is done

        // (*) Actually paint elements (Y) by using the g2 graphics context
    }
}

However, it seems I can't get a hold of the Graphics context outside of the predefined paint methods that convention tells me to overwrite. I get the following exception at (*) (when actually trying to paint with g2):

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at CellLabel.paintY(CellLabel.java:77)
at AutomatonUI.createAndShowGUI(AutomatonUI.java:50)
at AutomatonUI.access$000(AutomatonUI.java:22)
at AutomatonUI$1.run(AutomatonUI.java:29)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:744)
at java.awt.EventQueue.access$400(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:697)
at java.awt.EventQueue$3.run(EventQueue.java:691)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$1.doIntersectionPrivilege(ProtectionDomain.java:75)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:714)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

I have a solid amount of experience coding business logic and number-crunching, but I'm quite new to Swing (and UI programming) in general, so please excuse me (and educate me) if I'm doing something horribly wrong.

Upvotes: 1

Views: 323

Answers (2)

NESPowerGlove
NESPowerGlove

Reputation: 5496

A better approach might be to separate your logic from the Swing GUI library. For example, defining a CellContainer class that contains Cell objects, which can be updated before each render (where in the update you can update Cells from CellContainer however you wish). You could then have CellContainerPanel and a CellLabel class that extend the appropriate Swing class which take a reference to CellContainer and Cell each which just draw the state that their references make available.

Upvotes: 2

Aify
Aify

Reputation: 3537

IIRC, your paintComponent function needs to call super.paintComponent(g);

The last time I wrote a program with swing, i declared a static graphics object in a class that everything could access (this is probably a horrible thing to do, but it was a workaround which helped me to complete my task). Instead of storing the information inside of a two dimensional array, store "information" objects inside the array. Those information objects (object x, for example) can contain all the information about the cell itself, as well as a "draw(g)" method. That draw method can dictate how this cell is drawn. In the paintComponent function, you can now call

//where g is the graphics object
for (array x : array of arrays) {
    for (informationObject y : x) {
        y.draw(g);
    }
}

Now you can do all the calculations inside the object itself, instead of having to deal with obtaining external information and doing calculations outside. (Then, the code will be easier to debug as well)

Upvotes: 0

Related Questions