Zusätzliche Fenster mit Rastergitter in JFrame einfügen


#21

Danke für die Links!

Meine Idee ist, das ich einfach auf einen der Buttons klicke und anschließend auf ein weiteres Rastergitter auf dem JFrame.
In diesem Rastergitter habe ich viele JPanel.
Sobald ich eines der JPanel anklicke, soll das Objekt auf diesem JPanel platziert werden.
Das zu platzierende Objekt wird durch das anklicken des Buttons festgelegt.

Das heißt ich muss beim klicken auf einen Button, ein Objekt erzeugen.

Nur wie kann ich es mit der Maus angreifen und am JPanel ablegen?

Mit einem GlassPane kann odch nichts am JPanel ablegen.

MfG


#22

Doch die GlassPane solltest du nur verwenden, um das Objekt zu zeichnen, (aber nur falls das mit dem Cursor [How to change cursor icon] nicht funktioniert.) Was passiert wenn du mit der Maus wo hinklickst hat nicht direkt etwas mit der GlassPane zu tun.

Prinzipiell kannst du auch JavaFX nehmen. Das ist die modernere und daher auch zu bevorzugende UI technologie von Java. Solltest du aber aus einem Grund bereits auf Swing festgelegt sein so funktioniert das natürlich auch.

Aber vl fängst du einfach einmal mit Schritt 1 an. Füge ActionListerner zu deinen Buttons hinzu diese sollten in dann in einer eigenen ModelKlasse setzen welches Objekt gerade ausgewählt wurde.

Wenn das funktioniert, dann kannst du Schritt 2 machen: füge MouseListener zu deinen Panels im Rastergitter hinzu und wenn diese einen Mouse-click registrieren, dann sollen sie in der Model-klasse nachsehen, welches Objekt ausgewählt ist und dieses Objekt dann zeichnen

Schritt 3 setze dich mit dem Cursor oder der GlassPane (siehe Hyperlinks oben) auseinander und finde die für dich passende Lösung den Cursor zu zeichnen.

Du darfst dir das Problem nicht so vorstellen, dass das Objekt tatsächlich am Mouse Cursor hängt, sondern das ist nur eine Repräsentation für den späteren User.


#23

Super!

Ich werde das gleich heute mal programmieren.

Meld mich dann.

MfG


#24

Wie kann ich nachsehen welches Objekt ausgewählt wurde, damit ich es dann auf dem JPanel gezeichnet werden soll.

MfG


#25

In deinem Code. Im Program. Im Ernst: Die Fragen sind vieeel zu vage (und hängen viel zu sehr von dem Code ab, den wir nicht kennen) als dass man sie vernünftig beantworten könnte.

Grob: Wenn der Button geklickt wird, muss man sich irgendwo merken, welches Objekt gerade bewegt wird. Wenn man in das Gitter klickt, muss dieses Objekt dort hingelegt werden.

Ich hab’ hier mal schnell was hingepfuscht, aber das ist natürlich nur eine Skizze.

package bytewelt;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;


public class LevelEditor extends JPanel
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        LevelEditor levelEditor = new LevelEditor();
        f.getContentPane().add(levelEditor);
        
        f.setSize(400,400);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
    
    private Object currentObject;
    private LevelEditorButtonPanel levelEditorButtonPanel;
    private LevelEditorGridPanel levelEditorGridPanel;
    
    public LevelEditor()
    {
        super(new BorderLayout());
        levelEditorButtonPanel = new LevelEditorButtonPanel(this);
        levelEditorGridPanel = new LevelEditorGridPanel(this, 4, 4);
        add(levelEditorButtonPanel, BorderLayout.WEST);
        add(levelEditorGridPanel, BorderLayout.CENTER);
    }
    
    private Cursor createDummyCustomCursor(Object object) 
    {
        BufferedImage image = new BufferedImage(
            40, 40, BufferedImage.TYPE_INT_ARGB);
        Graphics g = image.createGraphics();
        g.setColor(Color.GRAY);
        g.fillRect(0, 0, 40, 40);
        g.setColor(Color.BLACK);
        g.drawString(String.valueOf(object), 10, 20);
        g.dispose();
        return Toolkit.getDefaultToolkit().createCustomCursor(
            image, new Point(0,0), "dummy");
    }    
    
    public void setCurrentObject(Object currentObject)
    {
        this.currentObject = currentObject;
        if (currentObject != null)
        {
            setCursor(createDummyCustomCursor(currentObject));
        }
        else
        {
            setCursor(null);
        }
    }
    
    public Object getCurrentObject()
    {
        return currentObject;
    }

}

