Swing Wie zeichne/animiere ich im MVC Muster?

failed78

Mitglied
Hallo,


Wir sollen einen Ocean mit folgenden Eigenschaften programmieren:


-Ocean soll ein Hintergrundbild haben.
-Dem Ocean sollen Objekte (Fische etc) hinzugefügt werden können.
-Objekte sollen gelöscht werden können.
-Start und Stop button zum starten bzw stoppen der Animation.


Das ganze ist so halbwegs nach dem MVC Muster designed ( die Kommunikation zwischen Model und View findet
ausschließlich über den Controller statt).
Das Model enthält eine LinkedList mit Objekten (diese haben x,y Koordinaten)
Im View befindet sich die GUI.
Der Controller initialisiert beides und enthält die ActionListener.


Das hinzufügen bzw löschen funktioniert soweit jedoch bin ich mir nicht sicher wie ich das ganze nun Animiere.
Ich habe mir verschiede Tutorials durchgelesen die mich aber eher verwirrt haben und für mein Verständnis teilweise sogar widersprüchlich waren.


Hier erst mal der Code:




Controller:


Java:
package infpp.oceanlife;


import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;


public class OceanLifeController {
    private OceanLifeModel oceanLifeModel;
    private OceanLifeView oceanLifeView;


    public OceanLifeController() {
        oceanLifeModel = new OceanLifeModel();
        oceanLifeView = new OceanLifeView(oceanLifeModel.ocean.getAvailableOceanObjects());
        addListeners();
    }




    public void start() {
        //TODO test löschen
        oceanLifeView.oceanLifeConsoleView.printOceanData(oceanLifeModel.ocean.toString());
        for (int i = 0; i < 1; i++) {
            oceanLifeModel.ocean.move();
            upDateDeleteBox();
            oceanLifeView.oceanLifeConsoleView.printOceanData(oceanLifeModel.ocean.toString());
        }


    }


    private void upDateDeleteBox(){
        oceanLifeView.oceanLifeGUI.updateDeleteComboBox(oceanLifeModel.ocean.getOceanObjectsAsStringList());
    }








    private void addListeners() {
        oceanLifeView.oceanLifeGUI.setAddListener(new AddListener());
        oceanLifeView.oceanLifeGUI.setDeleteListener(new DeleteListener());
        oceanLifeView.oceanLifeGUI.setSaveListener(new SaveListener());
        oceanLifeView.oceanLifeGUI.setLoadListener(new LoadListener());
        oceanLifeView.oceanLifeGUI.setStartListener(new StartListener());
        oceanLifeView.oceanLifeGUI.setStopListener(new StopListener());
        oceanLifeView.oceanLifeGUI.setStepListener(new StepListener());
    }


    class AddListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            String object = oceanLifeView.oceanLifeGUI.getAddCbValue();
            if (oceanLifeView.oceanLifeGUI.getXFieldValue().equals("") || oceanLifeView.oceanLifeGUI.getXFieldValue().equals("")) {
                oceanLifeModel.ocean.addOceanObject(object);
            } else {
                int x = Integer.parseInt(oceanLifeView.oceanLifeGUI.getXFieldValue());
                int y = Integer.parseInt(oceanLifeView.oceanLifeGUI.getYFieldValue());


                oceanLifeModel.ocean.addOceanObject(object, x, y);
            }
            oceanLifeView.oceanLifeConsoleView.printOceanData(oceanLifeModel.ocean.toString());


            upDateDeleteBox();


        }


    }


    class DeleteListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            //TODO test löschen
            System.out.println(oceanLifeView.oceanLifeGUI.getDeleteIndex());


            int deleteIndex = oceanLifeView.oceanLifeGUI.getDeleteIndex();
            if (deleteIndex >= 0){
                oceanLifeModel.ocean.deleteObjectAtIndex(deleteIndex);


                //TODO test löschen
                upDateDeleteBox();
                System.out.println("delete");
            }else{
             
                System.out.println("nothing selected");
            }
        }
    }


    class SaveListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("save");
        }
    }


    class LoadListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("load");
        }
    }


    class StartListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("start");
        }
    }


    class StopListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("stop");
        }
    }


    class StepListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {


            oceanLifeModel.ocean.move();


            //TODO test löschen ?
            upDateDeleteBox();


            oceanLifeView.oceanLifeConsoleView.printOceanData(oceanLifeModel.ocean.toString());






        }
    }




}


View:


Java:
package infpp.oceanlife;


import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
import java.util.LinkedList;




public class OceanLifeGUI extends JFrame {






