If i create non-resizable JFrames, and windows Aero is enabled setLocation does not seem to take account of the window border correctly.
In the following code I would expect the second frame to be positioned to the right of the first frame, instead the borders are overlapping. If Aero is disabled or if I remove the calls to setResizable this is done as expected.
import java.awt.Rectangle;
import javax.swing.JFrame;
public class FrameBorders {
public static void main(String[] args) {
JFrame frame1 = new JFrame("frame 1");
JFrame frame2 = new JFrame("frame 2");
frame1.setResizable(false);
frame2.setResizable(false);
frame1.setVisible(true);
Rectangle bounds = frame1.getBounds();
frame2.setLocation(bounds.x+bounds.width, bounds.y);
frame2.setVisible(true);
}
}
Am I doing something wrong or is this a bug?
How can I display 2 unresizable dialogs side by side without having overlapping borders?
Edit: added screenshots (also changed frame2 to a JDialog instead of a JFrame)
Aero On:
Aero Off:
Aero On but resizable:
What are the problems with settings bounds on non-resizable containers?
Suppose you adjust the bounds to look good on your platform. Suppose the user's platform has a font with different, say larger, FontMetrics. This example is somewhat contrived, but you get the idea. If you change the bounds of a non-resizable container, be sure any text is visible regardless of the host platform's default font.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/**
* #see http://stackoverflow.com/a/12532237/230513
*/
public class Evil extends JPanel {
private static final String s =
"Tomorrow's winning lottery numbers: 42, ";
private JLabel label = new JLabel(s + "3, 1, 4, 1, 5, 9", JLabel.LEFT);
public Evil() {
this.add(label);
}
private void display() {
JFrame f = new JFrame("Evil");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this, BorderLayout.WEST);
f.pack();
int w = SwingUtilities.computeStringWidth(
label.getFontMetrics(label.getFont()), s);
int h = f.getHeight();
f.setSize(w, h);
f.setResizable(false);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Evil().display();
}
});
}
}
It seems that this is not a Java issue but rather an aero appcompat issue , as described here.
One solution that I see in Java is to let the windows be resizable then work around the setMaximumSize bug
Related
When running below simple app and changing font anti-aliasing (WIN+R "sysdm.cpl" -> System Properties -> Advanced -> Performance Settings -> Smooth edges of screen fonts) one can see that the JTextArea gets the wrong font (and stays with it also if toggle anti-aliasing again):
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class Main
{
public static void main(String[] args)
{
JFrame frame = new JFrame();
JPanel mainPanel = new JPanel();
JLabel label = new JLabel("labelMessage:");
mainPanel.add(label);
final JTextArea textArea = new JTextArea("textAreaMessage")
{
// #Override
// public void setFont(Font f)
// {
// super.setFont(label.getFont());
// }
};
textArea.setEditable(false);
textArea.setOpaque(false);
textArea.setFont(label.getFont());
mainPanel.add(textArea);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setVisible(true);
}
}
Only, when uncommenting the override of setFont it works like one would expect.
When setting there a breakpoint one can see that when the font anti-aliasing is changed there is a AATextListener fired:
Where at some point the defaults are installed:
editor.setFont(UIManager.getFont(prefix /*TextArea*/ + ".font"));
So it is clear why it is happening but I do not know how to solve it in a good fashion.
Possible solutions:
Override the methods like shown above in the code
Use e.g. -Dswing.useSystemFontSettings=false (or -Dswing.aatext=true or awt.useSystemAAFontSettings) BUT then the font looks ugly so not acceptable without further enhancements
Set via UIManager the defaults
Remove/disable the listener? Seems to be hard coded and not be doable easy?
or 3. could be a solution but would require much work in a big legacy app.
NOTE: This is just a simple example. In our app the problem is much bigger as not only set fonts are lost, but almost everything, like borders of buttons, the custom ComponentUIs etc. And the described way how to reproduce it was just to have it easy reproducible, means we encounter this issue also spontaneously when e.g. the app runs for some hours. We then see that some windows event is firing the listener and destroying the look and feel of our app (often the PC was not used at all for almost an hour when this suddenly happens).
Maybe I will also ask the OpenJDK community the next days, as well. If so, I will link the ticket ID here.
Windows version: Windows Enterprise 10 Version 20H2 (Build 19042.1348)
Java version: zulu11.50.19-ca-jdk11.0.12-win_x64
Demo which listens for change like proposed by #VGR. When font anti aliasing is changed the UI will be re-rendered.
import java.awt.Toolkit;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class Main
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> {
final JFrame frame = new JFrame();
final JPanel mainPanel = new JPanel();
Toolkit tk = Toolkit.getDefaultToolkit();
tk.addPropertyChangeListener("awt.font.desktophints"/*SunToolkit.DESKTOPFONTHINTS*/, new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent evt)
{
SwingUtilities.invokeLater(() -> render(mainPanel));
}
});
render(mainPanel);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setVisible(true);
});
}
private static void render(JPanel parent)
{
parent.removeAll();
final JPanel panel = new JPanel();
final JLabel label = new JLabel("labelMessage:");
panel.add(label);
final JTextArea textArea = new JTextArea("textAreaMessage");
textArea.setEditable(false);
textArea.setOpaque(false);
textArea.setFont(label.getFont());
panel.add(textArea);
parent.add(panel);
parent.revalidate();
parent.repaint();
}
}
It's an old question but I'd like to explain what's going on here.
textArea.setFont(label.getFont());
You use the font of the label for textArea. Since you didn't change the font, the font is a UIResource, therefore the font gets reset to the default font whenever UI components are updated because of a desktop property change or any other update, like Look-and-Feel change.
When such an event occurs, the UI of all the components is updated using updateUI() method of JComponent.
Creating a new instance of Font removes the magic of UIResource:
Font labelFont = label.getFont();
textArea.setFont(labelFont.deriveFont(labelFont.getStyle()));
The font of the textArea is not reset by updateUI. Yet the font will not change even when you want it to change, for example when the Look-and-Feel of your app is updated to another LaF.
A better solution is to override updateUI method and set the font for textArea there:
final JTextArea textArea = new JTextArea("textAreaMessage") {
#Override
public void updateUI() {
super.updateUI();
setFont(UIManager.getFont("Label.font"));
}
};
This approach handles both the initial setup as well as any updates. This can be a new reusable component which is used to display selectable text, if you also set the background and foreground colors and make the text non-editable by default. (If you set the background and foreground colors, which I recommend doing, you won't need textArea.setOpaque(false);, it makes paint and repaint faster.)
The runnable example:
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class UIFontUpdate {
private final JFrame frame;
public static void main(String[] args) {
SwingUtilities.invokeLater(UIFontUpdate::new);
}
private UIFontUpdate() {
frame = new JFrame("UI Font Update");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel mainPanel = new JPanel();
JLabel label = new JLabel("labelMessage:");
mainPanel.add(label);
final JTextArea textArea = new JTextArea("textAreaMessage") {
#Override
public void updateUI() {
super.updateUI();
setFont(UIManager.getFont("Label.font"));
}
};
textArea.setEditable(false);
textArea.setOpaque(false);
mainPanel.add(textArea);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new Thread(this::callUpdateUI).start();
}
public void callUpdateUI() {
try {
Thread.sleep(2000);
} catch (InterruptedException ignored) { }
System.out.println("UI updating...");
SwingUtilities.invokeLater(() ->
SwingUtilities.updateComponentTreeUI(frame));
}
}
I'm simulating update to properties without actually changing system settings. I'm using callUpdateUI which is run on another thread, and after 2 seconds it updates the UI.
To test that the approach works when changing the Look-and-Feel, change the code callUpdateUI to:
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException
| IllegalAccessException
| UnsupportedLookAndFeelException ignored) {
}
SwingUtilities.updateComponentTreeUI(frame);
});
I am trying to make a modeless dialog menu in Swing that is displayed upon the press of a button. The dialog contains several menu items. My problem is that the dialog window is much wider than necessary. I am looking for help on setting the window width.
Here's what the output looks like. Notice that the window containing the menu items is much wider than the items themselves. That's what I want to fix.
Here's minimal code that shows this problem:
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test().run();
}
TestDialog testDialog;
private void run() {
JFrame jframe = new JFrame();
JButton jbutton = new JButton("test");
jframe.add(jbutton);
jbutton.setBounds(130, 100, 100, 40);
jframe.setSize(400, 500);
jframe.setLayout(null);
jframe.setVisible(true);
testDialog = new TestDialog(SwingUtilities.windowForComponent(jframe));
jbutton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
testDialog.show();
}
});
}
private class TestDialog {
JDialog jdialog;
public TestDialog(Window parent) {
jdialog = new JDialog(parent, "Test", Dialog.ModalityType.MODELESS);
jdialog.setPreferredSize(new Dimension(100, 0));
jdialog.setLayout(new BoxLayout(jdialog.getContentPane(), BoxLayout.Y_AXIS));
JMenuItem jmenuItem1 = new JMenuItem("MenuItem 1");
Dimension jmiDimension = jmenuItem1.getPreferredSize();
System.out.printf("jmenuItem1 is %f x %f%n", jmiDimension.getWidth(), jmiDimension.getHeight());
jdialog.add(jmenuItem1);
jdialog.add(new JMenuItem("MenuItem 2"));
jdialog.pack();
Dimension d = jdialog.getSize();
System.out.printf("jdialog is %f x %f%n", d.getWidth(), d.getHeight());
}
public void show() {
jdialog.setVisible(true);
}
}
}
The program prints this output, showing that the dialog is 324 pixels wide but the menu items are 87:
jmenuItem1 is 87.000000 x 21.000000
jdialog is 324.000000 x 88.000000
I have also tried using the jdialog.setSize() and jdialog.setMaximumSize() methods. And I've tried setting the maximum size of the menu items. None of them seem to have any affect upon the dialog's window size.
I also tried a GridLayout, rather than a BoxLayout - that also made no difference.
I also tried setting the width of the dialog's content pane and layered pane. Still no difference.
I noted that the dialog has no owner nor parent.
testDialog = new TestDialog(SwingUtilities.windowForComponent(jframe));
You don't need to use the windowForComponent(...) method. You already have a reference to the parent frame:
testDialog = new TestDialog( jframe );
Don't attempt to hard code sizes. Each component will determine its own preferred size and the then layout manager will use this information to determine the overall size.
//jdialog.setPreferredSize(new Dimension(100, 0));
A JMenuItem was designed to be used on a JMenu. Instead you should be using a JButton to add to a regular panel.
I don't have a problem with the size. The width/height of the dialog is as expected.
Note I use JDK 11 on Windows 10. When I ran your original code, the dialog had no size since the setPreferredSize() statement caused the height to be 0. So I'm not sure how you get the display shown in your image.
And yes the dialog width is wider than the component added to the frame because there is a minimum width to the dialog to be able to be able to display the title bar even if no components are added to the dialog.
If I add an instance of WorldWindowGLJPanel to my JFrame then remove it and add it again, the panel's GL viewport gets recalculated to a Rectangle that is much smaller than the available space. The dimension appears to consistently be 116x26. What this means is the frame becomes mostly blank with just a small piece of the WorldWind panel displaying in the bottom left corner of the frame. Resizing the frame appears to reset the viewport but is there a way to reset the viewport programmatically?
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import gov.nasa.worldwind.Model;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.awt.WorldWindowGLJPanel;
public class WorldWindTest {
public static void main(String[] args) {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final WorldWindowGLJPanel wwPanel = new WorldWindowGLJPanel();
Model wwModel = (Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
wwPanel.setModel(wwModel);
JPanel buttons = new JPanel(new FlowLayout());
buttons.add(new JButton(new AbstractAction("Re-add WorldWind panel") {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
frame.getContentPane().remove(wwPanel);
frame.getContentPane().add(wwPanel);
frame.getContentPane().repaint();
}
}));
frame.getContentPane().add(buttons, BorderLayout.NORTH);
frame.getContentPane().add(wwPanel);
frame.setSize(800, 600);
frame.setLocationRelativeTo(null);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.setVisible(true);
}
});
}
}
4 and half years late to the party, but posting my solution because I couldn't find one when I ran into this issue.
This problem seems to have nothing to do with WorldWindowGLJPanel, I believe it's a problem with any GLJPanel.
When you remove the GLJPanel from its parent, it resets its internal pixel scaling to some sentinel values. That's why it becomes tiny, but continues to (sorta) work.
The following 3 lines of code fixed it for me (I add them to a hierarchy listener and call them when the parent changes, but you can add them to the SSCCE above in the button handler)
wwPanel.initializeBackend(false);
wwPanel.reshape(0, 0, 0, 0);
wwPanel.revalidate();
initializeBackend tells OpenGL to initialize the backend, if it isn't initialized (Not 100% sure what causes it, but it seems to be tied to moving between parent windows).
Reshape will overwrite those pixel scale sentinel values, and so it'll actually calculate the proper size to display.
Revalidate causes it to recalculate the size of the panel, which I believe kicks off the size calculations now that those sentinel values aren't in the way.
I want to know how to place JButtons at a particular coordinate in the JFrame. All day I have seen layouts. This does not suit my purpose. I would prefer something like setBounds. Rumour has it that it does not work but setLocation does. I tried it but, the program disregards the setLocation line and sets it to a Layout.
CODE
import javax.swing.JButton;
import javax.swing.JFrame;
import java.awt.BorderLayout;
public class SwingUI extends JFrame {
public SwingUI() {
JFrame frm = new JFrame("OmegaZ");
JButton btn = new JButton("ClickMe");
frm.getContentPane().add(btn, BorderLayout.NORTH);
frm.setSize(400, 400);
frm.setVisible(true);
frm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
btn.setLocation(100, 200);
}
public static void main(String[] args) {
new SwingUI();
}
}
Any help is appreciated.
Many Thanks
You can do absolute positioning with a null layout. You do all the work in that case.
I have only JTabbedPane inside JFrame. JTabbedPane sets its dimensions to biggest page width/height.
As pages has different size is it possible to force JTabbedPane to change its dimensions when selecting other page?
http://grab.by/3hIg
Top one is how it behave now and bottom one is how i want it to behave (i resized frame by hand)
This is fairly simple. It involves dynamic calculation of differences between your pages dimensions and the using them to force preferred size on you JTabbedPane. I did a quick experiment and it worked. So instead of putting a lot of text here - here is the code. It is not perfect but you should get an idea. Questions are welcome, of course.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
private static int maxW = 0;
private static int maxH = 0;
public static void main(String[] args) {
final JFrame f = new JFrame();
final JTabbedPane tabs = new JTabbedPane();
tabs.add( createPanel(Color.RED, 100, 100), "Red");
tabs.add( createPanel(Color.GREEN, 200, 200), "Green");
tabs.add( createPanel(Color.BLUE, 300, 300), "Blue");
final Dimension originalTabsDim = tabs.getPreferredSize();
tabs.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
Component p = ((JTabbedPane) e.getSource()).getSelectedComponent();
Dimension panelDim = p.getPreferredSize();
Dimension nd = new Dimension(
originalTabsDim.width - ( maxW - panelDim.width),
originalTabsDim.height - ( maxH - panelDim.height) );
tabs.setPreferredSize(nd);
f.pack();
}
});
f.setContentPane(tabs);
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static final JPanel createPanel( Color color, int w, int h ) {
JPanel p = new JPanel();
p.setBackground(color);
p.setPreferredSize( new Dimension(w, h));
maxW = Math.max(w, maxW);
maxH = Math.max(h, maxH);
return p;
}
}
I think another option is to dynamically change the panels of each tab when the tab is selected:
install a listener on JTabbedPane selection
install an empty panel on every tab but the selected tab by default (that contains the real panel for that tab)
in the selection listener:
remove the panel from the previously selected tab (ie, replace it with an empty panel)
change the empty panel by the real panel in the newly selected tab
call pack() on the window/dialog containing the JTabbedPane
Disclaimer: I haven't tested this approach but I believe it should work according to what you want.
Please also note that dynamically changing the size of the dialog based on the selected tab is not very user-friendly from a pure GUI viewpoint.
How about this?
tabbedPane.addChangeListener(new ChangeListener(){
#Override
public void stateChanged(ChangeEvent arg0) {
Component mCompo=tabbedPane.getSelectedComponent();
tabbedPane.setPreferredSize(mCompo.getPreferredSize());
BasicFrame.this.pack();
}
});