Search |
||
JXLayer 3.0 - Painting implementationPosted by alexfromsun on July 17, 2008 at 4:26 AM PDT
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 issuesWith 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.
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:
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. ConclusionThe 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:
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 >>
Swing Comments
Comments are listed in date ascending order (oldest first)
Submitted by polygoncell on Wed, 2008-07-23 01: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
Submitted by alexfromsun on Wed, 2008-07-23 05:45.
Hello Polygoncell
I had a look and posted a comment
Thanks
alexp
Submitted by jmborer on Fri, 2008-07-18 01: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.
Submitted by alexfromsun on Fri, 2008-07-18 03:29.
Hello Jmborer
The 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
Submitted by polygoncell on Fri, 2008-07-18 03: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...
Submitted by alexfromsun on Fri, 2008-07-18 04:25.
Hello Polygoncell
Thank for your blog, I appreciate it I'll post my comment there alexp
Submitted by polygoncell on Tue, 2008-07-22 03: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
polygoncell
Submitted by alexfromsun on Tue, 2008-07-22 05: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
alexp
Submitted by navinkjha on Mon, 2008-10-27 14: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.
-Navin
Submitted by navinkjha on Wed, 2008-10-29 06: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.
-Navin
Submitted by navinkjha on Wed, 2008-10-29 09: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.
-Navin
Submitted by alexfromsun on Fri, 2008-11-07 10:11.
Hello Navin
Could you provide a small runnable test case for this problem?
(I quickly created one, it works well for me)
Thanks
alexp
Submitted by navinkjha on Wed, 2008-11-12 13: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.
Submitted by navinkjha on Wed, 2008-11-12 14: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 ?
Submitted by navinkjha on Wed, 2008-11-12 15: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.
Submitted by navinkjha on Thu, 2008-11-13 11: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);
}
}
Submitted by alexfromsun on Fri, 2008-11-14 10: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
alexp
Submitted by navinkjha on Fri, 2008-11-14 13: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);
}
});
}
}
Submitted by alexfromsun on Sat, 2008-11-15 10: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 you
Submitted by navinkjha on Sat, 2008-11-15 16: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.
|
||
|
|