setBorder method for JLabel causing paint problem - java

I have a custom class that extends JLabel. For specific instances of that class, I want to add some spacing to the text on the left side. I need the spacing as I'm setting the background of this JLabel and I don't want the text to bump up right next to the edge of the colored background. I fished around quite a bit and implemented this (inside the paint function):
if (condition) {
bgColor = Color.red;
setBackground(bgColor);
setOpaque(true);
// This line merely adds some padding on the left
setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
}
else {
setOpaque(false);
}
This appears to work in that it adds the spacing I want, however it has an unfortunate side effect in that it appears to break the repainting of the whole rest of the application...it appears that only that particular component is repainting and not the rest of the application. I eventually tracked it down to the setBorder call specifically...setting ANY kind of border appears to cause the same broken behavior. We have two different versions of our application, one that runs in Java 1.5 and one that runs in Java 1.6, the Java 1.6 version appears to work correctly while the Java 1.5 version doesn't. It is not possible to upgrade the older version to Java 1.6...I need something that will work in Java 1.5. Also, I tried this (just to see what it looked like):
setHorizontalTextPosition(JLabel.CENTER);
And that also appears to break the repainting in exactly the same way. I looked through the source of our application and found other places where we set borders (including empty borders), but couldn't find any on JLabels (only panels, buttons, etc). Anybody see anything like this before? Know how to fix it? Or perhaps another way to obtain the spacing I require that may work around the bug? Thanks.

The problem is that you're calling that code inside the paint method. You should not do that because it will freeze the EDT with unwanted loops in the swing painting pipeline.
You should put that code on the constructor and change the component design state elsewhere on the app life cycle.
If you want to know a little bit more about Swing painting please read the "Swing painting pipeline" post on pushing-pixels.org.
Note that you can use BorderFactory.createCompoundBorder to combine any two borders. Then you can set spacing with the emptyBorder and any other to draw the outer border.
EDIT: Example added.
package com.stackoverflow.swing.paintpipeline;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.Border;
public class JLabelSetBorderPaintProblem extends JLabel {
public JLabelSetBorderPaintProblem(String text) {
super(text);
}
/*
* #see javax.swing.JComponent paint(java.awt.Graphics)
*/
#Override
public void paint(Graphics g) {
super.paint(g);
// You can not call setBorder here.
// Please check javadoc.
}
/*
* #see javax.swing.JComponent paintBorder(java.awt.Graphics)
*/
#Override
protected void paintBorder(Graphics g) {
super.paintBorder(g);
// Here is where the Swing painting pipeline draws the current border
// for the JLabel instance.
// Please check javadoc.
}
// Start me here!
public static void main(String[] args) {
// SetBorder will dispatch an event to Event Dispatcher Thread to draw the
// new border around the component - you must call setBorder inside EDT.
// Swing rule 1.
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
// Inside EDT
JFrame frame = new JFrame("JLabel setBorder example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Add the JLabel
final JLabelSetBorderPaintProblem label = new JLabelSetBorderPaintProblem("Just press or wait...");
frame.add(label);
// And change the border...
label.addMouseListener(new MouseAdapter() {
#Override public void mousePressed(MouseEvent e) {
label.setBorder(BORDERS.get(new Random().nextInt(BORDERS.size())));
}
});
// ...whenever you want
new Timer(5000, new ActionListener() {
#Override public void actionPerformed(ActionEvent e) {
label.setBorder(BORDERS.get(new Random().nextInt(BORDERS.size())));
}
}).start();
frame.pack();
frame.setVisible(true);
}
});
}
public static final List<Border> BORDERS;
static {
BORDERS = new ArrayList<Border>();
BORDERS.add(BorderFactory.createLineBorder(Color.BLACK));
BORDERS.add(BorderFactory.createLineBorder(Color.RED));
BORDERS.add(BorderFactory.createEtchedBorder());
BORDERS.add(BorderFactory.createTitledBorder("A border"));
}
}

Related

Change the selected state of a JToggleButton (e.g. JCheckBox) without triggering its item listeners [duplicate]

