修改Windows系统字体抗锯齿后Java Swing UI设置丢失问题求解
运行如下示例程序时,修改Windows系统字体抗锯齿设置(操作路径:WIN+R输入sysdm.cpl→系统属性→高级→性能设置→平滑屏幕字体边缘),可观察到JTextArea字体显示异常,即便再次切换抗锯齿开关也无法恢复正常。
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); } }
仅当取消注释代码中setFont方法的重写逻辑时,功能表现才符合预期。在该方法处设置断点可发现,修改字体抗锯齿设置时会触发AATextListener,执行流程中会调用editor.setFont(UIManager.getFont(prefix /*TextArea*/ + ".font"))加载默认配置,这是问题的核心诱因。
上述仅为最简复现场景,实际业务应用中问题影响范围更广,不仅自定义字体丢失,按钮边框、自定义ComponentUI等几乎所有UI配置都会被重置。除手动修改抗锯齿设置外,应用运行数小时后也会自发触发该问题,通常出现在PC空闲近1小时后,由未知Windows事件触发监听器破坏应用外观。
当前运行环境:Windows 10 20H2企业版(内部版本19042.1348),Java版本为zulu11.50.19-ca-jdk11.0.12-win_x64。
- 重写组件的setFont等相关方法:侵入性较强,大型遗留应用中改造工作量极大,落地难度高
- 添加启动参数如
-Dswing.useSystemFontSettings=false(或-Dswing.aatext=true、awt.useSystemAAFontSettings):会导致字体显示效果差,无额外优化的前提下不可接受 - 通过UIManager修改全局默认配置:大型遗留应用中改造工作量极大,落地难度高
- 移除或禁用对应监听器:该监听器为硬编码实现,操作难度极高
方案1:全局移除AATextListener(推荐,侵入性最低)
在应用启动初期通过反射移除Toolkit中注册的AATextListener,直接阻断系统字体抗锯齿变更事件的监听,不会影响原有字体抗锯齿效果,仅阻止Swing自动重置UI配置的行为,全程仅需新增一个工具类+1行启动调用,无需修改任何业务组件代码,对遗留应用友好。
工具类代码示例:
import java.awt.Toolkit; import java.beans.PropertyChangeListener; import java.lang.reflect.Field; public class AATextListenerDisabler { public static void disable() { try { Toolkit toolkit = Toolkit.getDefaultToolkit(); // 移除awt.font.desktophints事件的AATextListener监听 for (PropertyChangeListener listener : toolkit.getPropertyChangeListeners("awt.font.desktophints")) { if (listener.getClass().getName().contains("AATextListener")) { toolkit.removePropertyChangeListener("awt.font.desktophints", listener); break; } } } catch (Exception e) { // 兼容不同JDK版本,异常可忽略,不影响应用正常运行 e.printStackTrace(); } } }
使用方法:在main方法启动初始化UI前调用AATextListenerDisabler.disable()即可。
方案2:全局覆盖UIManager默认配置(次选,兼容性更好)
如果担心反射兼容性问题,可以在应用启动时统一覆盖UIManager中所有组件的默认配置,即便AATextListener触发默认配置加载,也会加载和业务一致的配置,不会出现显示异常,该方案同时可以解决按钮、边框等其他UI配置被重置的问题。
示例代码:
import javax.swing.UIManager; import java.awt.Font; import javax.swing.plaf.FontUIResource; public class UIDefaultsInit { public static void init(Font businessFont) { // 覆盖所有组件默认字体 java.util.Enumeration<Object> keys = UIManager.getDefaults().keys(); while (keys.hasMoreElements()) { Object key = keys.nextElement(); Object value = UIManager.get(key); if (value instanceof FontUIResource) { UIManager.put(key, businessFont); } } // 如有其他自定义UI配置,可参照上述逻辑统一覆盖默认值 } }
使用方法:在应用启动初始化UI前调用UIDefaultsInit.init(业务使用的自定义字体实例)即可。
内容的提问来源于stack exchange,提问作者Constantin Butzke

