Taming a Dragon With Your Mouse: Building Dragon Cursor Chase in a Single Java File

There’s something delightful about tiny, self‑contained graphics demos. No frameworks, no build systems, no node_modules folder the size of a small moon — just one .java file, javac, and a window that does something you didn’t expect from Swing.

DragonCursorChaseMinimal is exactly that: a neon dragon made of 22 glowing circles that slithers after your cursor, blinks lazily at you, and breathes fire when you click. It fits in about 150 lines. Let’s take a tour of how it works and why the tricks inside it are worth stealing for your own doodles.

The idea in one paragraph

The dragon is a chain of points. The head chases the mouse with a simple spring‑like ease. Every other body segment just follows the one in front of it at a fixed distance. Render each point as a glowing circle, rotate a stylized head on top of the first point, and sprinkle particles when the user clicks. That’s the whole trick.

The body: a follow‑the‑leader chain

The state of the dragon is two parallel arrays of coordinates:

static final int N = 22;
final double[] x = new double[N], y = new double[N];

The head (index 0) does the actual chasing — a classic exponential smoothing toward the target:

x[0] += (mouseX - x[0]) * .28;
y[0] += (mouseY - y[0]) * .28;

That .28 is the “springiness”. Lower values make a lazier, more elegant dragon; higher values make a caffeinated one.

The rest of the body is where the magic happens. Each segment is pulled toward the previous one, but clamped to a fixed distance of 19 pixels:

for (int i = 1; i < N; i++) {
    double dx = x[i] - x[i - 1], dy = y[i] - y[i - 1];
    double d = Math.max(.001, Math.hypot(dx, dy));
    x[i] = x[i - 1] + dx / d * 19;
    y[i] = y[i - 1] + dy / d * 19;
}

This is sometimes called a distance constraint or a one‑pass rope solver. It gives you smooth, snake‑like body motion for basically free. The Math.max(.001, …) guard is a tiny but important detail — it prevents a division by zero when two segments occupy the same point (which happens on the very first frame).

Getting the head to point the right way

Because we saved the head’s previous position, we can compute its heading with a single atan2:

angle = Math.atan2(y[0] - oldY, x[0] - oldX);

That angle is then used both to rotate the head graphics and to aim the fire breath. It’s a nice example of how one derived value can unify several visual effects.

Painting the body: glow for the price of one extra oval

Each segment is drawn twice — once large and translucent for the halo, once smaller and opaque for the core:

g.setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(), 55));
g.fillOval((int) x[i] - r - 5, (int) y[i] - r - 5, (r + 5) * 2, (r + 5) * 2);
g.setColor(c);
g.fillOval((int) x[i] - r, (int) y[i] - r, r * 2, r * 2);

That’s a poor‑man’s bloom effect. No shaders, no compositing tricks, just two ovals per segment. The radius shrinks along the body (5 + 15 * t) so the tail tapers naturally, and the hue drifts slightly with Color.getHSBColor(.44 + .10 * t, …) for a subtle gradient from teal to sea‑green.

The loop iterates backwards (for (int i = N - 1; i > 0; i--)), so bigger segments closer to the head end up painted on top of smaller tail segments. Painter’s algorithm at its most literal.

The head: a rotated coordinate system

Rather than doing trigonometry to place each eye, ear, and nostril, the code creates a child Graphics2D, translates it to the head position, and rotates it:

Graphics2D h = (Graphics2D) g.create();
h.translate(x[0], y[0]);
h.rotate(angle);

Now every subsequent drawing call — the snout Path2D, the triangular ears, the eyes — can be written in the dragon’s own local space, with x pointing forward. Notice how the two ears are just mirrored polygons:

h.fillPolygon(new int[]{-12, -22, 1}, new int[]{-17, -39, -20}, 3);
h.fillPolygon(new int[]{-12, -22, 1}, new int[]{ 17,  39,  20}, 3);

The two eyes get a cheap blinking animation by squishing their vertical radius with a sine wave:

