Java loses UI settings after changing system font anti-aliasing - java

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);
});

Related

JScrollPane cannot detect text in need of scrolling

This is a simplified version of my code, if this is not enough to diagnose the problem, comment and I'll post more code:
//All Necessary Imports
JFrame window;
Container container;
JPanel mainTextPanel;
JScrollPane scrollPane;
JTextArea mainText;
Font normalFont = new Font("Times New Roman", Font.PLAIN,30);
ChoiceHandler choiceHandler = new ChoiceHandler();
//Unrelated Variables
public class setUp()
{
public setUp()
{
window = new JFrame();
window.setSize(825,600);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().setBackground(Color.white);
window.setLayout(null);
container = window.getContentPane();
mainTextPanel = new JPanel();
mainTextPanel.setBounds(100,100,600,250);
mainTextPanel.setBackground(Color.white);
container.add(mainTextPanel);
//more code goes here
}
}
public class mainText extends setUp
{
public mainText()
{
mainText = new JTextArea("Tons of text here");
mainText.setBounds(100,100,600,250);
mainText.setBackground(Color.white);
mainText.setForeground(Color.black);
mainText.setFont(normalFont);
mainText.setLineWrap(true);
scrollPane = new
JScrollPane(mainText,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
mainTextPanel.add(scrollPane);
mainTextPanel.revalidate();
mainTextPanel.repaint();
mainTextPanel.setVisible(true);
//more code goes here
}
}
My problem is that when I run the code, everything works except that the ScrollPane apparently cannot detect the fact that the text is out of bounds. The scrollbar is there, the only problem is that you cannot scroll with it, it looks like a scroll bar from when the text does not exceed the limit.
When I removed the ALWAYS modifier in creating the scrollbar, the scrollbar disappears, again demonstrating that the scrollbar simply does not detect the out-of-bounds text.
I'd just like to point out to you why null layouts suck ... code
This is how your code appears on my system. To be honest, there is scroll bar there, but your attempt to do away with the layout management API has caused the JScrollPane to exceed the physical bounds of the parent container.
You also don't seem to understand how the coordinate system works in Swing, in relationship to the parent component's context, but if you work with a layout manager instead, you wouldn't need to.
So, after updating your code a bit to use a layout managers
You know have a solution which can be scrolled...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
mainText mt = new mainText();
mt.window.pack();
mt.window.setLocationRelativeTo(null);
mt.window.setVisible(true);
}
});
}
public class setUp {
JFrame window;
Container container;
JPanel mainTextPanel;
JScrollPane scrollPane;
JTextArea mainText;
Font normalFont = new Font("Times New Roman", Font.PLAIN, 30);
public setUp() {
window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().setBackground(Color.white);
container = window.getContentPane();
mainTextPanel = new JPanel(new BorderLayout());
mainTextPanel.setBackground(Color.white);
container.add(mainTextPanel);
//more code goes here
}
}
public class mainText extends setUp {
public mainText() {
mainText = new JTextArea("Tons of text here");
mainText.setBackground(Color.white);
mainText.setForeground(Color.black);
mainText.setFont(normalFont);
mainText.setLineWrap(true);
scrollPane = new JScrollPane(mainText, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
mainTextPanel.add(scrollPane);
mainTextPanel.revalidate();
mainTextPanel.repaint();
//more code goes here
}
}
}
But where is the horizontal scroll bar?
mainText.setLineWrap(true); would have removed the need for it
But the JTextArea is to small!
So? Supply some appropriate sizing hints which the component can use to make better determinations about how large it might like to be.
Something like mainText = new JTextArea("Tons of text here", 1, 10); produces
So, the long and short answer is, use the layout management API, Swing has been designed around it's use and it will save you a lot of head scratching and stupid edge cases
But on my system the scroll bars don't appear!?
😓 Go back to the start, do not collect $200 and read it again. The reason why it would appear differently is because of the lack of layout support
But I don't want to use/understand layout managers!
Tough. If you want to avoid these kind of "weird" issues, then the layout management API is the right direction to head in. Take the time to experiment with the different layout managers and try different combinations, you're not stuck to using just one, almost all complex UIs will make use a at least two if not more layout managers in a single compound UI

Passing containers between JFrame and JDialog causing missing panels?

