[Swing] Où décider de l'image à afficher ?

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Je développe une application Java Swing — désolé hein, j’ai pas eu le choix — avec une structure MVC : le modèle et la vue sont séparés, tandis que le contrôleur… Pour l’instant, la couche contrôleur se rapporte aux classes MouseListener anonymes…

L’application utilise un canevas ( une extension de JPanel avec un paintComponent maison ) pour afficher les éléments graphiques ; chaque entité modèle est représentée par une image.

Ma question est simple : où et comment faire le choix de l’image — celui-ci dépend du type de l’entité considérée, et peut varier selon les valeurs de ses propriétés, par exemple : un personnage qui grossit visuellement quand la valeur de son poids augmente ?

Je suppose qu’une flopée de if (... instanceof ... && ... == ...) dans le paintComponent me vaudrait une radiation de l’ordre des programmeurs qui ont une âme…

Cordialement, Novax.


PS : Il me semble que pour charger une image the right way en Swing, on utilise getClass().getResource("..."), ce qui m’intéresse ici touche plus à la manière et l’endroit auxquels on fait le choix, dans le but de ne pas poutrer sa belle structure MVC de bon matin…

PPS : Si vous avez une meilleure idée pour le titre du sujet, je suis également preneur.


Solution

  • Solution 1 : implémenter une interface Drawable dans les classes modèle ;
  • Solution 2 : faire implémenter cette interface à des classes suivant le pattern Adapter.

o_O : ça n’est pas le plus important, mais il est bon de remarquer que les classes * Adapter ne pointent pas sur les bonnes classes modèle ( Ant et Dog sont inversées ) sur le diagramme… Ce défaut ne sera vraisemblablement jamais corrigé.

Note : les solutions présentées n’ont pas encore été testées !

+0 -0

Bonjour,

Conceptuellement parlant, la bonne façon de faire ce que tu veux faire est d’utiliser les interfaces.

J’explique :

  • Tu crées une interface Displayable qui possède une méthode dont la signature sera public void display(Graphics g)
  • Toutes tes entités modèles qui représentent des images, tu leur fait implémenter une classe Displayable
  • Dans ton canevas, au lieu de manipuler des entités non génériques, tu manipulera uniquement des Displayable et donc dans ton la redéfinition de paintComponent tu n’auras qu’à appliquer la méthode display(g) pour afficher l’élément voulu.

Voilà pour le principe.

Et ça ne casse pas la structure MVC ? Je veux dire : placer du code relatif à la vue dans les classes modèle

Si tes entités sont directement des modèles, tu peux aussi passer par une classe intermédiaire pour séparer le modèle de la vue. En gros quelque chose comme ça.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Entite1 {
   Type attribut1;
   Type attribut2;

   public void methode1() {
   }
}

class Entite1Ctrl implement Displayable{
   Entite1 objet;

   public display(Graphics g) {
      // affichage de l'entité
   }
}

Est-ce que le fait d’utiliser une interface atténue ce manquement à la séparation des logiques ?

Les interfaces te permettent de ranger les choses au bon endroit (grâce à la généricité), car sinon, comme tu l’auras remarqué, ta classe paintComponent risquera d’être bourrée de if

Je viens de penser à un truc encore plus farfelu : utiliser le principe des Value Converters de WPF.

Un portage simple en Java donnerait :

1
2
3
public interface ValueConverter<TSource, TTarget> {
   public TTarget convert(TSource object);
}

Et dans la vue :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class Canvas extends JPanel {
   private ValueConverter<JeanLEntite, Icon> converter;

   public Canvas() {
      super();

      converter = (e) -> {
         // ...
      };
   }

   @Override
   public void paintComponent(Graphics gr) {
      for (JeanLEntite e : Anything.getEntities()) {
         drawIcon(e.getX(), e.getY(), converter.convert(e)); // Utilisation ici !
      }
   }
}

Qui est choqué ?


EDIT : Dans le cas d’une collection hétérogène, ça oblige quand même à faire des tests instanceof… Alors que dans la première solution, on peut s’appuyer sur le polymorphisme.

Est-ce que la solution Adapter a le même problème ?

+0 -0

Qui est choqué ?

Je suis pas certain d’avoir bien compris le concept, mais le problème de faire ce que tu fais dans paintComponent là, c’est que si la façon de dessiner est très différente selon les entités, tu surchargera ta méthode. Et crois moi, cette méthode a besoin d’être la moins conditionnelle possible.

