Introduction
Java has matured over the years. It has been a decade and a half since its inception, and the language is still going strong and is keen on exploring newer technological paradigms and parlance. Since the coinage of the famed phrase "Write Once, Run Anywhere" (affectionately abbreviated to WORA), the language has broken all traditional programming barriers. Till date, there is no mainstream programming language that can compile code that runs everywhere. Who knew that a simple reinvention of C++ would go on to become the mother of all modern programming languages? In fact, all .NET programming languages owe their existence to the wonderful language that is Java.
Programming in Java
Although this article is written for extreme beginners in Java, it still requires the knowledge of the fundamentals of the language and a little understanding of how the Java Swing framework works. Swing, basically, is a windowing framework that helps developers write code to create Windows and desktop applications. Throughout this article, I will try my best to explain some of the design principles behind Swing as well.
Building custom UI components in Swing
Although the Swing framework has pretty much every UI component available at the developer's disposal, i.e., buttons, labels, text fields, etc., there are still things that one would want to create themselves. It turns out that creating such custom components in Swing is extremely easy to do. And, once you start working your way around the initial confusion, you'll begin enjoying the experience. One of the main concerns that I had before writing this article was probably the fact that there was no decent article available on CodeProject that explained how to create a custom component in Swing. So, here goes.
Every custom component built using the Swing framework has to follow some very important guidelines. The first of which is inheriting the javax.swing.JComponent
class. Following is how you would go about creating a custom component:
Listing 1.1:
import javax.swing.JComponent;
public class FancyButton extends JComponent
{
public FancyButton()
{
}
}
In a nutshell, the above code creates a custom component named FancyButton
, which identifies itself as a component just by inheriting the javax.swing.JComponent
class. You would not be able to run the code, however. The reasons being:
- you do not have a main method;
- you do not have a window to display your custom component on.
Let's do that now.
Listing 1.2:
import javax.swing.JComponent;
import javax.swing.JFrame;
public class FancyButton extends JComponent
{
public FancyButton()
{
}
public static void main(String[] args)
{
JFrame frame = new JFrame("Test Frame");
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(
JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
We have now set the stage for our experiments to begin. In the above code, we create a javax.swing.JFrame
, which is basically Java's version of a desktop window with a title and buttons for minimize, restore/maximize, and close. Here we provide the title of the window as "Test Frame", appropriate enough for this tutorial. But, note one important thing: we haven't yet constructed our custom component, nor have we placed it on the frame yet. Let's do that now.
Listing 1.3:
import javax.swing.JComponent;
import javax.swing.JFrame;
public class FancyButton extends JComponent
{
public FancyButton()
{
}
public static void main(String[] args)
{
JFrame frame = new JFrame("Test Frame");
frame.setSize(400, 400);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
FancyButton button = new FancyButton();
frame.getContentPane().add(button);
frame.setVisible(true);
}
}
You'd surely be disappointed if you run the code now. That's because you'd see the same old JFrame
as in the previous example and nothing much would have had changed. A reason for that would be that your component is not being painted on the screen. Let's fix this now. Plus, you ought to note the growing imports at the beginning of the program code. In the next update to our code, we'll use dynamic imports with the use of *
(asterisk) so that all classes that fall under the java.awt
and javax.swing
packages get imported without being explicitly imported into the program code. We will also truncate the main(String[])
method for the sake of clarity, but you should include that in your program from the previous example.
Listing 1.4:
import java.awt.*;
import javax.swing.*;
public FancyButton extends JComponent
{
public FancyButton()
{
}
@Override
public void paintComponent(Graphics graphics)
{
graphics.setColor(Color.red);
graphics.fillRect(0, 0, this.getWidth(), this.getHeight());
}
public static void main(String[] args)
{
}
}
This time when you'll run the code, you'll note that the whole window frame would turn into a red colored surface (see the screenshot below). Everything that turns red on the frame is the area bounds for our newly painted component. Painting on a component is enabled by overriding the paintComponent(Graphics)
method in the JComponent
class. Notice the explicit use of the @Override
annotation before the method declaration. This ensures that the method is properly overridden in the sub-class FancyButton
.
Figure 1.1: Screenshot for Listing 1.4
Because our component is spread across the entirety of the window frame, we witness the complete window frame as a block of red.
Within the body of the paintComponent(Graphics)
method, we use the acquired java.awt.Graphics
objects to paint. The graphics.setColor(Color)
method sets the color buffer to red, and in the next line, the graphics
object is used again to create a rectangle at point [0, 0] on the window frame with the width and height of the component. This is achieved using the graphics.fillRect(int x, int y, int width, int height)
method. Now, since we placed the component right in the center of the window frame, it takes up all the space on the frame, giving the illusion of the window frame turning red throughout.
Understanding the basics of the Graphics class
Now, before we start exploring the process of painting components, we need to learn a bit more about the java.awt.Graphics
class. The Graphics
class presents us with various methods to paint different shapes, line strokes and fonts on any component or container, thus enabling us to completely design a component from scratch. Hence, by overriding the paintComponent(Graphics)
method on any component, we get the possibilities of designing an aesthetically appealing design for our components. Some of the methods this class has to offer are as follows:
Shape fill methods
fillRect(Rectangle rectangle)
fillRect(int x, int y, int width, int height)
fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
fillOval(int x, int y, int width, int height)
fillPolygon(Polygon polygon)
fillPolygon(int[] xPoints, int[] yPoints, int nPoints)
Shape stroke/draw methods
drawRect(Rectangle rectangle)
drawRect(int x, int y, int width, int height)
drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
drawOval(int x, int y, int width, int height)
drawPolygon(Polygon polygon)
drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
drawLine(int x1, int y1, int x2, int y2)
Spoiled with choice, we may choose any of these methods to paint pretty shapes to define the design and structure of our component. The difference between the shape fill and shape stroke methods is very simple. Shape fill methods fill the area of a certain shape with the color buffer (the color we set with the setColor(Color)
method of the Graphics
class). While the shape stroke/draw methods only draw a bounding edge (a border) to the shape. We will find use to almost all these methods over the course of further parts to this tutorial. For now, we would need to define what kind of component we'd like to create.
Since, for the sake of this tutorial, we already named our component FancyButton
, you would already have guessed it would be something to do with being a button. But, before we go any further, we need to define what we really want our button to do, or at least how we would want our button to look like. I call this process the Component Design Checklist.
Handy tip # 1: Build yourself a Component Design Checklist
It might sound like a mundane task, but when it comes down to it, there's no better way than to keep tabs on your progress. The best place to start would be to create a simple checklist of do's and dont's. This simple checklist would help us prioritize what needs to be done and what we should not care about.
Figure 1.2: A simple Component Design Checklist
An initial attempt at creating a Component Design Checklist. This helps confine my creativity to a simple list of do's and dont's.
Following the checklist I created to confine myself, I made the following amends to Listing 1.4:
Listing 1.5:
import java.awt.*;
import javax.swing.*;
public FancyButton extends JComponent
{
public FancyButton()
{
}
@Override
public void paintComponent(Graphics graphics)
{
graphics.setColor(Color.gray);
graphics.fillRoundRect(2, 2, this.getWidth() - 4,
this.getHeight() - 4, 30, 30);
}
}
The amends include changing the color of the component from flashing red to a decent gray so it's easier on the eyes. Instead of fillRect(...)
, I used a fillRoundRect(...)
with arcWidth
and arcHeight
as 30px. This simple effect gives the button a more pleasing and rounded look. Plus, I managed to add a 2px border around the component by changing the values within the fillRoundRect(...)
method. This would help me to later visualize the border settings that I mentioned in my checklist. If you look at the screenshot below, you'll immediately notice the difference.
Figure 1.3: Screenshot for Listing 1.5
This screenshot displays the amends made to the initial code after implementing a few of the actions in my Component Design Checklist. But soon afterwards, you begin to see some new and immediate problems emerge. One such problem is the dithering of the rounded corners. Apparently, this happens because, by default, Java 2D painting/drawing routines do not render anti-aliased output. That should be easy to fix. More than that, this problem goes directly onto my checklist as: Enable anti-alias rendering.
In revisions to follow, we will look at how we solve these individual problems to create an aesthetically pleasing custom UI component for Swing. Stay tuned for more.
History