    // topLeft
    JComboBox addComboBox;
    JLabel x = new JLabel("X:");
    JLabel y = new JLabel("Y:");
    JTextField xField = new JTextField(4);
    JTextField yField = new JTextField(4);
    JButton addObject = new JButton("ADD");


    // topRight
    JComboBox deleteComboBox = new JComboBox();
    JButton delete = new JButton("DELETE");


    // bottomLeft
    JButton save = new JButton("SAVE");
    JButton load = new JButton("LOAD");
    JButton quit = new JButton("QUIT");


    // bottomRight
    JButton start = new JButton("START");
    JButton stop = new JButton("STOP");
    JButton step = new JButton("STEP");




    OceanLifeGUI(String[] availableOceanObjects) {




        setTitle("OceanLife");


        addComboBox = new JComboBox(availableOceanObjects);


        //Icons
        addObject.setIcon(new ImageIcon("src/resources/add.png"));
        delete.setIcon(new ImageIcon("src/resources/delete.png"));
        save.setIcon(new ImageIcon("src/resources/save.png"));
        load.setIcon(new ImageIcon("src/resources/load.png"));
        quit.setIcon(new ImageIcon("src/resources/quit.png"));
        start.setIcon(new ImageIcon("src/resources/start.png"));
        stop.setIcon(new ImageIcon("src/resources/stop.png"));
        step.setIcon(new ImageIcon("src/resources/step.png"));


        //panels
        JPanel top = new JPanel(); // contains topLeft,topRight
        JPanel bottom = new JPanel(); // contains bottomLeft, bottomRight
        JPanel topleft = new JPanel(); // contains add Object
        JPanel topRight = new JPanel(); // contains delete Object
        JPanel bottomLeft = new JPanel(); // contains save, load
        JPanel bottomRight = new JPanel(); // contains start, stop, step
        PaintingSheet paintingSheet = new PaintingSheet(); // draw area


        topleft.add(addComboBox);
        topleft.add(x);
        topleft.add(xField);
        topleft.add(y);
        topleft.add(yField);
        topleft.add(addObject);


        topRight.add(deleteComboBox);
        topRight.add(delete);


        bottomLeft.add(save);
        bottomLeft.add(load);
        bottomLeft.add(step);


        bottomRight.add(start);
        bottomRight.add(stop);
        bottomRight.add(step);


        top.setLayout(new BorderLayout());
        top.add(topleft, BorderLayout.WEST);
        top.add(topRight, BorderLayout.EAST);


        bottom.setLayout(new BorderLayout());
        bottom.add(bottomLeft, BorderLayout.WEST);
        bottom.add(bottomRight, BorderLayout.EAST);




        this.add(top, BorderLayout.NORTH);
        this.add(bottom, BorderLayout.SOUTH);
        this.add(paintingSheet, BorderLayout.CENTER);












        // ...
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(1920, 1180);
        this.setLocationRelativeTo(null);
        this.setResizable(false);
        this.setVisible(true);








    }




    public class PaintingSheet extends JPanel {


        private Image background;


        public PaintingSheet() {
            this.background = Toolkit.getDefaultToolkit().createImage("src/resources/background.jpg");
        }




        public void paintComponent(Graphics g) {
            super.paintComponent(g);




            g.drawImage(this.background, 0,0, this);




        }




    }








    public void updateDeleteComboBox(LinkedList<String> objectStrings){
        deleteComboBox.removeAllItems();
        for (String object : objectStrings ){
            deleteComboBox.addItem(object);
        }
    }






    public String getAddCbValue() {
        return String.valueOf(addComboBox.getSelectedItem());
    }


    public String getXFieldValue() {
        return xField.getText();
    }


    public String getYFieldValue() {
        return yField.getText();
    }


    public int getDeleteIndex() {
        return deleteComboBox.getSelectedIndex();
    }


    public void setAddListener(ActionListener l) {
        addObject.addActionListener(l);
    }


    public void setDeleteListener(ActionListener l) {
        delete.addActionListener(l);
    }


    public void setSaveListener(ActionListener l) {
        save.addActionListener(l);
    }


    public void setLoadListener(ActionListener l) {
        load.addActionListener(l);
    }


    public void setStartListener(ActionListener l) {
        start.addActionListener(l);
    }


    public void setStopListener(ActionListener l) {
        stop.addActionListener(l);
    }


    public void setStepListener(ActionListener l) {
        step.addActionListener(l);
    }


}


In paintComponent() möchte ich jetzt meine Objekte zeichnen.
Im Controller dann eine Spielschleife oder einen Thread ? erzeugen. Also :


