When it is clicked on JLabel, I want to understand if the click was on "Icon part", or "Text part" of the JLabel, so that different action can be taken. Is there a clever way to do that? Or just I have to solve it relatively with the coordinates of the icon and text?
+1 to #aymeric comment.
What about having two different JLabels
However I do understand why you might be hesitating
negative: requires maintenance of 2 labels.
My clever (:P) solution to this is create your own abstract component - which accepts icon and text as parameters for constructor - by extending JPanel and than adding 2 JLabels to the JPanel, each label has its on MouseAdapter which calls abstract method xxxClicked() (thus any implementing class must override these methods).
Here is an example I made:
import java.awt.FlowLayout;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ImageIcon ii = null;
try {
//I dont remmend getScaledInstance just used it for speed of code writing
ii = new ImageIcon(ImageIO.read(new URL("http://www.candonetworking.com/java.gif")).getScaledInstance(32, 32, Image.SCALE_SMOOTH));
} catch (Exception ex) {
ex.printStackTrace();
}
MyLabel ml = new MyLabel(ii, "Something") {
#Override
void iconClicked() {
System.out.println("Icon clicked");
}
#Override
void textClicked() {
System.out.println("Text clicked");
}
};
frame.add(ml);
frame.pack();
frame.setVisible(true);
}
});
}
}
abstract class MyLabel extends JPanel {
JLabel iconLabel;
JLabel textLabel;
MouseAdapter iconMA;
MouseAdapter textMA;
public MyLabel(ImageIcon icon, String text) {
iconLabel = new JLabel(icon);
textLabel = new JLabel(text);
iconMA = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent me) {
super.mouseClicked(me);
iconClicked();
}
};
textMA = new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent me) {
super.mouseClicked(me);
textClicked();
}
};
iconLabel.addMouseListener(iconMA);
textLabel.addMouseListener(textMA);
add(iconLabel);
add(textLabel);
}
abstract void iconClicked();
abstract void textClicked();
public JLabel getIconLabel() {
return iconLabel;
}
public JLabel getTextLabel() {
return textLabel;
}
}
Related
I must use a swing-ui designer tool to create my UI, that only supports graphically editing JPanels. Those panels (they basically contain complex button designs) to work like a JButton. I cannot use anything other than JPanel as base class of these panels (UI editor limitation).
What is the most generic solution to do this?
Create a custom button that uses the panel's draw method instead of
it's own?
Create a base-panel class that reimplements the whole
button?
Another more elegant solution?
Here is a quick demo, to show you how you could use borders to simulate a button.
The demo also reacts to mouse and key events :
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.border.EtchedBorder;
public class JPanelButton extends JPanel {
Border raisedetched = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
Border loweredetched = BorderFactory.createEtchedBorder(EtchedBorder.LOWERED);
public static void main(final String[] args) {
JFrame frame = new JFrame();
final JPanelButton panel = new JPanelButton();
panel.raiseBorder();
panel.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(final MouseEvent e) {
panel.lowerBorder();
}
#Override
public void mouseReleased(final MouseEvent e) {
panel.raiseBorder();
}
});
panel.setFocusable(true); // you need this or the panel won't get the key events
panel.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(final KeyEvent e) {
panel.lowerBorder();
}
#Override
public void keyReleased(final KeyEvent e) {
panel.raiseBorder();
}
});
frame.setContentPane(panel);
frame.setSize(100, 100);
frame.setVisible(true);
}
public void raiseBorder() {
setBorder(raisedetched);
}
public void lowerBorder() {
setBorder(loweredetched);
}
}
Simply add MouseListener.
JPanel jp = new JPanel();
jp.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e) {
System.out.println("Clicked");
}
});
If this answer isn't specific enough, leave a comment and I'll give you more explanation.
I found a cool way in another question to create a JButton whose actions are written and viewed in an easy way:
public JButton makeToolbarButton(String title, String actionCommand) {
JButton button = new JButton(title);
button.setActionCommand(actionCommand);
button.addActionListener(this);
return button;
}
The class this method is in implements ActionListener, and the buttons commands are assigned by:
public void actionPerformed(ActionEvent e) {
int action = Integer.parseInt(e.getActionCommand());
switch(action) {
case 1:
System.out.println("This button pressed.");
break;
}
}
And the buttons are made by:
JButton button1 = makeToolbarButton("Button 1", "1");
So my question is: can I add KeyStrokes to a button by this method? I tried something like this (inside of the makeToolbarButton method):
button.getInputMap().put(KeyStroke.getKeyStroke("B"), "button_pressed");
button.getActionMap().put("button_pressed", button.getAction());
But I figure this doesn't work because the action command isn't actually assigning an action to a specific button. Is there a way to add something to the makeToolbarButton() method and a parameter for the KeyStroke to accomplish this?
I think you're missing the point of the Action API. A Action is intended to provide a single, self contained, unit of work. This means that the actionCommand really isn't required, as when the actionListener event is triggered, you know exactly the context in which it's been executed
I'd also avoid using KeyStroke.getKeyStroke(String), as the text is a verbose description of what you want to do (ie pressed B or something, but needless to say, it's a pain to get right)
So, the following demonstrates how you might use Actions and assign them to a button AND a key binding
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ActionTest {
public static void main(String[] args) {
new ActionTest();
}
public ActionTest() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
add(createButton(new ActionOne(), KeyStroke.getKeyStroke(KeyEvent.VK_1, 0)));
add(createButton(new ActionTwo(), KeyStroke.getKeyStroke(KeyEvent.VK_2, 0)));
}
public JButton createButton(Action action, KeyStroke keyStroke) {
JButton btn = new JButton(action);
btn.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(keyStroke, "button_pressed");
btn.getActionMap().put("button_pressed", action);
return btn;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.dispose();
}
}
public class ActionOne extends AbstractAction {
public ActionOne() {
putValue(NAME, "1");
putValue(Action.ACTION_COMMAND_KEY, "Action.one");
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand());
}
}
public class ActionTwo extends AbstractAction {
public ActionTwo() {
putValue(NAME, "2");
putValue(Action.ACTION_COMMAND_KEY, "Action.two");
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand());
}
}
}
See How to Use Actions for more details
I am trying to create a JTextArea which scrolls to bottom every time a text is appended to that text area. Otherwise, the user should be able to scroll top and see previous message. I used this code:
JTextArea terminalText = new JTextArea();
JPanel terminal = new JPanel();
terminal.setLayout(new BorderLayout());
add(terminal); //Adds the terminal to mother JPanel
//I added scrollbar to my JTextArea
JScrollPane scroll = new JScrollPane(terminalText);
terminal.add(scroll, BorderLayout.CENTER);
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
scroll.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
public void adjustmentValueChanged(AdjustmentEvent e) {
e.getAdjustable().setValue(e.getAdjustable().getMaximum());
}});
So far this code seems to make my text area scroll to bottom of the terminalText text area every time I append something to terminalText using terminalText.append.
However, the user cannot use scroll bar to scroll to the top to see previous message. Is there a way to fix this? Should I be using DocumentListener to achieve this?
Check out Smart Scrolling.
If the scrollbar is at the bottom, then as text is appended you will see the new text.
If the user has scrolled to a different position, then the viewport will stay there until the user scrolls back to the bottom.
As a simple (and rough) proof of concept...
This basically adds a DocumentListener to the JTextArea and on any Document event, use setCaretPosition to move the caret to the end of the document.
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Date;
import java.util.WeakHashMap;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.JTextComponent;
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();
}
JTextArea ta = new JTextArea(10, 20);
ta.setWrapStyleWord(true);
ta.setLineWrap(true);
MoveToTheBottom.install(ta);
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(ta));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer timer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
ta.append(new Date().toString() + "\n");
}
});
timer.start();
}
});
}
public static class MoveToTheBottom implements DocumentListener {
private static WeakHashMap<JTextComponent, DocumentListener> registry = new WeakHashMap<>(25);
private JTextComponent parent;
protected MoveToTheBottom(JTextComponent parent) {
this.parent = parent;
parent.getDocument().addDocumentListener(this);
}
public static void install(JTextComponent parent) {
MoveToTheBottom bottom = new MoveToTheBottom(parent);
registry.put(parent, bottom);
}
public static void uninstall(JTextComponent parent) {
DocumentListener listener = registry.remove(parent);
if (listener != null) {
parent.getDocument().removeDocumentListener(listener);
}
}
#Override
public void insertUpdate(DocumentEvent e) {
parent.setCaretPosition(e.getDocument().getLength());
}
#Override
public void removeUpdate(DocumentEvent e) {
parent.setCaretPosition(e.getDocument().getLength());
}
#Override
public void changedUpdate(DocumentEvent e) {
parent.setCaretPosition(e.getDocument().getLength());
}
}
}
The example demonstrates a possible re-usable API which you can use to "install" and "uninstall" the support as reqiured
I am messing around with some ideas for a side project and I would like to create a GUI using Java swing that doesn't look like it is from Windows95. One of the ideas I was kicking around was to use JLabels as buttons instead of the standard JButton. This would allow me to customize hover, drag, and movement effects as I like.
Research into the MouseAdapter class should allow me to do everything I intend, unfortunately I am having some trouble implementing the hover effect as I wanted as the JLabel does not appear to update. I have tried updating the Frame directly by calling frame.update(getGraphics()); but that does not appear to work as I think it does.
Can I get some advice on how to update the label properly.
Note: This is just an example with no effort put in to organize the code efficiently
public class Window extends JFrame {
/**
*
*/
private static final long serialVersionUID = 5259700796854880162L;
private JTextField textField;
private JLabel lblNewLabel;
static Window frame;
int i = 0;
public Window() {
JPanel panel = new JPanel();
getContentPane().add(panel, BorderLayout.CENTER);
panel.setLayout(null);
lblNewLabel = new JLabel("New label");
lblNewLabel.setBackground(Color.LIGHT_GRAY);
lblNewLabel.setBounds(137, 38, 114, 70);
panel.add(lblNewLabel);
lblNewLabel.addMouseListener(new LabelAdapter());
textField = new JTextField();
textField.setBounds(122, 119, 86, 20);
panel.add(textField);
textField.setColumns(10);
}
private class LabelAdapter extends MouseAdapter {
#Override
public void mouseClicked(MouseEvent e) {
textField.setText(String.valueOf(i));
i++;
}
#Override
public void mouseEntered(MouseEvent e) {
lblNewLabel.setBackground(Color.CYAN);
}
#Override
public void mouseExited(MouseEvent e) {
lblNewLabel.setBackground(Color.LIGHT_GRAY);
}
}
/**
* #param args
*/
public static void main(String[] args) {
frame = new Window();
frame.setSize(900, 700);
frame.setVisible(true);
}
}
Window is reserver name for awt.Window, change this name to e.g. MyWindow
JPanel has implemented FlowLayout, you can't to use NullLayout use built_in LayoutManager, then to use JFrame.pack() before JFrame.setVisible for proper sizing on the screen
JLabel is transparent, change that by using JLabel.setOpaque(true);
refresh of Backgroung Color from Mouse over/hover isn't possible without JLabel.repaint() as last code line in concrete mouse_event, repaint() missing in JLabel API
On top of mKorbel's answer...
I don't know why you're going to so much effort, when you could actually make a button look like a label.
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class NotALabel {
public static void main(String[] args) {
new NotALabel();
}
public NotALabel() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
final JButton btn = new JButton("Am I label or a button?");
btn.setContentAreaFilled(false);
btn.setBorderPainted(false);
btn.setFocusPainted(false);
btn.setOpaque(true);
btn.getModel().addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
ButtonModel model = (ButtonModel) e.getSource();
if (model.isRollover()) {
btn.setBackground(Color.CYAN);
} else {
btn.setBackground(null);
}
}
});
btn.addActionListener(new ActionListener() {
private int count = 0;
#Override
public void actionPerformed(ActionEvent e) {
count++;
((JButton) e.getSource()).setText("I'm a super button!! Or label...");
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
frame.add(btn);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
You should also consider trying to Setting the look and feel or even possibly Modifying the look and feel
I have some simple code where I'm trying to get keyboard events into a Java applet. The code runs just fine when being run with appletviewer, but when I'm loading it from a browser (tried both Chrome and Firefox), the JApplet won't get focus on click.
Trying exactly the same code with Applet instead of JApplet works without a problem.
Here's my code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Test extends JApplet {
String s = "";
public void init() {
setFocusable(true);
setEnabled(true);
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
s = "KEY PRESSED: " + e.getKeyCode();
repaint();
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
boolean ret = requestFocusInWindow();
s = "requestFocusInWindow: " + ret;
repaint();
}
});
requestFocusInWindow();
}
public void start() {
requestFocusInWindow();
}
public void paint(Graphics g) {
super.paint(g);
requestFocusInWindow();
g.setColor(Color.BLACK);
s = "Focus owner: " + isFocusOwner() + ", " + s;
g.drawString(s, 24, 24);
}
}
Applets should be created on Event Dispatch Thread by wrapping code in overridden init() method in SwingUtilities.invokeAndWait() block
Dont use KeyListener for JApplet/Swing components use KeyBindings
call requestFocusInWindow() on JApplet after creating and adding all content to container (this is not necessary with keybindings though)
Also dont do drawing in paint() rather add JPanel to container and override paintComponent(..)
Here is a small example, its a simple JLabel with a dummy label and textfield added to the container with a KeyBinding for A only; so when A is pressed it will be added to JLabel text:
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.lang.reflect.InvocationTargetException;
import javax.swing.AbstractAction;
import javax.swing.JApplet;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
public class Test extends JApplet {
#Override
public void init() {
try {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
final JLabel label = new JLabel("Text:");
final JLabel label2 = new JLabel("Dummy label");
final JTextField jtf = new JTextField("Dummy Field");
label2.setFocusable(true);
label.setFocusable(true);
//allwos user to add letter A to JLabel
label.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_A, 0), "A");
label.getActionMap().put("A", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
String tmp = label.getText();
label.setText(tmp + "A");
}
});
setLayout(new GridLayout(3, 1));
add(label);
add(label2);
add(jtf);
}
});
} catch (InterruptedException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
}