JXLayer 3.0 - Painting implementation
Posted by alexfromsun on July 17, 2008 at 7:26 AM EDT
The JXLayer's functionality consists of two parts: painting issues and input event processing.
In this entry I'll describe painting in details, the second part will come shortly.
Painting issues
With the JXLayer you can easily customize the visual appearance of your component and the most trivial example is painting on top of a component. However you should know some details to implement it properly. Imagine that you need to implement a custom panel with a red line goes from one corner to another, like this:Since the panel may contain child components we need to make sure that this line will paint over them. For this reason, overridden JComponent.paintComponent() will not work for us, because it is called before child components are painted. The only solution in this case is to override JComponent.paint() method and do our custom painting after the entire component is painted.
class MyPanel extends JPanel {
// Note that we override paint(), not paintComponent()
// because we want to paint over panel's children
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.RED);
g.drawLine(0,getHeight(), getWidth(), 0);
}
}
Let's put some components inside this panel and have a look:
Wow, it seems to work! But...
It works only when the whole panel is repainted, if you move the mouse over the buttons and type something in the textField, you'll see this picture:
Here we found the main problem that prevents us from decorating the Swing containers:
When a component is repainted, the paint() method from its parent doesn't get called.
There are three known solutions, how to make the container repaint when any of its children are repainted:
- Make every child component not opaque.
- Implement and set a custom global RepaintManager.
- Put a transparent panel on top of our container.
When a component is covered by another transparent component, they are always repainted together and the painting starts from their common ancestor, in our case this is JXLayer component.
Swing is smart enough to not paint more than necessary. If a JXLayer child is repainted, it doesn't mean that the whole JXLayer is repainted - painting happens only within the bounds of the child component.
Conclusion
The JXLayer's glassPane guarantees that layer's paint() method will be notified when any of its child components are repainted. It allows us to implement LayerUIs that paint over the layer:
class MyLayerUI extends AbstractLayerUI<JComponent> {
// Override paintLayer(), not paint()
protected void paintLayer(Graphics2D g2, JXLayer<JComponent> l) {
// You can paint before the layer
// it makes sense if you make the layer not opaque
// in the installUI() method of this LayerUI
super.paintLayer(g2, l);
// You can paint after the layer
// and your painting will always cover all layer's child component
}
}
You can even paint the layer's content more than once!
In this demo when you move the mouse over the button, the animation is started and the layer is painted as is, then the scaling transformation is applied and it is painted once again. FlashButtonDemo is added to the jxlayer's demo package.
See you on the JXLayer forum
Thanks
alexp
Related Topics >>
Blog Links >>
- Login or register to post comments
- Printer-friendly version
- alexfromsun's blog
- 3226 reads