double eyeH = 6 * Math.max(.12, Math.abs(Math.sin(time * .22)));

The Math.max(.12, …) keeps the eyes from ever fully closing, so the dragon looks alert rather than sleepy. And crucially, h.dispose() is called when we’re done — always dispose the graphics contexts you create(), or you’ll leak state into the parent.

Fire breath: particles with a lifetime

Clicking sets a timestamp:

public void mousePressed(MouseEvent e) {
    fireUntil = System.currentTimeMillis() + 550;
}

For 550 milliseconds after the click, each frame spawns four Flame particles, each with:

  • a position offset forward from the head by 38 pixels along angle,
  • a velocity fanned out by up to ±0.275 radians from the heading,
  • a random lifetime between 28 and 46 frames.

The particle physics is trivial but tuned:

void update() {
    x += vx;
    y += vy;
    vx *= .965;
    vy = vy * .965 + .025;
    life--;
}

Horizontal velocity decays; vertical velocity decays and gets a gentle downward tug. Result: flames shoot forward, slow down, and drift down like hot embers.

Rendering each flame is a single circle whose size, color, and alpha are all functions of remaining life:

float age = f.life / (float) f.maxLife;
int r = Math.max(2, (int) (18 * age));
Color c = Color.getHSBColor(.02f + .12f * age, 1, 1);

Young flames are bigger, more yellow, and more opaque. Old flames shrink into small, dim red pixels before vanishing. That single hue interpolation from .02 (red) to .14 (orange‑yellow) is doing a lot of aesthetic heavy lifting.

The background: one gradient to rule them all

The dark deep‑blue backdrop is drawn once per frame as a radial gradient:

g.setPaint(new RadialGradientPaint(
        new Point2D.Double(getWidth() / 2.0, getHeight() / 2.0),
        Math.max(getWidth(), getHeight()) * .7f,
        new float[]{0, 1},
        new Color[]{new Color(22, 35, 76), new Color(3, 5, 14)}));
g.fillRect(0, 0, getWidth(), getHeight());

It’s the cheapest way to make the scene feel like it has depth. The neon colors of the dragon pop against it precisely because the corners fade almost to black.

The animation loop: javax.swing.Timer at 60 fps

There’s no thread management, no game loop, no Thread.sleep in a run() method. Just:

new Timer(16, e -> update()).start();

A javax.swing.Timer fires its callback on the Event Dispatch Thread every 16 ms — roughly 60 fps. Inside update() we move the dragon, tick the particles, then call repaint(). Because everything runs on the EDT, there are no synchronization concerns between input (mouse events) and rendering. For a demo of this size, it’s the right tool.

Why this pattern is worth stealing

A few takeaways that generalize beyond dragons:

  1. Chains of points + a distance constraint are a shockingly good approximation of ropes, snakes, tentacles, and hair. One loop, no physics library.
  2. Translate + rotate a child Graphics2D whenever you’re drawing a directional object. Trying to bake rotation into every coordinate by hand is a recipe for off‑by‑one‑radian bugs.
  3. Two‑pass “halo + core” drawing gives you a convincing glow without touching any compositing APIs or BufferedImages.
  4. Particles = position + velocity + life. That’s genuinely all you need for 90% of “juice” effects.
  5. javax.swing.Timer is fine. For interactive art at 60 fps, the EDT will not let you down.

Running it

Save the file as DragonCursorChaseMinimal.java and, from the same folder:

javac DragonCursorChaseMinimal.java
java DragonCursorChaseMinimal

A 720×1280 window opens. Move your mouse. The dragon follows. Click and hold — it breathes fire in whichever direction it’s currently pointed. Let go and the flames drift, cool, and disappear.

That’s it. One file, one dragon, zero dependencies. Sometimes the best way to remember why you liked programming is to make something that has no business existing and put it on your screen for an afternoon.

The Complete Code

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

