2

In a Swing application, I'm drawing elements on a JPanel overriding the paintComponent method.

I have to draw cats, dogs, camels, and more... I guess that polluting paintComponent with a series of if (... instanceof ...) statements would be a bad thing, so I'm looking for another way to draw my pets.

With WPF ( C# ), I'd have used Data Templates, i.e. a way to tell the View how to display a Model entity.

So, I came up with a solution ported from JS:

import my.super.Callback; // Because Runnable does not take parameters.

/**
 * The class that draws in the canvas.
 */
public class CanvasDrawer {
    private HashMap<Class, Callback<Graphics2D, Object>> drawers;
    private Graphics2D graphics;

    public CanvasDrawer(Graphics2D g) {
        graphics = g;
        drawers = new HashMap<Class, Callback<Graphics2D, Object>>();
    }

    /**
     * Defines a way to draw the objects of a certain type in the canvas.
     */
    public <T> void setDataTemplate(Class<T> type, Callback<Graphics2D, T> drawer) {
        drawers.put(type, (Callback<Graphics2D, Object>)drawer);
    }

    /**
     * Actually draws an object in the canvas.
     */
    public void draw(Object object) {
        drawers.get(object.getClass()).run(graphics, object);
    }
}

... Which could be used like this:

class HuntingPartyView extends JPanel {
    CanvasDrawer drawer;

    public HuntingPartyView() {
        drawer = new CanvasDrawer((Graphics2D)getGraphics());

        // Where the drawImage method is intentionally skipped in this code.
        drawer.setDataTemplate(   Cat.class, (g, o) -> drawImage(g, 'res/cat.png',    o.x, o.y));
        drawer.setDataTemplate(   Dog.class, (g, o) -> drawImage(g, 'res/dog.png',    o.x, o.y));
        drawer.setDataTemplate(Sniper.class, (g, o) -> drawImage(g, 'res/sniper.png', o.x, o.y));
        drawer.setDataTemplate(Ranger.class, (g, o) -> drawImage(g, 'res/ranger.png', o.x, o.y));
    }

    @Override
    public void repaintComponent(Graphics g) {
        // Where Game is part of the Model layer.
        for (Animal animal : Game.singleton.animalList) drawer.draw(animal);
        for (Hunter hunter : Game.singleton.hunterList) drawer.draw(hunter);
    }
}

... But the code of the CanvasDrawer above gives me an unchecked warning when I compile it:

$ javac *.java -d out -Xdiags:verbose -Xlint:unchecked
CanvasDrawer.java:15: warning: [unchecked] unchecked cast
                drawers.put(type, (Callback<Graphics2D, Object>)drawer);
                                                                ^
  required: Callback<Graphics2D,Object>
  found:    Callback<Graphics2D,T>
  where T is a type-variable:
    T extends Object declared in method <T>setDataTemplate(Class<T>,Callback<Graphics2D,T>)
1 warning

Question 1. Did I miss something during my research? I mean: do Data Templates exist in Java Swing?
Question 2. Is there another way to do what I want to do? Am I going the right way?
Question 3. Do I really have to use @SuppressWarnings("unchecked")?

  • See [*A Swing Architecture Overview*](http://www.oracle.com/technetwork/java/architecture-142923.html). – trashgod May 31 '17 at 15:58
  • I don't get it: I retain that the Controller has been collapsed in the View. To me, it sounds like the `...Listener` classes are the Controllers, and they are _collapsed_ because one controller could be an anonymous class. I also didn't know that thing with the components models; are you suggesting that I use those data models somehow? –  Jun 01 '17 at 05:37

2 Answers2

1

I guess that polluting paintComponent with a series of if (... instanceof ...) statements would be a bad thing

Correct.

One way is to to just keep an ArrayList of Animals where each animal has a draw method to paint itself.

Something like:

@Override
protected void paintComponent(Graphics g)
{
    super.paintComponent(g);

    for (Animal animal: animals)
    {
        animal.draw(g);
    }
}

Animal can be a simple interface or an abstract class.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • Isn't it a bad thing to put such a _view-ui-painting logic_ in the --- supposedly decoupled --- model ? –  May 31 '17 at 18:39
1

With reference to A Swing Architecture Overview, a complete guide to Swing design is beyond the scope of this site, but a few guidelines may help you going forward.

  1. Did I miss something during my research? I mean: do Data Templates exist in Java Swing? Not explicitly. Focusing on the tutorial example, a Swing JList displays a ListModel<E>. The direct subclass, AbstractListModel<E>, adds event listener management using the scheme examined here. The concrete subclass, DefaultListModel<E>, is a typical implementation. Depending on need, your model might implement the interface directly, leverage the event plumbing of the abstract model or simply contain a concrete model instance, as outlined in How to Use Lists: Creating a Model.

  2. Is there another way to do what I want to do? Am I going the right way? Several common ways to implement the observer pattern are mentioned here.

  3. Do I really have to use @SuppressWarnings("unchecked")? Instead of a raw class like Object, you can specify a type parameter such as CanvasDrawer<T>, as described here. See also Class Literals as Runtime-Type Tokens.

  4. Isn't it a bad thing to put such a view-ui-painting logic in the—supposedly decoupled—model? The example outlined is a view component; the model might contain a List<Animal> that is accessed when the observing view updates itself in response to an event.

  5. It sounds like the …Listener classes are the controller; and they are collapsed because one controller could be an anonymous class. One appealing design approach allows clients to inject a new controller, as shown here for .

  6. Are you suggesting that I use those data models somehow? Only where appropriate. There are simply too many ways to display a List<Animal> to say more.

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • I don't understand; your update uses the type-token pattern I cited. – trashgod Jun 04 '17 at 12:52
  • Sorry, my previous comment was incomplete. **3**: It's the purpose of `Callback`. Did you mean something else? --- **4**: I couldn't get a thing. My point is: is that MVC compliant to declare a `draw` view dependent method in middle of the model layer? --- **5**: So they... Can be? Or can't? In the example you gave, the controller is injected with the view so the controller can call `addObserver(this)` ( well, something like this ) by itself. --- **PS**: What is _type-token pattern_? –  Jun 04 '17 at 12:57
  • I'm not sure about the rest, but _type-token_ would include your usage above and `JTable::setDefaultRenderer`, seen [here](https://stackoverflow.com/a/14770029/230513); calling a _pattern_ may be unwarranted. – trashgod Jun 04 '17 at 17:38