Swing: Create a UWP (“Metro”)–like button

跟風遠走 提交于 2019-12-04 19:13:46

There are a number of ways you "might" do this, you could

  • Create a factory method to apply the properties and listeners you need to mimic the functionality
  • Create a new class which extends from JButton or AbstractButton and provide core functionality/properties you need in a self contained package
  • You could provide you own UI delegate, and customise the look and feel of the button at the core

Each method has it's pros and cons and you need to decide which better meets you overall needs.

For example, it's much easier to supply a customised look and feel delegate into an existing code base, as you don't need to change the code, other then to install the look and feel when you want to use it

The following is an example of using a look and feel delegate, this is just a proof of concept and there are probably a lot of additional functionality/work that needs to be done - for example, I'd like to supply more hints about the colors to use and the thickness of the roll over border, for example

import java.awt.Color;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javaapplication24.Test.MetroLookAndFeel;
import javax.swing.AbstractButton;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.plaf.basic.BasicButtonUI;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setBorder(new EmptyBorder(10, 10, 10, 10));
            JButton fancyPB = new JButton("Restart Now");
            fancyPB.setUI(new MetroLookAndFeel());

            JButton normalPB = new JButton("Restart Now");

            setLayout(new GridBagLayout());
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            gbc.insets = new Insets(4, 4, 4, 4);

            add(fancyPB, gbc);
            add(normalPB, gbc);
        }

    }

    public class MetroLookAndFeel extends BasicButtonUI {

        // This could be computed properties, where the border color
        // is determined based on other properties
        private Border focusBorder = new CompoundBorder(new LineBorder(Color.DARK_GRAY, 3), new EmptyBorder(7, 13, 7, 14));
        private Border unfocusedBorder = new EmptyBorder(10, 14, 10, 14);

        @Override
        protected void installDefaults(AbstractButton b) {

            super.installDefaults(b);
            Font f = new Font("Segoe UI", Font.PLAIN, 20);
            Color gray = new Color(204, 204, 204);
            b.setFont(f);
            b.setBackground(gray);
            b.setContentAreaFilled(false);
            b.setFocusPainted(false);
            // This seems like an oddity...
            b.setFocusable(false);
            b.setForeground(Color.BLACK);
//            b.setBorder(BorderFactory.createEmptyBorder(10, 14, 10, 14));
            b.setBorder(unfocusedBorder);
        }

        @Override
        protected void installListeners(AbstractButton b) {
            super.installListeners(b);
            b.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseEntered(MouseEvent e) {
                    ((JButton)e.getSource()).setBorder(focusBorder);
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    ((JButton)e.getSource()).setBorder(unfocusedBorder);
                }                
            });
        }

    }

}

Here is an example

import java.awt.Color;
import java.awt.Font;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
import javax.swing.border.CompoundBorder;
import javax.swing.border.LineBorder;

public class WinButton {

    public static void main(String[] args) {
        final JButton button = createWinButton("Restart now");
        JFrame frm = new JFrame("Test");
        JPanel layoutPanel = new JPanel();
        layoutPanel.add(button);
        frm.add(layoutPanel);
        frm.setSize(200, 200);
        frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frm.setLocationRelativeTo(null);
        frm.setVisible(true);
    }

    private static JButton createWinButton(String text) {
        final JButton button = new JButton(text);
        Font f = new Font("Segoe UI", Font.PLAIN, 20);
        Color gray = new Color(204, 204, 204);
        button.setFont(f);
        button.setBackground(gray);
        button.setContentAreaFilled(false);
        button.setFocusPainted(false);
        button.setFocusable(false);
        button.setForeground(Color.BLACK);
        button.setOpaque(true);
        button.setBorder(BorderFactory.createEmptyBorder(10, 14, 10, 14));
        button.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e) {
                Color borderColor = new Color(100, 100, 100);
                button.setBorder(new CompoundBorder(new LineBorder(borderColor, 3), BorderFactory.createEmptyBorder(7, 11, 7, 11)));
            }

            @Override
            public void mouseExited(MouseEvent e) {
                button.setBorder(BorderFactory.createEmptyBorder(10, 14, 10, 14));
            }
        });
        return button;
    }
}

In order to manipulate mouse events, such as hovering, you'll have to treat these events yourself, one of the ways to do is to create your own button.


Edit

Adding a mouse listener to your button, as answered by Sergiy Medvynskyy, would be a better practice since there is no need to tinker with the button class itself.


You may also have to play with the LaF (UIManager Look and Feel) since it can add undesired effects to UI components.

note that this can affect all of the interface components.

Following is a basic example.

Button:

import java.awt.Color;
import java.awt.Font;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.border.Border;

public class UWPButton extends JButton{

    private final Border regularBorder;
    private final Border hoverBorder;

    private final Color bgColor = new Color(204, 204, 204);
    private final Color transparent;
    private final Font metroFont = new Font("Segoe UI", Font.PLAIN, 20);

    public UWPButton() {
        super();

        // thickness of the button's borders
        int thickness = 4;

        // Create a transparent color, to use for the regular border
        // could also be the bgColor (204 204 204)
        Color c = new Color(1f, 0f, 0f, 0f);
        transparent = c;

        // Creates a compound border to be present on the button majority of the time.
        // uses an invisible line border on the outside in order to change border smoothly
        // the inside border is just an empty border for insets.
        regularBorder = BorderFactory.createCompoundBorder(
                BorderFactory.createLineBorder(transparent, thickness), //outside
                BorderFactory.createEmptyBorder(10, 14, 10, 14));       //inside

        // Creates a compound border which will be used when the mouse hovers the button.
        // the outside border has a darker colour than the background
        // the inside border is just an empty border for insets.
        hoverBorder = BorderFactory.createCompoundBorder(
                BorderFactory.createLineBorder(bgColor.darker(), thickness), //outside
                BorderFactory.createEmptyBorder(10, 14, 10, 14));            //inside

        // configures the button
        initButton();
    }

    // Here is where the mouse events are treated.
    @Override
    protected void processMouseEvent(MouseEvent e) {
        // Gets the ID of the event, 
        // if it is a mouse entering the button, sets the new border
        // if it is the mouse exiting the button, resets the border to the default.
        switch(e.getID()){
            case MouseEvent.MOUSE_ENTERED:
                this.setBorder(hoverBorder);
                break;
            case MouseEvent.MOUSE_EXITED:
                this.setBorder(regularBorder);
                break;
        }

        // the parent then does all the other handling.
        super.processMouseEvent(e); 
    }


    // Configures the button.
    private void initButton(){

        setFont(metroFont);
        setBorder(regularBorder);
        setBackground(bgColor);

        setFocusPainted(false);
    }

}

Changing the LaF: If you created your frame through an IDE drag and drop, you should have something like this inside your main (this is from netbeans):

CHANGE "Nimbus" to "Metro"(or remove the LaF setting) so the interface manager won't render all the Nimbus stuff. Note: there is no "Metro" LaF, this is just to disable the applied LaF.

// other stuff above
//
// change "Nimbus" for something else or just remove this try-catch block
try {
    for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {

        if ("Nimbus".equals(info.getName())) {
            javax.swing.UIManager.setLookAndFeel(info.getClassName());
            break;
        }
    }
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
        java.util.logging.Logger.getLogger(NewJFrame.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
// other stuff bellow

More on the LaF (Look and Feel): https://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!