Comments
by navinkjha - 2008-11-15 17:29
That is exactly the problem I found. No need to apologize, I take critism very well, just another opportunity to learn :-) I put the wrapper component because a lot of people don't like to write a few extra lines of code. I posted the test case at the forum.by alexfromsun - 2008-11-15 11:35
Hello Navin I can see the problem now, thanks ! I run your test case, quickly click another application and when the layer is locked the locked visual effect doesn't appear, it shows up only if you click the test application Is it the problem you found? I did that statement about DisableComponent because in my opinion you can do well without it. JXLayer is wrapper for a component and this class is one more wrapper for another one (two more wrappers for me). Anyway I am sorry for being too critical. I'll look how to solve this problem shortly, meanwhile could you please post this test case to JXLayer forum? http://forums.java.net/jive/forum.jspa?forumID=140 It is more convenient to communicate there Thank you Navin See youby navinkjha - 2008-11-14 14:19
Alex, I am guessing you didn't like calls to invalidate etc. or is it something else ? I took your code as an example and found a situation where it break. Also in the code below if you comment timer.start() and uncomment action listener for the button it works. Not sure why. package testdisable; import org.jdesktop.jxlayer.plaf.ext.LockableUI; import org.jdesktop.jxlayer.JXLayer; import java.awt.*; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import javax.swing.*; /** * @author Navin Jha */ public class TestDisabler { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { final JFrame f = new JFrame(); JPanel panel = new JPanel(new GridLayout(1, 1)); JButton button = new JButton("test"); panel.add(button); final JButton lockUnlockButton = new JButton("lock"); final LockableUI ui = new LockableUI() { @Override public void paint(Graphics g, JComponent c) { super.paint(g, c); if (isLocked()) { g.setColor(new Color(128, 128, 128, 128)); g.fillRect(0, 0, c.getWidth(), c.getHeight()); } } }; JXLayer layer = new JXLayer(button, ui); f.add(layer); final Timer timer = new Timer(5000, new ActionListener() { public void actionPerformed(ActionEvent e) { ui.setLocked(true); lockUnlockButton.setText("Locked Now!"); } }); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(lockUnlockButton, BorderLayout.SOUTH); f.pack(); // comment this and uncomment the add action listener and it works timer.start(); /* lockUnlockButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { timer.start(); } });*/ f.setVisible(true); } }); } }by alexfromsun - 2008-11-14 11:23
Hello Navin Here is my test which works fine for me, click the lower button, switch to another application and see the big button is locked in 5 second import org.jdesktop.jxlayer.JXLayer; import org.jdesktop.jxlayer.plaf.ext.LockableUI; import javax.swing.*; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.*; public class MyTest { private static void createGui() { final JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final LockableUI ui = new LockableUI() { @Override public void paint(Graphics g, JComponent c) { super.paint(g, c); if (isLocked()) { g.setColor(new Color(128, 128, 128, 128)); g.fillRect(0, 0, c.getWidth(), c.getHeight()); } } }; JXLayer layer = new JXLayer(new JButton("Lockable"), ui); frame.add(layer); final JButton button = new JButton("Lock in 5 sec"); frame.add(button, BorderLayout.SOUTH); final Timer timer = new Timer(5000, new ActionListener() { public void actionPerformed(ActionEvent e) { ui.setLocked(true); button.setText("Locked!"); } }); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { timer.start(); } }); frame.setSize(200, 200); frame.setLocationRelativeTo(null); frame.setVisible(true); } public static void main(String[] args) throws Exception { SwingUtilities.invokeLater(new Runnable() { public void run() { MyTest.createGui(); } }); } } I didn't dig up your code to see where the problem is, to tell the truth this DisableComponent class hurts me Thanks alexpby navinkjha - 2008-11-13 12:56
Alex, I finally was able to built a simple test case, I am simply pasting the two files. Once the test window comes up just focus on other applications and make sure the test application is not overlapping any other application. package testdisable; // standard packages import java.awt.Cursor; import java.awt.Color; import java.awt.Container; import java.awt.Graphics2D; import javax.swing.JComponent; // third party packages import org.jdesktop.jxlayer.JXLayer; import org.jdesktop.jxlayer.plaf.ext.LockableUI; /** * A Wrapper that allows a JComponent to be wrapped in a JXLayer so that the JComponent * and all it's children can be enabled/disabled. * * @author Navin Jha */ public class DisableComponent { // wrapper jxlayer private final JXLayer layer; private final JComponent component; // locking componets private final LockableUI lockableUI; /** * @param component that need to be enabled for enable/disable of itself and it's * child components. */ public DisableComponent(JComponent component) { lockableUI = new TranslucentLayer(); layer = new JXLayer(component, lockableUI); this.component = component; // by default use default cursor, locked layer uses busy cursor by default lockableUI.setLockedCursor(Cursor.getDefaultCursor()); } /** * @param enable - if true, enable the component else disable the component. */ public void setEnabled(boolean enable) { lockableUI.setLocked(!enable); if (!enable) { component.invalidate(); Container container = component.getParent(); if (container != null) container.validate(); else component.validate(); } } /** * @return The JXLayer that needs to be added instead of component passed as parameter. */ public JXLayer getJXLayer() { return layer; } // create custom LayerUI private class TranslucentLayer extends LockableUI { @Override protected void paintLayer(Graphics2D g2, JXLayer l) { // this paints layer as is super.paintLayer(g2, l); // custom painting: // here we paint translucent foreground // over the whole layer g2.setColor(new Color(135, 206, 250, 75)); g2.fillRect(0, 0, l.getWidth(), l.getHeight()); } } } --------------------------------------------------------------------------------- package testdisable; import java.awt.*; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import javax.swing.*; /** * @author Navin Jha */ public class TestDisabler { public static void main(String[] args) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { System.out.println("error setting l &f " + e); } JFrame f = new JFrame(); JPanel panel = new JPanel(new GridLayout(5, 1)); JButton button = new JButton("test"); JLabel titleLabel = new JLabel("test some background"); JTextField text = new JTextField("text"); JSpinner spinner = new JSpinner(new SpinnerNumberModel()); JComboBox combo = new JComboBox(new String[]{"Bravo", "Alpha", "Charlie", "Chuck", "Delta", "Alpho"}); panel.add(titleLabel); panel.add(spinner); panel.add(button); panel.add(text); panel.add(combo); final DisableComponent d = new DisableComponent(panel); f.add(d.getJXLayer(), BorderLayout.CENTER); final JButton lockUnlockButton = new JButton("lock/unlock"); lockUnlockButton.addActionListener(new ActionListener() { private boolean flag = true; public void actionPerformed(ActionEvent e) { d.setEnabled(flag); flag = flag ? false : true; } }); Timer timer = new Timer(10000, new ActionListener() { public void actionPerformed(ActionEvent e) { d.setEnabled(false); lockUnlockButton.setText("Lock Now!"); } }); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.add(lockUnlockButton, BorderLayout.SOUTH); f.pack(); timer.start(); f.setVisible(true); } }by navinkjha - 2008-11-12 16:21
One more thing: I put a print statement inside in the above paintLayer(...) method and In case when lock effect doesn't take place, the statement is not printed so that means it is not getting called in that case. I click on the window header and paintLayer(...) does get called and everything happens as desired.by navinkjha - 2008-11-12 15:59
something I should have mentioned: I have custom layer which I copied your blog I guess: private class TranslucentLayer extends LockableUI { @Override protected void paintLayer(Graphics2D g2, JXLayer l) { // this paints layer as is super.paintLayer(g2, l); // custom painting: // here we paint translucent foreground // over the whole layer g2.setColor(new Color(135, 206, 250, 75)); g2.fillRect(0, 0, l.getWidth(), l.getHeight()); } } Is it possible that the getWdith() and getHeight() return zero for some reason and then then when I click on the window has non zero values ?by navinkjha - 2008-11-12 14:54
Alex, I need extract code from my application for. Here is what is happening. I am clicking at other application windows and at some point an event happens that is suppose to lock the window. I can see thevent being logged in the log file but the window not being in the locked state. The moment I click on it gets locked. I even tried something like this: lockableUI.setLocked(!enable); if (!enable) { component.invalidate(); Container container = component.getParent(); if (container != null) container.validate(); else component.validate(); } where the "component" is the component being wrapped. I will try to get the test case shortly. Thanks.by alexfromsun - 2008-11-07 11:11
Hello Navin Could you provide a small runnable test case for this problem? (I quickly created one, it works well for me) Thanks alexpby navinkjha - 2008-10-29 10:20
I said it too soon. Even in GUI thread setLocked(true) doesn't work if the window is not focussed. setLocked(false) does work though. -Navinby navinkjha - 2008-10-29 07:28
Ignore my question, it was my fault. It was being executed in a listener that I assumed to be a gui thread and it was not. I sincerely apologize. -Navinby navinkjha - 2008-10-27 15:45
Alex, When window is not focussed it seems that the setLocked(..) method for LockableUI does not paint. If you click on the window it immediately paints. Any way to show the locked effect even when window is not focussed? This is very useful when user has mutiple application running he needs to be alerted of something and the window is not focussed. -Navinby alexfromsun - 2008-07-22 06:19
Hello polygoncell When JTextField gets the focus, the mouse cursor starts blinking, it leads to the textField's repaint and that's why paintlayer() is also called there is no problem as for SwingAppFramework - I prefer to discuss it on the dedicated alias, see https://appframework.dev.java.net Thanks alexpby polygoncell - 2008-07-22 04:35
Hello Alexp thanks for your nice comments on my blog. I really appreciate it! I have posted a new comment. Please take a look, thanks. Another issue: I just found something strange in the example TextValidationDemo, IconValidationUI: if the JTextField gets the focus, the paintlayer() method will be called again and again without an end. It hapens only with the IconValidationUI. Is that okay? BTW: I heard that you got the Swing app framework under your control. That is a great news! Thanks for your choice. I am using this framework heavily in my project and really like to see the rebirth of the framework. What I am actually waiting for are the MultiFrameApplication and the TrayApplication(based on the TrayIcon from java 6. Yes I have read Hans Mueler's last email). The SingleFrameApplication is good but not suitable for some a little bit complicated Swing applications. best regards polygoncellby alexfromsun - 2008-07-18 05:25
Hello PolygoncellThank for your blog, I appreciate it I'll post my comment there
alexp
by polygoncell - 2008-07-18 04:42
Hello Alexp, The JXLayer is great! I really like it. I have write a blog for using the JXLayer and the hibernate validator for building a common validation module for Swing application. Would you like to take a look? Maybe it will help some people who want to use the JXLayer for validation. here is the blog: http://polygoncell.blogspot.com/2008/07/validation-module-for-swing-appl...by alexfromsun - 2008-07-18 04:29
Hello JmborerThe purpose of isOptimizedPaintingEnabled() is to enable correct painting of overlapped components
e.g. JRootPane has it overridden to to return false when its glassPane is visible,
JXLayer does the same
Thanks
alexp
by jmborer - 2008-07-18 02:09
Hmm. Interesting point. We had that kind of challenge: when we added a translucent panel (which means opaque), we encountered the repainting issue. Another way to fix that is to override method isOptimizedPaintingEnabled(false) (or something like that) on the parent component.by alexfromsun - 2008-07-23 06:45
Hello Polygoncell I had a look and posted a comment Thanks alexpby polygoncell - 2008-07-23 02:19
Hello Alexp, After getting the suggustion from you, I have post an update blog. Would you like to take a look? Thanks. here is it: http://polygoncell.blogspot.com/2008/07/update-common-validation-module-... regards Polygoncell