public class DragonCursorChaseMinimal extends JPanel {
    static final int N = 22;
    final double[] x = new double[N], y = new double[N];
    final List<Flame> fire = new ArrayList<>();
    final Random random = new Random();
    double mouseX = 360, mouseY = 640, angle, time;
    long fireUntil;

    DragonCursorChaseMinimal() {
        setPreferredSize(new Dimension(720, 1280));
        setBackground(new Color(4, 7, 20));
        for (int i = 0; i < N; i++) {
            x[i] = mouseX;
            y[i] = mouseY + i * 19;
        }

        MouseAdapter mouse = new MouseAdapter() {
            public void mouseMoved(MouseEvent e) {
                aim(e);
            }

            public void mouseDragged(MouseEvent e) {
                aim(e);
            }

            public void mousePressed(MouseEvent e) {
                fireUntil = System.currentTimeMillis() + 550;
            }

            void aim(MouseEvent e) {
                mouseX = e.getX();
                mouseY = e.getY();
            }
        };
        addMouseMotionListener(mouse);
        addMouseListener(mouse);
        new Timer(16, e -> update()).start();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JFrame frame = new JFrame("Dragon Cursor Chase");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setContentPane(new DragonCursorChaseMinimal());
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }

    void update() {
        double oldX = x[0], oldY = y[0];
        x[0] += (mouseX - x[0]) * .28;
        y[0] += (mouseY - y[0]) * .28;
        angle = Math.atan2(y[0] - oldY, x[0] - oldX);

        for (int i = 1; i < N; i++) {
            double dx = x[i] - x[i - 1], dy = y[i] - y[i - 1];
            double d = Math.max(.001, Math.hypot(dx, dy));
            x[i] = x[i - 1] + dx / d * 19;
            y[i] = y[i - 1] + dy / d * 19;
        }

        if (System.currentTimeMillis() < fireUntil) emitFire();
        for (Iterator<Flame> it = fire.iterator(); it.hasNext(); ) {
            Flame f = it.next();
            f.update();
            if (f.life <= 0) it.remove();
        }
        time += .05;
        repaint();
    }

    void emitFire() {
        for (int i = 0; i < 4; i++) {
            double a = angle + (random.nextDouble() - .5) * .55;
            double speed = 7 + random.nextDouble() * 6;
            fire.add(new Flame(x[0] + Math.cos(angle) * 38,
                    y[0] + Math.sin(angle) * 38,
                    Math.cos(a) * speed, Math.sin(a) * speed,
                    28 + random.nextInt(18)));
        }
    }

