Java GUI für Game erstellen: Platzierungsprobleme?

1 Antwort

Vom Beitragsersteller als hilfreich ausgezeichnet

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.


lkeyz 
Beitragsersteller
 04.04.2020, 19:28

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

0
lkeyz 
Beitragsersteller
 04.04.2020, 19:55
@lkeyz

Und wie bekomm ich den alten Abstand zwischen den Kreisen wieder?

0
regex9  04.04.2020, 19:57
@lkeyz

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.

0
regex9  04.04.2020, 19:59
@lkeyz

Du addierst auf den x-Wert beim Zeichnen deinen Abstand von 10 auf den Kreisdurchmesser mit auf.

0
lkeyz 
Beitragsersteller
 04.04.2020, 20:27
@regex9

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.

0
regex9  04.04.2020, 21:01
@lkeyz

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, ...).

0
lkeyz 
Beitragsersteller
 04.04.2020, 21:37
@regex9

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.

0
regex9  04.04.2020, 21:49
@lkeyz

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, ...).

0
lkeyz 
Beitragsersteller
 05.04.2020, 09:12
@regex9

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?

0
regex9  05.04.2020, 12:34
@lkeyz

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.

1