I was recently given with the task of completely revising one of my company's software Gui appearance.
Up until now I only done basic look-n-feel changes. Mostly upgrading the Gui from the default Metal L&F to System L&F with some GridBagConstraints handling.
But this task is something different:
We have a graphic designer who drew the desired design:
I know how to create this kind of functionality. I might use a double JTabbedPanes or just add Jpanels all around.
My problem is how to incorporate the graphics with gui objects.
What should I ask the designer to give me in order to make a Java Swing application that looks like this - with the rounded tabs and the merging of tab-panel colors?
How can I make JPanels or a JTabbedPanes wear this fancy costume when all I am given right now is this drawing above?
Most custom Swing GUIs are made using JLabels with ImageIcons and custom handling code. For example:
public class CustomGUI extends JFrame {
private JLabel frame, border, grip, exit, minimize;
private Point initialClick;
public CustomGUI() {
initComponents();
}
private void initComponents() {
setTitle("CustomGUI");
setMinimumSize(new Dimension(640, 480));
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setUndecorated(true);
setBackground(new Color(0, 0, 0, 0));
setLayout(null);
pack();
frame = new JLabel();
frame.setBounds(0, 0, 640, 480);
frame.setIcon(new ImageIcon(getClass().getResource("frame.png"));
border = new JLabel();
border.setBounds(0, 0, 640, 480);
border.setIcon(new ImageIcon(getClass().getResource("border.png"));
grip = new JLabel();
grip.setBounds(215, 0, 376, 25);
grip.setIcon(new ImageIcon(getClass().getResource("grip.png"));
grip.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent evt) {
initialClick = evt.getPoint();
}
});
grip.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent evt) {
int thisX = getLocation().x;
int thisY = getLocation().y;
int xMoved = (thisX + evt.getX()) - (thisX + initialClick.x);
int yMoved = (thisY + evt.getY()) - (thisY + initialClick.y);
int posX = thisX + xMoved;
int posY = thisY + yMoved;
setLocation(posX, posY);
}
});
exit = new JLabel();
exit.setBounds(620, 4, 32, 16);
exit.setIcon(new ImageIcon(getClass().getResource("exit.png"));
exit.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent evt) {
dispatchEvent(new WindowEvent(new UI(), WindowEvent.WINDOW_CLOSING));
}
#Override
public void mouseEntered(MouseEvent evt) {
exit.setIcon(new ImageIcon(getClass().getResource("exitGlow.png"));
}
#Override
public void mouseExited(MouseEvent evt) {
exit.setIcon(new ImageIcon(getClass().getResource("exit.png"));
}
});
minimize = new JLabel();
minimize.setBounds(600, 4, 16, 16);
minmize.setIcon(new ImageIcon(getClass().getResource("minimize.png"));
minimize.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent evt) {
setState(Frame.ICONIFIED);
}
#Override
public void mouseEntered(MouseEvent evt) {
minimize.setIcon(new ImageIcon(getClass().getResource("minimizeGlow.png"));
}
#Override
public void mouseExited(MouseEvent evt) {
minimize.setIcon(new ImageIcon(getClass().getResource("minimize.png"));
}
});
add(minimize);
add(exit);
add(grip);
add(border);
add(frame);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
new CustomGUI().setVisible(true);
});
}
}
This code creates a custom GUI with a custom frame, border, and a grip for dragging the window, including exit and minimize buttons that glow when your mouse enters them, making them look proffesional. (The images for each component need to be in the root of the project for this to work.) If you want to create custom tabbed panes, create JLabels with ImageIcons that look like rounded tabs or anything that you want them to look like, each with its corresponding text on it, and add handling that will make it look selected by changing its color or adding a border around it and bring its corresponding panel to the front, hiding all other panels.
Alternatively, you can create a custom Look and Feel, but this is harder to do and does not give you the freedom that using JLabels with ImageIcons does.
You can also use JavaFX, a rich GUI platform that was designed to replace Swing, but as far as Swing goes, the above code is your best bet for a Swing application.
Related
I'm developing a Java application that demands a customized button. I'm using Swing for the GUI and found myself limited to some tricky solutions.
Here's one I found (from this website). It's supposed to use a custom image for the button and make it round.
public class RoundButton extends JButton
{
private static final long serialVersionUID = 1L;
protected Shape shape, base;
public RoundButton()
{
this(null, null);
}
public RoundButton(Icon icon)
{
this(null, icon);
}
public RoundButton(String text)
{
this(text, null);
}
public RoundButton(Action a)
{
this();
setAction(a);
}
public RoundButton(String text, Icon icon)
{
setModel(new DefaultButtonModel());
init(text, icon);
if(icon==null) return;
setBorder(BorderFactory.createEmptyBorder(1,1,1,1));
setBackground(Color.BLACK);
setContentAreaFilled(false);
setFocusPainted(false);
//setVerticalAlignment(SwingConstants.TOP);
setAlignmentY(Component.TOP_ALIGNMENT);
init_shape();
}
protected void init_shape()
{
if(!getBounds().equals(base))
{
Dimension s = getPreferredSize();
base = getBounds();
shape = new Ellipse2D.Float(0, 0, s.width-1, s.height-1);
}
}
#Override
public Dimension getPreferredSize()
{
Icon icon = getIcon();
Insets i = getInsets();
int iw = Math.max(icon.getIconWidth(), icon.getIconHeight());
return new Dimension(iw+i.right+i.left, iw+i.top+i.bottom);
}
#Override
protected void paintBorder(Graphics g)
{
init_shape();
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(getBackground());
//g2.setStroke(new BasicStroke(1.0f));
g2.draw(shape);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
}
#Override
public boolean contains(int x, int y)
{
init_shape();
return shape.contains(x, y);
//or return super.contains(x, y) && ((image.getRGB(x, y) >> 24) & 0xff) > 0;
}
}
Here's some test code I wrote:
public class BtnTest extends JFrame
{
private static final long serialVersionUID = 1L;
private RoundButton btn;
public BtnTest()
{
init_components();
}
private void init_components()
{
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 400);
setBackground(Color.BLACK);
setResizable(false);
btn = new RoundButton(
new ImageIcon("/path/to/file.png"));
btn.setBounds(50, 50, 50, 50);
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e)
{
System.out.println("click");
}
});
add(btn);
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
BtnTest frame = new BtnTest();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
This is the result:
The problem is: the image's rendered away from the button location, so my ActionListener isn't triggered when I press the icon, but when the region inside the black circle (top left) is clicked. Can someone explain me why, and offer a solution?
PS: I'm in my first year of Java programming, so make it as simple as possible please.
PS2: JavaFX and other external solutions are out of question, this must be done pure Java.
The problem is: the image's rendered away from the button location,
What is happening is that you are adding the button to the frame without using a constraint. By default this means the component is added to BorderLayout.CENTER, which means the component will be sized to fill the entire frame.
Also, by default, when you paint an Icon in a JLabel, the Icon is centered in the spaced available to the label, so you see the Icon in the center of the frame. Try resizing the frame to see the Icon move.
However, you hard code the painting of the Border to be painted at the (0, 0) location of the label so it paints at the top/left.
The contains() method is also defined from the top/left of the component, so mouse detection only works from the top/left, not the center.
This means your button must always be painted at its "preferred size" in order for it to be painted properly and for the contains(...) method to work.
A simple way to demonstrate this is to use:
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout( new FlowLayout() );
The FlowLayout does respect the preferred size of the button, so the Icon and Border will be painted properly.
Other options (instead of changing the layout of the frame) are to use a "wrapper" panel for the button. For example:
//add(btn);
JPanel wrapper = new JPanel(); // default to FlowLayout
wrapper.add( btn );
add( wrapper );
Now when you add the panel to the frame, the panel will grow in size, but the button will still be painted at its preferred size.
btn.setBounds(50, 50, 50, 50);
Also, not you should NOT be using this method. It is the job of the layout manager to set the size/location of the component. The above statement is effectively ignored.
I am attempting to make a simple traffic light frame. This is being made by the main frame TrafficBox:
public class TrafficBox extends JFrame {
public TrafficBox() {
setLayout(new BorderLayout(100,100));
add(new TrafficLight(Color.GREEN), BorderLayout.NORTH);
add(new TrafficLight(Color.ORANGE), BorderLayout.CENTER);
add(new TrafficLight(Color.RED), BorderLayout.SOUTH);
setBounds(100, 100, 800, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TrafficBox();
}
});
}
which then as seen, adds in 3 TrafficLight components which are based on JPanel, and have the following code:
public class TrafficLight extends JPanel {
private final int BALL_DIAMETER = 100;
private Color color;
public TrafficLight(Color color) {
//setSize(BALL_DIAMETER, BALL_DIAMETER);
this.color = color;
new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent e) {
repaint();
}
}).start();
}
public void paintComponent(Graphics g) {
g.setColor(color);
g.fillOval(0, 0, BALL_DIAMETER, BALL_DIAMETER);
}
This does sort of what I want, it draws all 3 circles as expected although a majority of the North(green) and south(red) lights are being cut off. I assume this is because the north/south spot are much smaller than the center.
I've tried using setSize(); to set the size of the panels to the size of the circles, however that does not work. Is there a way I can make it so that the full circle will be visible?
So, most layout managers will need some "hints" to be provided by the components in order to know how they want to be laid out.
You will need to override the getPreferredSize and return a size which best meets your needs, for example...
public class TrafficLight extends JPanel {
private final int BALL_DIAMETER = 100;
//...
public Dimension getPreferredSize() {
return new Dimension(BALL_DIAMETER, BALL_DIAMETER);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent();
g.setColor(color);
g.fillOval(0, 0, BALL_DIAMETER, BALL_DIAMETER);
}
Also, paintComponent doesn't ever need to be public no-one else should be calling it and you should call super.paintComponent() before performing any custom painting.
I'd also recommend maybe using GridLayout for this
I'd also argue that TrafficLight doesn't need a Timer of it's own and the should be controlled externally, but that's me
setBounds(100, 100, 800, 600);
Is best avoided, use pack() to size the window to the preferred size of the content and setLocation to position it
Simple fix, needed to be using setPreferredSize() rather than setSize.
I'm building a UI in Java. I want to create new components, like a JLabel, using a button. So every time I click a button it creates a new JLabel and places them in a specific JPanel.
Then, I want to be able to do some things with the labels based on how the user clicks on them.
With a left mouse press I want them to be able to drag the labels around the screen.
With a right mouse click I want to be open a new window where certain data can be entered, tied to the label (which might involve dynamically creating variables).
I've been toying around with some code I've Googled around for. I can get a button to create new labels in a panel, but when I try to get them to drag, I can only get one label at a time to appear, and after a second button press, moving the label isn't smooth, it jumps around.
I haven't even tried to implement any of the right mouse click things yet. If anyone can point me in the right direction, I'd appreciate it.
public class Testing {
JFrame frame;
//Launch the application.
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
Testing window = new Testing();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
//Create the application.
public Testing() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
JPanel area;
JButton btnCreate;
JLabel dragLabel;
frame = new JFrame();
frame.setBounds(100, 100, 511, 542);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
frame.setVisible(true);
area = new JPanel();
area.setBounds(10, 11, 477, 404);
frame.getContentPane().add(area);
area.setLayout(new BorderLayout());
btnCreate = new JButton("Create Label");
dragLabel = new JLabel("Drag Me");
btnCreate.setBounds(10, 425, 477, 67);
frame.getContentPane().add(btnCreate);
btnCreate.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
area.add(dragLabel);
area.revalidate();
DragListener drag = new DragListener();
dragLabel.addMouseListener(drag);
dragLabel.addMouseMotionListener(drag);
}
});
}
}
class DragListener extends MouseInputAdapter
{
Point location;
MouseEvent pressed;
public void mousePressed(MouseEvent me) {
pressed = me;
}
public void mouseDragged(MouseEvent me)
{
if(SwingUtilities.isLeftMouseButton(me)){
Component component = me.getComponent();
location = component.getLocation(location);
int x = location.x - pressed.getX() + me.getX();
int y = location.y - pressed.getY() + me.getY();
component.setLocation(x, y);
}
}
}
EDIT - I'm fairly certain the primary issue is in how the JLabel itself is being added to the panel. Every time the button is being pushed it's adding the same label over again, and this is gumming up the works.
Unfortunately, I'm not sure how to deal with that. I've done a bit more digging, and since dynamic variables aren't possible, I'm going to have to use an array or a map or some sort. With that, it appears I can declare arrays of components. Would something like that be necessary for my purposes?
Really odd stuff in your code. I don't want to go everything, and I'm not an expert by any stretch of the imagination, but I tried to remove redundant or contradictory stuff. I suspect a part of what you did was just copy pasting bits without really fitting them into the code.
Anyway, you needed to create the label inside the listener, so that it creates a new one everytime you click. Otherwise you only ever create one label and just reuse the same everytime.
I implemented a dialog on right click to enter the label name, don't know what you wanted to do but at least it detects right clicks.
Also in general it's easier to use layout managers instead of hardcoding everything. Here you had a borderlayout but were ignoring it.
class Main {
//Launch the application.
public static void main(String[] args) {
DrageableLabel window = new DrageableLabel();
}
}
public class DrageableLabel {
public DrageableLabel() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize() {
JFrame frame = new JFrame();
Container area = frame.getContentPane();
area.setLayout(new BorderLayout());
JButton btnCreate = new JButton("Create Label");
btnCreate.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) {
/*
This is where you create your new window
for now I've added a dialog that takes a string parameter and creates a label with that string
I moved the method code to create a new drageable label outside the actionlistener to make it less confusing and reuseable
Either build w-e you want directly in here
or call a method that does it (which I prefer)
*/
String string = JOptionPane.showInputDialog(frame, "Enter your message", "Messages", JOptionPane.CANCEL_OPTION);
addDrageableLabel(string, area);
} else if (SwingUtilities.isLeftMouseButton(e)) {
addDrageableLabel("Drag me", area);
}
}
});
area.add(btnCreate, BorderLayout.SOUTH);
frame.setBounds(100, 100, 511, 542);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
// This is the method that creates and adds a drageable label
public void addDrageableLabel(String labelName, Container container) {
JLabel dragLabel = new JLabel(labelName);
container.add(dragLabel, BorderLayout.CENTER);
container.validate();
DragListener drag = new DragListener();
dragLabel.addMouseListener(drag);
dragLabel.addMouseMotionListener(drag);
}
}
class DragListener extends MouseInputAdapter {
Point location;
MouseEvent pressed;
#Override
public void mousePressed(MouseEvent me) {
pressed = me;
}
#Override
public void mouseDragged(MouseEvent me) {
Component component = me.getComponent();
location = component.getLocation(location);
int x = location.x - pressed.getX() + me.getX();
int y = location.y - pressed.getY() + me.getY();
component.setLocation(x, y);
}
}
I'm trying to create a custom mouse over event on a JButton. The reason being that my JButton is currently an image, so I had to remove all the borders and animations and what not. so I did this:
btnSinglePlayer.setOpaque(false);
btnSinglePlayer.setContentAreaFilled(false);
btnSinglePlayer.setBorderPainted(false);
And that works perfect to only display the image, and the button does in fact work. I want to know if there's any pre-built methods perhaps that can do this, or how I would go about learning to do what I want.
More specifically, what I want the image to do when I mouse over is for it to get just a bit bigger.
I have tried these so far, and did nothing:
btnSinglePlayer.setRolloverIcon(singlePlayerButton);
btnSinglePlayer.setPressedIcon(singlePlayerButton);
for Icon to use implemented methods in API
you can to use ButtonModel with ChangeListener
(by default) for JButtons JComponents there no reason to use Mouse(Xxx)Listener or its MouseEvent, all those events are implemented and correctly
As an alternative You can achieve this by registering MouseListener to the JButton and override mouseEntered() ,mouseExited() , mousePressed() and mouseReleased() method.For Example:
final ImageIcon icon1 = new ImageIcon("tray.gif");
final JButton button = new JButton(icon1);
final int width = icon1.getIconWidth();
final int height = icon1.getIconHeight();
button.addMouseListener(new MouseAdapter()
{
public void mouseEntered(MouseEvent evt)
{
icon1.setImage((icon1.getImage().getScaledInstance(width + 10, height,Image.SCALE_SMOOTH)));
//button.setIcon(icon1);
}
public void mouseExited(MouseEvent evt)
{
icon1.setImage((icon1.getImage().getScaledInstance(width , height,Image.SCALE_SMOOTH)));
}
public void mousePressed(MouseEvent evt)
{
icon1.setImage((icon1.getImage().getScaledInstance(width + 5, height,Image.SCALE_SMOOTH)));
}
public void mouseReleased(MouseEvent evt)
{
icon1.setImage((icon1.getImage().getScaledInstance(width + 10, height,Image.SCALE_SMOOTH)));
}
});
button.setOpaque(false);
button.setContentAreaFilled(false);
button.setBorderPainted(false);
I have been searching for a reason for this behavior in my code for quite some time now. I do not want to dive too deeply into the Swing API to figure why this occurs. I would appreciate any information regarding what causes this problem.
This is a simplified version of the application I am writing, the problems seems to be when I click draw the first time, the image will not paint onto the panel, but when I click it the second time, it would paint the image perfectly. Any drawing done after will work correctly, but the initial paint problem is annoying me greatly. Thanks for any help! :)
public class ImageTester {
public static void main(String[] args) {
final JFrame window = new JFrame("Image Tester");
final JPanel pane = new JPanel();
JButton draw = new JButton("Draw");
JButton paint = new JButton("Paint");
Toolkit k = Toolkit.getDefaultToolkit();
final Image i = k.createImage("tester.jpg");
//pane.getGraphics().drawImage(i, 22, 15, null);
draw.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
System.out.println(pane.getGraphics());
pane.getGraphics().drawRect(5, 5, 15, 15);
pane.getGraphics().drawImage(i, 15, 15, null);
System.out.println("Performance");
}
});
paint.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
}
});
pane.add(draw);
pane.add(paint);
window.add(pane);
window.setVisible(true);
window.setSize(new Dimension(400, 400));
window.setLocationRelativeTo(null);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Besides the advice of camickr..
Images load asynchronously using Toolkit.createImage(). Either use ImageIO.read(URL/File/InputStream) or add a MediaTracker.
E.G.
On first run I see.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.net.URL;
import javax.imageio.ImageIO;
public class ImageTester {
public static void main(String[] args) throws Exception {
final JFrame window = new JFrame("Image Tester");
JButton draw = new JButton("Draw");
JButton paint = new JButton("Paint");
final Image i = ImageIO.read(new URL(
"http://pscode.org/media/citymorn2.jpg"));
ImagePanel gui = new ImagePanel();
gui.setImage(i);
draw.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
}
});
paint.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
}
});
gui.add(draw);
gui.add(paint);
window.add(gui);
window.setVisible(true);
window.setSize(new Dimension(400, 400));
window.setLocationRelativeTo(null);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
class ImagePanel extends JPanel {
Image i;
public void setImage(Image image) {
i = image;
}
public void paintComponent(Graphics g) {
//System.out.println(pane.getGraphics());
super.paintComponent(g);
g.drawRect(5, 5, 15, 15);
g.drawImage(i, 15, 15, null);
System.out.println("Performance");
}
}
Don't use the getGraphics() method. That painting will be lost the next time Swing determines a component needs to repaint itself.
Custom painting is done by overriding the paintComponent() method of a JPanel (or JComponent) and then you add the panel to the frame.
See Custom Painting for more information and examples.
When you use createImage(), the image data is loaded, but it isn't translated into renderable pixels until it knows what component it's going to be drawn on. The Toolkit.prepareImage() method can do this. Add this line to the end of your program, and the paint problem will go away:
k.prepareImage(i, -1, -1, pane);