// Das Panel mit den Buttons für die Items
class LevelEditorButtonPanel extends JPanel 
{
    public LevelEditorButtonPanel(LevelEditor levelEditor)
    {
        super(new GridLayout(0,1));
        
        for (int i=0; i<3; i++)
        {
            JButton b = new JButton(String.valueOf(i));
            
            // Hier ist das "item" einfach ein String
            Object currentObject = String.valueOf(i);
            
            // Wenn der Button geklickt wird, wird das objekt
            // als das "aktuelle Objekt" an den editor übergeben
            b.addActionListener(e -> {
                levelEditor.setCurrentObject(currentObject);
            });
            
            add(b);
        }
    }
}

// Eine Zelle im JPanel-Gitter
class LevelEditorCell extends JPanel 
{
    private Object object;
    
    LevelEditorCell()
    {
        setBorder(BorderFactory.createLineBorder(Color.BLACK));
    }
    
    // Hier kann man das Objekt setzen, das gezeichnet werden soll
    void setObject(Object object) 
    {
        this.object = object;
        repaint();
    }
    
    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        if (object != null)
        {
            g.drawString(String.valueOf(object), 10, 20);
        }
    }
}

// Das panel mit dem Gitter
class LevelEditorGridPanel extends JPanel 
{
    public LevelEditorGridPanel(LevelEditor levelEditor, int rows, int cols)
    {
        super(new GridLayout(rows, cols));
        for (int r=0; r<rows; r++)
        {
            for (int c=0; c<cols; c++)
            {
                LevelEditorCell cell = new LevelEditorCell();
                
                // Wenn auf die Zelle geklickt wird, dann wird vom
                // Editor das "aktuelle Objekt" abgeholt und in
                // die Zelle gelegt
                cell.addMouseListener(new MouseAdapter()
                {
                    @Override
                    public void mouseClicked(MouseEvent e)
                    {
                        Object currentObject = levelEditor.getCurrentObject();
                        cell.setObject(currentObject);
                        levelEditor.setCurrentObject(null);
                    }
                });
                add(cell);
            }
        }
    }
}

Aber ich war auch neugierig: Wenn man für den Cursor ein “großes” Bild verwendet (hier 40x40) dann wird es (zumindest auf Win8.1) auf 32x32 runterskaliert :neutral_face:


#26

Danke fürs testen. hab ich noch nicht gewusst, bin mir aber sicher, dass das irgendwo spezifiziert ist. Falls das wür den TO ein Problem ist, kann er noch immer auf die Lösung mit der GlassPane umschwenken, die ist aber etwas komplizierter.

LG
Amunra


#27

Ich werde das ganze jetzt mal so machen wie Ihr das gesagt habt, danke!
Ist für mich auch ein ganz neues Thema.

Eine Frage noch:

wenn ich die Buttons auf dem JPanel erstelle und den ActionListener folgendermaßen hinzufüge:

button2 = new JButton("C"+j);
button2.setBackground(Color.YELLOW);
button2.addActionListener(this);
rasterpanel.add(button2);

Reagiert nur der letzte Button im JPanel auf einen klick, bei den ganzen anderen passiert nichts.

Daraufhin dachte ich mir ich muss die Buttons anhand eines Arrays erstellen:

button1array = new JButton**[j];
button1array**[j].setBackground(Color.BLUE);
button1array**[j].addActionListener(this);
rasterpanel.add(button1array**[j]);

Nur leider wird hier nichts mehr gezeichnet.

Beide codes werden in einer Methode die ich mit der actionPerformed-Methode aufrufe ausgeführt.

Natürlich war die Methode immer nur mit einem der beiden Codes bestückt.
Für den ersten code wurde nur eine For-Schleife verwendet für den zweiten natürlich zwei verschachtelte.

Nur beim zweiten Code passiert überhaupt nichts. Die Buttons werden nicht einmal auf dem JPanel gezeichnet??

Was mache ich falsch, wie kann ich sonst die ActionListener hinzufügen?

Beim ersten Code wird scheinbar nur für den letzten Button ein Code hinzugefügt.
Sonst würden ja alle Buttons bei einem klick was in der Konsole ausgeben.

??


#28

Poste mal mehr code. Wo die Buttons erstellt werden, und ggf. die actionPerformed-Methode


#29

Hier die if-Anweisung in der actionPerformed:

else if(event.getSource() == this.button){ // Hier wird das JPanel mit den Buttons aufgerufen
         DieMethode(); // Bereits hier sollten schon die Buttons am JPanel erscheinen tun sie aber nicht.
}