I've got a Swing application with a model and a view. In the view (GUI) there are a lot of components, each of them mapping to some property of a model object and displaying it's value.
Now there are some UI components that automatically trigger the updating of some model properties when their value changes in the UI. This requires me to reload the complete model in the UI. This way I'm entering an infinite update loop, as every model reload in the UI triggers another model reload.
I have a flag indicating the load process, which I'd like to use to temporarily suppress the listener notifications, while the UI fields are being set from the model. So my question is:
Is there a way to globally temporarily disable some component's listeners in Swing without removing and reattaching them?
You could use a common base class for your listeners and in it, have a static method to turn the listeners on or off:
public abstract class BaseMouseListener implements ActionListener{
private static boolean active = true;
public static void setActive(boolean active){
BaseMouseListener.active = active;
}
protected abstract void doPerformAction(ActionEvent e);
#Override
public final void actionPerformed(ActionEvent e){
if(active){
doPerformAction(e);
}
}
}
Your listeners would have to implement doPerformAction() instead of actionPerformed().
(This would be awful in an enterprise scenario, but in a single-VM model like in Swing, it should work just fine)
Normally I use a flag indicating API changes or user changes. For each of the listeners I would check the flag and if it's API changes just return.
While searching stackoverflow, I found this question. I thought to add my opinion/answer.
It is really^inf bad idea to temporarily disable event listeners in Swing. If your code is broken (or something else goes wrong), you may not be able to bring your application back to life - respond to user and other events.
If you want to discard (respond but do nothing) to user events, you may use glass pane which can just ignore the events.
If your EDT is busy (which again you must avoid as much as possible) and you wanted to discard user action for that period, you may still use a glasspane and remove it using invokeLater to remove the pane after all the events have been responded (ignored by the glasspane) to.
Full details including an SSCE can be found in this question.
java wait cursor display problem
One option that might work for you is just to put a glass pane up while loading in order to block events during that time:
http://download.oracle.com/javase/tutorial/uiswing/components/rootpane.html#glasspane
As mentioned above, the GlassPane is helpful in this regard.
Here is a simple example:
import javax.swing.JFrame;
import javax.swing.JButton;
import javax.swing.SwingWorker;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class GlassPaneExample extends JFrame implements ActionListener {
private JButton btnDisable;
private JButton btnTestOne;
private JButton btnTestTwo;
private MyGlassPane glass;
private boolean actionAllowed = true;
public GlassPaneExample() {
// init JFrame graphics
setBounds(300, 300, 300, 110);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout());
setVisible(true);
// init buttons
btnTestOne = new JButton("Button one");
add(btnTestOne);
btnTestTwo = new JButton("Button two");
add(btnTestTwo);
btnDisable = new JButton("Disable ActionListeners for 2 seconds");
add(btnDisable);
// create Glass pane
glass = new MyGlassPane();
setGlassPane(glass);
// add listeners
btnTestOne.addActionListener(this);
btnTestTwo.addActionListener(this);
btnDisable.addActionListener(this);
}
public static void main(String[] args) {
new GlassPaneExample();
}
#Override
public void actionPerformed(ActionEvent e) {
JButton src = (JButton)e.getSource();
if (src.equals(btnDisable)) {
// setting glasspane visibility to 'true' allows it to receive mouse events
glass.setVisible(true);
setCursor(new Cursor(Cursor.WAIT_CURSOR));
SwingWorker sw = new SwingWorker() {
#Override
protected Object doInBackground()
throws Exception {
Thread.sleep(2000);
return null;
}
#Override
public void done() {
// set cursor and GlassPane back to default state
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
glass.setVisible(false);
// allow actions to be received again
actionAllowed = true;
}
};
sw.execute();
} else if (actionAllowed) {
if (src.equals(btnTestOne)) {
JOptionPane.showMessageDialog(this, "BUTTON ONE PRESSED");
} else if (src.equals(btnTestTwo)) {
JOptionPane.showMessageDialog(this, "BUTTON TWO PRESSED");
}
}
}
class MyGlassPane extends JPanel {
public MyGlassPane() {
setOpaque(false);
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
actionAllowed = false;
}
});
}
//Draw an cross to indicate glasspane visibility
public void paintComponent(Graphics g) {
g.setColor(Color.red);
g.drawLine(0, 0, getWidth(), getHeight());
g.drawLine(getWidth(), 0, 0, getHeight());
}
}
}
This question looks like a similar problem and no satisfactory solution to it.
I found this article helpful in critically examining my own designs.
Is there a way to globally temporarily disable some component's listeners in Swing without removing and reattaching them?
Every JComponent maintains an EventListenerList, which is accessible to your subclass. If necessary, you can always operate on the list directly or build the desired behavior into your custom implementation of EventListener

paintComponent does painting on its own

My problem is, when I press a button paintComponent should be called then a figure should be drawn on the JPanel, Unfortunately paintComponent draws the figure when the program is loaded, in that case the button is useless.
I made a small version of my program, to make it easy and fast to read and detect the problem.
This code here is not the original one but it demonstrates the same problem.
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JPanel;
public class TestPaint extends JPanel implements ActionListener {
private JButton button_1 = new JButton( "Draw Oval" );
public TestPaint() {
add(button_1);
}
#Override
public void actionPerformed(ActionEvent e) {
if ( e.getSource() == button_1 )
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(10, 10, 100, 100);
}
}
To run the program
import javax.swing.JFrame;
public class RunPaint {
public static void main(String[] args) {
TestPaint paint_g = new TestPaint();
JFrame frame = new JFrame("Testing");
frame.add(paint_g);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 300);
frame.setVisible(true);
}
}
As a simple solution you can create an instance variable for your class:
private Boolean buttonPressed = false;
Then in your actionListener you set the value to true.
and in your paintComponent() method you add code like:
if (buttonPressed)
g.drawOval(...);
A better (and more complicated solution) is to keep a List of objects to paint. Initially the List will be empty, and when you press the button you add an object to the List. Then the painting code just iterates through the List to paint the objects.
Check out Custom Painting Approaches for more ideas. The example code doesn't do exactly this, but it does show how to paint from a List.
Let your actionPerformed() implementation add the desired geometric figure to a List<Shape> and have paintComponent() iterate through the list to render the shapes. A complete example is seen here.

How to show Modified contents in Applet without resizing

I've created an applet game, but when I modify some of the contents, I need to (maximise or minimise) resize the window to show my modified applet.
even when I add a label, or anything, it needs resizing since I've not used the paint method.(no use of repaint).
Help me with this, how to show modified contents without resizing...
here's a sample code that have same problem.
import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.Label;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JApplet;
public class Appl extends JApplet implements ActionListener{
Button b = new Button();
public void init()
{
setLayout(new FlowLayout());
setSize(300,300);
setVisible(true);
add(b);
b.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
if(e.getSource()==b)
{
add(new Label("Button clicked"));
repaint();
}
}
}
If I remember correctly you just call the repaint method after the modifications of your content, then it should show up.
Repaint is always implicitly called when you resize the Applet.
Edit: Applying the validate medthod on the Japplet Container works for me in the given example. This also redraws added components, repaint just calls the paint method. try it :-)

AWT Scrollpane scrollbars flash on resize