    protected void paintComponent(Graphics raw) {
        super.paintComponent(raw);
        Graphics2D g = (Graphics2D) raw.create();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g.setPaint(new RadialGradientPaint(
                new Point2D.Double(getWidth() / 2.0, getHeight() / 2.0),
                Math.max(getWidth(), getHeight()) * .7f,
                new float[]{0, 1},
                new Color[]{new Color(22, 35, 76), new Color(3, 5, 14)}));
        g.fillRect(0, 0, getWidth(), getHeight());

        for (int i = N - 1; i > 0; i--) {
            double t = 1 - i / (double) N;
            int r = (int) (5 + 15 * t);
            Color c = Color.getHSBColor((float) (.44 + .10 * t), .82f, .95f);
            g.setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(), 55));
            g.fillOval((int) x[i] - r - 5, (int) y[i] - r - 5, (r + 5) * 2, (r + 5) * 2);
            g.setColor(c);
            g.fillOval((int) x[i] - r, (int) y[i] - r, r * 2, r * 2);
        }

        Graphics2D h = (Graphics2D) g.create();
        h.translate(x[0], y[0]);
        h.rotate(angle);
        h.setColor(new Color(40, 255, 190, 65));
        h.fillOval(-32, -27, 68, 54);
        h.setColor(new Color(35, 210, 150));
        h.fillRoundRect(-25, -20, 56, 40, 22, 22);

        Path2D snout = new Path2D.Double();
        snout.moveTo(18, -12);
        snout.lineTo(40, 0);
        snout.lineTo(18, 12);
        snout.closePath();
        h.setColor(new Color(70, 240, 170));
        h.fill(snout);

        h.setColor(new Color(160, 255, 230));
        h.fillPolygon(new int[]{-12, -22, 1}, new int[]{-17, -39, -20}, 3);
        h.fillPolygon(new int[]{-12, -22, 1}, new int[]{17, 39, 20}, 3);

        double eyeH = 6 * Math.max(.12, Math.abs(Math.sin(time * .22)));
        h.setColor(Color.WHITE);
        h.fill(new Ellipse2D.Double(2, -14, 12, eyeH));
        h.fill(new Ellipse2D.Double(2, 8, 12, eyeH));
        h.setColor(new Color(10, 20, 25));
        h.fillOval(8, -13, 4, 4);
        h.fillOval(8, 9, 4, 4);
        h.dispose();

        for (Flame f : fire) {
            float age = f.life / (float) f.maxLife;
            int r = Math.max(2, (int) (18 * age));
            Color c = Color.getHSBColor(.02f + .12f * age, 1, 1);
            g.setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(), (int) (210 * age)));
            g.fillOval((int) f.x - r / 2, (int) f.y - r / 2, r, r);
        }

        g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, Math.max(18, getWidth() / 27)));
        g.setColor(new Color(240, 250, 255, 220));
        centre(g, "MOVE THE CURSOR", 56);
        g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, Math.max(13, getWidth() / 45)));
        g.setColor(new Color(190, 215, 235, 175));
        centre(g, "Click to breathe fire • Java Swing • 1 file", 84);
        g.dispose();
    }

    void centre(Graphics2D g, String text, int y) {
        g.drawString(text, (getWidth() - g.getFontMetrics().stringWidth(text)) / 2, y);
    }

    static class Flame {
        double x, y, vx, vy;
        int life, maxLife;

        Flame(double x, double y, double vx, double vy, int life) {
            this.x = x;
            this.y = y;
            this.vx = vx;
            this.vy = vy;
            this.life = this.maxLife = life;
        }

        void update() {
            x += vx;
            y += vy;
            vx *= .965;
            vy = vy * .965 + .025;
            life--;
        }
    }
}

How do I write a simple analog clock using Java 2D?

Here is a simple Java code for an analog clock using Java 2D features in Swing.

Please adjust the code according to your needs. This code will create a new JFrame and continually update it every second with the current time.

package org.kodejava.swing;

import javax.swing.*;
import java.awt.*;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class AnalogClock extends JPanel {

    public AnalogClock() {
        setPreferredSize(new Dimension(400, 300));
        setBackground(Color.WHITE);
        new Timer(1000, e -> repaint()).start();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        int side = Math.min(getWidth(), getHeight());
        int centerX = getWidth() / 2;
        int centerY = getHeight() / 2;

        GregorianCalendar time = new GregorianCalendar();
        int second = time.get(Calendar.SECOND);
        int minute = time.get(Calendar.MINUTE);
        int hour = time.get(Calendar.HOUR_OF_DAY);

        drawHand(g2d, side/2 - 10, second / 60.0, 0.5f, Color.RED);
        drawHand(g2d, side/2 - 20, minute / 60.0, 2.0f, Color.BLUE);
        drawHand(g2d, side/2 - 40, hour / 12.0, 4.0f, Color.BLACK);

        // Draw clock numbers and circle
        drawClockFace(g2d, centerX, centerY, side/2 - 40);
    }

    private void drawHand(Graphics2D g2d, int length, double value, float stroke, Color color) {
        double angle = Math.PI * 2 * (value - 0.25);
        int endX = (int) (getWidth() / 2 + length * Math.cos(angle));
        int endY = (int) (getHeight() / 2 + length * Math.sin(angle));

        g2d.setColor(color);
        g2d.setStroke(new BasicStroke(stroke));
        g2d.drawLine(getWidth() / 2, getHeight() / 2, endX, endY);
    }

    // Added method to draw the clock face and numbers
    private void drawClockFace(Graphics2D g2d, int centerX, int centerY, int radius) {
        g2d.setStroke(new BasicStroke(2.0f));
        g2d.setColor(Color.BLACK);
        g2d.drawOval(centerX - radius, centerY - radius, 2 * radius, 2 * radius);

        for (int i = 1; i <= 12; i++) {
            double angle = Math.PI * 2 * (i / 12.0 - 0.25);
            int dx = centerX + (int) ((radius + 20) * Math.cos(angle));
            int dy = centerY + (int) ((radius + 20) * Math.sin(angle));

            g2d.drawString(Integer.toString(i), dx, dy);
        }
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Analog Clock");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new AnalogClock());
        frame.pack();
        frame.setVisible(true);
    }
}

In this code, each “hand” of the clock is moved by calculating its angle according to the current time. The drawHand() method takes the length of the hand, the proportion of its rotation (for an hour hand, this would be the current hour divided by 12), the stroke width to draw with, and the color to draw, then calculates the end point of the hand line and draws it from the center of the clock.

The drawClockFace(), which draws a circle using the drawOval() method (because a circle is a type of oval), and adds numbers around the edge of the circle using the drawString() method. In both cases, the position of each element is calculated based on the sine and cosine of the angle that represents each hour on the clock face.

Analog Clock

Analog Clock

Note: Swing is an old but reliable technology that allows coding of GUI elements, however, it’s no longer actively developed. If you are developing a new project, consider using JavaFX as it is more modern and actively developed.

Filtering JList Component Models

Filter items in a long list are often accomplished using the JTextField component. As the user inputs into the JTextField component, the set of items shown in the list is narrowed to just those things that correspond to the input received from the user.

It is necessary to utilize two elements to implement this function of the JList component, one of which is a model that filters a set of elements based on some text. The other executes the filter method when the user enters text.

Implementing the input field is a simpler job, so let’s start with it in our review of the implementation process. The JTextField component model is a document used with a Swing set of components. It is necessary to implement the DocumentListener interface in the model in order to monitor input to a Document. Text input, updating, and deletion are tracked using three methods defined below:

  • public void insertUpdate (DocumentEvent event)
  • public void changedUpdate (DocumentEvent event)
  • public void removeUpdate (DocumentEvent event)

When the model attributes are updated, the changedUpdate() method is used to update the model. It is possible that it will not be realized. In order to avoid duplicating filtering actions across all three methods, the generic method generated in the custom model is simply called by the other two. A detailed explanation of the JTextField component, which is used for filtering in the JList component, may be found in the following section:

JTextField input = new JTextField(); 
String lastSearch = ""; 

DocumentListener listener = new DocumentListener() { 
    public void insertUpdate(DocumentEvent event) { 
        Document doc = event.getDocument(); 
        lastSearch = doc.getText(0, doc.getLength()); 
        ((FilteringModel)getModel()).filter(lastSearch); 
    } 

    public void removeUpdate(DocumentEvent event) { 
        Document doc = event.getDocument(); 
        lastSearch = doc.getText(0, doc.getLength()); 
        ((FilteringModel)getModel()).filter(lastSearch); 
    }

    public void changedUpdate(DocumentEvent event) {
    } 
}; 

input.getDocument().addDocumentListener(listener);

In order to avoid being restricted to just using the JTextField component that was generated using the JList, the installJTextField() method is used, which attaches the event listener to the component that was built using the JList in the first place. In addition, a mechanism is provided to eliminate this match. Through the usage of these methods, the user of a filtering JList may choose to use their own JTextField in place of the default one.

public void installJTextField(JTextField input) { 
    input.getDocument().addDocumentListener(listener); 
} 

public void unnstallJTextField(JTextField input) { 
    input.getDocument().removeDocumentListener(listener); 
}

After that, the filtering model is taken into consideration. This case implements the filter() function, which is invoked by methods that implement the DocumentListener interface, as seen below. To put this strategy into action, you’ll need to have two lists of objects on hand: a source list and a filtered list of items. Because you are inheriting from the AbstractListModel class, you must implement some of the methods listed below in your code:

  • Constructor
  • Method for adding items to the model is being implemented in this project.
  • getElementAt() is used to get an element.
  • getSize() is used to retrieve sizes.
  • The constructor produces two instances of the List objects. The type of objects that are stored as List elements does not matter. Therefore List objects are generated to carry items of the following types:
List<Object> list; 
List<Object> filteredList; 

public FilteringModel() { 
    list = new ArrayList<>(); 
    filteredList = new ArrayList<>(); 
}

Model elements are added by adding them to the original model and then filtering the resulting model with the previously added elements. Optimization of this approach may be achieved by using a method to filter a single element when it is added; however, in this implementation, the filter() function is invoked when an element is added, which is also used to filter the whole list. (It should be noted that the event implementation in the DocumentListener also invokes the filter() method.) As a result, even when only one item is added to the list, the whole list is filtered, with each item that matches the search parameters being added to the filtered list.

public void addElement(Object element) { 
    list.add(element); 
    filter(); 
}

The size of the returned model is the same as the size of the filtered list, but not the same as the original:

public int getSize() { 
    return filteredList.size(); 
}

Similar to the technique for obtaining the size of a model, the method for obtaining an item from a list returns elements from the filtered list rather than the original list. In order to avoid having to go through the complete list, it has been implemented as follows:

public Object getElementAt(int index) { 
    Object returnValue; 
    if (index < filteredList.size()) { 
        returnValue = filteredList.get(index); 
    } else { 
        returnValue = null;
    } 
    return returnValue; 
}

Finally, the filter() method is responsible for most of the work. Because you have no way of knowing whether the new search string will broaden or limit the set of items, the quickest and most straightforward solution is to remove the whole filtered list and replace it with items that fit your search criteria from the original list. A match may be discovered at the beginning of a line as well as at any point throughout it. An example of searching for the letter “A” is shown below. This function enables you to locate items in a string that begin with the capital letter “A” or contain the letter “A” at any point in the string.

void filter(String search) {
    filteredList.clear();
    for (Object element: list) {
        if (element.toString().contains(search)) {
            filteredList.add(element); 
        } 
    } 
    fireContentsChanged(this, 0, getSize()); 
}

It is important to note that the search in this approach is case-sensitive. You may alter the method to implement a case-insensitive search and start the search at the beginning of the string.

After you have added entries to the filtered list, you may also sort the results. This operation requires your familiarity with the model’s contents. The function toString() is currently used by search, which does not indicate that it may include elements of a suitable type that can also be sorted when it is performed.

Here is a full implementation of the JList filter element with an inner class ListModel, as seen in the accompanying code sample. This class implements the DocumentListener interface, which the text component uses to listen for new documents. Although the addition of this class may seem needless at first look, given that filtering is only done for this model, the specification of behavior in this implementation is the most accurate.

package org.kodejava.swing;

import javax.swing.AbstractListModel;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import java.util.ArrayList;
import java.util.List;

public class FilteringJList extends JList<Object> {
    private JTextField input;

    public FilteringJList() {
        setModel(new FilteringModel());
    }

    public void installJTextField(JTextField input) {
        if (input != null) {
            this.input = input;
            FilteringModel model = (FilteringModel) getModel();
            input.getDocument().addDocumentListener(model);
        }
    }

    public void uninstallJTextField(JTextField input) {
        if (input != null) {
            FilteringModel model = (FilteringModel) getModel();
            input.getDocument().removeDocumentListener(model);
            this.input = null;
        }
    }

    public void setModel(ListModel<Object> model) {
        if (!(model instanceof FilteringModel)) {
            throw new IllegalArgumentException();
        } else {
            super.setModel(model);
        }
    }

    public void addElement(Object element) {
        ((FilteringModel) getModel()).addElement(element);
    }

    private static class FilteringModel extends AbstractListModel<Object> implements DocumentListener {
        List<Object> list;
        List<Object> filteredList;
        String lastFilter = "";

        public FilteringModel() {
            list = new ArrayList<>();
            filteredList = new ArrayList<>();
        }

        public void addElement(Object element) {
            list.add(element);
            filter(lastFilter);
        }

        public int getSize() {
            return filteredList.size();
        }

        public Object getElementAt(int index) {
            Object returnValue;
            if (index < filteredList.size()) {
                returnValue = filteredList.get(index);
            } else {
                returnValue = null;
            }
            return returnValue;
        }

        void filter(String search) {
            filteredList.clear();
            for (Object element : list) {
                if (element.toString().contains(search)) {
                    filteredList.add(element);
                }
            }
            fireContentsChanged(this, 0, getSize());
        }

        public void insertUpdate(DocumentEvent event) {
            Document doc = event.getDocument();
            try {
                lastFilter = doc.getText(0, doc.getLength());
                filter(lastFilter);
            } catch (BadLocationException ble) {
                System.err.println("Bad location: " + ble);
            }
        }

        public void removeUpdate(DocumentEvent event) {
            Document doc = event.getDocument();
            try {
                lastFilter = doc.getText(0, doc.getLength());
                filter(lastFilter);
            } catch (BadLocationException ble) {
                System.err.println("Bad location: " + ble);
            }
        }

        public void changedUpdate(DocumentEvent event) {
        }
    }
}

It is now necessary to develop a test program. The following six lines will be crucial in the event. They build a JList component, attach it to the JScrollPane component, and then attach a text box to it as seen in the code:

FilteringJList list = new FilteringJList();
JScrollPane pane=new JScrollPane(list);
frame.add(pane,BorderLayout.CENTER);
JTextField text=new JTextField();list.installJTextField(text);
frame.add(text,BorderLayout.NORTH);

To the model, new components are introduced in the program’s primary body. The model shown below includes a list of Christmas gifts, the names of Santa’s reindeer, the names of London Underground lines, and the letters of the Greek alphabet.

package org.kodejava.swing;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import java.awt.BorderLayout;
import java.awt.EventQueue;

public class JListFiltersDemo {
    public static void main(String[] args) {
        Runnable runner = () -> {
            JFrame frame = new JFrame("Filtering List");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            FilteringJList list = new FilteringJList();
            JScrollPane pane = new JScrollPane(list);
            frame.add(pane, BorderLayout.CENTER);
            JTextField text = new JTextField();
            list.installJTextField(text);
            frame.add(text, BorderLayout.NORTH);
            String[] elements = {
                    "Partridge in a pear tree", "Turtle Doves", "French Hens",
                    "Calling Birds", "Golden Rings", "Geese-a-laying",
                    "Swans-a-swimming", "Maids-a-milking", "Ladies dancing",
                    "Lords-a-leaping", "Pipers piping", "Drummers drumming",
                    "Dasher", "Dancer", "Prancer", "Vixen", "Comet", "Cupid",
                    "Donner", "Blitzen", "Rudolf", "Bakerloo", "Center",
                    "Circle", "District", "East London", "Hammersmith and City",
                    "Jubilee", "Metropolitan", "Northern", "Piccadilly Royal",
                    "Victoria", "Waterloo and City", "Alpha", "Beta", "Gamma",
                    "Delta", "Epsilon", "Zeta", "Eta", "Theta", "Iota", "Kappa",
                    "Lambda", "Mu", "Nu", "Xi", "Omicron", "Pi", "Rho", "Sigma",
                    "Tau", "Upsilon", "Phi", "Chi", "Psi", "Omega"};
            for (String element : elements) {
                list.addElement(element);
            }
            frame.setSize(500, 500);
            frame.setVisible(true);
        };
        EventQueue.invokeLater(runner);
    }
}
Filtering JList Component Models Demo

Filtering JList Component Models Demo

Because this filtering strategy is based on the JList component and its accompanying JTextField component, it will operate successfully if your list’s entries are appropriately displayed when you use the function toString(). Creating a Filter interface that is provided to the model when filtering operations are performed might be useful for doing more complicated filtering tasks.

In this example, the only item that is not addressed is the process of selection. By default, when the contents of the model list change, the JList does not update the selection of the model list. Filtering may be used to either retain the chosen item or emphasize the first item in the list, depending on the desired behavior.

Even though the original JList component does not explicitly offer the functionality, there are techniques to implement filtering. Overriding the getNextMatch() function allows you to alter the default behavior if you so want.

How to define JRadioButton label position?

In this code snippet you’ll see how to define JRadioButton label position. By default, the label will be displayed on the right side of the button. In the code below you will see some examples for placing the label on the left side, at the top and the bottom of the JRadioButton.

To define the label position we use the combination of the setHorizontalTextPosition() and setVerticalTextPosition() method and specified the position using one of the available constant in SwingConstants interface.

package org.kodejava.swing;

import javax.swing.*;

public class RadioButtonLabelPosition {
    public static void main(String[] args) {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        JPanel panel = new JPanel();

        // Create JRadioButton with label on the right
        JRadioButton button1 = new JRadioButton("Button One");
        button1.setHorizontalTextPosition(SwingConstants.RIGHT);

        // Create JRadioButton with label on the left
        JRadioButton button2 = new JRadioButton("Button Two");
        button2.setHorizontalTextPosition(SwingConstants.LEFT);

        // Create JRadioButton with label at the bottom centered.
        JRadioButton button3 = new JRadioButton("Button Three");
        button3.setVerticalTextPosition(SwingConstants.BOTTOM);
        button3.setHorizontalTextPosition(SwingConstants.CENTER);

        // Create JRadioButton with label at the top centered.
        JRadioButton button4 = new JRadioButton("Button Four");
        button4.setVerticalTextPosition(SwingConstants.TOP);
        button4.setHorizontalTextPosition(SwingConstants.CENTER);

        panel.add(button1);
        panel.add(button2);
        panel.add(button3);
        panel.add(button4);

        frame.add(panel);
        frame.setSize(500, 300);
        frame.setVisible(true);
    }
}

Here is a screen capture result of the code snippet above:

JRadioButton Label Position

How do I change JFrame state programmatically?

In the following code snippet you’ll learn how to programmatically change the frame state of a JFrame component in a Swing application. The JFrame states are represented as a bitwise masks. You can minimize, maximize or make the JFrame state to normal, using the JFrame.setExtendedState() method.

You can pass the following values as the parameter to the method:

  • Frame.NORMAL
  • Frame.ICONIFIED
  • Frame.MAXIMIZED_HORIZ
  • Frame.MAXIMIZED_VERT
  • Frame.MAXIMIZED_BOTH
package org.kodejava.swing;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;

public class SwingFrameState extends JFrame {
    public SwingFrameState() throws HeadlessException {
        initUI();
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(
                () -> new SwingFrameState().setVisible(true));
    }

    private void initUI() {
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLayout(new FlowLayout());

        final JButton minimize = new JButton("Minimize");
        final JButton maximize = new JButton("Maximize");
        final JButton normal = new JButton("Normal");

        add(normal);
        add(minimize);
        add(maximize);

        pack();
        setSize(500, 200);

        ActionListener listener = e -> {
            if (e.getSource() == normal) {
                setExtendedState(Frame.NORMAL);
            } else if (e.getSource() == minimize) {
                setExtendedState(Frame.ICONIFIED);
            } else if (e.getSource() == maximize) {
                setExtendedState(Frame.MAXIMIZED_BOTH);
            }
        };

        minimize.addActionListener(listener);
        maximize.addActionListener(listener);
        normal.addActionListener(listener);
    }
}

The screenshot of the output from the code snippet above is:

Change JFrame State Programmatically

Change JFrame State Programmatically