else if(event.getSource() == this.arraybuttons**[j]){  // Hier werden die Buttons abgefragt, da fehlen noch die Schleifen.
		System.out.println("Test");
}

#30

Im ersten Fall gibt es offenbar nur einen button. Bei sowas wie

class Gui {
    private JButton button;
    for (int i=0; i<2; i++) {
        button = new JButton();
        button.addActionListener(this);
    }
}

wird bei jedem Schleifendurchlauf der button-Variablen ein neuer Wert (d.h. ein neuer Button) zugewiesen - und am Ende ist es eben der letzte button, d.h. dass event.getSource() == this.button nur für den letzten button jemals true sein kann.

Das zweite sollte eigentlich gehen, ist aber nicht besonders elegant.

Ganz allgemein empfehle ich, “Top-Level-Klassen” (also die “Hauptklasse” eines Programmes) praktisch nie ActionListener oder andere willkürliche interfaces implementieren zu lassen. Erstens, weil diese Abfragen mit event.getSource() krampfig sind, zweitens, weil sowas einfach nicht “öffentlich sichtbar” sein sollte, und drittens, weil man es sich dann meistens sparen kann, die Buttons in Fields zu legen (d.h. du brauchst das button2 und button1array dann vermutlich nicht).

Gerade, wenn man nur ActionListener an buttons hängen will, bieten sich anonyme innere Klassen bzw. mit Java 8 eben Lambdas an. (Sofern man nicht ganz “professionell” mit Actions arbeitet ;-)). Diese Listener können/sollten dann meistens nur eine Funktion aufrufen, die eben genau besagt, was bei einem Buttonklick gemacht werden soll.

class Gui {
    for (int i=0; i<2; i++) {
        JButton button = new JButton();
        int index = i;
        button.addActionListener(e -> buttonPressed(index));
    }
}
private void buttonPressed(int index) { 
    // Wird aufgerufen, wenn der button gepresst wurde. 
    /// Der index ist entsprechend 0 oder 1
}

#31

Also in dieser inneren Klasse füge ich den ActionListener den Buttons hinzu:

class Gui {
  for (int i=0; i<2; i++) {
      JButton button = new JButton();
      int index = i;
      button.addActionListener(e -> buttonPressed(index));
  }

}

In dieser Methode sage ich dann, was passieren soll wenn einer der Buttons gedrückt wurde:

private void buttonPressed(int index) { 
   // Wird aufgerufen, wenn der button gepresst wurde. 
   // Der index ist entsprechend 0 oder 1

}

Habe ich das so richtig verstanden?


#32

Das sollte nicht heißen, dass du eine innere Klasse namens “Gui” erzeugen solltest. Das sollte nur andeuten, dass das irgendwo in deinem Gui passiert - vermutlich im Konstruktor von einer Gui-Klasse (oder einer Methode) - dort, wo die Buttons erstellt werden sollen, kann die Schleife hin.


#33

Das war mir schon klar.:slight_smile:

  1. Das heißt, ich füge den ActionListener der inneren Klasse hinzu und nicht der Top-Level?
  2. “e” ist das event, was hat der Pfeil “->” für eine bedeutung?

MfG


#34

Was… hat das mit einer inner class zu tun?

Der -> ist eine lambda expression. Das

button.addActionListener(e -> buttonPressed(index));

ist gleichbedeutend mit

button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        buttonPressed(index);
    }
});

aber eben offensichtlich kürzer…


#35

Meine Variante mit dem Array funktioniert!

Ich habe nur vergessen, das Array zu befüllen mit Buttons: array** = new JButton();
Oben habe ich es nur erzeugt, aber nicht mit Buttons befüllt.
Jetzt passt alles.

Danke an euch!

MfG


#36

Ich les schon noch mit.

Du hast das falsch flektiert! Es müsste ein lambda expression heißen.

lambda expression -> Lambda-Expression -> Lambda-Ausdruck -> Ausdruck -> ein Ausdruck. :eyes:


#37

Das ist natürlich das wichtige. Im Nachhinein hatte ich eher überlegt, ob ich die Aussage so stehen lassen sollte, weil der Pfeil selbst ja keine Lambda Expression ist, sondern nur ein Teil davon - aber für Details kann man in https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.27 nachlesen.


#38

Jeder weiß doch (jetzt), was gemeint ist.

new leitet eine anonyme innere Klasse ein, wäre genauso problematisch. Weil dazu ja noch mehr.

Ich wollte auch nicht unangemessen kritisieren, sondern das Ganze etwas auflockern. Falls dennoch zu sehr zu schnell zu weit vom Thema ab -> wieder entfernen. :frowning: