I need to find the way to show several actions inside of another action like Netbeans does with Run Main Project icon.
You can see there is a default action Run Main Project and if you click in the little arrow next to the green play icon, you can select a specific action like Run .
I was checking the code of Netbeans but I can't find the code to make this in my application.
Ah, (one of) the holy grails of UI components, a split button. Over a number of years I've tried to find one which would perform well under multiple look and feels and failed dismally.
Many used multiple buttons or just resorted to using a JComboBox
Like many things, I stumbled across one which was pretty well done, but which I had to modify to suit my needs, unfortunately, I don't remember the original version or author, sorry. (If you believe this code is based on yours, please leave a comment with a link to the original and I will evaluate and provide appropriate credit)
Basically, if you click the button, it will run the "default" action (Bananas) otherwise you can select one of the sub elements and it will execute it
public class SplitButton extends JButton {
private int separatorSpacing = 4;
private int splitWidth = 22;
private int arrowSize = 8;
private boolean onSplit;
private Rectangle splitRectangle;
private JFrame popupMenu;
private boolean alwaysDropDown;
private Color arrowColor = Color.BLACK;
private Color disabledArrowColor = Color.GRAY;
private Image image;
private MouseHandler mouseHandler;
private boolean toolBarButton;
private PopupWindowEventHandler popupWindowEventHandler;
/**
* Creates a button with initial text and an icon.
*
* #param text the text of the button
* #param icon the Icon image to display on the button
*/
public SplitButton() {
super();
addMouseMotionListener(getMouseHandler());
addMouseListener(getMouseHandler());
// Default for no "default" action...
setAlwaysDropDown(true);
InputMap im = getInputMap(WHEN_FOCUSED);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "PopupMenu.close");
am.put("PopupMenu.close", new ClosePopupAction());
}
public SplitButton(Action defaultAction, Action... actions) {
this();
setAction(defaultAction);
for (Action action : actions) {
addAction(action);
}
}
public SplitButton(String text, Icon icon, Action... actions) {
this((Action) null, actions);
setText(text);
setIcon(icon);
}
public SplitButton(String text, Action... actions) {
this((Action) null, actions);
setText(text);
}
public JSplitButton(Icon icon, Action... actions) {
this((Action) null, actions);
setIcon(icon);
}
#Override
public void setAction(Action a) {
super.setAction(a);
if (a != null) {
setAlwaysDropDown(false);
}
}
/**
* Creates a pre-configured button suitable for being used on a JToolBar
*
* #param defaultAction
* #param actions
* #return
*/
public static SplitButton createToolBarButton(Action defaultAction, Action... actions) {
JSplitButton btn = new JSplitButton(defaultAction, actions);
btn.configureForToolBar();
return btn;
}
/**
* Creates a pre-configured "options only" button suitable for being used on
* a JToolBar
*
* #param text
* #param icon
* #param actions
* #return
*/
public static SplitButton createToolBarButton(String text, Icon icon, Action... actions) {
JSplitButton btn = new JSplitButton(icon, actions);
btn.setToolTipText(text);
btn.configureForToolBar();
return btn;
}
/**
* Used to determine if the button is begin configured for use on a tool bar
*
* #return
*/
public boolean isToolBarButton() {
return toolBarButton;
}
/**
* Configures this button for use on a tool bar...
*/
public void configureForToolBar() {
toolBarButton = true;
if (getIcon() != null) {
setHideActionText(true);
}
setHorizontalTextPosition(JButton.CENTER);
setVerticalTextPosition(JButton.BOTTOM);
setFocusable(false);
}
protected MouseHandler getMouseHandler() {
if (mouseHandler == null) {
mouseHandler = new MouseHandler();
}
return mouseHandler;
}
protected AbstractButton getButtonFor(Action action) {
Container parent = ((JFrame) getPopupWindow()).getContentPane();
AbstractButton btn = null;
for (Component comp : parent.getComponents()) {
if (comp instanceof AbstractButton) {
Action childAction = ((AbstractButton) comp).getAction();
if (action.equals(childAction)) {
btn = (AbstractButton) comp;
break;
}
}
}
return btn;
}
/**
* Returns the index of the specified action within the popup window or -1
* of it does not exist
*
* #param action
* #return
*/
public int indexOfAction(Action action) {
Container parent = ((JFrame) getPopupWindow()).getContentPane();
AbstractButton btn = getButtonFor(action);
return btn == null ? -1 : parent.getComponentZOrder(btn);
}
/**
* Adds the specified action to the popup menu...
*
* This simply calls getPopupWindow().add(action)
*
* #param action Add
*/
public void addAction(Action action) {
addActionAt(action, -1);
}
protected int getOptionsCount() {
return ((JFrame) getPopupWindow()).getContentPane().getComponentCount();
}
protected void addActionAt(Action action, int index) {
if (index < 0 || index >= getOptionsCount()) {
getPopupWindow().add(createMenuItem(action));
} else {
getPopupWindow().add(createMenuItem(action), index);
}
}
protected void removeAction(Action action) {
AbstractButton btn = getButtonFor(action);
if (btn != null) {
getPopupWindow().remove(btn);
}
}
/**
* Creates a new JMenuItem from the supplied Action. This is used to
* provided the ability for subclasses to either change the type of menu
* item used by the button or add additional functionality (like listeners)
* should they be required
*
* #param action
* #return
*/
protected JMenuItem createMenuItem(Action action) {
return new JMenuItem(action);
}
#Override
public Insets getInsets() {
Insets insets = (Insets) super.getInsets().clone();
insets.right += splitWidth;
return insets;
}
#Override
public Insets getInsets(Insets insets) {
Insets insets1 = getInsets();
insets.left = insets1.left;
insets.right = insets1.right;
insets.bottom = insets1.bottom;
insets.top = insets1.top;
return insets1;
}
/**
* Returns the window that acts as the buttons popup window
*
* #return
*/
public Window getPopupWindow() {
if (popupMenu == null) {
popupMenu = new JFrame();
popupMenu.setFocusableWindowState(false);
popupMenu.setUndecorated(true);
popupMenu.setContentPane(createPopupWindowContentPane());
popupMenu.setAlwaysOnTop(true);
DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
String name = evt.getPropertyName();
if ("focusOwner".equalsIgnoreCase(name)
|| "permanentFocusOwner".equalsIgnoreCase(name)
|| "focusedWindow".equalsIgnoreCase(name)
|| "activeWindow".equalsIgnoreCase(name)) {
Window focusedWindow = DefaultKeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
if (!popupMenu.equals(focusedWindow)) {
closePopupWinodw();
}
}
}
});
}
return popupMenu;
}
protected Container createPopupWindowContentPane() {
return new DefaultMenuPane();
}
protected void closePopupWinodw() {
getPopupWindow().setVisible(false);
if (popupWindowEventHandler != null) {
Toolkit.getDefaultToolkit().removeAWTEventListener(popupWindowEventHandler);
}
}
protected void showPopupWindow() {
Window popup = getPopupWindow();
popup.pack();
Point pos = getLocationOnScreen();
popup.setLocation(pos.x + (getWidth() - popup.getWidth()), pos.y + getHeight());
popup.setVisible(true);
if (popupWindowEventHandler == null) {
popupWindowEventHandler = new PopupWindowEventHandler();
}
Toolkit.getDefaultToolkit().addAWTEventListener(popupWindowEventHandler, AWTEvent.MOUSE_EVENT_MASK);
}
/**
* Returns the separatorSpacing. Separator spacing is the space above and
* below the separator( the line drawn when you hover your mouse over the
* split part of the button).
*
* #return separatorSpacingimage = null; //to repaint the image with the new
* size
*/
public int getSeparatorSpacing() {
return separatorSpacing;
}
/**
* Sets the separatorSpacing.Separator spacing is the space above and below
* the separator( the line drawn when you hover your mouse over the split
* part of the button).
*
* #param spacing
*/
public void setSeparatorSpacing(int spacing) {
if (spacing != separatorSpacing && spacing >= 0) {
int old = separatorSpacing;
this.separatorSpacing = spacing;
image = null;
firePropertyChange("separatorSpacing", old, separatorSpacing);
revalidate();
repaint();
}
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* #return true if alwaysDropdown, false otherwise.
*/
public boolean isAlwaysDropDown() {
return alwaysDropDown;
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* If true, this will prevent the button from raising any actionPerformed
* events for itself
*
* #param value true to show the attached dropdown even if the button part
* is clicked, false otherwise
*/
public void setAlwaysDropDown(boolean value) {
if (alwaysDropDown != value) {
this.alwaysDropDown = value;
firePropertyChange("alwaysDropDown", !alwaysDropDown, alwaysDropDown);
}
}
/**
* Gets the color of the arrow.
*
* #return arrowColor
*/
public Color getArrowColor() {
return arrowColor;
}
/**
* Set the arrow color.
*
* #param color
*/
public void setArrowColor(Color color) {
if (arrowColor != color) {
Color old = arrowColor;
this.arrowColor = color;
image = null;
firePropertyChange("arrowColor", old, arrowColor);
repaint();
}
}
/**
* gets the disabled arrow color
*
* #return disabledArrowColor color of the arrow if no popup attached.
*/
public Color getDisabledArrowColor() {
return disabledArrowColor;
}
/**
* sets the disabled arrow color
*
* #param color color of the arrow if no popup attached.
*/
public void setDisabledArrowColor(Color color) {
if (disabledArrowColor != color) {
Color old = disabledArrowColor;
this.disabledArrowColor = color;
image = null; //to repaint the image with the new color
firePropertyChange("disabledArrowColor", old, disabledArrowColor);
}
}
/**
* Splitwidth is the width of the split part of the button.
*
* #return splitWidth
*/
public int getSplitWidth() {
return splitWidth;
}
/**
* Splitwidth is the width of the split part of the button.
*
* #param width
*/
public void setSplitWidth(int width) {
if (splitWidth != width) {
int old = splitWidth;
this.splitWidth = width;
firePropertyChange("splitWidth", old, splitWidth);
revalidate();
repaint();
}
}
/**
* gets the size of the arrow.
*
* #return size of the arrow
*/
public int getArrowSize() {
return arrowSize;
}
/**
* sets the size of the arrow
*
* #param size
*/
public void setArrowSize(int size) {
if (arrowSize != size) {
int old = arrowSize;
this.arrowSize = size;
image = null; //to repaint the image with the new size
firePropertyChange("setArrowSize", old, arrowSize);
revalidate();
repaint();
}
}
/**
* Gets the image to be drawn in the split part. If no is set, a new image
* is created with the triangle.
*
* #return image
*/
public Image getImage() {
if (image == null) {
Graphics2D g = null;
BufferedImage img = new BufferedImage(arrowSize, arrowSize, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) img.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
g.setColor(popupMenu != null ? arrowColor : disabledArrowColor);
//this creates a triangle facing right >
g.fillPolygon(new int[]{0, 0, arrowSize / 2}, new int[]{0, arrowSize, arrowSize / 2}, 3);
g.dispose();
//rotate it to face downwards
img = rotate(img, 90);
BufferedImage dimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
g = (Graphics2D) dimg.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(img, null, 0, 0);
g.dispose();
for (int i = 0; i < dimg.getHeight(); i++) {
for (int j = 0; j < dimg.getWidth(); j++) {
if (dimg.getRGB(j, i) == Color.WHITE.getRGB()) {
dimg.setRGB(j, i, 0x8F1C1C);
}
}
}
image = Toolkit.getDefaultToolkit().createImage(dimg.getSource());
}
return image;
}
/**
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Graphics gClone = g.create();//EDIT: Hervé Guillaume
Color oldColor = g.getColor();
splitRectangle = new Rectangle(getWidth() - splitWidth, 0, splitWidth, getHeight());
g.translate(splitRectangle.x, splitRectangle.y);
int mh = getHeight() / 2;
int mw = splitWidth / 2;
g.drawImage(getImage(), mw - arrowSize / 2, mh + 2 - arrowSize / 2, null);
if (!alwaysDropDown) {
if (getModel().isRollover() || isFocusable()) {
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.background"));
g.drawLine(1, separatorSpacing + 2, 1, getHeight() - separatorSpacing - 2);
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.shadow"));
g.drawLine(2, separatorSpacing + 2, 2, getHeight() - separatorSpacing - 2);
}
}
g.setColor(oldColor);
g.translate(-splitRectangle.x, -splitRectangle.y);
}
/**
* Rotates the given image with the specified angle.
*
* #param img image to rotate
* #param angle angle of rotation
* #return rotated image
*/
private BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.rotate(Math.toRadians(angle), w / 2, h / 2);
g.drawImage(img, null, 0, 0);
return dimg;
}
#Override
protected void fireActionPerformed(ActionEvent event) {
// This is a little bit of a nasty trick. Basically this is where
// we try and decide if the buttons "default" action should
// be fired or not. We don't want it firing if the button
// is in "options only" mode or the user clicked on
// on the "drop down arrow"....
if (onSplit || isAlwaysDropDown()) {
showPopupWindow();
} else {
super.fireActionPerformed(event);
}
}
protected class MouseHandler extends MouseAdapter {
#Override
public void mouseExited(MouseEvent e) {
onSplit = false;
repaint(splitRectangle);
}
#Override
public void mouseMoved(MouseEvent e) {
if (splitRectangle.contains(e.getPoint())) {
onSplit = true;
} else {
onSplit = false;
}
repaint(splitRectangle);
}
}
protected class PopupWindowEventHandler implements AWTEventListener {
#Override
public void eventDispatched(AWTEvent event) {
if (popupMenu.isVisible()) {
switch (event.getID()) {
case MouseEvent.MOUSE_RELEASED:
Object source = event.getSource();
if (source instanceof Component) {
Window win = SwingUtilities.getWindowAncestor((Component) source);
if (!popupMenu.equals(win)) {
closePopupWinodw();
}
}
break;
}
}
}
}
protected class ClosePopupAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
closePopupWinodw();
}
}
protected class DefaultMenuPane extends JPanel {
public DefaultMenuPane() {
setBorder(UIManager.getBorder("PopupMenu.border"));
setBackground(UIManager.getColor("PopupMenu.background"));
setLayout(new GridLayout(0, 1));
}
}
}
It would be configured something like ...
SplitButton btn = new SplitButton();
btn.setAction(new FruitAction("Banana", new BananaIcon(32, 32)));
btn.addAction(new FruitAction("Apple", new AppleIcon(32, 32)));
btn.addAction(new FruitAction("Black Berry", new BlackBerriesIcon(32, 32)));
btn.addAction(new FruitAction("Grapes", new GrapesIcon(32, 32)));
btn.addAction(new FruitAction("Peach", new PeachIcon(32, 32)));
btn.addAction(new FruitAction("Strewberry", new StrewberriesIcon(32, 32)));
And, for reference, the fruit action looks like...
public class FruitAction extends AbstractAction {
public FruitAction(String text, Icon icon) {
putValue(NAME, text);
putValue(SMALL_ICON, icon);
putValue(SHORT_DESCRIPTION, text);
}
#Override
public void actionPerformed(ActionEvent e) {
JOptionPane.showMessageDialog(null, "I am " + getValue(NAME), "Fruit", JOptionPane.INFORMATION_MESSAGE);
}
}
This is use a custom vector based icon library, so obviously, I won't be including that, but it gives you an idea of how to configure it
I too have been looking for a decent JSplitButton for a while, but everything I found as a standalone component was rather disappointing.
MadProgrammer's answer looked very promising, but it did not work all that well for me. I'm not 100% sure what it might be caused by, whether it's by design or because of the L&F I'm using but the provided component had focus issues. Specifically, the items of the popup did not show any hover indicators, indicating they weren't receiving focus. Also upon clicking an item in the popup the popup did not close itself.
Anyway, I rewrote parts of the component and made it use a JPopupMenu instead of a custom JFrame to avoid handling focus myself. The component set's the popup menu as its popup menu using the JComponent.setComponentPopupMenu() and then just invoking the popup menu upon clicking the dropdown arrow. This also makes it that it's possible to right-click the button to show the popup directly.
The popup has focus and behaves like a regular JPopupMenu, supporting adding stuff like separators and whatnot.
Note: The L&F used in the image is flatlaf
The button's default action can be set like a normal JButton using addActionListener() or using actions by calling setAction().
SplitButton btn = new SplitButton("Click me");
btn.addActionListener((e) -> {
System.out.println("Button clicked");
});
SplitButton btn2 = new SplitButton(new AbstractAction("Click me") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button clicked");
}
});
The popup menu can be created separately and then assigned to the button using setPopupMenu() or you can add individual items as actions to the menu without the need of creating it yourself using addAction and addActionAt.
JPopupMenu popup = new JPopupMenu();
popup.add(new JMenuItem("A popup option"));
popup.add(new JMenuItem("JMenuItem with icon", Icons.deleteBin));
popup.addSeparator();
btn.setPopupMenu(popup);
btn.addAction(new AbstractAction("Or don't", Icons.alert) {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Or don't clicked");
}
});
btn.addAction(new AbstractAction("Click me in a different way") {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Click me in a different way clicked");
}
});
The popup menu can be retrieved simply by using getPopupMenu().
The full code
Again, original by MadProgrammer and whoever he got it from :D
/**
* A swing split button implementation.
* A JButton that has an additional section with an arrow icon on the right that when clicked
* shows a JPopupMenu that is positioned flush with the button.
*
* The implementation sets the buttons pop-up menu using setComponentPopupMenu()
* meaning that in addition to clicking the drop-down arrow, user can also right click
* the button to open the pop-up menu.
*
* Author: DUDSS - 21.02.2020
* I modified the button to use a JPopupMenu instead of a custom JFrame to avoid hacky
* focus workarounds and fix focus issues.
*
* Credit:
* Modified version of a split button by MadProgrammer.
* https://stackoverflow.com/questions/36352707/actions-inside-of-another-action-like-netbeans
* It's original author seems to be unknown.
*
*/
public class SplitButton extends JButton {
private int separatorSpacing = 4;
private int splitWidth = 22;
private int arrowSize = 8;
private boolean onSplit;
private Rectangle splitRectangle;
private boolean alwaysDropDown;
private Color arrowColor = Color.BLACK;
private Color disabledArrowColor = Color.GRAY;
private Image image;
private MouseHandler mouseHandler;
private boolean toolBarButton;
private JPopupMenu jpopupMenu;
/**
* Creates a button with initial text and an icon.
*
* #param text the text of the button
* #param icon the Icon image to display on the button
*/
public SplitButton() {
super();
addMouseMotionListener(getMouseHandler());
addMouseListener(getMouseHandler());
// Default for no "default" action...
setAlwaysDropDown(true);
InputMap im = getInputMap(WHEN_FOCUSED);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "PopupMenu.close");
am.put("PopupMenu.close", new ClosePopupAction());
}
public SplitButton(Action defaultAction) {
this();
setAction(defaultAction);
}
public SplitButton(Action defaultAction, JPopupMenu popup) {
this();
setAction(defaultAction);
setPopupMenu(popup);
}
public SplitButton(Action defaultAction, Action... actions) {
this();
setAction(defaultAction);
for (Action a : actions) {
addAction(a);
}
}
public SplitButton(String text) {
this();
setText(text);
}
public SplitButton(String text, Icon icon) {
this();
setText(text);
setIcon(icon);
}
public SplitButton(String text, JPopupMenu popup) {
this();
setText(text);
setPopupMenu(popup);
}
public SplitButton(String text, Icon icon, JPopupMenu popup) {
this();
setText(text);
setIcon(icon);
setPopupMenu(popup);
}
/**
* Creates a pre-configured button suitable for being used on a JToolBar
*
* #param defaultAction
* #param actions
* #return
*/
public static SplitButton createToolBarButton(Action defaultAction, Action... actions) {
SplitButton btn = new SplitButton(defaultAction, actions);
btn.configureForToolBar();
return btn;
}
/**
* Creates a pre-configured "options only" button suitable for being used on
* a JToolBar
*
* #param text
* #param icon
* #param actions
* #return
*/
public static SplitButton createToolBarButton(String text, Icon icon, JPopupMenu popupMenu) {
SplitButton btn = new SplitButton(text, icon);
btn.setPopupMenu(popupMenu);
btn.setToolTipText(text);
btn.configureForToolBar();
return btn;
}
#Override
public void addActionListener(ActionListener l) {
if (l != null) {
setAlwaysDropDown(false);
}
super.addActionListener(l);
}
#Override
public void setAction(Action a) {
super.setAction(a);
if (a != null) {
setAlwaysDropDown(false);
}
}
public void addActionAt(Action a, int index) {
getPopupMenu().insert(a, index);
}
public void addAction(Action a) {
getPopupMenu().add(a);
}
public void setPopupMenu(JPopupMenu popup) {
jpopupMenu = popup;
this.setComponentPopupMenu(popup);
}
/**
* Returns the buttons popup menu.
*
* #return
*/
public JPopupMenu getPopupMenu() {
if (jpopupMenu == null) {
jpopupMenu = new JPopupMenu();
}
return jpopupMenu;
}
/**
* Used to determine if the button is begin configured for use on a tool bar
*
* #return
*/
public boolean isToolBarButton() {
return toolBarButton;
}
/**
* Configures this button for use on a tool bar...
*/
public void configureForToolBar() {
toolBarButton = true;
if (getIcon() != null) {
setHideActionText(true);
}
setHorizontalTextPosition(JButton.CENTER);
setVerticalTextPosition(JButton.BOTTOM);
setFocusable(false);
}
protected MouseHandler getMouseHandler() {
if (mouseHandler == null) {
mouseHandler = new MouseHandler();
}
return mouseHandler;
}
protected int getOptionsCount() {
return getPopupMenu().getComponentCount();
}
/*protected void addActionAt(Action action, int index) {
if (index < 0 || index >= getOptionsCount()) {
getPopupWindow().add(createMenuItem(action));
} else {
getPopupWindow().add(createMenuItem(action), index);
}
}*/
/*protected void removeAction(Action action) {
AbstractButton btn = getButtonFor(action);
if (btn != null) {
getPopupWindow().remove(btn);
}
}*/
#Override
public Insets getInsets() {
Insets insets = (Insets) super.getInsets().clone();
insets.right += splitWidth;
return insets;
}
#Override
public Insets getInsets(Insets insets) {
Insets insets1 = getInsets();
insets.left = insets1.left;
insets.right = insets1.right;
insets.bottom = insets1.bottom;
insets.top = insets1.top;
return insets1;
}
protected void closePopupMenu() {
getPopupMenu().setVisible(false);
}
protected void showPopupMenu() {
if (getOptionsCount() > 0) {
JPopupMenu menu = getPopupMenu();
menu.setVisible(true); //Necessary to calculate pop-up menu width the first time it's displayed.
menu.show(this, (getWidth() - menu.getWidth()), getHeight());
}
}
/**
* Returns the separatorSpacing. Separator spacing is the space above and
* below the separator( the line drawn when you hover your mouse over the
* split part of the button).
*
* #return separatorSpacingimage = null; //to repaint the image with the new
* size
*/
public int getSeparatorSpacing() {
return separatorSpacing;
}
/**
* Sets the separatorSpacing.Separator spacing is the space above and below
* the separator( the line drawn when you hover your mouse over the split
* part of the button).
*
* #param spacing
*/
public void setSeparatorSpacing(int spacing) {
if (spacing != separatorSpacing && spacing >= 0) {
int old = separatorSpacing;
this.separatorSpacing = spacing;
image = null;
firePropertyChange("separatorSpacing", old, separatorSpacing);
revalidate();
repaint();
}
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* #return true if alwaysDropdown, false otherwise.
*/
public boolean isAlwaysDropDown() {
return alwaysDropDown;
}
/**
* Show the dropdown menu, if attached, even if the button part is clicked.
*
* If true, this will prevent the button from raising any actionPerformed
* events for itself
*
* #param value true to show the attached dropdown even if the button part
* is clicked, false otherwise
*/
public void setAlwaysDropDown(boolean value) {
if (alwaysDropDown != value) {
this.alwaysDropDown = value;
firePropertyChange("alwaysDropDown", !alwaysDropDown, alwaysDropDown);
}
}
/**
* Gets the color of the arrow.
*
* #return arrowColor
*/
public Color getArrowColor() {
return arrowColor;
}
/**
* Set the arrow color.
*
* #param color
*/
public void setArrowColor(Color color) {
if (arrowColor != color) {
Color old = arrowColor;
this.arrowColor = color;
image = null;
firePropertyChange("arrowColor", old, arrowColor);
repaint();
}
}
/**
* gets the disabled arrow color
*
* #return disabledArrowColor color of the arrow if no popup attached.
*/
public Color getDisabledArrowColor() {
return disabledArrowColor;
}
/**
* sets the disabled arrow color
*
* #param color color of the arrow if no popup attached.
*/
public void setDisabledArrowColor(Color color) {
if (disabledArrowColor != color) {
Color old = disabledArrowColor;
this.disabledArrowColor = color;
image = null; //to repaint the image with the new color
firePropertyChange("disabledArrowColor", old, disabledArrowColor);
}
}
/**
* Splitwidth is the width of the split part of the button.
*
* #return splitWidth
*/
public int getSplitWidth() {
return splitWidth;
}
/**
* Splitwidth is the width of the split part of the button.
*
* #param width
*/
public void setSplitWidth(int width) {
if (splitWidth != width) {
int old = splitWidth;
this.splitWidth = width;
firePropertyChange("splitWidth", old, splitWidth);
revalidate();
repaint();
}
}
/**
* gets the size of the arrow.
*
* #return size of the arrow
*/
public int getArrowSize() {
return arrowSize;
}
/**
* sets the size of the arrow
*
* #param size
*/
public void setArrowSize(int size) {
if (arrowSize != size) {
int old = arrowSize;
this.arrowSize = size;
image = null; //to repaint the image with the new size
firePropertyChange("setArrowSize", old, arrowSize);
revalidate();
repaint();
}
}
/**
* Gets the image to be drawn in the split part. If no is set, a new image
* is created with the triangle.
*
* #return image
*/
public Image getImage() {
if (image == null) {
Graphics2D g = null;
BufferedImage img = new BufferedImage(arrowSize, arrowSize, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) img.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
g.setColor(jpopupMenu != null ? arrowColor : disabledArrowColor);
//this creates a triangle facing right >
g.fillPolygon(new int[]{0, 0, arrowSize / 2}, new int[]{0, arrowSize, arrowSize / 2}, 3);
g.dispose();
//rotate it to face downwards
img = rotate(img, 90);
BufferedImage dimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
g = (Graphics2D) dimg.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(img, null, 0, 0);
g.dispose();
for (int i = 0; i < dimg.getHeight(); i++) {
for (int j = 0; j < dimg.getWidth(); j++) {
if (dimg.getRGB(j, i) == Color.WHITE.getRGB()) {
dimg.setRGB(j, i, 0x8F1C1C);
}
}
}
image = Toolkit.getDefaultToolkit().createImage(dimg.getSource());
}
return image;
}
/**
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Graphics gClone = g.create();//EDIT: Hervé Guillaume
Color oldColor = g.getColor();
splitRectangle = new Rectangle(getWidth() - splitWidth, 0, splitWidth, getHeight());
g.translate(splitRectangle.x, splitRectangle.y);
int mh = getHeight() / 2;
int mw = splitWidth / 2;
g.drawImage(getImage(), mw - arrowSize / 2, mh + 2 - arrowSize / 2, null);
if (!alwaysDropDown) {
if (getModel().isRollover() || isFocusable()) {
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.background"));
g.drawLine(1, separatorSpacing + 2, 1, getHeight() - separatorSpacing - 2);
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.shadow"));
g.drawLine(2, separatorSpacing + 2, 2, getHeight() - separatorSpacing - 2);
}
}
g.setColor(oldColor);
g.translate(-splitRectangle.x, -splitRectangle.y);
}
/**
* Rotates the given image with the specified angle.
*
* #param img image to rotate
* #param angle angle of rotation
* #return rotated image
*/
private BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.rotate(Math.toRadians(angle), w / 2, h / 2);
g.drawImage(img, null, 0, 0);
return dimg;
}
#Override
protected void fireActionPerformed(ActionEvent event) {
// This is a little bit of a nasty trick. Basically this is where
// we try and decide if the buttons "default" action should
// be fired or not. We don't want it firing if the button
// is in "options only" mode or the user clicked on
// on the "drop down arrow"....
if (onSplit || isAlwaysDropDown()) {
showPopupMenu();
} else {
super.fireActionPerformed(event);
}
}
protected class MouseHandler extends MouseAdapter {
#Override
public void mouseExited(MouseEvent e) {
onSplit = false;
repaint(splitRectangle);
}
#Override
public void mouseMoved(MouseEvent e) {
if (splitRectangle.contains(e.getPoint())) {
onSplit = true;
} else {
onSplit = false;
}
repaint(splitRectangle);
}
}
protected class ClosePopupAction extends AbstractAction {
#Override
public void actionPerformed(ActionEvent e) {
closePopupMenu();
}
}
}
Related
I want to create a button that has an icon with text on the top and arrow on the corner. Upon clicking on it, a menu is displayed. I was able to achieve all of above except for the arrow part. I know I can have an an image file that has the icon and arrow saved in one png file. However, I don't want to alter the icon file. Here is the code I have so far.
How can I add an arrow ( whether programmtically or from another arrow image file to the button)
public class JButtonMenu extends JToggleButton {
JPopupMenu popup;
public JButtonMenu(ImageIcon img, String title, String []list) {
super(name);
this.popup = new JPopupMenu();
this.buttonId = buttonId;
this.setMenuList(list); //This is another method
setIcon(img);
setVerticalTextPosition(SwingConstants.TOP);
setHorizontalTextPosition(SwingConstants.CENTER);
}
public void setMenuList(String[]list){
if(list == null){
return;
}
for(String item:list){
popup.add(new JMenuItem(new AbstractAction(item) {
public void actionPerformed(ActionEvent e) {
JMenuItem menuItem = (JMenuItem)e.getSource();
int index= popup.getComponentIndex(menuItem);
menuItemListener.itemSelectedListener(buttonId,index, menuItem.getText());
}
}));
}
}
}
This is basically a watered down version of this implementation of a split button but which focuses on the need for painting an additional image as well as some of the other functionality you'll need to implement to ensure that the original text and icon are offset accurtaly.
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
SplitButton btn = new SplitButton();
btn.setText("This is a split button");
JFrame frame = new JFrame("Testing");
frame.setLayout(new GridBagLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(btn);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class SplitButton extends JButton {
private int separatorSpacing = 4;
private int splitWidth = 30;
private int arrowSize = 8;
private Rectangle splitRectangle;
private Color arrowColor = Color.BLACK;
private Color disabledArrowColor = Color.GRAY;
private Image image;
public SplitButton() {
super();
}
#Override
public Insets getInsets() {
Insets insets = (Insets) super.getInsets().clone();
insets.right += splitWidth;
return insets;
}
#Override
public Insets getInsets(Insets insets) {
Insets insets1 = getInsets();
insets.left = insets1.left;
insets.right = insets1.right;
insets.bottom = insets1.bottom;
insets.top = insets1.top;
return insets1;
}
/**
* Returns the separatorSpacing. Separator spacing is the space above and
* below the separator( the line drawn when you hover your mouse over the
* split part of the button).
*
* #return separatorSpacingimage = null; //to repaint the image with the new
* size
*/
public int getSeparatorSpacing() {
return separatorSpacing;
}
/**
* Sets the separatorSpacing.Separator spacing is the space above and below
* the separator( the line drawn when you hover your mouse over the split
* part of the button).
*
* #param spacing
*/
public void setSeparatorSpacing(int spacing) {
if (spacing != separatorSpacing && spacing >= 0) {
int old = separatorSpacing;
this.separatorSpacing = spacing;
image = null;
firePropertyChange("separatorSpacing", old, separatorSpacing);
revalidate();
repaint();
}
}
/**
* Gets the color of the arrow.
*
* #return arrowColor
*/
public Color getArrowColor() {
return arrowColor;
}
/**
* Set the arrow color.
*
* #param color
*/
public void setArrowColor(Color color) {
if (arrowColor != color) {
Color old = arrowColor;
this.arrowColor = color;
image = null;
firePropertyChange("arrowColor", old, arrowColor);
repaint();
}
}
/**
* gets the disabled arrow color
*
* #return disabledArrowColor color of the arrow if no popup attached.
*/
public Color getDisabledArrowColor() {
return disabledArrowColor;
}
/**
* sets the disabled arrow color
*
* #param color color of the arrow if no popup attached.
*/
public void setDisabledArrowColor(Color color) {
if (disabledArrowColor != color) {
Color old = disabledArrowColor;
this.disabledArrowColor = color;
image = null; //to repaint the image with the new color
firePropertyChange("disabledArrowColor", old, disabledArrowColor);
}
}
/**
* Splitwidth is the width of the split part of the button.
*
* #return splitWidth
*/
public int getSplitWidth() {
return splitWidth;
}
/**
* Splitwidth is the width of the split part of the button.
*
* #param width
*/
public void setSplitWidth(int width) {
if (splitWidth != width) {
int old = splitWidth;
this.splitWidth = width;
firePropertyChange("splitWidth", old, splitWidth);
revalidate();
repaint();
}
}
/**
* gets the size of the arrow.
*
* #return size of the arrow
*/
public int getArrowSize() {
return arrowSize;
}
/**
* sets the size of the arrow
*
* #param size
*/
public void setArrowSize(int size) {
if (arrowSize != size) {
int old = arrowSize;
this.arrowSize = size;
image = null; //to repaint the image with the new size
firePropertyChange("setArrowSize", old, arrowSize);
revalidate();
repaint();
}
}
/**
* Gets the image to be drawn in the split part. If no is set, a new image
* is created with the triangle.
*
* #return image
*/
public Image getImage() {
if (image == null) {
Graphics2D g = null;
BufferedImage img = new BufferedImage(arrowSize, arrowSize, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) img.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, img.getWidth(), img.getHeight());
g.setColor(isEnabled() ? arrowColor : disabledArrowColor);
//this creates a triangle facing right >
g.fillPolygon(new int[]{0, 0, arrowSize / 2}, new int[]{0, arrowSize, arrowSize / 2}, 3);
g.dispose();
//rotate it to face downwards
img = rotate(img, 90);
BufferedImage dimg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
g = (Graphics2D) dimg.createGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(img, null, 0, 0);
g.dispose();
for (int i = 0; i < dimg.getHeight(); i++) {
for (int j = 0; j < dimg.getWidth(); j++) {
if (dimg.getRGB(j, i) == Color.WHITE.getRGB()) {
dimg.setRGB(j, i, 0x8F1C1C);
}
}
}
image = Toolkit.getDefaultToolkit().createImage(dimg.getSource());
}
return image;
}
/**
*
* #param g
*/
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//Graphics gClone = g.create();//EDIT: Hervé Guillaume
Color oldColor = g.getColor();
splitRectangle = new Rectangle(getWidth() - splitWidth, 0, splitWidth, getHeight());
g.translate(splitRectangle.x, splitRectangle.y);
int mh = getHeight() / 2;
int mw = splitWidth / 2;
g.drawImage(getImage(), mw - arrowSize / 2, mh + 2 - arrowSize / 2, null);
if (getModel().isRollover() || isFocusable()) {
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.background"));
g.drawLine(1, separatorSpacing + 2, 1, getHeight() - separatorSpacing - 2);
g.setColor(UIManager.getLookAndFeelDefaults().getColor("Button.shadow"));
g.drawLine(2, separatorSpacing + 2, 2, getHeight() - separatorSpacing - 2);
}
g.setColor(oldColor);
g.translate(-splitRectangle.x, -splitRectangle.y);
}
/**
* Rotates the given image with the specified angle.
*
* #param img image to rotate
* #param angle angle of rotation
* #return rotated image
*/
private BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.rotate(Math.toRadians(angle), w / 2, h / 2);
g.drawImage(img, null, 0, 0);
return dimg;
}
}
}
Swing has a well defined and documented painting process, in order to perform custom painting you need to work within the constraints of the API otherwise you will end up with no end ot issues.
Take a look at Painting in AWT and Swing and Performing Custom Painting for more details
A quick way to do it would be
Image img1=imageIcon1.getImage();
Image img2=imageIcon2.getImage();
Graphics g=img1.getGraphics();
g.drawImage(img2, x, y, sizex, sizey, null)
where x, y is where you put the second icon (arrow) on the first icon and sizex, sizey is the reduced size of the second icon. You can change these by trying.
You have to have another method
public JButtonMenu(ImageIcon imageIcon1, ImageIcon imageIcon2, String title, String []list) {
// above segment here
// continue with the rest of the code
}
I have been working on an application for blackberry, which orginally had two buttons (btn1 and btn2) on the screen. Now I've added a third and i am experiencing some difficulties (btn3).
Originally btn1 and btn2 were side by side, and clicking outside the button, but below it would activate the button ... a design flaw but could be overlooked.
However, I need to add a button below btn1 and when i did that two weird things occured: First is that, even if i click btn3 which is BELOW btn1, focus shifts to btn1 and btn1 is called. And clicking btn2 shifts focus to btn3 and it is activated.
I'm not entirely sure why this is happening but i'm playing around with the code pasted below. Any little help is appreciated.
btn1 = new CustomButtonField("", Bitmap.getBitmapResource("button-disabled_1a.png"), Bitmap.getBitmapResource("button-normal_2.png"));
btn2 = new CustomButtonField("", Bitmap.getBitmapResource("button-disabled_3.png"), Bitmap.getBitmapResource("button-normal_4.png"));
btn3 = new CustomButtonField("", Bitmap.getBitmapResource("button-disabled5.png"), Bitmap.getBitmapResource("button-normal_6.png"));
Background bg = BackgroundFactory.createBitmapBackground(Bitmap.getBitmapResource("background.png"));
HorizontalFieldManager vfm = new HorizontalFieldManager(){
public int getPreferredHeight() {
// TODO Auto-generated method stub
return Display.getHeight();
}
public int getPreferredWidth() {
// TODO Auto-generated method stub
return Display.getWidth();
}
protected void sublayout(int maxWidth, int maxHeight) {
// TODO Auto-generated method stub
int count = getFieldCount();
for(int i = 0 ; i < count ; i++ ){
Field f = getField(i);
if(f == btn1 ){
setPositionChild(f, (getPreferredWidth() >> 1) - f.getPreferredWidth(), getPreferredHeight()>>1);
layoutChild(f, getPreferredWidth(), getPreferredHeight());
}else if (f == btn2 ){
setPositionChild(f, (getPreferredWidth() >> 1) +30, getPreferredHeight()>>1);
layoutChild(f, getPreferredWidth(), getPreferredHeight());
}else if (f == lblName ){
setPositionChild(f, 30, getPreferredHeight()>>1 - btnLicense.getPreferredHeight());
layoutChild(f, ( getPreferredWidth() * 3 ) >> 2, getPreferredHeight());
}else if (f == btn3 ){
setPositionChild(f, (getPreferredWidth() >> 1) - f.getPreferredWidth() -0 , getPreferredHeight()- getPreferredHeight()+280);
layoutChild(f, getPreferredWidth(), getPreferredHeight());
}
}
setExtent(getPreferredWidth(),getPreferredHeight());
}
public void subpaint(Graphics graphics){
int count = getFieldCount();
for(int i = 0 ; i < count ; i++ ){
net.rim.device.api.ui.Field f = getField(i);
paintChild(graphics,f);
}
}
};
Custom Button Field
package com.app.ui.component;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.Graphics;
public class CustomButtonField extends Field {
/** To set background image for button field */
private Bitmap bkImage;
/** To set Focus image for button field */
private Bitmap bkFocusImage;
/** int value for Field Width */
private int fieldWidth;
/** int value for Field Height */
private int fieldHeight;
/** Text to write on Button */
private String text;
/** Text Color on Button */
private int textColor = Color.WHITE;
/** Default Font for Button */
private Font defaultFont = Font.getDefault();
/**
* Constructor with
* #param text
* #param image
* #param focusImage
*/
public CustomButtonField (String text, Bitmap image, Bitmap focusImage) {
this(text, image, focusImage, 0);
}
/**
* Constructor with
* #param text
* #param image
* #param focusImage
* #param style
*/
public CustomButtonField(String text, Bitmap image, Bitmap focusImage, long style) {
super(Field.FOCUSABLE | style);
this.text = text;
bkImage = image;
this.bkFocusImage = focusImage;
fieldHeight = bkImage.getHeight();
fieldWidth = bkImage.getWidth();
}
/**
* To get the exact width needed by the field borderWidth - used to show the
* width of focused rectangle around the button
*/
public int getPreferredWidth() {
return fieldWidth;
}
/**
* To get the exact width needed by the field borderHeight - used to show
* the height of focused rectangle around the button
*/
public int getPreferredHeight() {
return fieldHeight;
}
protected void layout(int width, int height) {
setExtent(getPreferredWidth(), getPreferredHeight());
}
/**
* To set the background according to focused state of the field
*/
protected void drawFocus(Graphics graphics, boolean flag) {
graphics.setFont(defaultFont);
if (bkFocusImage != null) {
graphics.drawBitmap((getPreferredWidth() - bkFocusImage.getWidth()) / 2,(getPreferredHeight() - bkFocusImage.getHeight()) / 2,
bkFocusImage.getWidth(), bkFocusImage.getHeight(),bkFocusImage, 0, 0);
}
graphics.setColor(Color.WHITE);
int textWidth = defaultFont.getAdvance(text);
graphics.drawText(text, (fieldWidth - textWidth) / 2,(fieldHeight - defaultFont.getHeight()) / 2);
}
protected void paint(Graphics graphics) {
graphics.setFont(defaultFont);
if (bkImage != null) {
graphics.drawBitmap((getPreferredWidth() - bkImage.getWidth()) / 2,(getPreferredHeight() - bkImage.getHeight()) / 2,
bkImage.getWidth(), bkImage.getHeight(), bkImage, 0, 0);
}
graphics.setColor(textColor);
int color = (isEnabled())?Color.BLACK:Color.DARKGRAY;
graphics.setColor(color);
int textWidth = defaultFont.getAdvance(text);
graphics.drawText(text, (fieldWidth - textWidth) / 2,(fieldHeight - defaultFont.getHeight()) / 2);
}
protected boolean navigationClick(int status, int time) {
fieldChangeNotify(0);
return true;
}
}
This is a pretty easy problem to have when you're first implementing custom BlackBerry buttons and fields. First of all, the problem here is that your CustomButtonField class, which is a button field written from scratch, is not properly determining which touch events (or navigation events) are within its extent (inside the field's area).
One way to fix this is to modify your navigationClick() method, and implement the touchEvent() method:
protected boolean touchEvent( TouchEvent message ) {
int x = message.getX( 1 );
int y = message.getY( 1 );
if( x < 0 || y < 0 || x > getExtent().width || y > getExtent().height ) {
// Outside the field
return false;
}
switch( message.getEvent() ) {
case TouchEvent.UNCLICK:
fieldChangeNotify(0);
return true;
}
return super.touchEvent( message );
}
protected boolean navigationClick(int status, int time) {
if (status != 0) { // you did not have this check
fieldChangeNotify(0);
}
return true;
}
Another option, that I would actually recommend, is to replace your entire CustomButtonField class with one of the samples from BlackBerry's Advanced UI library
You can use the BitmapButtonField, and the BaseButtonField that it extends, to achieve the same functionality, with proper touch / click handling.
While you're there, take a look at some of the other UI classes in that library, as you'll probably find them quite useful.
I am trying to get a button with round edges. My button has a background color of yellow. I am not able to get the round edge for my button. Here is the code i am trying
class RoundedBorder implements Border {
int radius;
RoundedBorder(int radius) {
this.radius = radius;
}
public Insets getBorderInsets(Component c) {
return new Insets(this.radius+1, this.radius+1, this.radius+2, this.radius);
}
public boolean isBorderOpaque() {
return true;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
g.drawRoundRect(x,y,width-1,height-1,radius,radius);
}
}
jButton1.setText(aContinue);
jButton1.setBackground(new java.awt.Color(255, 255, 0));
jButton1.setBorder(new RoundedBorder(20));
I am not able to get round edges using this piece of code.. Here is how my button looks .
I want to have round edges with no overflowing background color.
Option1- Use Images
Option2- Use the following code (Extracted from Make a button round)
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class RoundButton extends JButton {
public RoundButton(String label) {
super(label);
// These statements enlarge the button so that it
// becomes a circle rather than an oval.
Dimension size = getPreferredSize();
size.width = size.height = Math.max(size.width,
size.height);
setPreferredSize(size);
// This call causes the JButton not to paint
// the background.
// This allows us to paint a round background.
setContentAreaFilled(false);
}
// Paint the round background and label.
protected void paintComponent(Graphics g) {
if (getModel().isArmed()) {
// You might want to make the highlight color
// a property of the RoundButton class.
g.setColor(Color.lightGray);
} else {
g.setColor(getBackground());
}
g.fillOval(0, 0, getSize().width-1,
getSize().height-1);
// This call will paint the label and the
// focus rectangle.
super.paintComponent(g);
}
// Paint the border of the button using a simple stroke.
protected void paintBorder(Graphics g) {
g.setColor(getForeground());
g.drawOval(0, 0, getSize().width-1,
getSize().height-1);
}
// Hit detection.
Shape shape;
public boolean contains(int x, int y) {
// If the button has changed size,
// make a new shape object.
if (shape == null ||
!shape.getBounds().equals(getBounds())) {
shape = new Ellipse2D.Float(0, 0,
getWidth(), getHeight());
}
return shape.contains(x, y);
}
// Test routine.
public static void main(String[] args) {
// Create a button with the label "Jackpot".
JButton button = new RoundButton("Jackpot");
button.setBackground(Color.green);
// Create a frame in which to show the button.
JFrame frame = new JFrame();
frame.getContentPane().setBackground(Color.yellow);
frame.getContentPane().add(button);
frame.getContentPane().setLayout(new FlowLayout());
frame.setSize(150, 150);
frame.setVisible(true);
}
}
Option3- Use Look and Feel that supports Round Buttons http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/NimbusLookandFeel_OBE2012/CustomizingLandF.html
Option4- Go with JavaFX and use CSS. There are free CSS scripts supporting this
Found this great example by oracle which supplies a class which creates RoundButton.
Here is an example using an edited RoundButton class to create RoundedButton class:
import java.awt.AWTEvent;
import java.awt.AWTEventMulticaster;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Test {
public Test() {
initComponents();
}
private void initComponents() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JTextField tf = new JTextField("");
RoundedButton rb = new RoundedButton("Go");
rb.setBackground(Color.yellow);
rb.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
JOptionPane.showMessageDialog(frame, "You said: " + tf.getText());
}
});
frame.add(tf, BorderLayout.NORTH);
frame.add(rb);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
}
class RoundedButton extends Component {
ActionListener actionListener; // Post action events to listeners
String label; // The Button's text
protected boolean pressed = false; // true if the button is detented.
/**
* Constructs a RoundedButton with no label.
*/
public RoundedButton() {
this("");
}
/**
* Constructs a RoundedButton with the specified label.
*
* #param label the label of the button
*/
public RoundedButton(String label) {
this.label = label;
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
/**
* gets the label
*
* #see setLabel
*/
public String getLabel() {
return label;
}
/**
* sets the label
*
* #see getLabel
*/
public void setLabel(String label) {
this.label = label;
invalidate();
repaint();
}
/**
* paints the RoundedButton
*/
#Override
public void paint(Graphics g) {
// paint the interior of the button
if (pressed) {
g.setColor(getBackground().darker().darker());
} else {
g.setColor(getBackground());
}
g.fillRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 20, 20);
// draw the perimeter of the button
g.setColor(getBackground().darker().darker().darker());
g.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, 20, 20);
// draw the label centered in the button
Font f = getFont();
if (f != null) {
FontMetrics fm = getFontMetrics(getFont());
g.setColor(getForeground());
g.drawString(label, getWidth() / 2 - fm.stringWidth(label) / 2, getHeight() / 2 + fm.getMaxDescent());
}
}
/**
* The preferred size of the button.
*/
#Override
public Dimension getPreferredSize() {
Font f = getFont();
if (f != null) {
FontMetrics fm = getFontMetrics(getFont());
int max = Math.max(fm.stringWidth(label) + 40, fm.getHeight() + 40);
return new Dimension(max, max);
} else {
return new Dimension(100, 100);
}
}
/**
* The minimum size of the button.
*/
#Override
public Dimension getMinimumSize() {
return new Dimension(100, 100);
}
/**
* Adds the specified action listener to receive action events from this
* button.
*
* #param listener the action listener
*/
public void addActionListener(ActionListener listener) {
actionListener = AWTEventMulticaster.add(actionListener, listener);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
/**
* Removes the specified action listener so it no longer receives action
* events from this button.
*
* #param listener the action listener
*/
public void removeActionListener(ActionListener listener) {
actionListener = AWTEventMulticaster.remove(actionListener, listener);
}
/**
* Determine if click was inside round button.
*/
#Override
public boolean contains(int x, int y) {
int mx = getSize().width / 2;
int my = getSize().height / 2;
return (((mx - x) * (mx - x) + (my - y) * (my - y)) <= mx * mx);
}
/**
* Paints the button and distribute an action event to all listeners.
*/
#Override
public void processMouseEvent(MouseEvent e) {
Graphics g;
switch (e.getID()) {
case MouseEvent.MOUSE_PRESSED:
// render myself inverted....
pressed = true;
// Repaint might flicker a bit. To avoid this, you can use
// double buffering (see the Gauge example).
repaint();
break;
case MouseEvent.MOUSE_RELEASED:
if (actionListener != null) {
actionListener.actionPerformed(new ActionEvent(
this, ActionEvent.ACTION_PERFORMED, label));
}
// render myself normal again
if (pressed == true) {
pressed = false;
// Repaint might flicker a bit. To avoid this, you can use
// double buffering (see the Gauge example).
repaint();
}
break;
case MouseEvent.MOUSE_ENTERED:
break;
case MouseEvent.MOUSE_EXITED:
if (pressed == true) {
// Cancel! Don't send action event.
pressed = false;
// Repaint might flicker a bit. To avoid this, you can use
// double buffering (see the Gauge example).
repaint();
// Note: for a more complete button implementation,
// you wouldn't want to cancel at this point, but
// rather detect when the mouse re-entered, and
// re-highlight the button. There are a few state
// issues that that you need to handle, which we leave
// this an an excercise for the reader (I always
// wanted to say that!)
}
break;
}
super.processMouseEvent(e);
}
}
Another clean way to make this happen is to define a custom ButtonUI that would draw a rounded corner button.
I have a table (JTable) of values which is updated in real time about every 10 seconds (last 2 columns out of 4 updated, for each row). I want this table to continuous scroll up so I used the example from:
http://wiki.byte-welt.net/wiki/MarqueePanelV
The scrolling wraps around, first line is shown after last line and works fine except one thing:
When I do the update of the DefaultTableModel (I'm using setValueAt() function), the values are printed at their original position (like the table is not scrolled). Only the portion with the 2 columns is refreshed, created some fracture in the view. The view is corrected at the next scroll (in 100 milliseconds) but is still visible on the screen. More, since I'm stopping the scroll when mouse is over the table, the update will create a visible fracture in the table view.
Any idea on how can I fix this problem?
Here is a code example showing the problem:
package scrollingtable;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.table.DefaultTableModel;
class MarqueePanelV extends JPanel
implements ActionListener, AncestorListener, WindowListener {
private boolean paintChildren;
private boolean scrollingPaused;
private int scrollOffset;
private int wrapOffset;
private int preferredHeight = -1;
private int scrollAmount;
private int scrollFrequency;
private boolean wrap = false;
private int wrapAmount = 0;
private boolean scrollWhenFocused = true;
private Timer timer = new Timer(100, this);
/**
* Convenience constructor that sets both the scroll frequency and
* scroll amount to a value of 5.
*/
public MarqueePanelV() {
this(20, 1);
}
/**
*
* #param scrollFrequency
* #param scrollAmount
*/
#SuppressWarnings("LeakingThisInConstructor")
public MarqueePanelV(int scrollFrequency, int scrollAmount) {
setScrollFrequency(scrollFrequency);
setScrollAmount(scrollAmount);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
addAncestorListener(this);
}
/*
* Translate the location of the children before they are painted so it
* appears they are scrolling bottom to top
*/
#Override
public void paintChildren(Graphics g) {
// Need this so we don't see a flicker of the text before scrolling
if (!paintChildren) {
return;
}
int x = super.getPreferredSize().height;
// Normal painting as the components scroll bottom to top
Graphics2D g2d = (Graphics2D) g;
g2d.translate(0, -scrollOffset);
super.paintChildren(g);
g2d.translate(0, scrollOffset);
// Repaint the start of the components on the bottom edge of the panel once
// all the components are completely visible on the panel.
// (Its like the components are in two places at the same time)
if (isWrap()) {
//wrapOffset = scrollOffset - super.getPreferredSize().height - wrapAmount;
wrapOffset = scrollOffset - x - wrapAmount;
g2d.translate(0, -wrapOffset);
super.paintChildren(g);
g2d.translate(0, wrapOffset);
}
}
/*
* The default preferred size will be half the size of the components added to
* the panel. This will allow room for components to be scrolled on and off
* the panel.
*
* The default height can be overriden by using the setPreferredHeight() method.
*/
#Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.height = (preferredHeight == -1) ? d.height / 2 : preferredHeight;
return d;
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
public int getPreferredHeight() {
return preferredHeight;
}
/**
* Specify the preferred height on the panel. A value of -1 will cause the
* default preferred with size calculation to be used.
*
* #param preferredHeight preferred height of the panel in pixels
*/
public void setPreferredHeight(int preferredHeight) {
this.preferredHeight = preferredHeight;
revalidate();
}
/**
* Get the scroll amount.
*
* #return the scroll amount in pixels
*/
public int getScrollAmount() {
return scrollAmount;
}
/**
* Specify the scroll amount. The number of pixels to scroll every time
* scrolling is done.
*
* #param scrollAmount scroll amount in pixels
*/
public void setScrollAmount(int scrollAmount) {
this.scrollAmount = scrollAmount;
}
/**
* Get the scroll frequency.
*
* #return the scroll frequency
*/
public int getScrollFrequency() {
return scrollFrequency;
}
/**
* Specify the scroll frequency. That is the number of times scrolling
* should be performed every second.
*
* #param scrollFrequency scroll frequency
*/
public void setScrollFrequency(int scrollFrequency) {
this.scrollFrequency = scrollFrequency;
int delay = 1000 / scrollFrequency;
timer.setInitialDelay(delay);
timer.setDelay(delay);
}
/**
* Get the scroll only when visible property.
*
* #return the scroll only when visible value
*/
public boolean isScrollWhenFocused() {
return scrollWhenFocused;
}
/**
* Specify the scrolling property for unfocused windows.
*
* #param scrollWhenVisible when true scrolling pauses when the window
* loses focus. Scrolling will continue when
* the window regains focus. When false
* scrolling is continuous unless the window
* is iconified.
*/
public void setScrollWhenFocused(boolean scrollWhenFocused) {
this.scrollWhenFocused = scrollWhenFocused;
}
/**
* Get the wrap property.
*
* #return the wrap value
*/
public boolean isWrap() {
return wrap;
}
/**
* Specify the wrapping property. Normal scrolling is such that all the text
* will scroll from bottom to top. When the last part of the text scrolls off
* the bottom edge scrolling will start again from the bottom edge. Therefore
* there is a time when the component is blank as nothing is displayed.
* Wrapping implies that as the end of the text scrolls off the top edge
* the beginning of the text will scroll in from the bottom edge. So the end
* and the start of the text is displayed at the same time.
*
* #param wrap when true the start of the text will scroll in from the bottom
* edge while the end of the text is still scrolling off the top
* edge. Otherwise the panel must be clear of text before it
* will begin again from the bottom edge.
*/
public void setWrap(boolean wrap) {
this.wrap = wrap;
}
/**
* Get the wrap amount.
*
* #return the wrap amount value
*/
public int getWrapAmount() {
return wrapAmount;
}
/**
* Specify the wrapping amount. This specifies the space between the end of the
* text on the top edge and the start of the text from the bottom edge when
* wrapping is turned on.
*
* #param wrapAmount the amount in pixels
*/
public void setWrapAmount(int wrapAmount) {
this.wrapAmount = wrapAmount;
}
/**
* Start scrolling the components on the panel. Components will start
* scrolling from the bottom edge towards the top edge.
*/
public void startScrolling() {
paintChildren = true;
scrollOffset = -getSize().height;
timer.start();
}
/**
* Stop scrolling the components on the panel. The conponents will be
* cleared from the view of the panel
*/
public void stopScrolling() {
timer.stop();
paintChildren = false;
repaint();
}
/**
* The components will stop scrolling but will remain visible
*/
public void pauseScrolling() {
if (timer.isRunning()) {
timer.stop();
scrollingPaused = true;
}
}
/**
* The components will resume scrolling from where scrolling was stopped.
*/
public void resumeScrolling() {
if (scrollingPaused) {
timer.restart();
scrollingPaused = false;
}
}
/**
* Adjust the offset of the components on the panel so it appears that
* they are scrolling from bottom to top.
*/
#Override
public void actionPerformed(ActionEvent e) {
scrollOffset += scrollAmount;
int height = super.getPreferredSize().height;
if (scrollOffset > height) {
scrollOffset = isWrap() ? wrapOffset + scrollAmount : -getSize().height;
}
//System.out.println("scroll offset: " + scrollOffset);
repaint();
}
/**
* Get notified when the panel is added to a Window so we can use a
* WindowListener to automatically start the scrolling of the components.
*/
#Override
public void ancestorAdded(AncestorEvent event) {
SwingUtilities.windowForComponent(this).addWindowListener(this);
}
#Override
public void ancestorRemoved(AncestorEvent event) {
}
#Override
public void ancestorMoved(AncestorEvent event) {
}
// Implement WindowListener
#Override
public void windowOpened(WindowEvent e) {
startScrolling();
}
#Override
public void windowClosing(WindowEvent e) {
stopScrolling();
}
#Override
public void windowClosed(WindowEvent e) {
stopScrolling();
}
#Override
public void windowIconified(WindowEvent e) {
pauseScrolling(); }
#Override
public void windowDeiconified(WindowEvent e) {
resumeScrolling(); }
#Override
public void windowActivated(WindowEvent e) {
if (isScrollWhenFocused()) {
resumeScrolling();
}
}
#Override
public void windowDeactivated(WindowEvent e) {
if (isScrollWhenFocused()) {
pauseScrolling();
}
}
}
public class Main extends JDialog implements ActionListener {
private JTable table;
private DefaultTableModel model;
MarqueePanelV mpv = new MarqueePanelV();
private Timer timer2;
public static void main(String[] args) {
// TODO code application logic here
new Main();
}
Main() {
setSize(600, 400);
this.setLocation(300, 300);
table = new JTable();
model = new DefaultTableModel(20, 2);
table.setModel(model);
table.getColumnModel().getColumn(0).setPreferredWidth(280);
table.getColumnModel().getColumn(1).setPreferredWidth(280);
for(int i = 0; i < 20; i++) {
model.setValueAt(i, i, 0);
model.setValueAt(100 + i, i, 1);
}
mpv.add(table);
add(mpv);
mpv.setWrap(true);
mpv.setWrapAmount(0);
mpv.startScrolling();
table.addMouseListener(new MouseListener() {
// Implement MouseListener
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {
mpv.pauseScrolling();
}
#Override
public void mouseExited(MouseEvent e) {
mpv.resumeScrolling();
}
});
setVisible(true);
timer2 = new Timer(2000, this);
timer2.setInitialDelay(1000);
timer2.setDelay(2000);
timer2.start();
}
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < 20; i++) {
model.setValueAt(100 + i, i, 1);
}
}
}
Problem was solved by calling mpv.repaint(); in the timer2 action:
package scrollingtable;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import javax.swing.BoxLayout;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import javax.swing.table.DefaultTableModel;
class MarqueePanelV extends JPanel
implements ActionListener, AncestorListener, WindowListener {
private boolean paintChildren;
private boolean scrollingPaused;
private int scrollOffset;
private int wrapOffset;
private int preferredHeight = -1;
private int scrollAmount;
private int scrollFrequency;
private boolean wrap = false;
private int wrapAmount = 0;
private boolean scrollWhenFocused = true;
private Timer timer = new Timer(100, this);
/**
* Convenience constructor that sets both the scroll frequency and
* scroll amount to a value of 5.
*/
public MarqueePanelV() {
this(20, 1);
}
/**
*
* #param scrollFrequency
* #param scrollAmount
*/
#SuppressWarnings("LeakingThisInConstructor")
public MarqueePanelV(int scrollFrequency, int scrollAmount) {
setScrollFrequency(scrollFrequency);
setScrollAmount(scrollAmount);
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
addAncestorListener(this);
}
/*
* Translate the location of the children before they are painted so it
* appears they are scrolling bottom to top
*/
#Override
public void paintChildren(Graphics g) {
// Need this so we don't see a flicker of the text before scrolling
if (!paintChildren) {
return;
}
int x = super.getPreferredSize().height;
// Normal painting as the components scroll bottom to top
Graphics2D g2d = (Graphics2D) g;
g2d.translate(0, -scrollOffset);
super.paintChildren(g);
g2d.translate(0, scrollOffset);
// Repaint the start of the components on the bottom edge of the panel once
// all the components are completely visible on the panel.
// (Its like the components are in two places at the same time)
if (isWrap()) {
//wrapOffset = scrollOffset - super.getPreferredSize().height - wrapAmount;
wrapOffset = scrollOffset - x - wrapAmount;
g2d.translate(0, -wrapOffset);
super.paintChildren(g);
g2d.translate(0, wrapOffset);
}
}
/*
* The default preferred size will be half the size of the components added to
* the panel. This will allow room for components to be scrolled on and off
* the panel.
*
* The default height can be overriden by using the setPreferredHeight() method.
*/
#Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
d.height = (preferredHeight == -1) ? d.height / 2 : preferredHeight;
return d;
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
public int getPreferredHeight() {
return preferredHeight;
}
/**
* Specify the preferred height on the panel. A value of -1 will cause the
* default preferred with size calculation to be used.
*
* #param preferredHeight preferred height of the panel in pixels
*/
public void setPreferredHeight(int preferredHeight) {
this.preferredHeight = preferredHeight;
revalidate();
}
/**
* Get the scroll amount.
*
* #return the scroll amount in pixels
*/
public int getScrollAmount() {
return scrollAmount;
}
/**
* Specify the scroll amount. The number of pixels to scroll every time
* scrolling is done.
*
* #param scrollAmount scroll amount in pixels
*/
public void setScrollAmount(int scrollAmount) {
this.scrollAmount = scrollAmount;
}
/**
* Get the scroll frequency.
*
* #return the scroll frequency
*/
public int getScrollFrequency() {
return scrollFrequency;
}
/**
* Specify the scroll frequency. That is the number of times scrolling
* should be performed every second.
*
* #param scrollFrequency scroll frequency
*/
public void setScrollFrequency(int scrollFrequency) {
this.scrollFrequency = scrollFrequency;
int delay = 1000 / scrollFrequency;
timer.setInitialDelay(delay);
timer.setDelay(delay);
}
/**
* Get the scroll only when visible property.
*
* #return the scroll only when visible value
*/
public boolean isScrollWhenFocused() {
return scrollWhenFocused;
}
/**
* Specify the scrolling property for unfocused windows.
*
* #param scrollWhenVisible when true scrolling pauses when the window
* loses focus. Scrolling will continue when
* the window regains focus. When false
* scrolling is continuous unless the window
* is iconified.
*/
public void setScrollWhenFocused(boolean scrollWhenFocused) {
this.scrollWhenFocused = scrollWhenFocused;
}
/**
* Get the wrap property.
*
* #return the wrap value
*/
public boolean isWrap() {
return wrap;
}
/**
* Specify the wrapping property. Normal scrolling is such that all the text
* will scroll from bottom to top. When the last part of the text scrolls off
* the bottom edge scrolling will start again from the bottom edge. Therefore
* there is a time when the component is blank as nothing is displayed.
* Wrapping implies that as the end of the text scrolls off the top edge
* the beginning of the text will scroll in from the bottom edge. So the end
* and the start of the text is displayed at the same time.
*
* #param wrap when true the start of the text will scroll in from the bottom
* edge while the end of the text is still scrolling off the top
* edge. Otherwise the panel must be clear of text before it
* will begin again from the bottom edge.
*/
public void setWrap(boolean wrap) {
this.wrap = wrap;
}
/**
* Get the wrap amount.
*
* #return the wrap amount value
*/
public int getWrapAmount() {
return wrapAmount;
}
/**
* Specify the wrapping amount. This specifies the space between the end of the
* text on the top edge and the start of the text from the bottom edge when
* wrapping is turned on.
*
* #param wrapAmount the amount in pixels
*/
public void setWrapAmount(int wrapAmount) {
this.wrapAmount = wrapAmount;
}
/**
* Start scrolling the components on the panel. Components will start
* scrolling from the bottom edge towards the top edge.
*/
public void startScrolling() {
paintChildren = true;
scrollOffset = -getSize().height;
timer.start();
}
/**
* Stop scrolling the components on the panel. The conponents will be
* cleared from the view of the panel
*/
public void stopScrolling() {
timer.stop();
paintChildren = false;
repaint();
}
/**
* The components will stop scrolling but will remain visible
*/
public void pauseScrolling() {
if (timer.isRunning()) {
timer.stop();
scrollingPaused = true;
}
}
/**
* The components will resume scrolling from where scrolling was stopped.
*/
public void resumeScrolling() {
if (scrollingPaused) {
timer.restart();
scrollingPaused = false;
}
}
// Implement ActionListener
/**
* Adjust the offset of the components on the panel so it appears that
* they are scrolling from bottom to top.
*/
#Override
public void actionPerformed(ActionEvent e) {
scrollOffset += scrollAmount;
int height = super.getPreferredSize().height;
if (scrollOffset > height) {
scrollOffset = isWrap() ? wrapOffset + scrollAmount : -getSize().height;
}
repaint();
}
// Implement AncestorListener
/**
* Get notified when the panel is added to a Window so we can use a
* WindowListener to automatically start the scrolling of the components.
*/
#Override
public void ancestorAdded(AncestorEvent event) {
SwingUtilities.windowForComponent(this).addWindowListener(this);
}
#Override
public void ancestorRemoved(AncestorEvent event) {
}
#Override
public void ancestorMoved(AncestorEvent event) {
}
// Implement WindowListener
#Override
public void windowOpened(WindowEvent e) {
startScrolling();
}
#Override
public void windowClosing(WindowEvent e) {
stopScrolling();
}
#Override
public void windowClosed(WindowEvent e) {
stopScrolling();
}
#Override
public void windowIconified(WindowEvent e) {
pauseScrolling(); }
#Override
public void windowDeiconified(WindowEvent e) {
resumeScrolling(); }
#Override
public void windowActivated(WindowEvent e) {
if (isScrollWhenFocused()) {
resumeScrolling();
}
}
#Override
public void windowDeactivated(WindowEvent e) {
if (isScrollWhenFocused()) {
pauseScrolling();
}
}
}
public class Main extends JDialog implements ActionListener {
private JTable table;
private DefaultTableModel model;
MarqueePanelV mpv = new MarqueePanelV();
private Timer timer2;
public static void main(String[] args) {
// TODO code application logic here
new Main();
}
Main() {
setSize(600, 400);
this.setLocation(300, 300);
table = new JTable();
model = new DefaultTableModel(20, 2);
table.setModel(model);
table.getColumnModel().getColumn(0).setPreferredWidth(280);
table.getColumnModel().getColumn(1).setPreferredWidth(280);
for(int i = 0; i < 20; i++) {
model.setValueAt(i, i, 0);
model.setValueAt(100 + i, i, 1);
}
mpv.add(table);
add(mpv);
mpv.setWrap(true);
mpv.setWrapAmount(0);
mpv.startScrolling();
table.addMouseListener(new MouseListener() {
// Implement MouseListener
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {
mpv.pauseScrolling();
}
#Override
public void mouseExited(MouseEvent e) {
mpv.resumeScrolling();
}
});
setVisible(true);
timer2 = new Timer(2000, this);
timer2.setInitialDelay(1000);
timer2.setDelay(2000);
timer2.start();
}
int k = 0;
public void actionPerformed(ActionEvent e) {
for(int i = 0; i < 20; i++) {
model.setValueAt(100 + i + k, i, 1);
}
k++;
mpv.repaint();
}
}
How to give this button background to be as jbutton
i want to give this button metal color or grediant color how to make that?
import java.awt.*;
import java.awt.event.*;
RoundButton - a class that produces a lightweight button.
Lightweight components can have "transparent" areas, meaning that
you can see the background of the container behind these areas.
#SuppressWarnings("serial")
public class RoundButton extends Component {
ActionListener actionListener; // Post action events to listeners
String label; // The Button's text
protected boolean pressed = false; // true if the button is detented.
/**
* Constructs a RoundButton with no label.
*/
public RoundButton() {
this("");
}
/**
* Constructs a RoundButton with the specified label.
* #param label the label of the button
*/
public RoundButton(String label) {
this.label = label;
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
/**
* gets the label
* #see setLabel
*/
public String getLabel() {
return label;
}
/**
* sets the label
* #see getLabel
*/
public void setLabel(String label) {
this.label = label;
invalidate();
repaint();
}
/**
* paints the RoundButton
*/
public void paint(Graphics g) {
int s = Math.min(getSize().width - 1, getSize().height - 1);
// paint the interior of the button
if(pressed) {
g.setColor(getBackground().darker().darker());
} else {
g.setColor(getBackground());
}
g.fillArc(0, 0, s, s, 0, 360);
// draw the perimeter of the button
g.setColor(getBackground().darker().darker().darker());
g.drawArc(0, 0, s, s, 0, 360);
// draw the label centered in the button
Font f = getFont();
if(f != null) {
FontMetrics fm = getFontMetrics(getFont());
g.setColor(getForeground());
g.drawString(label,
s/2 - fm.stringWidth(label)/2,
s/2 + fm.getMaxDescent());
}
}
/**
* The preferred size of the button.
*/
public Dimension getPreferredSize() {
Font f = getFont();
if(f != null) {
FontMetrics fm = getFontMetrics(getFont());
int max = Math.max(fm.stringWidth(label) + 40, fm.getHeight() + 40);
return new Dimension(max, max);
} else {
return new Dimension(100, 100);
}
}
/**
* The minimum size of the button.
*/
public Dimension getMinimumSize() {
return new Dimension(100, 100);
}
/**
* Adds the specified action listener to receive action events
* from this button.
* #param listener the action listener
*/
public void addActionListener(ActionListener listener) {
actionListener = AWTEventMulticaster.add(actionListener, listener);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
/**
* Removes the specified action listener so it no longer receives
* action events from this button.
* #param listener the action listener
*/
public void removeActionListener(ActionListener listener) {
actionListener = AWTEventMulticaster.remove(actionListener, listener);
}
/**
* Determine if click was inside round button.
*/
public boolean contains(int x, int y) {
int mx = getSize().width/2;
int my = getSize().height/2;
return (((mx-x)*(mx-x) + (my-y)*(my-y)) <= mx*mx);
}
/**
* Paints the button and distribute an action event to all listeners.
*/
public void processMouseEvent(MouseEvent e) {
#SuppressWarnings("unused")
Graphics g;
switch(e.getID()) {
case MouseEvent.MOUSE_PRESSED:
// render myself inverted....
pressed = true;
// Repaint might flicker a bit. To avoid this, you can use
// double buffering (see the Gauge example).
repaint();
break;
case MouseEvent.MOUSE_RELEASED:
if(actionListener != null) {
actionListener.actionPerformed(new ActionEvent(
this, ActionEvent.ACTION_PERFORMED, label));
}
// render myself normal again
if(pressed == true) {
pressed = false;
// Repaint might flicker a bit. To avoid this, you can use
// double buffering (see the Gauge example).
repaint();
}
break;
case MouseEvent.MOUSE_ENTERED:
break;
case MouseEvent.MOUSE_EXITED:
if(pressed == true) {
// Cancel! Don't send action event.
pressed = false;
// Repaint might flicker a bit. To avoid this, you can use
// double buffering (see the Gauge example).
repaint();
// Note: for a more complete button implementation,
// you wouldn't want to cancel at this point, but
// rather detect when the mouse re-entered, and
// re-highlight the button. There are a few state
// issues that that you need to handle, which we leave
// this an an excercise for the reader (I always
// wanted to say that!)
}
break;
}
super.processMouseEvent(e);
}
}
java.awt.Component does not support opacity/transparency, only Swing components do.
You should take the time to read through
Performing Custom Painting
Graphics2D
Painting in AWT and Swing
Writing Event Listeners
You have a number issues to start with...
You should extend from a light weight (Swing) component, as they support transparency
There is no need to override processMouseEvent, you should use a MouseListener instead
Your contains method should take into consideration the "shape" of the button
You should use the inbuilt event management API when adding new listeners
You should favor paintComponent over paint when painting lightweight components
YOU MUST CALL super.paintXxx from any paint method you override, there is only a very small number of times when you wouldn't, and then you become responsible for taking over there work
You need to mark the component as transparent by call setOpaque(false)
Unpressed/Pressed
public class TestRoundButton {
public static void main(String[] args) {
new TestRoundButton();
}
public TestRoundButton() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setBackground(Color.RED);
frame.setLayout(new GridBagLayout());
frame.add(new RoundButton(":)"));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RoundButton extends JPanel {
ActionListener actionListener; // Post action events to listeners
String label; // The Button's text
protected boolean pressed = false; // true if the button is detented.
private MouseListener listener;
/**
* Constructs a RoundButton with no label.
*/
public RoundButton() {
this("");
}
#Override
public void addNotify() {
super.addNotify();
if (listener == null) {
listener = new MouseHandler();
addMouseListener(listener);
}
}
#Override
public void removeNotify() {
removeMouseListener(listener);
super.removeNotify();
}
/**
* Constructs a RoundButton with the specified label.
*
* #param label the label of the button
*/
public RoundButton(String label) {
this.label = label;
//...
setOpaque(false);
// Use a mouse listener instead
// enableEvents(AWTEvent.MOUSE_EVENT_MASK);
}
/**
* gets the label
*
* #see setLabel
*/
public String getLabel() {
return label;
}
/**
* sets the label
*
* #see getLabel
*/
public void setLabel(String label) {
this.label = label;
invalidate();
repaint();
}
/**
* paints the RoundButton
*/
// Prefer paintComponent over paint...
#Override
protected void paintComponent(Graphics g) {
// YOU MUST CALL super.paintXxx THERE IS NO EXCUSE NOT TO, EVER!!
super.paintComponent(g);
int s = Math.min(getSize().width - 1, getSize().height - 1);
Graphics2D g2d = (Graphics2D) g.create();
// paint the interior of the button
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
Color highlight = getBackground();
if (pressed) {
highlight = highlight.darker();
}
Color darklight = highlight.darker();
LinearGradientPaint lgp = new LinearGradientPaint(
new Point(0, 0),
new Point(0, s),
new float[]{0, 1f},
new Color[]{highlight, darklight});
Ellipse2D shape = new Ellipse2D.Float(0, 0, s, s);
g2d.setPaint(lgp);
g2d.fill(shape);
// draw the perimeter of the button
g2d.setColor(getBackground().darker().darker().darker());
g2d.draw(shape);
// draw the label centered in the button
Font f = getFont();
if (f != null) {
FontMetrics fm = getFontMetrics(getFont());
g2d.setColor(getForeground());
g2d.drawString(label,
s / 2 - fm.stringWidth(label) / 2,
s / 2 + fm.getMaxDescent());
}
g2d.dispose();
}
/**
* The preferred size of the button.
*/
public Dimension getPreferredSize() {
Font f = getFont();
if (f != null) {
FontMetrics fm = getFontMetrics(getFont());
int max = Math.max(fm.stringWidth(label) + 40, fm.getHeight() + 40);
return new Dimension(max, max);
} else {
return new Dimension(100, 100);
}
}
/**
* The minimum size of the button.
*/
public Dimension getMinimumSize() {
return new Dimension(100, 100);
}
/**
* Adds the specified action listener to receive action events from this
* button.
*
* #param listener the action listener
*/
public void addActionListener(ActionListener listener) {
// actionListener = AWTEventMulticaster.add(actionListener, listener);
// enableEvents(AWTEvent.MOUSE_EVENT_MASK);
listenerList.add(ActionListener.class, listener);
}
/**
* Removes the specified action listener so it no longer receives action
* events from this button.
*
* #param listener the action listener
*/
public void removeActionListener(ActionListener listener) {
// actionListener = AWTEventMulticaster.remove(actionListener, listener);
listenerList.add(ActionListener.class, listener);
}
/**
* Determine if click was inside round button.
*/
public boolean contains(int x, int y) {
// This needs to work more on the actual painted shape...
int mx = getSize().width / 2;
int my = getSize().height / 2;
return (((mx - x) * (mx - x) + (my - y) * (my - y)) <= mx * mx);
}
/**
* Paints the button and distribute an action event to all listeners.
*/
// public void processMouseEvent(MouseEvent e) {
// #SuppressWarnings("unused")
// Graphics g;
// switch (e.getID()) {
// case MouseEvent.MOUSE_PRESSED:
// // render myself inverted....
// pressed = true;
//
// // Repaint might flicker a bit. To avoid this, you can use
// // double buffering (see the Gauge example).
// repaint();
// break;
// case MouseEvent.MOUSE_RELEASED:
// if (actionListener != null) {
// actionListener.actionPerformed(new ActionEvent(
// this, ActionEvent.ACTION_PERFORMED, label));
// }
// // render myself normal again
// if (pressed == true) {
// pressed = false;
//
// // Repaint might flicker a bit. To avoid this, you can use
// // double buffering (see the Gauge example).
// repaint();
// }
// break;
// case MouseEvent.MOUSE_ENTERED:
//
// break;
// case MouseEvent.MOUSE_EXITED:
// if (pressed == true) {
// // Cancel! Don't send action event.
// pressed = false;
//
// // Repaint might flicker a bit. To avoid this, you can use
// // double buffering (see the Gauge example).
// repaint();
//
// // Note: for a more complete button implementation,
// // you wouldn't want to cancel at this point, but
// // rather detect when the mouse re-entered, and
// // re-highlight the button. There are a few state
// // issues that that you need to handle, which we leave
// // this an an excercise for the reader (I always
// // wanted to say that!)
// }
// break;
// }
// super.processMouseEvent(e);
// }
public class MouseHandler extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
pressed = true;
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
pressed = false;
repaint();
}
}
}
}
Now, having done all that, you might like to take a serious look at javax.swing.AbstractButton as you base component