Hintergrund in Java GUI?

2 Antworten

Du kannst deine Komponenten in einer eigenen Panel-Klasse einbinden, die auf ihrer Fläche ein Bild zeichnet.

Ich zeige dazu eine angepasste Fassung deines Codes. Es gibt da eh einige Punkte, die verändert werden sollten.

Die Panel-Klasse sieht zuerst einmal so aus:

class ImagePanel extends JPanel {
  private Image backgroundImage;

  public ImagePanel(Image backgroundImage, LayoutManager layout) {
    super(layout);
    this.backgroundImage = backgroundImage;
  }

  @Override
  public Dimension getPreferredSize() {
    return new Dimension(backgroundImage.getWidth(this), backgroundImage.getHeight(this));
  }

  @Override
  protected void paintComponent(Graphics graphics) {
    super.paintComponent(graphics);
    graphics.drawImage(backgroundImage, 0, 0, getWidth(), getHeight(), null);
  }
}

Die Klasse für das Fenster kann so aussehen:

class GameWindow extends JFrame {
  private JButton startButton;
  private JButton howToPlayButton;
  private JButton copyrightButton;

  public GameWindow() {
    super("Games by Abie D. Tate");
    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

    final int frameWidth = 746;
    final int frameHeight = 562;

    var container = new JPanel();
    var verticalLayout = new GridLayout(3, 1);
    verticalLayout.setVgap(30);
    container.setLayout(verticalLayout);
    container.setOpaque(false);

    startButton = new JButton("Spiel starten");
    startButton.addActionListener(event -> onStartGameClick(event));
    startButton.setPreferredSize(new Dimension(176, 48));
    container.add(startButton);

    howToPlayButton = new JButton("How to play");
    howToPlayButton.addActionListener(event -> onHowToPlayClick(event));
    howToPlayButton.setPreferredSize(new Dimension(176, 48));
    container.add(howToPlayButton);

    copyrightButton = new JButton("Lizenzen");
    copyrightButton.addActionListener(event -> onCopyrightClick(event));
    copyrightButton.setPreferredSize(new Dimension(176, 48));
    container.add(copyrightButton);

    ImagePanel content = new ImagePanel(new ImageIcon("background.jpg").getImage(), new GridBagLayout());
    content.setBorder(new EmptyBorder(20, 20, 20, 20));
    content.add(container);
    add(content);

    setSize(frameWidth, frameHeight);
    setLocationRelativeTo(null);
    setVisible(true);
  }

  public void onStartGameClick(ActionEvent event) {
  }

  public void onHowToPlayClick(ActionEvent event) {
  }

  public void onCopyrightClick(ActionEvent event) {
    if (event.getSource() == copyrightButton) {
      CopyrightFrame copyrightFrame = new CopyrightFrame();
    }
  }
}

Die main-Methode habe ich an dieser Stelle einmal in eine eigene Klasse ausgelagert.

public class Main {
  public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> new GameWindow());
  }
} 

1) Zur Benennung deiner Elemente:

Alle Klassennamen sollten mit einem Großbuchstaben beginnen, um sich von Variablennamen deutlich abzuheben.

Negativbeispiel:

frame frame = new frame();
frame.setDefaultLookAndFeelDecorated(true);

Es ist beim Lesen nicht mehr unterscheidbar, ob auf ein Objekt oder eine Klasse zugegriffen wird. Das die Methode in Zeile 2 statisch ist, ist nicht erkennbar.

Von einem Mix aus snake_stil, Abkürzungen (cp) und ungarisch-angehauchter Notation (bCopyright1) würde ich ebenfalls absehen. Gib den Elementen einfach klare Namen und orientiere dich an den üblichen Java-Konventionen (z.B. camelCase-Schreibweise). So gestaltet sich dein Programm auf jeden Fall besser lesbar.

2) Brauchst du solche Kommentare für Anfang/Ende wirklich? Felder unterscheiden sich doch deutlich von Methoden, vor allem wenn man deren Definition in den Konstruktor verlegt.

3) Nutze immer Swing-Komponenten, wo möglich, denn die können auch gänzlich von den Swing-Features profitieren. Die AWT-Komponente Button wurde in Swing von der JButton-Klasse abgelöst.

4) Ungenutzte Felder (wie die für das Label oder die Menüitems) können raus.

5) Der einzelne Aufruf des Basiskonstruktors ist nicht notwendig. Er würde nur benötigt werden, wenn der Basiskonstruktor Argumente fordert (siehe unteres Beispiel) oder du einen parameterisierten Konstruktor aufrufen möchtest.

Beispiel:

class Animal {
  public Animal(String name) {
  }
}

class Lion {
  public Lion() {
    super("Lion");
  }
}

Der Basiskonstruktor muss aufgerufen werden, da er ein Argument fordert.

Fall 2 lässt sich bei deinem Code anwenden. Der setTitle-Aufruf kann raus und stattdessen direkt an die Basis weitergegeben werden.

super("Games by Abie D. Tate");

6) Die Variablen für die Fenstergröße habe ich als konstant definert. Das ist nicht zwingend notwendig, passt aber für diesen Fall.

7) Die aufwendige Berechnung der Startkoordinaten für das Fenster ist nicht notwendig. Wenn du die setLocationRelativeTo-Methode aufrufst und ihr null übergibst, richtet sich das Fenster am Bildschirm aus und wird standardmäßig zentriert.

8) Der Aufruf von setUndecorated ist unnötig. Das Fenster hat initial immer einen Rahmen.

9) Wenn du die Skalierung des Fensters deaktivierst, kannst du zwar ein fixes Layout erzwingen, doch für Nutzer mit anderer Bildschirmauflösung kann die Anwendung schnell unbedienbar werden. So erstellst du also eine nutzerunfreundliche Oberfläche.

