Often times, new programmers will immediately jump into making graphical applications using their new-found skills, without getting a firm grasp of how to design something that doesn't require refactoring literally any time new functionality is required. Obviously, we can't just collectively tell all new programmers to just be patient; that will never work. So hopefully this article can serve as a guide to put you on the right track to developing 2D applications (games, most likely) using Java and Swing.
I will be referencing snippets of code that come from this repository, so check there if you want to read through all the code yourself.
Since this is quite a large guide, you can skip to a particular section if you want:
At first, it's super easy to make a new JFrame and start drawing on it, and you can add action listeners everywhere, whenever they're needed. If you ever plan on expanding beyond a simple 20-line drawing method however, you should organize your code using the Model-View-Controller pattern. For example, in my Before Asteroids sample game, I have split up my code into a few different packages.
control - Contains all my game's listeners for player interaction.
model - Contains all the code that makes up the core game model.
view - Contains all the code for rendering the game model.
physics - Some special physics code that is needed when updating the game model.
util - Extra utilities for things like file IO.
In most cases, it's especially useful to define a single class that represents your entire game's state; a so-called Game Model. This model would contain all the entities in your game that exist in the world, for example. Or in the case of something like a simple tic-tac-toe game, it would contain data about the status of each of the nine squares on the board.
Keep this design pattern in mind, and working on your project as it grows larger hopefully won't become as much of a headache anymore.
Setting up a Swing GUI
Making a Swing GUI usually consists of two basic components: the JFrame that acts as your application's window, and a JPanel that is used for actually drawing your model. Usually we start by defining our own frame to extend from the base JFrame, and set its properties in a constructor.
Inside the frame, there's a single GamePanel that we give a reference to the game model. This panel is responsible for rendering the model to the screen, and usually we can just define it as a child class of Swing's JPanel.
The Game Loop
First of all, this guide won't go too in-depth about the details of game loops in general, but focuses specifically on their implementation with Java/Swing. For more information, you can read this excellent article on the subject.
In Swing GUIs, your application is by-default multi-threaded, since Swing has its own thread for managing the user interface. In order to run our game and not interfere with Swing's own GUI processing, we should have our game update logic run in its own thread. The thread should periodically update the model, according to some defined frequency, and it should also trigger the rendering of frames of your game, once again according to some defined frequency. In the example below, we update physics 60 times per second, and render the model also 60 times per second, but these values can easily be changed due to how I've set up the math.
We can add our game updater thread to the end of our frame's constructor, so that when we initialize the game frame, the game loop begins.
Listeners and Control
Most often, you'll want something to happen when the player presses a key or clicks the mouse. To do this, you should create your own class which extends from KeyAdapter or MouseAdapter, depending on what you want to do.
You can also directly implement KeyListener and MouseListener at the same time, but this isn't as clean, and you have to implement every method declared in the interfaces, even if you aren't using them.
Let's take a look at a common case: doing something when a key is pressed. To do so, we override the KeyAdapter's keyPressed method like so:
Handling mouse events works almost the same way, but instead we check what button of the mouse was pressed.