Model.updateGame();
OceanLifeGUI.setOceanObjects(Model.getOceanObjects());
OceanLifeGUI.PaintingSheet.repaint();
Thread.sleep(x);




1.) kann man das so machen oder gibt es bessere Lösungen?
2.) nutze ich im Controller einen Thread oder eine Schleife?
3.) wie sorge ich dafür dass nur die Objekte neue gezeichnet werden nicht aber der Hintergrund? (Dieser bewegt sich ja nicht und muss dann ja nicht neu gezeichnet werden.




Ich danke schon mal im Voraus
 

Harry Kane

Top Contributor
Was ist „oceanLifeModel.ocean.getAvailableOceanObjects()”? Sieht aus wie der Zugriff auf eine public Variable --> unschön. Und was sind die „OceanObjects“? Welcher Objeckttyp verbirgt sich dahinter?
Der Zugriff auf oceanLifeView.oceanLifeGUI erfolgt offenbar nach demselben Schema mit einer public Variablen.

Die Logik der ganzen Listener ist in den inneren Klassen von OceanLifeController aufgehängt. Die Gui Elemente, an denen die Listener hängen, sind in der OceanLifeGUI definiert. In den Methodenrümpfen der actionPerformed-Methoden musst du allerdings auf weitere Gui Elemente aus oceanLifeView.oceanLifeGUI zugreifen. Ich finde das umständlich. Wenn ein ActionListener Referenzen auf Objekte braucht, um seine Arbeit zu tun, kann man ihm die Referenzen auch im Konstruktor übergeben. Beispielsweise könntest du einem DeleteListener eine Referenz auf die deleteComboBox und den ocean (was auch immer das sein mag) übergeben. Dann könnte sich der Listener in der actionPerformed direkt den selected index der deleteComboBox besorgen, aus dem ocean das Object löschen und dann den Inhalt der deleteComboBox über ocean.getOceanObjectsAsStringList() aktualisieren.
So hast du zwar formal eine Trennung zwischen Controller und View, allerdings immer noch zahlreiche Kreuz- und Querbeziehungen.
 

failed78

Mitglied
Hey

erst mal vielen Dank für deine Hilfe.

Was ist „oceanLifeModel.ocean.getAvailableOceanObjects()”? Sieht aus wie der Zugriff auf eine public Variable --> unschön.
Stimmt danke werd ich ändern.

Und was sind die „OceanObjects“? Welcher Objeckttyp verbirgt sich dahinter?
Die Klassen Fisch, Bubble, Stone etc erben von OceanObject und haben x, y Werte , Name , toSting(); etc

Die Logik der ganzen Listener ist in den inneren Klassen von OceanLifeController aufgehängt. Die Gui Elemente, an denen die Listener hängen, sind in der OceanLifeGUI definiert. In den Methodenrümpfen der actionPerformed-Methoden musst du allerdings auf weitere Gui Elemente aus oceanLifeView.oceanLifeGUI zugreifen. Ich finde das umständlich. Wenn ein ActionListener Referenzen auf Objekte braucht, um seine Arbeit zu tun, kann man ihm die Referenzen auch im Konstruktor übergeben. Beispielsweise könntest du einem DeleteListener eine Referenz auf die deleteComboBox und den ocean (was auch immer das sein mag)
Ocean hat Höhe,Breite,die LinkedList mit OceanObjects, addOceanObject(), deleteOceanObject.
Im Ocean sollen sich die Oceanobjekte befinden/bewegen

Dann könnte sich der Listener in der actionPerformed direkt den selected index der deleteComboBox besorgen, aus dem ocean das Object löschen und dann den Inhalt der deleteComboBox über ocean.getOceanObjectsAsStringList() aktualisieren.
So hast du zwar formal eine Trennung zwischen Controller und View, allerdings immer noch zahlreiche Kreuz- und Querbeziehungen.
Klingt gut danke.

Mein eigentliches Problem löst das aber leider nicht.
Wie animiere ich die Oceanobjekte ?

Einmal lese ich dass ich im Controller kein Thread nutzen kann weil Thread.sleep(x) den EDT blockt und repaint() verhindert.
Und dann
Animation is a complex subject in game programming. Java games are expected to run on multiple operating systems with different hardware specifications. Threads give the most accurate timing solutions.
Java games animation

Wenn ich nun in OceanLifeGUI einen swingTimer nutzen würde bräuchte ich für

ocean.updateOceanobjects()
repaint();

eine Referenzen auf den Ocean in der OceanLifeGUI was ja dem MVC Muster wiederspricht.
 
Zuletzt bearbeitet:

Harry Kane

Top Contributor
Ein javax.swing.Timer informiert ActionListener immer dann, wenn sein Zeitintervall abgelaufen ist.
Du kannst entweder ocean ActionListener implementieren lassen, so dass er sich inregelmässigen Intervallen selbst "updated", oder du kannst dafür einen separaten ActionListener schreiben, der eine REferenz auf den ocean bekommt. In beiden Fällen hat die OceanLifeGUI keine dauerhafte Referenz auf den ocean.
Und sobald sich der ocean geändert hat, sollte er einen z. B. OceanChangeEvent auslösen, der von geeigneten Listenern wie z. B. der OceanLifeGUI gefangen wird und dann geeignete Aktionen auslöst, wie z. B. das Neuzeichnen einer JComponent.
 

Iustinianus

Neues Mitglied
Hallo,

ich arbeite gerade exakt an dem gleichen Projekt und bin genau so weit wie du. Also hänge ich genau an der gleichen Stelle fest. Auch ich konnte nichts finden, was mich wirklich voran bringt. Es muss doch irgendwo eine verständliche Erklärung dafür geben, wie man ein paar Objekte mit simplen Bewegungen in einem Frame animieren kann!? Langsam bin ich am verzweifeln. :-(
 

Foxei

Bekanntes Mitglied
Hallo an die beiden mit dem Problem,
ich habe nicht ganz verstanden wo das Problem liegt ich habe aber heraus gelesen das es um eine Frame geht auf der man Objekte via Code animieren können soll.

Ich habe ein kurzes Projekt in dieser Art geschrieben. Sollte das dem entsprechen was Ihr sucht dann sagt bescheidet ich werde dann ausführlich erklären wie das funktioniert.

Gruß Simon

Download
 

failed78

Mitglied
Du kannst entweder ocean ActionListener implementieren lassen, so dass er sich inregelmässigen Intervallen selbst "updated", oder du kannst dafür einen separaten ActionListener schreiben, der eine REferenz auf den ocean bekommt.
Bin mir nicht sicher ob ich dich richtig verstanden habe. Ich habe jetzt folgendes versucht.

Java:
public class PaintingSheet extends JPanel  {        private Image background;
        private Timer timer;




        public PaintingSheet() {
            timer = new Timer(25, this);
            this.background = Toolkit.getDefaultToolkit().createImage("src/resources/background.jpg");
        }


        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawImage(this.background, 0, 0, this);
        }
    }

und würde analog zu den anderen Listenern im Controller eine Klasse schreiben deren actionPerformed Methode
ocean.updateModel()
gui.setOceanObjects()
paintingSheet.repaint()
aufruft

Beim initialisieren des Timers müsste ich das this ja durch die entsprechende Klasse im Controller ersetzen,weiß aber nicht genau wie ich das machen soll.
Könntest du mir ein konkretes auf mein Programm bezogenes Codebeispiel geben?
 

failed78

Mitglied
Hallo an die beiden mit dem Problem,
ich habe nicht ganz verstanden wo das Problem liegt ich habe aber heraus gelesen das es um eine Frame geht auf der man Objekte via Code animieren können soll.

Ich habe ein kurzes Projekt in dieser Art geschrieben. Sollte das dem entsprechen was Ihr sucht dann sagt bescheidet ich werde dann ausführlich erklären wie das funktioniert.

Gruß Simon

Download

Entspricht dem ja.
Wäre toll wenn du das erklären könntest.
 

Foxei

Bekanntes Mitglied
Hallo an die beiden mit dem Problem,
es ging bei dem Problem ja darum wie ich es realisiere alle AnimationsObjekte Zeichnen zu lassen und Trotzdem nach dem MVC System vorzugehen und das ganze dann auch noch zu bewegen. Ich möchte gleich vorweg nehmen ruckelfrei geht es mit der Timer Methode nicht aber das ist hier glaube ich noch nicht relevant.
Mein Projekt besteht aus 8 Klassen:
Animation.java
Die Animation ist eine abstrakte klasse von der aus alle Objekte abgeleitet werden können die gezeichnet werden sollen. Sie nutzt wie vorgegeben eine X/Y Koordinaten und eine Größe sowie das zu zeichnende Bild der Animation.
Java:
package seaanimation.object;

import java.awt.image.BufferedImage;

/**
 *
 * @author Simon Schäfer <simon.schaefer@teamkoeln.com>
 */
public abstract class Animation {
    private int x;
    private int y;
    private int width;
    private int height;
    private BufferedImage img;
    
    public abstract void launch();
    
    public abstract void act();

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public BufferedImage getImg() {
        return img;
    }

    public void setImg(BufferedImage img) {
        this.img = img;
    }
}

Renderer.java
Im Renderer werden alle AnimationsObjekte die sich in der Liste befinden auf den Componenten gerendert.
Java:
package seaanimation.view;

import java.awt.Graphics;
import java.util.ArrayList;
import javax.swing.JPanel;
import seaanimation.object.Animation;

/**
 *
 * @author Simon Schäfer <simon.schaefer@teamkoeln.com>
 */
public class Renderer extends JPanel{

    private ArrayList<Animation> list;
    
    @Override
    protected void paintComponent(Graphics g) {
        if(list!=null){
            list.stream().forEach((animation) -> {
                g.drawImage(animation.getImg(), animation.getX(), animation.getY(), animation.getWidth(), animation.getHeight(),this);
            });
        }
       
    }

    public void setList(ArrayList<Animation> list) {
        this.list = list;
    }
}

View.java
Das View ist nur eine Simple JFrame die den Renderer beherbergte.
Java:
package seaanimation.view;

import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JToggleButton;

/**
 *
 * @author Simon Schäfer <simon.schaefer@teamkoeln.com>
 */
public class View {
    private final JFrame frame;
    private final JToggleButton btn;
    private final Renderer renderer;

    private final Dimension CANVASSIZE=new Dimension(1024,768);
    public View() {
        this.frame = new JFrame();
        this.btn = new JToggleButton();
        this.renderer = new Renderer();
    }
    
    public void launch(){
        frame.setLayout(new BorderLayout());
        frame.setTitle("Animation");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        renderer.setPreferredSize(CANVASSIZE);
        renderer.setDoubleBuffered(true);
        frame.add(renderer,BorderLayout.CENTER);
        
        btn.setText("Toggle Animation");
        frame.add(btn,BorderLayout.SOUTH);
        
        frame.pack();
    }

    public JFrame getFrame() {
        return frame;
    }

    public JToggleButton getBtn() {
        return btn;
    }

    public Renderer getCanvas() {
        return renderer;
    }
}

ViewController.java

Im Vioew Controller Starten und Stopen wir den Timer und geben dem Renderer seine Liste die er Rendern soll.
Der Action Listener sogt nur dafür das die Act Methode in jedem AnimationsObjekt ausgeführt wird bevor der Renderer neu Zeichnet.
Java:
package seaanimation.view;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JToggleButton;
import javax.swing.Timer;
import seaanimation.object.Animation;

/**
 *
 * @author Simon Schäfer <simon.schaefer@teamkoeln.com>
 */
public class ViewController implements ActionListener {

    private final View view;
    private JFrame frame;
    private JToggleButton btn;
    private Renderer renderer;
    private final Timer timer;

    private final ArrayList<Animation> list;

    public ViewController() {
        view = new View();
        list = new ArrayList<>();
        timer = new Timer(30, this);
    }

    public void launch() {
        view.launch();
        frame = view.getFrame();
        btn = view.getBtn();
        renderer = view.getCanvas();
        renderer.setList(list);

        btn.addActionListener((ActionEvent e) -> {
            if (timer.isRunning()) {
                timer.stop();
            } else {
                timer.start();
            }
        });
    }

    public void show() {
        frame.setVisible(true);
    }

    public void addAnimation(Animation ani) {
        ani.launch();
        list.add(ani);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        list.stream().forEach((animation) -> {
            animation.act();
        });
        renderer.repaint();
    }

}

Main.java
Die Main weist den Controller an das View aufzubauen und übergibt die AnimationsObjekte an die Liste der Kontrollers die Gleichzeitig die Liste des Renderers ist.
Java:
package seaanimation;

import seaanimation.object.Background;
import seaanimation.object.Fish0;
import seaanimation.view.ViewController;

/**
 *
 * @author Simon Schäfer <simon.schaefer@teamkoeln.com>
 */
public class Main {

    private final ViewController controller;
    
    public Main() {
        controller=new ViewController();
    }
    
    public void launch(){
        Resources.launch();
        controller.launch();
        
        controller.show();
        
        controller.addAnimation(new Background());
        controller.addAnimation(new Fish0(100, 100));
    }
    
    public static void main(String[] args){
        Main main =new Main();
        main.launch();
    }
}
Gruß Simon
p.s. alle weiterren Klassen sind nicht relevant aber im Source enthalten.
Source
 

Ähnliche Java Themen

Neue Themen


Oben