I am having issues with the java.awt.ScrollPane class's SCROLLBARS_AS_NEEDED display policy handling component resize. Ideally, if I have a ScrollPane that contains a Component that is significantly smaller than the Scrollpane and I shrink the Scrollpane to a size that is still greater than the child component, no scrollbars will appear. However, in practice both scrollbars seem to flicker while the resize operation is occurring and may even persist until the next redraw after the operation completes. Here is a small example to demonstrate what I mean:
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.ScrollPane;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Tester implements Runnable{
/**
* #param args
*/
public static void main(String[] args) {
System.setProperty("sun.awt.noerasebackground", "true");
Tester t = new Tester();
SwingUtilities.invokeLater(t);
}
#SuppressWarnings("serial")
#Override
public void run() {
JFrame frame = new JFrame("Tooltip tester");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 800);
Canvas c = new Canvas(){
#Override
public void paint(Graphics g){
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
}
};
c.setPreferredSize(new Dimension(400, 400));
ScrollPane s = new ScrollPane();
s.add(c);
frame.add(s);
frame.setVisible(true);
}
}
Shrinking the window in the application above should cause scrollbars to flash. I believe this is caused by a bug in the implementation of the peer of the ScrollPane. (Source Here) I've copied the method that I believe has the error (line 145 in the link).
Dimension getChildSize() {
ScrollPane sp = (ScrollPane)target;
if (sp.countComponents() > 0) {
Component c = sp.getComponent(0);
return c.size();
} else {
return new Dimension(0, 0);
}
}
To me, it seems like the getChildSize() method should call c.getPreferredSize() rather than c.size(). If the ScrollPane child's size is greater than its current preferred size, it should be able to shrink without scroll bars showing up (in my opinion). To test this theory, I overrode the size() method in my example from above:
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.ScrollPane;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class Tester implements Runnable{
/**
* #param args
*/
public static void main(String[] args) {
System.setProperty("sun.awt.noerasebackground", "true");
Tester t = new Tester();
SwingUtilities.invokeLater(t);
}
#SuppressWarnings("serial")
#Override
public void run() {
JFrame frame = new JFrame("Tooltip tester");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 800);
Canvas c = new Canvas(){
private Dimension prefSize = new Dimension();
#Override
public void paint(Graphics g){
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
}
#Override
public void setPreferredSize(Dimension preferredSize) {
prefSize = preferredSize;
super.setPreferredSize(preferredSize);
}
#Override
public Dimension size() {
return prefSize;
}
};
c.setPreferredSize(new Dimension(400, 400));
ScrollPane s = new ScrollPane();
s.add(c);
frame.add(s);
frame.setVisible(true);
}
}
This code behaves exactly how I want it to. However, overriding the size() method to do something other than what the documentation says it should do isn't a particularly elegant way to fix this problem. I feel like I must be doing something wrong here. Is it possible that a fairly basic functionality of the ScrollPane peer for X11 is broken?
A few other related notes:
-Use of java.awt.Canvas is non-negotiable for my purposes. In my actual application, I am drawing something very quickly using Java AWT Native Interface.
-While I read the documentation about new improvements to light/heavyweight compatibility in Java 7 and later versions of Java 6, I didn't really see an improvement in either case so I decided to stick with awt.ScrollPane over swing.JScrollPane. Either way, a ScrollPane should work correctly in the simple case I showed above.
hard to help you without SSCCE, for future readers
However, in practice both scrollbars seem to flicker while the resize operation is occurring and may even persist until the next redraw after the operation completes
used LCD/LED panel caused those flickering (including MsExcell e.g., btw many times discused),
same flickering is for Swing JFrame with JScrollPane
not presented on CRT or Plasma display
if you playing FullHD video on PC, then every good multimedia players waiting until resize ended, thenafter fill available space into its container
you have to add ComponentListener, put there Swing Timer with small delay (KMPlayer show me 350-500milisecs), untill resize continue call for Timer#restart,
Canvas is good workaround for CAD / CAM, OpenGL(CL), good Java video players are based on AWT
you have to avoiding mixing Swing Container with AWT contents, lets everything is based on AWT (undecorated container)

Fire mouse event on underlying components

I'm looking for a way to pass mouse events to components covered by other components. To illustrate what I mean, here's a sample code. It contains two JLabels, one is twice smaller and entirely covered with a bigger label. If you mouse over the labels, only the bigger one fires mouseEntered event however.
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;
import javax.swing.border.LineBorder;
public class MouseEvtTest extends JFrame {
public MouseEvtTest() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setLayout(null);
setSize(250, 250);
MouseAdapter listener = new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
System.out.printf("Mouse entered %s label%n", e.getComponent().getName());
}
};
LineBorder border = new LineBorder(Color.BLACK);
JLabel smallLabel = new JLabel();
smallLabel.setName("small");
smallLabel.setSize(100, 100);
smallLabel.setBorder(border);
smallLabel.addMouseListener(listener);
add(smallLabel);
JLabel bigLabel = new JLabel();
bigLabel.setName("big");
bigLabel.setBorder(border);
bigLabel.setSize(200, 200);
bigLabel.addMouseListener(listener);
add(bigLabel, 0); //Add to the front
}
public static void main(String[] args) {
new MouseEvtTest().setVisible(true);
}
}
What would be the best way to fire mouse entered event on the smaller label when cursor is moved to the coordinates above it? How would it work in case where there would be multiple components stacked on top of each other? What about the remaining mouse events, like mouseClicked, mousePressed, mouseReleased, etc.?
Take a look at Alexander Potochkin's blog entry on A Well-Behaved GlassPane
In your listener:
bigLabel.dispatchEvent(mouseEvent);
Of course, you will have to define bigLabel as final
Well to understand whats happening you need to understand how Z-Ordering works. As a quick overview, the component that was added last is painted first. So in your case you want to add the small component before the big component.
// add(bigLabel, 0); //Add to the front
add(bigLabel); // add to the end so it is painted first
The OverlayLayout might help explain this better and give you another option.

Categories