10) Ein Kernkonzept von Swing ist die Anordnung von Komponenten durch Layout Manager. Sie sorgen auch dafür, dass die Größen von Komponenten dynamisch, richtig berechnet werden. Sie auszuschalten:

cp.setLayout(null);

ist in der Regel eine schlechte Idee (bzw. bad practice), die auch zu den häufigsten Problemen führt (z.B. der falschen Berechnung von Komponentengrößen).

Beschäftige dich also entweder mit Layout Managern oder nutze ein Swing-Buildertool (z.B. WindowBuilder in Eclipse oder den GUI Designer von IntelliJ), wenn dir die Entwicklung mit den Layout Managern als zu schwierig erscheint.

Die setBounds-Methode solltest du grundsätzlich nicht benötigen. Wenn du explizite Größen vorgeben möchtest, nutze setPreferredSize.

Ich habe im obigen Fall ein Panel mit einem GridLayout verwendet, um die Buttons vertikal zu positionieren. Dieses Panel wird wiederum in ein Panel eingebettet, welches das GridBagLayout nutzt, um das innere Panel zu zentrieren.

11) Den Aufruf der Event Handler-Methoden habe ich mittels Lambda-Expression verkürzt.

Aus

addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent evt) {
    onStartGameClick(evt);
  }
});

wird

addActionListener(event -> onStartGameClick(event));

12) Spätestens wenn Action Listener ins Spiel kommen, die die grafische Oberfläche ändern können sollen, ist es eine gute Idee, den Anwendungsteil dafür auf den Event Dispatch Thread zu verschieben. Ich habe das bei meinem Code bereits über die invokeLater-Methode angestoßen.

13) Das Laden des Bildes ist bei mir noch recht einfach gehalten. Ich gehe einfach davon aus, dass das Bild im selben Ordner liegt, wie die class-Dateien. Wenn du für dein Programm jedoch später einmal eine Releaseversion (JAR o.ä.) bauen möchtest, wäre es besser, das Bild als Ressource zu laden.

Hilfreiche Beiträge dazu findest du hier:

import java.awt.*;

import java.awt.event.*;

import javax.swing.*;

import javax.swing.event.*;

public class frame extends JFrame {

 private Button bSpielstarten1 = new Button();

 private Button bHowtoplay1 = new Button();

 private Button bCopyright1 = new Button();

 private JMenu jMenuBar1_File = new JMenu("Spiel");

 private JMenu jMenuBar1_File_New = new JMenu("Neues Spiel");

 private JLabel Label1 = new JLabel();

 public frame() {

  super();

  setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);

  int frameWidth = 746;

  int frameHeight = 562;

  setSize(frameWidth, frameHeight);

  Dimension d = Toolkit.getDefaultToolkit().getScreenSize();

  int x = (d.width - getSize().width) / 2;

  int y = (d.height - getSize().height) / 2;

  setLocation(x, y);

  setTitle("Games by Abie D. Tate");

  setResizable(false);

  Container cp = getContentPane();

  cp.setLayout(null);

  setUndecorated(false);

   

  // Hintergrundbild setzen

  ImageIcon backgroundImg = new ImageIcon("GameBackground.jpg");

  Image scaledImg = backgroundImg.getImage().getScaledInstance(frameWidth, frameHeight, Image.SCALE_SMOOTH);

  JLabel backgroundLabel = new JLabel(new ImageIcon(scaledImg));

  backgroundLabel.setBounds(0, 0, frameWidth, frameHeight);

  cp.add(backgroundLabel);

  backgroundLabel.setVisible(true);

   

  // Anfang Komponenten

  bSpielstarten1.setBounds(288, 104, 176, 48);

  bSpielstarten1.setLabel("Spiel starten");

  bSpielstarten1.addActionListener(new ActionListener() {

   public void actionPerformed(ActionEvent evt) {

    bSpielstarten1_ActionPerformed(evt);

   }

  });

  cp.add(bSpielstarten1);

  bHowtoplay1.setBounds(285, 224, 176, 48);

  bHowtoplay1.setLabel("How to play");

  bHowtoplay1.addActionListener(new ActionListener() {

   public void actionPerformed(ActionEvent evt) {

    bHowtoplay1_ActionPerformed(evt);

   }

  });

  cp.add(bHowtoplay1);

  bCopyright1.setBounds(288, 352, 176, 48);

  bCopyright1.setLabel("Lizenzen");

  bCopyright1.addActionListener(new ActionListener() {

   public void actionPerformed(ActionEvent evt) {

    bCopyright1_ActionPerformed(evt);

   }

  });

  cp.add(bCopyright1);

  // Ende Komponenten

  setVisible(true);

 }

 public static void main(String[] args) {

  new frame();

 }

 // Anfang Methoden

 public void bSpielstarten1_ActionPerformed(ActionEvent evt) {

  // TODO hier Quelltext einfügen

 }

 public void bHowtoplay1_ActionPerformed(ActionEvent evt) {

  // TODO hier Quelltext einfügen

 }

 //Nachdem der Button 'Copyright' gedrueckt wurde, wird die Klasse 'CopyrightFrame' geoeffnet in einem neuen Fenster

 public void bCopyright1_ActionPerformed(ActionEvent evt) {

  if (evt.getSource() == bCopyright1) {

   CopyrightFrame copyrightFrame = new CopyrightFrame();

  }

 }

}

AbeeDgoethe 
Fragesteller
 19.05.2023, 10:23

Danke sehr, es hat funktioniert! Aber jetzt sind die Buttons nicht mehr zu erkennen. Was muss da geändert/ergänzt werden? :)

0