Est-ce que la solution Adapter a le même problème ?

Justement, cette solution permet de se reposer sur le polymorphisme et donc de mieux répartir ton code. Tu n’auras jamais de problème d’instancof.

Comment tu ferais l’adapter sur une collection d’entités hétérogène ?

Je ne suis pas sur d’avoir compris ta question. Mais imaginons que tu aies 4 entités. A, B, C, D. Qui se traduisent ainsi :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class A {

}

class B {

}

class C {

}

class D {

}

Pour gagner en souplesse tu passe par des adaptateurs de composition. ce qui donnerait

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class componentA implements Displayable{
   A elt;
   public componentA(A elt) {
      this.elt = elt;
   }
   public display(Graphics g) {
      // affichage de l'entité de type A
   }
}

class componentB implements Displayable{
   B elt;
   public componentB(B elt) {
      this.elt = elt;
   }
   public display(Graphics g) {
      // affichage de l'entité de type B
   }
}

class componentC implements Displayable{
   C elt;
   public componentC(C elt) {
      this.elt = elt;
   }
   public display(Graphics g) {
      // affichage de l'entité de type C
   }
}

class componentD implements Displayable{
   D elt;
   public componentD(D elt) {
      this.elt = elt;
   }
   public display(Graphics g) {
      // affichage de l'entité de type D
   }
}

`

Ce qui devrait te donner un canevas dans ce gout là :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Windows extends JPanel {
   List<Displayable> entities = new ArrayList();
   int currentPosition = 0;

   public Windows() {
      entities.add(new componentA(...));
      entities.add(new componentA(...));
      entities.add(new componentB(...));
      entities.add(new componentD(...));

   }

   /* J'affiche l’élément suivant */
   public void switchNext() {
      currentPosition = (currentPosition + 1) % entities.size();
   }

   /* J'affiche l’élément précédent */
   public void switchPrevious() {
      currentPosition = (currentPosition - 1) % entities.size();
   }

   // les reste des bidules

   public void paintComponent(Graphics g) {
      entities.get(currentPosition).display();
      super.paintComponent(g);
   }

}

Bon, je crois que je suis allé un peu loin là, mais c’était pour te donner une idée générale du truc (plus simple finalement a expliquer avec du code)

Je vois très bien ce que tu veux faire avec tes classes component*.

Je vais reformuler ma question sur les collections hétérogènes :

Considérant les classes modèle suivantes :

1
2
3
4
abstract class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}
class Ant extends Animal {}

Comment convertir une List<Animal> en une List<Displayable> ? — Tel que, si j’ai tout compris :

1
2
3
4
interface Displayable {}
class CatDisplayable implements Displayable /* Using Cat */ {}
class DogDisplayable implements Displayable /* Using Dog */ {}
class AntDisplayable implements Displayable /* Using Ant */ {}
+0 -0

Comment convertir une List<Animal> en une List<Displayable> ?

La conversion ne peut se faire. Mais c’est normal, car logiquement dans ton ihm, tu ne devrais plus travailler qu’avec une List<Displayable>. C’est difficile de voir ce que tu essayes de faire si tu n’as pas de code sous la main.

Mais dans ton canevas, tu devrais travailler avec une liste de Displyable directement. J’ignore ou tu initialises tes entités, mais à ce moment là, tu devrais lui créer un *Displayable pour gérer son affichage.

Et comment j’effectue des opération "business" sur une liste de Displayable ?

Novax

En reprenant ton exemple ci-dessus on travaille sur la base de la classe abstraite :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
abstract class Animal {
   monOperationMetier1();
   monOperationMetier2();
   monOperationMetier3();
}

interface Displayable {
  Animal getInstance(); // méthode qui te renvoi ton instance d'animal
}

class Cat extends Animal {} // il va redéfinir les opérations métiers et getInstance()
class Dog extends Animal {} // il va redéfinir les opérations métiers et getInstance()
class Ant extends Animal {} // il va redéfinir les opérations métiers et getInstance()

Et donc par la suite tu fais :

1
2
3
4
Displayable monChat;
monChat.getInstance().monOperationMetier1();
monChat.getInstance().monOperationMetier2();
monChat.getInstance().monOperationMetier3();
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte