Swing custom title bar

Article directory

  • Swing custom title bar
    • need
    • The final effect is as shown below
    • step
    • additional requirements

Swing custom title bar

Requirements

If you want to achieve a title bar effect similar to IDEA, place the menu bar on the same row as the title bar, with the title in the center and buttons on the right. As shown in the picture:

The final effect is as shown

Steps

Use dependency FlatLaf

<!-- https://mvnrepository.com/artifact/com.formdev/flatlaf -->
<dependency>
    <groupId>com.formdev</groupId>
    <artifactId>flatlaf</artifactId>
    <version>3.2.5</version>
</dependency>
  1. Cancel system title bar

    frame.setUndecorated(true); can be achieved.

  2. Custom title bar

    class CustomTitlePane extends JMenuBar {<!-- -->
    public CustomTitlePane(JFrame frame) {<!-- -->
    
    // Create a blank panel to add left spacing
    JLabel blankLbl = new JLabel(" ");
    add(blankLbl);
    
    // add icon
    Image image = ImageManager.getImage("/images/logo.png");
    ImageIcon icon = new ImageIcon(image);
    Image scaledImage = icon.getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH);
    JLabel iconLabel = new JLabel(new ImageIcon(scaledImage));
    add(iconLabel);
    
    // Create a blank panel to add left spacing
    JLabel blankLbl2 = new JLabel(" ");
    add(blankLbl2);
    
    JMenuBar menuBar = ToolkitUtilities.getMenuBar();
    add(menuBar);
    
    //Create and add a title tag, center aligned
    JLabel titleLabel = new JLabel(frame.getTitle());
    titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
    add(Box.createHorizontalGlue());
    add(titleLabel);
    add(Box.createHorizontalGlue());
    
    //Add minimize button
    JButton minimizeButton = new JButton("-");
    minimizeButton.addActionListener(new ActionListener() {<!-- -->
    @Override
    public void actionPerformed(ActionEvent e) {<!-- -->
    frame.setExtendedState(JFrame.ICONIFIED);
    }
    });
    add(minimizeButton);
    
    //Add maximize button
    JButton maximizeButton = new JButton("[]");
    maximizeButton.addActionListener(new ActionListener() {<!-- -->
    @Override
    public void actionPerformed(ActionEvent e) {<!-- -->
    if (frame.getWidth() == screenSize.width) {<!-- -->
    frame.setSize(new Dimension(1280, 768));
    frame.setLocationRelativeTo(null);
    } else {<!-- -->
    frame.setBounds(0, workArea.y, screenSize.width, workArea.height);
    }
    }
    });
    add(maximizeButton);
    
    //Add close button
    JButton closeButton = new JButton("X");
    closeButton.addActionListener(new ActionListener() {<!-- -->
    @Override
    public void actionPerformed(ActionEvent e) {<!-- -->
    frame.dispose();
    }
    });
    add(closeButton);
    }
    
    @Override
    public void updateUI() {<!-- -->
    super.updateUI();
    
    //Set the appearance of the menu bar, using FlatMenuBarUI to match the FlatLaf theme
    setUI(new FlatMenuBarUI());
    }
    }
    
  3. Custom window drag and drop and resize

    1. First, the getResizeCursor method is provided to determine the current shape of the pointer based on the current position of the mouse. There are only two types according to requirements: the default pointer and the resized pointer.
    private static int getResizeCursor(JFrame frame, Point p) {<!-- -->
            Insets insets = frame.getInsets();
            int x = p.x;
            int y = p.y;
            int width = frame.getWidth();
            int height = frame.getHeight();
    
            if (x < BORDER_SIZE & amp; & amp; y < BORDER_SIZE) {<!-- -->
                return Cursor.NW_RESIZE_CURSOR;
            }
            if (x < BORDER_SIZE & amp; & y > height - BORDER_SIZE) {<!-- -->
                return Cursor.SW_RESIZE_CURSOR;
            }
            if (x > width - BORDER_SIZE & amp; & y < BORDER_SIZE) {<!-- -->
                return Cursor.NE_RESIZE_CURSOR;
            }
            if (x > width - BORDER_SIZE & amp; & y > height - BORDER_SIZE) {<!-- -->
                return Cursor.SE_RESIZE_CURSOR;
            }
            if (x < BORDER_SIZE) {<!-- -->
                return Cursor.W_RESIZE_CURSOR;
            }
            if (x > width - BORDER_SIZE) {<!-- -->
                return Cursor.E_RESIZE_CURSOR;
            }
            if (y < BORDER_SIZE) {<!-- -->
                return Cursor.N_RESIZE_CURSOR;
            }
            if (y > height - BORDER_SIZE) {<!-- -->
                return Cursor.S_RESIZE_CURSOR;
            }
            return Cursor.DEFAULT_CURSOR;
        }
    
    1. Here we add four types of event listeners for the mouse:

      • MouseMoved

        When the mouse moves, the current pointer position is determined according to the above method, and the mouse pointer icon is updated in a timely manner.

      • Mouse click mousePressed

        When the mouse clicks, it is judged based on the current pointer icon:

        ? If it is the default icon, it is considered a drag operation, and the current coordinates need to be recorded;

        ? If it is another icon, it is considered a resize operation.

      • mouse released mouseReleased

        The mouse needs to change back to the default pointer when released.

      • mouse drag mouseDragged

        When dragging the mouse, it can be divided into two situations based on the current pointer icon:

        ? If it is the default icon, it is considered to be a moving position operation, and the frame position is modified through the setLocation method;

        ? If it is not the default icon, it is considered to be a resize operation. For the resize operation, it is necessary to distinguish the specific type of the current pointer to determine which direction the displacement is, and at the same time, the distance between the current pointer coordinate point and the original pointer coordinate point needs to be calculated. Because during the resizing process, in addition to the size change, there is also a location change.

        ? At the same time, if you want to limit the minimum size of the window, I also limit it here through MIN_WIDTH and MIN_HEIGHT.

    private static void addResizeWindowSupport(JFrame frame) {<!-- -->
            frame.addMouseListener(new MouseAdapter() {<!-- -->
                @Override
                public void mousePressed(MouseEvent e) {<!-- -->
                    // Get the current pointer shape
                    cursor = getResizeCursor(frame, e.getPoint());
                    // If it is the default pointer, it may be a drag operation
                    if(cursor == Cursor.DEFAULT_CURSOR) {<!-- -->
                        mouseAtX = e.getPoint().x;
                        mouseAtY = e.getPoint().y;
                    }
                }
    
                @Override
                public void mouseReleased(MouseEvent e) {<!-- -->
                    // Release the pointer and change it back to the default pointer
                    cursor = Cursor.DEFAULT_CURSOR;
                }
            });
    
            frame.addMouseMotionListener(new MouseAdapter() {<!-- -->
                @Override
                public void mouseDragged(MouseEvent e) {<!-- -->
                    // During the dragging process of the pointer, if it is not the default pointer, it is changing the size.
                    if (cursor != Cursor.DEFAULT_CURSOR) {<!-- -->
                        Point p = e.getPoint();
                        SwingUtilities.convertPointToScreen(p, frame);
                        Point newLocation = frame.getLocation();
                        Dimension newSize = frame.getSize();
    
                        switch (cursor) {<!-- -->
                            case Cursor.N_RESIZE_CURSOR:
                                newSize.height = frame.getHeight() - p.y + frame.getLocation().y;
                                newLocation.y = p.y;
                                break;
                            case Cursor.S_RESIZE_CURSOR:
                                newSize.height = p.y - frame.getLocation().y;
                                break;
                            case Cursor.W_RESIZE_CURSOR:
                                newSize.width = frame.getWidth() - p.x + frame.getLocation().x;
                                newLocation.x = p.x;
                                break;
                            case Cursor.E_RESIZE_CURSOR:
                                newSize.width = p.x - frame.getLocation().x;
                                break;
                            case Cursor.NW_RESIZE_CURSOR:
                                newSize.height = frame.getHeight() - p.y + frame.getLocation().y;
                                newLocation.y = p.y;
                                newSize.width = frame.getWidth() - p.x + frame.getLocation().x;
                                newLocation.x = p.x;
                                break;
                            case Cursor.NE_RESIZE_CURSOR:
                                newSize.height = frame.getHeight() - p.y + frame.getLocation().y;
                                newLocation.y = p.y;
                                newSize.width = p.x - frame.getLocation().x;
                                break;
                            case Cursor.SW_RESIZE_CURSOR:
                                newSize.height = p.y - frame.getLocation().y;
                                newSize.width = frame.getWidth() - p.x + frame.getLocation().x;
                                newLocation.x = p.x;
                                break;
                            case Cursor.SE_RESIZE_CURSOR:
                                newSize.height = p.y - frame.getLocation().y;
                                newSize.width = p.x - frame.getLocation().x;
                                break;
                        }
                        frame.setBounds(newLocation.x, newLocation.y, Math.max(100, newSize.width), Math.max(100, newSize.height));
                    } else {<!-- -->
                        // If it is the default pointer, it is dragging
                        frame.setLocation(e.getXOnScreen() - mouseAtX, e.getYOnScreen() - mouseAtY);
                    }
                }
            });
    
            frame.addMouseMotionListener(new MouseAdapter() {<!-- -->
                @Override
                public void mouseMoved(MouseEvent e) {<!-- -->
                    frame.setCursor(Cursor.getPredefinedCursor(getResizeCursor(frame, e.getPoint())));
                }
            });
        }
    

Additional requirements

  1. The default window is maximized and does not occupy taskbar space

    If you don’t do anything, the default maximization will cause the taskbar to be covered. Here are some tips:

    //Get the screen size
    screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    // Get the size of the screen working area
    workArea = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
    // Set the initial position and size of the window to ensure that the bottom does not occupy the taskbar space
    frame.setBounds(0, workArea.y, screenSize.width, workArea.height);