I would like to know how to get a transparent JFrame in the latest version of Java.
Currently, you can only use
<JFrame>.setOpacity();
if the frame is not decorated.
I have no use for an undecorated frame, so I'd like to know how to go around this restriction and set the opacity of the frame to 0.5f while still keeping the title bar, resize options etc.
I have read the docs here: http://docs.oracle.com/javase/tutorial/uiswing/misc/trans_shaped_windows.html. The code only worked on Java 6 and no longer runs. The error, as I said, is:
Exception in thread "AWT-EventQueue-0" java.awt.IllegalComponentStateException: The frame is decorated
at java.awt.Frame.setOpacity(Frame.java:960)
at TranslucentWindowDemo$1.run(TranslucentWindowDemo.java:53)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
...
I have also tried setting the background (setBackground : Color) using a Color with custom Alpha value (new Color(int, int, int, Alpha)) but it throws the exact same error.
Setting the transaprency of a JPanel this way won't work, as it will still lay on the JFrame, which is not transparent.
I could find no other answer on Stack Overflow that correctly addressed this issue. In fact, a few suggested that this could be fixed with:
JFrame.setDefaultLookAndFeelDecorated(true);
But they were misinformed of perhaps referring to Java 7, as I have tested it and the result is just the same.
I have also tried to manually set the Look And Feel:
try {
for (final LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch [...]
And combining this with the solution suggested above also did not work.
Please refer to the code over to the example I linked above (Oracle doc) for a MCVE, as that is the one I'm using.
Any way around this?
As far as I can tell, the basic answer is: it is not possible, at least with the System look and feel. As indicated in Is The Java Tutorials Translucent Window example giving trouble to those playing with jdk7?, the JavaDocs clearly indicate that “the window must be undecorated” for setOpacity() to work.
It is however possible to do it with the (ugly) Cross-platform look and feel, that you can progrmmatically set as follows:
UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName());
In fact, as the cross-platform look and feel could be overridden through configuration, the safest would actually be to set it explicitly to Metal as follows:
UIManager.setLookAndFeel(new MetalLookAndFeel());
The reason this works, is that the JDK implementation of Frame.setOpacity() throws an exception when !isUndecorated(), and JFrame.frameInit() sets itself as undecorated when the look and feel's getSupportsWindowDecorations() returns true. It then calls getRootPane().setWindowDecorationStyle() with JRootPane.FRAME, indicating that the decorations will be provided by the root pane instead of the frame.
From what I can see in the JDK, the Metal look and feel is the only one for which getSupportsWindowDecorations() returns true, as it is the only one which overrides it, and the default implementation simply returns false.
However, some third-party look and feels support it too. This is the case for instance for the Tiny Look and Feel, as I just tried:
(Note that I took this screenshot on Ubuntu, TinyLAF just so happens to have a default theme that looks like Windows XP!)
See also this question for a list of known third-party look and feels.
Try adding this line before creating the JFrame window:
JFrame.setDefaultLookAndFeelDecorated(true);
Exactly that line, don't replace JFrame in the beggining, it needs to be JFrame.
(You can also spot this line in the tutorials you mentioned precicely placed before creating the window).
Actually this is possible, using the dirty solution of reflect. If we dig into setOpacity method (which is inherited from java.awt.Frame class) we will see the following code:
#Override
public void setOpacity(float opacity) {
synchronized (getTreeLock()) {
if ((opacity < 1.0f) && !isUndecorated()) {
throw new IllegalComponentStateException("The frame is decorated");
}
super.setOpacity(opacity);
}
}
where isUndecorated is a simple getter to the field named undecorated (inside java.awt.Frame class).
Changing the value of this field will do the trick and this exception won't be thrown.
Check this example i have made:
public class JFrameOpacity {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
setSystemLookAndFeel();
JFrame frame = new JFrame("Opacity to decorated Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new FlowLayout());
JButton decreaseOpacity = new JButton("Reduce Opacity");
decreaseOpacity.addActionListener(e -> {
if (frame.getOpacity() - 0.1f <= 0.1f)
frame.setOpacity(0.1f);
else
frame.setOpacity(frame.getOpacity() - 0.1f);
});
frame.add(decreaseOpacity);
JButton increaseOpacity = new JButton("Increase Opacity");
increaseOpacity.addActionListener(e -> {
if (frame.getOpacity() + 0.1f >= 1f)
frame.setOpacity(1f);
else
frame.setOpacity(frame.getOpacity() + 0.1f);
});
frame.add(increaseOpacity);
frame.setSize(300, 300);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
try {
undecorate(frame); //Change it after frame is visible
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
});
}
private static void undecorate(Frame frame) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Field undecoratedField = Frame.class.getDeclaredField("undecorated");
undecoratedField.setAccessible(true);
undecoratedField.set(frame, true);
}
private static void setSystemLookAndFeel() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
}
}
Preview:
I have tested this in The Microsoft Windows Look and Feel (Windows 7 x64) and it works. Note the comment i added when i call undecorate method. I made some tests on that and i realized that if you undecorate the frame before it gets at least one time visible, when you will make it visible it will be undecorated - it will not have this title bar and stuff.
I am not sure though if this is going to give other problems to the application but you can always change the value of the field, change its opacity and the set it back.
Related
Mac OS X Catalina
JDK OpenJDK 14.0.2 (same issue 14.0.1)
public class PriceUpdateForm extends JPanel {
/* lots of code omitted */
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> startGUI());
/* try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
System.exit(17);
} */
}
private static void startGUI() {
final var form = new PriceUpdateForm();
form.j = new JFrame();
form.j.getContentPane().add(form);
form.j.pack();
form.j.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
form.j.setVisible(true);
}
}
When this class's main method is invoked directly from the command line (or IntelliJ), it works as expected, even with the sleep commented out. The window stays visible until you click the close button, and then the program exits.
When, instead, I use a trampoline pattern where the class name is the first argument as shown below, the JVM is willing to exit even with the window visible. I had a hard time debugging this when the window disappeared right away, but if you uncomment the sleep, the JVM will exit after 3 seconds, with the window clearly visible until then.
Excerpt from main method of Trampoline
final Class<?> mainClass = Class.forName(args[0]);
final Method mainMethod = mainClass.getDeclaredMethod("main", newArgs.getClass());
mainMethod.invoke(null, new Object[]{newArgs});
newargs is just the original arguments minus the zeroth.
I get that without the sleep, the PriceUpdateForm.main is going to exit, but I don’t see why it takes the Window down with it. That’s been created on the GUI thread. It is not going out of scope, or, at least, its scope should be the same no matter how main was called: directly or by reflection.
Can someone explain this difference in behavior? I suppose I can make a years-long sleep, but that seems ugly.
This utility application takes a screenshot of multiple monitors by pressing a button on a JFrame. The intended logic of this method is as follows:
Create new Rectangle to represent a union of all the monitors' boundsm
Use InvokeAndWait to hide to JFrame, and ensure it is hidden before processing any further
Take a screenshot using the Robot class
Set the frame to visible again and return the image
Even with the final "return frame to visible" step commented out of the code, leaving me seeing no frame on the screen after execution, the frame is visible in the screenshot. I do not know why.
Using print statements, I have determined that the firing of the method to turn the JFrame invisible does run before taking the screenshot. I also attempted to use an if statement to check if the JFrame was visible before taking the screenshot, and the if statement was never triggered.
What is the solution to this?
public Image capture(ArrayList<Monitor> monitors) {
Rectangle bounds = new Rectangle();
monitors.stream().forEach(a -> Rectangle.union(bounds, a.getBounds(), bounds) );
Image image = null;
try {
EventQueue.invokeAndWait(() -> {
frame.setVisible(false);
System.out.println("Set frame invisible");
});
} catch (Exception ex) {
ex.printStackTrace();
}
try {
image = new Image(new Robot().createScreenCapture(bounds));
} catch (Exception ex) {
ex.printStackTrace();
}
//frame.setVisible(true);
return image;
}
This is not a definitive answer, but maybe it'll point you in the right direction.
I had a similar problem requiring a JFrame to go fullscreen on a 3D application using OpenGL.
What I assume is happening is that frame.setVisible() talks to the OS window manager requesting the window be hidden (or shown). Once the request is made and acknowledged, the method call is no longer blocking, and the thread in invokeAndWait() is now done invoking and waiting.
Your code proceeds to a take a screenshot, but the operating system is not guaranteed to have actually processed the request to minimize the Window. In your case, it appears that is doesn't.
The Solution (maybe?):
It looks like Java has a class called a WindowEvent doc here. You should be able to create a listener and/or a loop before the screenshot call that waits for a status update. Note the doc specifically says:
The window-deactivated event type. This event is delivered when the Window is no longer the active Window. Only a Frame or a Dialog can be the active Window. The native windowing system may denote the active Window or its children with special decorations, such as a highlighted title bar. The active Window is always either the focused Window, or the first Frame or Dialog that is an owner of the focused Window.
I suspect waiting for the WINDOW_DEACTIVATED and/or WINDOW_LOST_FOCUS could be the actual indicator of if the window has been minimized by the OS window manager.
Again, I'm basing this off of a mostly unrelated project I worked on several months ago, but hopefully some of this will help.
UPDATE
OP Implemented a Solution as follows:
public Image capture(ArrayList<Monitor> monitors)
{
Rectangle bounds = new Rectangle();
monitors.stream().forEach(a -> Rectangle.union(bounds, a.getBounds(), bounds) );
Image image = null;
try
{
EventQueue.invokeAndWait(() -> frame.setExtendedState(Frame.ICONIFIED));
while (frame.getExtendedState() != Frame.ICONIFIED) { }
image = new Image(new Robot().createScreenCapture(bounds));
}
catch (Exception ex)
{
ex.printStackTrace();
}
EventQueue.invokeLater(() -> frame.setExtendedState(Frame.NORMAL));
return image;
}
I need the real preferred size (result from the children) of a Pane but I only get -1. Is there any possibility to find it out?
Ok, here a sample code:
The output is -1.0, 0.0, -1.0, -1.0 but obviously the pane with the button has a "preferred" size!? Only the new Thread has as output the "real" size of the pane but not the preferred one because if I resize the window in the 3 seconds it shows the "real" size and not the preferred size of the beginning...
public class ButtonTest extends Application
{
public static void main(String [] a)
{
Application.launch(a);
}
#Override
public void start(Stage stage) throws Exception
{
BorderPane root = new BorderPane();
StackPane pane = new StackPane();
Button b = new Button("Testbutton");
pane.getChildren().add(b);
pane.setStyle("-fx-background-color: red;");
System.out.println(pane.getPrefWidth());
System.out.println(pane.getWidth());
System.out.println(pane.getMinWidth());
System.out.println(pane.getMaxWidth());
root.setCenter(pane);
Scene scene = new Scene(root);
Thread t = new Thread(new Runnable()
{
#Override
public void run()
{
try
{
Thread.sleep(3000);
} catch (InterruptedException e)
{
// TODO: Handle Exception
e.printStackTrace();
}
System.out.println(pane.getPrefWidth());
System.out.println(pane.getWidth());
System.out.println(pane.getMinWidth());
System.out.println(pane.getMaxWidth());
}
});
t.setDaemon(true);
t.start();
}
}
By default StackPane has the PrefWidth and PrefHeight set by the inherited constraint "USE_COMPUTED_SIZE" (witch values is equal to -1) defined in parent class "Region".
This definition means the preferable size will be computed in layout process.
Perhaps your concept is missplaced. Pref values are used to layout process know how it should arrange object in canvas. To know real size of componente you can use methods like I mentioned before 'getBoundsInParent()' or 'getBoundsInLocal()' (Diference between thoose are explained in javadoc).
BUT is important to understand a component just has his size computed during the layout process. Usually, layout process occurs on separated thread than your events or your main() thread. Thats why after sleeping 3 seconds let you do the trick. You can try something like using the following code to let your code be executed in the JavaFX GUI thread (started on your last line of code):
Platform.runLater(new Runnable() {
#Override public void run() {
<Your code in GUI Thread>
}
});
Hope it can give a little help. Sorry for not offer you a sample code, but I'm not in a machine with environment installed. Will try to better my answer latter.
Good luck.
(Please anyone, feel free to correct my english, I know it's terrible)
Ok, thank you very much for this explination. But the other methods don't return the right result, too...
Maybe this case helps to understand what my goal is:
One related problem is that I have a SplitPane, two parts, now I hide the second part (divider position = 1.0) and show only the first one, sometimes I like to show also the second part. I do this over repostioning of the dividers. But at any time it can happen, that the second part can be updated with new elements (=> update of the necessary size). Now, to reposition the divider (with an animation) I need the preferred size of the (hidden) second part?! Without real code for that do you have any idea how to solve the problem?
Update:
I solved the issue with the "real size" adding a public doubleproperty to the pane and bind it to the widthproperty of the pane. Now I could add a changelistener to this doubleproperty in the splitpane. After first time being visible I got the right pixel size.
public DoubleProperty realWidth;
pane.setPrefWidth(USE_PREF_SIZE);
pane.setMaxWidth(USE_PREF_SIZE);
realWidth = new SimpleDoubleProperty(0.0);
realWidth.bind(this.widthProperty());
=> Add a change listener from somewhere else
But know I have the problem that if I add a button (after 3 seconds) to the pane the splitpane (the changelistener on this realWidth property) is not notified...
Update: It is notified :D But not adding a button but adding e. g. a stackpane works as expected. Many thanks for your help to understand how it works!
My users like having multiple JFrames; it allows them to resize the different components and place them wherever they want on the screen. However, I have a request to make all the child windows come to the front together... in other words, lets say they maximize another window in front of all the windows, and then use the task bar to click on just one of the JFrames. How can I set it so that they all come to the front? Note: it is also possible to close the child windows; if they are actually hidden, I do not want them to come to the front. I have a class ApplicationModel that keeps track of whether a window is hidden or not.
Things I've tried:
Using windowActivated() and focusGained() to try to bring them all to the front. This usually results in an infinite loop. The problem is that my eventing framework sends these requests off the Event Dispatch Thread, so any sort of blocking with an AtomicBoolean doesn't last long enough.
The main problem is not that I can't make them come to the front... I have made them come to the front. The problem is that they KEEP trying to come to the front, as bringing a window to the front throws the focusGained and windowActivated events, which creates an endless loop...
Making one window the master, and making the others a JDialog. Unfortunately, either the windows are modeless (and therefore don't come to front with the master window), or they are modal, (and therefore block the master window).
How can I fix either of these problems, or is there an entirely different third solution?
You can use a boolean field as a flag to prevent the infinite loop:
private boolean movingAllFramesToFront;
public void windowActivated(WindowEvent event) {
if (movingAllFramesToFront) {
return;
}
movingAllFramesToFront = true;
List<Frame> frames = getAllApplicationFrames();
for (Frame frame : frames) {
if (!applicationModel.isHidden(frame)) {
frame.toFront();
}
}
event.getWindow().toFront();
event.getWindow().requestFocus();
EventQueue.invokeLater(new Runnable() {
public void run() {
movingAllFramesToFront = false;
}
);
}
Another thing you can try is the new autoRequestFocus property introduced in Java 1.7. I have never tried using it, but here's my understanding of how it works:
public void windowActivated(WindowEvent event) {
final List<Frame> frames = getAllApplicationFrames();
for (Frame frame : frames) {
if (!applicationModel.isHidden(frame)) {
frame.setAutoRequestFocus(false);
frame.toFront();
}
}
EventQueue.invokeLater(new Runnable() {
public void run() {
for (Frame frame : frames) {
if (!applicationModel.isHidden(frame)) {
frame.setAutoRequestFocus(true);
}
}
}
);
}
I have an application with a lot of windows and had a problem similar to yours. My workaround is:
#Override
public void windowActivated(WindowEvent e) {
if (e.getOppositeWindow() == null) {
//front every window
}
}
First I created a class "SlveFrame" (Slve being the name of my app), a child of "JFrame".
public class SlveFrame extends JFrame implements WindowListener {
static ArrayList<SlveFrame> frames = new ArrayList<SlveFrame>();
public SlveFrame () {
addWindowListener(this); / /to make JFrame fire WindowListener's method
}
/ /... every method added from WindowListener
#Override
public void windowActivated(WindowEvent e) {
if (e.getOppositeWindow() == null) { // return null if window is not from my (or Your) work
for (SlveFrame frame : frames) { // if you have no idea what this is, look for "for each loop java" in google
frame.toFront();
}
}
}
/**
* The use of SlveFrame is almost the same as Jframe
*/
#Override
public void setVisible (boolean b) {
if (b)
frames.add(this);
else
frames.remove(this); // may raise an exception if you're not careful
super.setVisible(b); // or your window will simply not be visible.
}
#Override
public void dispose () {
frames.dispose(this) // may raise an exception you'll want to handle
}
}
The trick being that WindowEvent.getOppositeWIndow() returns a Jframe if the JFrame (or child class) is from your own program, meaning that if you switch to another program or app (such as eclipse, Firefox or a text editor) then back to any of your windows, then a call to getOppositeWindow() will return a 'null'. A simple if (e.getOppositeWindow()) makes it fairly easy to determine whether your window gain focus in condition that would require you to bring every window to the front, or rather to let everything be.
The overriding of setVisible (boolean b) and dispose () are optional but allow the dev to use it as a regular window.
I hope i could be of some help. Sincerly ~a lama
I have to write an applet that brings up a password dialog. The problem is that dialog is set to be always on top but when user clicks on IE window dialog gets hidden behind IE window nevertheless. And since dialog is modal and holds all IE threads IE pane does not refresh and dialog window is still painted on top of IE (but not refreshed). This behaviour confuses users (they see dialog on top of IE but it looks like it has hanged since it is not refreshe).
So I need a way to keep that dialog on top of everything. But any other solution to this problem would be nice.
Here's the code:
PassDialog dialog = new PassDialog(parent);
/* do some non gui related initialization */
dialog.pack();
dialog.setLocationRelativeTo(null);
dialog.setAlwaysOnTop(true);
dialog.setVisible(true);
Resolution: As #shemnon noted I should make a window instead of (null, Frame, Applet) parent of modal dialog. So good way to initlialize parent was:
parent = javax.swing.SwingUtilities.getWindowAncestor(theApplet);
What argument are you using for the parent?
You may have better luck if you use the parent of the Applet.
javax.swing.SwingUtilities.getWindowAncestor(theApplet)
Using the getWindowAncestor will skip the applet parents (getRoot(component) will return applets). In at least some versions of Java there was a Frame that was equivalent to the IE window. YMMV.
Make a background Thread that calls toFront on the Dialog every 2 seconds.
Code that we use (I hope I got everything):
class TestClass {
protected void toFrontTimer(JFrame frame) {
try {
bringToFrontTimer = new java.util.Timer();
bringToFrontTask = new BringToFrontTask(frame);
bringToFrontTimer.schedule( bringToFrontTask, 300, 300);
} catch (Throwable t) {
t.printStackTrace();
}
}
class BringToFrontTask extends TimerTask {
private Frame frame;
public BringToFrontTask(Frame frame) {
this.frame = frame;
}
public void run()
{
if(count < 2) {
frame.toFront();
} else {
cancel();
}
count ++;
}
private int count = 0;
}
public void cleanup() {
if(bringToFrontTask != null) {
bringToFrontTask.cancel();
bringToFrontTask = null;
}
if(bringToFrontTimer != null) {
bringToFrontTimer = null;
}
}
java.util.Timer bringToFrontTimer = null;
java.util.TimerTask bringToFrontTask = null;
}
This is a shot in the dark as I'm not familiar with applets, but you could take a look at IE's built-in window.showModalDialog method. It's fairly easy to use. Maybe a combination of this and Noah's suggestion?
You might try launching a modal from JavaScript using the JavaScript integration (see http://www.raditha.com/java/mayscript.php for an example).
The JavaScript you would need would be something like:
function getPassword() {
return prompt("Enter Password");
}
And the Java would be:
password = jso.call("getPassword", new String[0]);
Unfortunately that means giving up all hope of having a nice looking modal. Good luck!