I have a JTabbedPane with a custom tab component. That component contains a JLabel (To display the tab title) and a JButton (A close button). When I change the text in the JLabel the JLabel stops receiving mouse events and I can no longer select that tab when I click directly on the label instead if I click around the label then I can select the tab. Any ideas?
A snippet of the code:
class ShellPanelTabComponent extends JPanel implements ActionListener{
private ShellPanel panel;
private JLabel label;
public ShellPanelTabComponent(final ShellPanel panel){
super(new FlowLayout(FlowLayout.LEFT, 0, 0));
this.panel = panel;
setOpaque(false);
label = new JLabel(panel.getTitle());
label.setFocusable(false);
add(label);
label.setBorder(BorderFactory.createEmptyBorder(2,0,0,5));
//now the button
CloseButton closeButton = new CloseButton(panel);
add(closeButton);
closeButton.addActionListener(this);
}
public void actionPerformed(ActionEvent ae) {
panel.getShell().removeShellPanel(panel);
}
/**
* #return the label
*/
public JLabel getLabel() {
return label;
}
}
I don't recall seeing such a problem in the TabComponentsDemo, discussed in How to Use Tabbed Panes. You might compare your code with that example as a reference.
Addendum: Re-factoring ButtonTabComponent to include getLabel(), this version of runTest() in TabComponentsDemo adds a button that evinces the desired behavior. In particular, each time the button is pressed, the tabs are redrawn to display the enlarged title.
Update: Modify correct tab component after pane.remove().
public void runTest() {
pane.removeAll();
for (int i = 0; i < tabNumber; i++) {
final int titleIndex = i;
String title = "Tab " + titleIndex;
final JButton button = new JButton("Relabel tab");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int index = pane.indexOfComponent(button);
ButtonTabComponent btc = (ButtonTabComponent)
pane.getTabComponentAt(index);
JLabel label = btc.getLabel();
pane.setTitleAt(index, label.getText() + titleIndex);
label.invalidate();
pane.repaint();
}
});
pane.add(title, button);
initTabComponent(i);
}
tabComponentsItem.setSelected(true);
pane.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
scrollLayoutItem.setSelected(false);
this.setPreferredSize(new Dimension(500, 200));
this.pack();
this.setLocationRelativeTo(null);
this.setVisible(true);
}
I seem to remember a question like this recently although I can't find the posting. I believe the problem is that the "custom component" receives the mouse event so it is not passed on to the tabbed pane. The solution suggested was to use the dispatchEvent(...) method to redispatch the mouse event to the proper tab.
The problem is related to the one that I posted here after I did more digging: Workaround for setToolTipText consuming mouse events?
Related
I'm new to java and working on a GUI assignment and I'm running into some issues. The premise is a simple GUI window that has a few mouse events and a few keyboard events.
I put together a window with some mouse events and once that was working started to add a couple JTextFields to the window but they're not showing up on the window and I'm not exactly sure why.
Here is the problem now. I created a new panel (panel2) to add the JTextFields to the window and I now see the JTextFields but it overtakes the entire window and the Mouse Events do not work with it. If I add the JTF to the panel above the Mouse Events then the JTF do not show up and the Mouse Events work.......
code
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class EventDemo extends JFrame {
private JPanel mousePanel;
private JPanel panel1;
private JPanel panel2;
private JPanel panel3;
private JLabel statusBar;
private JLabel directions1;
private JLabel directions2;
private JLabel directions3;
private JTextField textField1;
private JTextField textField2;
public EventDemo() {
super("EVENT DEMO PROGRAM");
panel1 = new JPanel();
panel2 = new JPanel();
//Add directions for the events to top of the window.
directions1 = new JLabel("Enter & Leave window." +
" Press & hold, release, drag, move cursor to display a message in statusbar." +
" Clicking in one spot will display coordinates.");
panel1.add(directions1);
add(panel1, BorderLayout.PAGE_START);
//Add mouse and statusBar to panel.
mousePanel = new JPanel();
mousePanel.setBackground(Color.WHITE);
add(mousePanel, BorderLayout.CENTER);
statusBar = new JLabel("Default");
add(statusBar, BorderLayout.SOUTH);
//Create handler object for Mouse events
TheHandler handler = new TheHandler();
mousePanel.addMouseListener(handler);
mousePanel.addMouseMotionListener(handler);
textField1 = new JTextField(10);
panel2.add(textField1, BorderLayout.SOUTH);
textField2 = new JTextField("Enter Text Here");
panel2.add(textField2, BorderLayout.SOUTH);
add(panel2);
TheHandler handlerJTF = new TheHandler();
textField1.addActionListener(handlerJTF);
textField2.addActionListener(handlerJTF);
}
private class TheHandler implements MouseListener, MouseMotionListener {
public void mouseClicked(MouseEvent event) {
statusBar.setText(String.format("YOU CLICKED THE MOUSE AT %d, %d", event.getX(), event.getY()));
}
public void mousePressed(MouseEvent event) {
statusBar.setText("YOU HAVE PRESSED THE MOUSE BUTTON.");
}
public void mouseReleased(MouseEvent event) {
statusBar.setText("YOU HAVE RELEASED THE MOUSE BUTTON.");
}
public void mouseEntered(MouseEvent event) {
statusBar.setText("YOU HAVE ENTERED THE WINDOW THE BACKGROUND CHANGES TO RED.");
mousePanel.setBackground(Color.RED);
}
public void mouseExited(MouseEvent event) {
statusBar.setText("EXITING THE WINDOW, BACKGROUND CHANGES BACK TO WHITE.");
mousePanel.setBackground(Color.WHITE);
}
//Mouse Motion events.
public void mouseDragged(MouseEvent event) {
statusBar.setText("YOU ARE DRAGGING THE MOUSE.");
}
public void mouseMoved(MouseEvent event) {
statusBar.setText("YOU ARE MOVING THE MOUSE AROUND.");
}
}
public void actionPerformed(ActionEvent event) {
textField1.getText();
textField2.getText();
}
public static void main(String[] args) {
EventDemo go = new EventDemo();
go.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
go.setSize(960, 300);
go.setVisible(true);
new EventDemo();
}
}
TheHandler does not implement ActionListener which is the required type of JTextField#addActionListener. See How to Write an Action Listeners and How to Use Text Fields for more details
Your actionPerformed method isn't actually defined in TheHandler class, but is a method of the EventDemo class
You should use the #Override annotation when you think you're overriding methods (or implementing methods from an interface) as this will cause a compiler error when you're wrong (for some reason)
Your fields aren't appearing for two reasons, first, textField is added to panel2 which is never added to anything and textField2 is been added to the frame (at the default position of BorderLayout.CENTER), but is been overriden/circumvented by add(mousePanel, BorderLayout.CENTER);, so it's actually never laid out by the frames BorderLayout. Have a look at How to Use Borders for more details
I have an action listener on a JButton and a JTextArea in the button. However when I click the button the text area swallows the event, and nothing happens to the button.
How can I click through the area?
Thank you
Button code
public class CustomFoodItemButton extends JButton{
public JTextArea buttonTitle;
/**
* Public constructor
* #param title
*/
public CustomFoodItemButton(String title) {
//Set button text by using a text area
buttonTitle = new JTextArea();
buttonTitle.setText(title);
buttonTitle.setFont(new Font("Helvetica Neue", Font.PLAIN, 15));
buttonTitle.setEditable(false);
buttonTitle.setWrapStyleWord(true);
buttonTitle.setLineWrap(true);
buttonTitle.setForeground(Color.white);
buttonTitle.setOpaque(false);
//Add the text to the center of the button
this.setLayout(new BorderLayout());
this.add(buttonTitle,BorderLayout.CENTER);
//Set the name to be the title (to track actions easier)
this.setName(title);
//Clear button so as to show image only
this.setOpaque(false);
this.setContentAreaFilled(false);
this.setBorderPainted(false);
//Set not selected
this.setSelected(false);
//Set image
setImage();
}
GUI Class code
private void addFoodItemButtons (JPanel panel){
//Iterate over menu items
for (FoodItem item : menuItems) {
//Create a new button based on the item in the array. Set the title to the food name
CustomFoodItemButton button = new CustomFoodItemButton(item.foodName);
//Add action listener
button.addActionListener(this);
}
}
EDIT For multiline JComponents, check out this answer: https://stackoverflow.com/a/5767825/2221461
It seems to me as if you're overcomplicating things. Why are you using a TextArea on a button if you can't edit the TextArea's text?
There is another constructor for JButtons:
JButton button = new JButton(item.foodname);
This will create a button with the value of 'item.foodname' as text.
You could then simplify your constructor:
public class CustomFoodItemButton extends JButton {
public CustomFoodItemButton(String title) {
super(title);
setName(title);
setOpaque(false);
setContentAreaFilled(false);
setBorderPainted(false);
setSelected(false);
setImage();
}
}
Please let me know if I misinterpreted your question.
I'm trying to add several JLabels to a JPanel along with mouse listeners using a loop. These JLabels are going to have mouse listeners so that they change their icon when clicked (Using label.setIcon()). However, I only want to have one "selected" at a time. So, I need them to know when another label is clicked so it knows to turn itself off before the new label gets selected. However, my problem is that because I'm adding these labels with a loop they all have the same MouseListener.
Can anyone teach me a simple way to accomplish this?
This is a short example, how you could implement it (please note, that I didn't use the icon, but change the label instead):
public class MouseListenerExample extends JFrame {
public static class MyMouseListener extends MouseAdapter {
private static final Collection<JLabel> labels = new ArrayList<JLabel>();
private final JFrame frame;
public MyMouseListener(JFrame frame, JLabel label) {
this.frame = frame;
labels.add(label);
}
#Override
public void mouseClicked(MouseEvent e) {
for (JLabel label : labels) {
String text = label.getText();
if (text.startsWith("X ")) {
label.setText(text.substring(2));
}
}
JLabel currentLabel = (JLabel) e.getComponent();
currentLabel.setText("X " + currentLabel.getText());
}
}
public MouseListenerExample() {
super("MouseListener Example");
Container c = getContentPane();
c.setLayout(new FlowLayout());
for (int i = 0; i < 10; i++) {
JLabel jLabel = new JLabel("Label " + i);
c.add(jLabel);
jLabel.addMouseListener(new MyMouseListener(this, jLabel));
}
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String[] args) {
new MouseListenerExample();
}
}
The main idea is, that you create a new MouseListener for each label, but keep a list of labels outside of each listener's scope (in this example I just use a static variable, but you could also have a field containing the list of labels in the frame.
edit: now solved, but can't mark as accepted for two days
In my class I have a JScrollPanel and that has a JPanel inside of it too.
My code resembles something like this:
import java.awt.Color;
import java.awt.Container;
import javax.swing.*;
public class MyClass {
private JPanel p;
private JScrollPane s;
private Container contentPane;
public MyClass(Container contentPane) {
this.contentPane = contentPane;
this.p = new JPanel();
this.p.setBackground(Color.WHITE);
BoxLayout boxLayout = new BoxLayout(this.p, BoxLayout.Y_AXIS);
this.p.setLayout(boxLayout);
this.s = new JScrollPane(this.p);
this.s.setSize(400, 364);
this.contentPane.add(this.s);
}
public final JLabel makeJLabel(String message) {
JLabel jLabel = new JLabel("<html><p style=\"padding-left:15px;padding-right:15px;width:280px;\">" + message.replaceAll("(\r\n|\n)", "<br />") + "</p></html>");
/*
some stuff here to calculate pref/max size and add an imageicon
*/
p.add(jLabel);
this.p.revalidate();
this.s.revalidate(); //just added because the above line made no effect
scrollToBottom();
return jLabel;
}
public void scrollToBottom() {
JScrollBar vertical = s.getVerticalScrollBar();
vertical.setValue(vertical.getMaximum());
}
}
Elsewhere in my class I have a method which adds a JLabel to the JPanel. This actual method is quite long, so I wont post it all, but this is the code which adds it to the panel: p.add(jLabel1);
All of these JLabels are added in a vertical fashion thanks to the Box Layout.
After the JLabel has been added to the JPanel I want the JScrollPane to scroll to the bottom. But this can't be done until after the JPanel has actually been drawn (painted?) onto JPanel. Otherwise I get this result:
So what I want to do is add some form of listener to the JPanel which detects when my JLabel has been painted to it, so that I can tell my JScrollPane to scroll to the bottom. I have already written a method which scrolls the pane to the bottom, but I don't have anywhere suitable to call it from yet.
Does anyone have any ideas on this please? Thanks.
I'm assuming you just want the label to be visible in the scrollpane so I would gues you should be able to do something like:
panel.add( label );
panel.revalidate();
label.scrollRectToVisible( label.getBounds() );
Or if you really do want to just scroll bo the bottom then you would do something like:
panel.revalidate();
scrollPane.getVerticalScrollBar().setValue( getVerticalScrollBar().getMaximum() );
Both of these answers assume the GUI is already visible.
The first part of Rob's answer is the way to go - the missing piece is to wrap the scrollRectToVisible into SwingUtilities.invokeLater. Doing so delays the scrolling until all pending events are processed, that is until all internal state is updated. A code snippet (in swingx test support speak, simply replace the frame creation and scrollpane wrapping with manually created code)
final JComponent panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
for(int i = 0; i < 10; i++)
panel.add(new JLabel("initial message " + i));
JXFrame frame = wrapWithScrollingInFrame(panel, "scroll to bottom");
Action action = new AbstractAction("addMessage") {
int count;
#Override
public void actionPerformed(ActionEvent e) {
final JLabel label = new JLabel("added message " + count++);
panel.add(label);
panel.revalidate();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.scrollRectToVisible(label.getBounds());
}
});
}
};
frame.add(new JButton(action), BorderLayout.SOUTH);
show(frame);
Scratched my head for ages over this, but after asking the question I finally figured it out.
To solve my problem all I need to do was listen for a change in value in the JScrollPane's scroll bar. If it changed, done some calculations, and scroll to the bottom if necessary.
Care has to be taken to ensure that you're not overriding the user moving the scroll bar however.
In particular you're looking at the track which is an AdjustmentEvent. This event is also fired when a user moves the scroll bar.
In order to allow the user to scroll without forcibly scroll it to the bottom, I always keep track of the maximum scroll bar value. If when track is fired the new max value is higher than the current one then a new item has been added and we should think about scrolling to the bottom. If the values are equal then the user is scrolling the scroll bar and we do nothing.
The event listeners can be found on this website and can be make to work very easily: Listening for Scrollbar Value Changes in a JScrollPane Container
import java.awt.Color;
import java.awt.Container;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import javax.swing.*;
public class MyClass {
private JPanel p;
private JScrollPane s;
private Container contentPane;
private int scrollBarMax;
public MyClass(Container contentPane) {
this.contentPane = contentPane;
this.p = new JPanel();
this.p.setBackground(Color.WHITE);
BoxLayout boxLayout = new BoxLayout(this.p, BoxLayout.Y_AXIS);
this.p.setLayout(boxLayout);
this.s = new JScrollPane(this.p);
this.s.setSize(400, 364);
this.contentPane.add(this.s);
this.s.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener(){
#Override
public void adjustmentValueChanged(AdjustmentEvent evt) {
if (evt.getValueIsAdjusting()) {
return;
}
if (evt.getAdjustmentType() == AdjustmentEvent.TRACK) {
if (scrollBarMax < s.getVerticalScrollBar().getMaximum()) {
if ((s.getVerticalScrollBar().getValue() + s.getVerticalScrollBar().getSize().height) == scrollBarMax) {
//scroll bar is at the bottom, show the last added JLabel
scrollBarMax = s.getVerticalScrollBar().getMaximum();
scrollToBottom();
} else {
//scroll bar is not at the bottom, user has moved it
scrollBarMax = s.getVerticalScrollBar().getMaximum();
}
}
}
}
});
scrollBarMax = s.getVerticalScrollBar().getMaximum();
}
public final JLabel makeJLabel(String message) {
JLabel jLabel = new JLabel("<html><p style=\"padding-left:15px;padding-right:15px;width:280px;\">" + message.replaceAll("(\r\n|\n)", "<br />") + "</p></html>");
/*
some stuff here to calculate pref/max size and add an imageicon
*/
p.add(jLabel);
scrollBarMax = s.getVerticalScrollBar().getMaximum();
return jLabel;
}
public void scrollToBottom() {
JScrollBar vertical = s.getVerticalScrollBar();
vertical.setValue(vertical.getMaximum());
}
}
I am making an applet and as part of my applet, I want this to happen: When the user presses "OK", the old components (some radio buttons) are removed, and a new JPanel is added, with a bunch of textfields.
However, I cannot figure out how to add a new component to the applet after it has started. I made the problem simpler by ignoring the removal part (Which I know how to do) and just adding a simple JLabel instead, but even that won't add!
Here is my code so far:
// imports omitted
public class Class extends Applet implements ActionListener
{
Button okButton;
CheckboxGroup radioGroup;
Checkbox radio1;
Checkbox radio2;
Checkbox radio3;
JLabel j;
public void init()
{
setLayout(new FlowLayout());
okButton = new Button("OK");
j = new JLabel("hello");
radioGroup = new CheckboxGroup();
radio1 = new Checkbox("Red", radioGroup,false);
radio2 = new Checkbox("Blue", radioGroup,true);
radio3 = new Checkbox("Green", radioGroup,false);
add(okButton);
add(radio1);
add(radio2);
add(radio3);
okButton.addActionListener(this);
}
public void repaint(Graphics g)
{
if (radio1.getState()) add(j);
}
public void actionPerformed(ActionEvent evt)
{
if (evt.getSource() == okButton) repaint();
}
}
What am I doing wrong?
You shouldn't override the repaint method, and certainly not add a component in this method. Just remove the radio buttons from the applet (using its remove method) and add the label in the applet in your actionPerformed method, the same way you add them in the init method.
You might have to call validate after.
Add components and then call validate() of your container. In this case yourApplet.validate(). This will trigger repainting and rearranging of all elements.
you could do something like
JFrame fr= new JFrame(); // global variables
JPanel panelToBeAdded = new JPanel();
JPanel initialPanel = new JPanel();
JTextField fieldToBeAdded = new JTextField();
panelToBeAdded.setPreferredSize( new Dimension(400,400));
initialPanel.setPreferredSize( new Dimension(400,400));
initialPanel.setVisible(true);
fr.add(initialPanel);
fr.setVisible(true);
fr.pack();
public void actionPerformed(ActionEvent ae) {
initialPanel.setVisible(false);
//radiobuttons.setVisible(false);---> hide the radio buttons
panelToBeAddedd.add(fieldToBeAddedd);
panelToBeAddedd.setVisible(true);
fr.add(panelToBeAddedd);
}
public void repaint( Graphics g ) {
// do something
}
What am I doing wrong?
Your repaint(Graphics) method is not the same method you are calling in your actionPerformed method.
Also, repaint is a pretty bad name for a method which is adding a new component.
public void swapComponents()
{
if (radio1.getState()) {
remove(radio1);
remove(radio2);
remove(radio3);
add(j);
validate();
}
}
public void actionPerformed(ActionEvent evt)
{
if (evt.getSource() == okButton) {
swapComponents();
}
}
When the user presses "OK", the old components (some radio buttons) are removed, and a new JPanel is added, with a bunch of textfields.
Use a CardLayout, as shown here. It is perfect for situations like this.