As for testing-reasons I tried to open a JDialog window with the panel and its contents of the main application frame. As I already had anything in there I wanted to see if I could simply set the JDialogs contentPane to the one I passed over. So in simplyfied form this came together:
testsforSO.java :
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class testsforSO extends JFrame {
private static final long serialVersionUID = -3890178393751567629L;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
testsforSO frame = new testsforSO();
frame.setSize(300, 300);
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public testsforSO() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setTitle("I am the Frame");
getContentPane().setLayout(new BorderLayout(0, 0));
JPanel panel = new JPanel();
panel.setLayout(null);
JButton btnRestart = new JButton("Restart");
btnRestart.setBounds(10, 10, 50, 50);
btnRestart.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
testsforSO.main(null);
dispose();
}
});
panel.add(btnRestart);
getContentPane().add(panel);
repaint();
// -----------DELETE These comments-------------
// JDialog myDialg = new JDialog(this);
// myDialg.setContentPane(panel);
// myDialg.setVisible(true);
// myDialg.setSize(300,300);
// myDialg.setLocation(new Point(250, 250));
// myDialg.setTitle("I am Dialog from within the script");
myDialog.main(panel);
}
}
and myDialog.java :
import java.awt.Container;
import java.awt.EventQueue;
import javax.swing.JDialog;
public class myDialog extends JDialog {
private static final long serialVersionUID = 7079322237622743228L;
public static void main(Container myContainer) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
myDialog frame = new myDialog(myContainer);
frame.setVisible(true);
frame.setContentPane(myContainer);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public myDialog(Container myContainer) {
setContentPane(myContainer);
getContentPane().setLayout(null);
setBounds(200,200,200,200);
}
}
When starting the main frame I assumed it would contain the restarting button as well as the dialog does. But interestingly it was only the dialog with the button.
However when hitting it, the main frame properly restarted, a second dialog was set up and it contained the button again. This time however the main frame had the button as well, just without any function. Clicking on it does absolutely nothing.
Then I tried further and checked if that behaviour would change if I added the dialog directly into the main applications code (see the commented lines) and, starting the application once again only the dialog in its own class showed the button. Even hitting this one now restarted properly but the button won't show up on any other window except the lonely declared dialog.
What am I missing here and how could I refactor my code to work properly if even at all in this manner?
Understand that Swing components can only be present in one container, and while you may see the visual residue of a component in a container, the actual component is only present in the last container added to.
Myself, if I wanted dialog and jframe to have the same content pane components, I'd create a factory method to create the contentPane, and then use it to create two unique but identical contentPanes.
Also, I'd be remiss if I didn't mention something about your use of null layouts. While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.
For instance, when I ran your code, this is the dialog that I saw:
You hard coded the button's size to be too small to show its text on my platform. If you had used layout managers, and called pack() on the top-level window, the button would show appropriately.

Java - JTextArea not shown after putting JscrollBar

I am new to Jframes and I wanted to design a window with a textbox and two buttons. I am able to get this working except for the scrollbar part.
I have written the below piece of code to enable scrollbar to the textarea.
private JTextArea outputPane;
outputPane = new JTextArea();
outputPane.setColumns(20);
outputPane.setRows(5);
outputPane.setFont(new Font("Monospaced", Font.PLAIN, 18));
outputPane.setBounds(12, 13, 408, 189);
contentPane.add(outputPane);
JScrollPane scrollPane = new JScrollPane(outputPane);
jScrollPane1.setBounds(399, 13, 21, 189);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
Now the problem is I am getting a disabled scrollbar on the window but I cannot see my Text Area.
Please help me out with the problem. I even tried using WindowsBuilder but I cannot figure it out.
Detailed explanation with the corrected code will be appreciated as I am still in the learning stage.
Thanks in advance.
Start by having a look at Laying Out Components Within a Container and How to Use Scroll Panes and How to Use Text Areas probably wouldn't hurt
Now the problem is I am getting a disabled scrollbar on the window but I cannot see my Text Area.
The likely problem is, you are seeing your JTextArea, the "disabled" scroll bar is simply because you're using scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, which will always display the scroll bar, even when there is nothing to be scrolled, so it probably looks empty.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
// Swing is not thread safe, so need to get started in the
// Event Dispatching Thread before we do anything
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
// I simply hate the default look and feel
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
// Always better to create an instance of a window
// to display you content then to extend from one
// directly...
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
// Our main UI, I do it this way so I'm not locked into a single
// use case and can decide how I want to use the view
public class TestPane extends JPanel {
public TestPane() {
// The default layout is a FlowLayout, so we want to change
// this will allow the main component to occupy the whole
// available space
setLayout(new BorderLayout());
// Providing "sizing" hints, 10 rows, 20 columns, this is
// platform independent, so it will size accordingly
JTextArea ta = new JTextArea(10, 20);
JScrollPane sp = new JScrollPane(ta);
add(sp);
}
}
}

How to remove minimize button and keep maximize and close button in JFrame

I want to remove only minimize button from JFrame
but want maximize and close button in JFrame title bar.
Here I am talking about removing not disabling.
I don't think removing the minimize button is a good thing. But may be you can use the setUndecorated() method to remove the title bar and window edges. And you'll have to add your own close and maximize buttons to perfom those action.
Here is an example :
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractAction;
import javax.swing.JButton;
public class Example {
public Example() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setUndecorated(true);//<---- this will disable the frame decorations
JPanel panel = new JPanel();
panel.add(new JLabel("titlebar"));
//Add button maximize
JButton button_max=new JButton("Maximize");
button_max.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if(frame.getExtendedState() == JFrame.NORMAL) {
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
} else {
frame.setExtendedState(JFrame.NORMAL);
}
}
});
panel.add(button_max);
//Add button close
JButton button_close = new JButton(new AbstractAction("Close") {
private static final long serialVersionUID = -4901571960357967734L;
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
panel.add(button_close);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
new Example();
}
}
Edit :
As #peeskillet states in the comment, even with this method the window still can be resized and draged by the user. The ComponentResizer class allows to do that.
Here is a an SO post which gives a good example to use this class with Jframe.
It's a very big hack, which works only with Synthetica L&F because it provides a painted title bar. Note: this L&F is not free to use. So if you use it you must by a license.
When you use this L&F you can iterate over all component starting from root pane to find an instance of SyntheticaTitlePane. On success you can try to access the field iconifyAction using Reflection Framework and use the method Action.setEnabled(false) on it.
I have no idea how to access the standard title bar because it's native. Probably it's impossible.

non resizable window border and positioning

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

Categories

Resources