Java GUI für Game erstellen: Platzierungsprobleme?
Hallo,
ich soll eine grafische Oberfläche für ein Spiel kreieren. Es soll aus einem JLabel (Überschrift), drei JButtons (für Start, eins setzen, zwei setzen) und eine Zeichnung (Graphics g) bestehen. Jetzt geht es erst einmal nur darum, dass richtig zu platzieren. Ich habe schon jegliche Layouts ausprobiert, aber ich bekomme es einfach nicht hin.
So soll es aussehen:
So bekomme ich es höchstmöglich hin:
Das ist der Code:
public class MyUI {
public static void main(String[] args) {
JFrame fenster = new JFrame("NIM-Spiel");
MyJPanel grafiken = new MyJPanel();
JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
JLabel ueberschrift = new JLabel("Variante des NIM-Spiels");+
ueberschrift.setFont(new Font("ARIAL", Font.BOLD, 25));
ueberschrift.setVerticalAlignment(JLabel.TOP);
ueberschrift.setHorizontalAlignment(JLabel.CENTER);
panel.add(ueberschrift);
JButton startButton = new JButton("START");
gbc.gridx = 0;
gbc.gridy = 1;
gbc.fill = GridBagConstraints.HORIZONTAL;
panel.add(startButton, gbc);
JButton button1 = new JButton("Eins setzen");
gbc.gridx = 0;
gbc.gridy = 2;
panel.add(button1, gbc);
JButton button2 = new JButton("Zwei setzen");
gbc.gridx = 0;
gbc.gridy = 3;
panel.add(button2, gbc);
fenster.setSize(1000, 570);
fenster.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
fenster.setLocationRelativeTo(null);
fenster.setResizable(false);
fenster.setContentPane(grafiken);
fenster.add(panel);
fenster.setVisible(true);
}
}
Mit dem GridBagLayout hatte ich die besten Ergebnisse. Aber perfekt ist das nicht, mit dem GridLayout ging es auch nicht. Das BorderLayout hat nur das JPanel richtig positioniert, dafür konnte ich das set vertical und horizontal alignment wegmachen.
Wäre es vielleicht die Lösung, ein BorderLayout zu kreieren, wo oben das JLabel platziert, unten dann die Zeichnung und in der Mitte für die Buttons ein GridLayout eingeschoben wird? Also ein Layout in einem Layout?
Das ist noch zusätzlich meine Klasse für die Zeichnung: https://pastebin.com/Z6QSAHpm
PS.: Erst durch Setzen von superpaint g wurde die Zeichnung überhaupt im Fenster erstellt. Woran liegt das?
1 Antwort
Ich denke, du möchtest es so haben:
Main.java
import java.awt.*;
import javax.swing.*;
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> initGameWindow());
}
private static JPanel getControlPanel() {
JPanel setButtonPanel = new JPanel();
setButtonPanel.setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.fill = GridBagConstraints.HORIZONTAL;
constraints.insets = new Insets(5, 5, 0, 5);
JButton startButton = new JButton("START");
constraints.ipady = 10;
constraints.gridwidth = 2;
constraints.gridx = 0;
constraints.gridy = 0;
setButtonPanel.add(startButton, constraints);
constraints.ipady = 0;
constraints.gridwidth = 1;
constraints.weightx = 0.5;
JButton setOneButton = new JButton("Eins setzen");
constraints.insets = new Insets(5, 5, 5, 2);
constraints.gridx = 0;
constraints.gridy = 1;
setButtonPanel.add(setOneButton, constraints);
JButton setTwoButton = new JButton("Zwei setzen");
constraints.insets = new Insets(5, 2, 5, 5);
constraints.gridx = 1;
constraints.gridy = 1;
setButtonPanel.add(setTwoButton, constraints);
return setButtonPanel;
}
private static void initGameWindow() {
JFrame frame = new JFrame("NIM-Spiel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout(0, 20));
JLabel headline = new JLabel("Variante des NIM-Spiels");
headline.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
headline.setFont(new Font("ARIAL", Font.BOLD, 25));
headline.setHorizontalAlignment(JLabel.CENTER);
JPanel controlPanelWrapper = new JPanel();
controlPanelWrapper.add(getControlPanel());
frame.add(headline, BorderLayout.PAGE_START);
frame.add(controlPanelWrapper);
frame.add(new GameBoard(), BorderLayout.PAGE_END);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
}
}
GameBoard.java
import java.awt.*;
import javax.swing.*;
public class GameBoard extends JPanel {
@Override
public Dimension getPreferredSize() {
return new Dimension(1000, 100);
}
@Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
graphics.setColor(new Color(225, 169, 59));
graphics.fillRect(30, 350, 925, 100);
graphics.setColor(new Color(221, 225, 224));
graphics.fillRect(35, 355, 915, 90);
graphics.setColor(Color.white);
final int circleWidth = 80;
for (int i = 0; i < 10; ++i) {
graphics.fillOval(50 + i * circleWidth, 0, circleWidth, 80);
}
}
}
Ich habe es mir einmal erlaubt, die Namen abzuändern. Mit den Abständen kann man noch etwas herumspielen.
Für die grundsätzliche Aufteilung des Layouts verwende ich das BorderLayout. Für die Buttons nutze ich das GridBagLayout und ein FlowLayout (controlPanelWrapper) sorgt dafür, dass sich die Buttons nicht aufgrund der Zeichnung mit in diese enorme Breite hinaus streckt. Den ContentPane würde ich nicht überschreiben.
Indem ich getPreferredSize für das GameBoard überschreibe, fordere ich die benötigte Größe ein (in der Höhe addiere ich noch 20, um einen Abstand nach unten zu erzeugen).
Die Kreise lassen sich mit einer Schleife einfacher zeichnen. Da würde ich auch nicht bei der y-Koordinate 359 beginnen, denn das würde erfordern, dass das Panel eine enorme Höhe im Fenster einnimmt.
(...) wäre vielleicht die Lösung (...) ein Layout in einem Layout?
Ja, das Verschachteln verschiedener Layouts (bzw. von Panels mit verschiedenen Layouts) ist meist die einfachste Lösung.
Erst durch setzen von superpaint g wurde die Zeichnung überhaupt im Fenster erstellt, an was liegt das?
Generell solltest du die paint-Methode erst einmal nie überschreiben, denn die führt implizit noch ein paar weitere Methoden aus, die für das Zeichnen der Komponente notwendig sind (bspw. werden Rahmen und Kindkomponenten gezeichnet).
Eine dieser Methoden, die sie aufruft, heißt paintComponents. Diese ist besser dafür geeignet, eigene Zeichenverfahren einzufügen. Aber auch da sollte die Basismethode zuvor aufgerufen werden. Sie zeichnet zunächst die Komponente (z.B. den Hintergrund), danach kannst du darüber malen.
Bei Methoden, die du überschreibst, solltest du außerdem die @Override-Annotation verwenden. Sie gibt dem Compiler vor, dass diese Methode eine Methode der Basisklasse überschreibt. Der Compiler wird das daraufhin prüfen und gegebenenfalls Fehler zurückgeben, falls du Tippfehler o.ä. eingebaut hast. So kannst du also Fehler schneller auffindig machen.
Du hast Recht, die sind mir entgangen. Aber schau einmal, wo sie gezeichnet werden:
graphics.fillRect(30, 350, 925, 100);
Das liegt (auf der y-Achse) weit ab vom noch sichtbaren Bereich. Setze die Position auf 0, die für die Kreise auf 100 (oder etwas höher) und die präferierte Größe für die Box muss natürlich ebenfalls nochmal angepasst werden.
Vielen Dank!!!! Sie konnten mir wirklich helfen, ich habe so viel jetzt dazugelernt, nur eine Frage habe ich wegen preferred size, wie genau funktioniert das jetzt, das JFrame wird 1000,100 groß und die Grafik wird darein geschoben oder wie setzen sich jetzt die Verhältnisse zusammen, bei getsize gibt es ja Probleme mit der Grafik, die wird dann nur noch abgeschnitten angezeigt und warum muss man ipady bei den Buttons benutzen, ich dachte dafür ist gridheight zuständig.
Ich finde auch bei vielen Tutorials im Internet werden immer nur in jedem Video einzelne Sachen besprochen, wie man einen JButton macht, ein JPanel, eine Grafik, was ein Layout ist, aber niemand zeigt wie alle zusammen in einem Fenster interagieren müssen, das finde ich echt nervig, weil ich will es mir richtig aneignen, nur kann ich über das grundlegende hinaus nicht mehr lernen.
Das Frame selbst sollte größer als 1000x100 Pixel werden. Die genaue Größe wird dynamisch berechnet. Die Methode pack sorgt dafür, dass die Elemente die Mindestgröße bekommen, die sie benötigen/präferieren. Mit der Methode setPreferredSize kann man einzelnen Komponenten eine bevorzugte Größe mitgeben (oder man überschreibt wie oben ihren Getter). Layout Manager berücksichtigen diese Angabe mal mehr, mal weniger.
Dein Panel mit der Zeichnung hatte zuvor vor allem das Problem, nicht vollständig in seiner Höhe und Breite aufgezogen worden zu sein. Daher habe ich die Zeichnungen vom Ursprung der y-Achse gestartet und explizit die benötigte Größe angefordert.
Das Feld ipady bestimmt den vertikalen Innenabstand des Buttons bzw. den vertikalen Abstand zwischen Buttontext und Buttonrand. Ich glaube, ich habe das nur spontan gesetzt, weil es etwas besser aussah. Jedenfalls wird in dem Fall eine feste Pixeleinheit genutzt, bei gridheight hingegen würdest du angeben, wie viele Zellen im Grid des Layouts belegt werden sollen (also für bspw. mehrspaltige Buttons). Ein Beispiel siehst du da ja auch: Der obere Button bekommt zwei Spalten (gridweight = 2), die unteren Buttons jeweils nur eine.
Für Swing kann ich an sich nur eine Quelle empfehlen: Die Oracle Tutorials. Sie sind visuell zwar nicht sonderlich ansprechend, behandeln dafür aber die meisten Themen, auch unter dem eigentlichen Konzept von Swing (wenn du in anderen Tutorials / Internetartikeln schaust, wirst du ja öfter auf Beispiele treffen, in denen man dagegen verstößt). Zu jeder Komponente gibt es mindestens ein vollständiges Beispiel, bei dem auch verschiedene Komponenten oft im Zusammenspiel miteinander zu sehen sind.
Aber davon ab, würde ich vielleicht nicht zu viel Zeit in Swing stecken (je nachdem, wie viel Spaß es dir macht). Ich finde, wenn du nach etwas Übung solche Aufgaben wie deine so bereits hinbekommst, kannst du dir folgend auch ein Buildertool nehmen (WindowBuilder von Eclipse / NetBeans Matisse) oder nach Tools wie dem MiG Layout Ausschau halten. JavaFX ist eine modernere Alternative zu Swing und generell muss man sagen, gibt es für andere Programmiersprachen (wie C#, C++, ...) schon lange bessere GUI-Toolkits (z.B. Windows Forms, WPF, Qt, ...).
Ok vielen, vielen Dank!!!!!! Ja das mit Java FX habe ich auch gesehen und ich hatte auch mal mit dem Scenebuilder Kontakt und Android Studio, nur wollte ich mal die Sachen mit Swing belegen, damit ich das vertiefte Verständis bekomme, wie etwas per Code belegt wird und nicht einfach Sachen in den Scene Builder zu stecken und automatisch den Code zu generieren, na ja egal, ich schau mir die nächsten Tage diese Java Tutorials an, wenn ich jetzt wirklich ein Spiel daraus mache, geht das auch problemlos über Swing bzw. wird sowas in den Tutorials auch genannt, bis jetzt hab ich nur einen Taschenrechner erstellt und das war über Java FX.
Ja, das ist auch der grundsätzlich richtige Ansatz, den ich auch gern empfehle.
Swing wurde zwar für flexible grafische Oberflächen konzipiert, aber nicht für die Entwicklung von Spielen. Hangman, Kartenspiele, vielleicht auch noch Tetris mögen gehen (wenn man es darauf anlegt, natürlich auch noch mehr), doch entsprechende Bibliotheken/Frameworks wären da deutlich vorzuziehen (libGDX, Slick2D, LWJGL, FXGL, ...).
Aber diese Frameworks kann man mit dem was ich in Swing gemacht habe problemlos kombinieren? Wenn ich eine Klasse mit einem Action Listener mache, der meinen 3 Buttons funktionen zuweist, kann ich den auch machen, wenn die Buttons im Main stehen und die Grafische Oberfläche auch ihre eigene Klasse hat, also kann man gut mit den Klassen interagieren, trotz das alles in anderen Klassen aufgeteilt ist?
Swing wird dann nicht mehr benötigt. In den Frameworks gibt es eigene UI-Elemente.
Normalerweise teilt man Funktionsbereiche auf Klassen auf. Es gibt sogar Entwurfsmuster dazu, die beschreiben, wie so etwas aussehen kann. Das MVC-Pattern teilt bspw. Oberfläche, Anwendungslogik und Vermittlungsschicht (z.B. deine Listener) in einzelne Klassen auf.
Vielen Dank für ihre Hilfe, genau so soll es aussehen, nur gibt es ein Problem mit dem Code, von der Zeichnung zeigt es mir zwar die ganzen Kreise an, aber nicht die zwei Rechtecke