如何禁用JLabel的自动HTML支持?

发布于 2024-09-16 06:13:20 字数 411 浏览 1 评论 0原文

Swing JLabel 会自动将任何以 开头的文本解释为 HTML 内容。如果此 HTML 的内容是具有无效 URL 的图像,这将导致整个 GUI 挂起,因为应加载此图像的 ImageFetche 将因 NPE 退出。

要重现此问题,只需创建一个 JLabel,如下所示

new JLabel("<html><img src='http:\\\\invalid\\url'>")

我知道有一个客户端属性可以阻止 JLabel 解释 HTML。但 JLabel 是许多 Swing 组件(如 JTree、JTable 等)的默认呈现器实现,这使得这对于几乎所有允许用户输入的 Swing 应用程序来说都是一个问题。因此,我没有实现大量的自定义渲染器,而是寻找一个全局解决方案来禁用 HTML 解释。

A Swing JLabel automatically interprets any text as HTML content, if it starts with <html>. If the content of this HTML is an image with invalid URL this will cause the whole GUI to hang since the ImageFetche which should load this image will quit by an NPE.

To reproduce this problem simply create a JLabel as follows

new JLabel("<html><img src='http:\\\\invalid\\url'>")

I know there is a client property to prevent the JLabel from interpreting HTML. But JLabel is the default renderer implementation for many Swing components(like JTree, JTable and so on) which makes this a problem for nearly any Swing application which allows user input. So instead of implementing tons of custom renderer I'm searching for a global solution to disable the HTML interpretation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

假扮的天使 2024-09-23 06:13:20

如果您创建自己的外观和感觉,就有办法。
我不确定它的性能如何,但它确实有效。假设您将扩展“Classic Windows”L&F。您至少需要 2 个类
一是 Look&Feel 本身,我们将其称为 WindowsClassicLookAndFeelExt。您只需要重写方法 initClassDefaults 即可。

package testSwing;

import javax.swing.UIDefaults;
import com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel;

public class WindowsClassicLookAndFeelExt extends WindowsClassicLookAndFeel    {
    @Override protected void initClassDefaults(UIDefaults table){
        super.initClassDefaults(table);
        Object[] uiDefaults = { "LabelUI", WindowsLabelExtUI.class.getCanonicalName()};
        table.putDefaults(uiDefaults);
    }
}

您还需要一个 WindowsLabelExtUI 类来管理所有 JLabels 并设置属性:

package testSwing;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import com.sun.java.swing.plaf.windows.WindowsLabelUI;

public class WindowsLabelExtUI extends WindowsLabelUI{
    static WindowsLabelExtUI singleton = new WindowsLabelExtUI();

    public static ComponentUI createUI(JComponent c){
        c.putClientProperty("html.disable", Boolean.TRUE);    
        return singleton;
    }
}

最后一个测试类,当您将主题设置为 WindowsClassicLookAndFeelExt 时

package testSwing;

import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.UIManager;


public class Main{
    public static void main(String[] args){
        try{                UIManager.setLookAndFeel(WindowsClassicLookAndFeelExt.class.getCanonicalName());
        }catch (Exception e){
            e.printStackTrace();
        }

        JFrame frame = new JFrame("JList Test");
        frame.setLayout(new FlowLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        String[] selections = {"<html><img src='http:\\\\invalid\\url'>", "<html><H1>Hello</h1></html>", "orange", "dark blue"};

        JList list = new JList(selections);

        list.setSelectedIndex(1);
        System.out.println(list.getSelectedValue());

        JLabel jLabel = new JLabel("<html><h2>standard Label</h2></html>");
        frame.add(new JScrollPane(list));
        frame.add(jLabel);
        frame.pack();

        frame.setVisible(true);
    }
}

,您将看到类似

替代文字

There is a way if you create your own look and feel.
I'm not sure how well this performs is this, but it works. Lets assume you will extend the "Classic Windows" L&F.You need at leas 2 classes
One is the Look&Feel itself, lets call it WindowsClassicLookAndFeelExt. You only need to override method initClassDefaults.

package testSwing;

import javax.swing.UIDefaults;
import com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel;

public class WindowsClassicLookAndFeelExt extends WindowsClassicLookAndFeel    {
    @Override protected void initClassDefaults(UIDefaults table){
        super.initClassDefaults(table);
        Object[] uiDefaults = { "LabelUI", WindowsLabelExtUI.class.getCanonicalName()};
        table.putDefaults(uiDefaults);
    }
}

You also need a WindowsLabelExtUI class to manage all JLabels and set the property:

package testSwing;
import javax.swing.JComponent;
import javax.swing.plaf.ComponentUI;
import com.sun.java.swing.plaf.windows.WindowsLabelUI;

public class WindowsLabelExtUI extends WindowsLabelUI{
    static WindowsLabelExtUI singleton = new WindowsLabelExtUI();

    public static ComponentUI createUI(JComponent c){
        c.putClientProperty("html.disable", Boolean.TRUE);    
        return singleton;
    }
}

And finally a test class when you set the theme as WindowsClassicLookAndFeelExt

package testSwing;

import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.UIManager;


public class Main{
    public static void main(String[] args){
        try{                UIManager.setLookAndFeel(WindowsClassicLookAndFeelExt.class.getCanonicalName());
        }catch (Exception e){
            e.printStackTrace();
        }

        JFrame frame = new JFrame("JList Test");
        frame.setLayout(new FlowLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        String[] selections = {"<html><img src='http:\\\\invalid\\url'>", "<html><H1>Hello</h1></html>", "orange", "dark blue"};

        JList list = new JList(selections);

        list.setSelectedIndex(1);
        System.out.println(list.getSelectedValue());

        JLabel jLabel = new JLabel("<html><h2>standard Label</h2></html>");
        frame.add(new JScrollPane(list));
        frame.add(jLabel);
        frame.pack();

        frame.setVisible(true);
    }
}

And you will see something like

alt text

倥絔 2024-09-23 06:13:20

对于简单的 JLabel,您可以

myLabel.putClientProperty("html.disable", Boolean.TRUE);

在要禁用 HTML 渲染的标签上调用 JComponent 方法。

参考:无法在 JLabel 中禁用 HTML 渲染


对于像 JTable 这样的东西, JTree 或 JList 您需要创建一个自定义单元格渲染器来设置此属性。下面是一个创建自定义单元格的示例(修改自此示例JList 的渲染器。

import java.awt.Component;
import java.awt.FlowLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;

public class JListTest {
    public static void main(String[] args) {
        JFrame.setDefaultLookAndFeelDecorated(true);
        JFrame frame = new JFrame("JList Test");
        frame.setLayout(new FlowLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        String[] selections = { "<html><img src='http:\\\\invalid\\url'>",
                "red", "orange", "dark blue" };
        JList list = new JList(selections);

        // set the list cell renderer to the custom class defined below
        list.setCellRenderer(new MyCellRenderer());

        list.setSelectedIndex(1);
        System.out.println(list.getSelectedValue());
        frame.add(new JScrollPane(list));
        frame.pack();

        frame.setVisible(true);
    }
}


class MyCellRenderer extends JLabel implements ListCellRenderer {
    public MyCellRenderer() {
        setOpaque(true);
        putClientProperty("html.disable", Boolean.TRUE);
    }
    
    public Component getListCellRendererComponent(
        JList list,
        Object value,
        int index,
        boolean isSelected,
        boolean cellHasFocus)
    {
        setText(value.toString());
        return this;
    }
}

我使用了 ListCellRenderer 文档作为自定义列表单元格渲染器的起点。

当我运行该示例时,您可以看到第一个列表条目中的 HTML 被渲染而不是被解释。

JList 自定义渲染器

For a simple JLabel, you can call the JComponent method

myLabel.putClientProperty("html.disable", Boolean.TRUE);

on the label where you want to disable HTML rendering.

Reference: Impossible to disable HTML Rendering in a JLabel


For something like a JTable, JTree, or JList you'll need to create a custom cell renderer that sets this property. Here's an example (modified from this example) that creates a custom cell renderer for a JList.

import java.awt.Component;
import java.awt.FlowLayout;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;

public class JListTest {
    public static void main(String[] args) {
        JFrame.setDefaultLookAndFeelDecorated(true);
        JFrame frame = new JFrame("JList Test");
        frame.setLayout(new FlowLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        String[] selections = { "<html><img src='http:\\\\invalid\\url'>",
                "red", "orange", "dark blue" };
        JList list = new JList(selections);

        // set the list cell renderer to the custom class defined below
        list.setCellRenderer(new MyCellRenderer());

        list.setSelectedIndex(1);
        System.out.println(list.getSelectedValue());
        frame.add(new JScrollPane(list));
        frame.pack();

        frame.setVisible(true);
    }
}


class MyCellRenderer extends JLabel implements ListCellRenderer {
    public MyCellRenderer() {
        setOpaque(true);
        putClientProperty("html.disable", Boolean.TRUE);
    }
    
    public Component getListCellRendererComponent(
        JList list,
        Object value,
        int index,
        boolean isSelected,
        boolean cellHasFocus)
    {
        setText(value.toString());
        return this;
    }
}

I used the example code from the ListCellRenderer documentation as a starting point for the custom list cell renderer.

When I run the example, you can see that the HTML in the first list entry is rendered instead of being interpreted.

JList Custom Renderer

掩于岁月 2024-09-23 06:13:20

由于无法将每个创建的 JLabelhtml.disable 属性全局设置为 true,因此这是一种 hacky 方法(我说 hacky 是因为我不确定影响性能,或者这样的解决方案是否可以投入生产)的方法是对每个创建的 JLabel 实例进行一些字节码拦截。像 ByteBuddy 这样的库可以做到这一点。我对 ByteBuddy 进行了一些实验,找到了一种设置 Java 代理,拦截对 JLabelsetText() 方法的调用。使用提供的文本创建 JLabel 时会调用此方法。

代理

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy;
import net.bytebuddy.agent.builder.AgentBuilder.Listener;
import net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy;
import net.bytebuddy.agent.builder.AgentBuilder.TypeStrategy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.matcher.StringMatcher;

import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.nio.file.Files;

import static java.util.Collections.singletonMap;
import static net.bytebuddy.description.type.TypeDescription.ForLoadedType;
import static net.bytebuddy.dynamic.ClassFileLocator.ForClassLoader.read;
import static net.bytebuddy.dynamic.loading.ClassInjector.UsingInstrumentation.Target.BOOTSTRAP;
import static net.bytebuddy.matcher.ElementMatchers.*;

public class JLabelAgent {

    private static final Class<?> INTERCEPTOR_CLASS = JLabelInterceptor.class;

    private JLabelAgent() {
    }

    public static void premain(String arg, Instrumentation instrumentation) throws Exception {
        injectBootstrapClasses(instrumentation);
        new AgentBuilder.Default()
        .with(RedefinitionStrategy.RETRANSFORMATION)
        .with(InitializationStrategy.NoOp.INSTANCE)
        .with(TypeStrategy.Default.REDEFINE)
        .ignore(new AgentBuilder.RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.").or(isSynthetic()), any(), any()))
        .with(new Listener.Filtering(
                new StringMatcher("javax.swing.JLabel", StringMatcher.Mode.EQUALS_FULLY),
                Listener.StreamWriting.toSystemOut()))
        .type(named("javax.swing.JLabel"))
        .transform((builder, type, classLoader, module) ->
                builder.visit(Advice.to(INTERCEPTOR_CLASS).on(named("setText")))
        )
        .installOn(instrumentation);
    }

    private static void injectBootstrapClasses(Instrumentation instrumentation) throws IOException {
        File temp = Files.createTempDirectory("tmp").toFile();
        temp.deleteOnExit();

        ClassInjector.UsingInstrumentation.of(temp, BOOTSTRAP, instrumentation)
            .inject(singletonMap(new ForLoadedType(INTERCEPTOR_CLASS), read(INTERCEPTOR_CLASS)));
    }
}

拦截

import javax.swing.JComponent;

import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.Argument;
import net.bytebuddy.asm.Advice.This;

public class JLabelInterceptor {


    @Advice.OnMethodEnter()
    public static void setText(@This Object label, @Argument(0) String text) {
        ((JComponent) label).putClientProperty("html.disable", Boolean.TRUE);
        System.out.println("Label text is " + text);
    }
}

器示例

public static void main(String[] args) throws Exception {
    JFrame frame = new JFrame("JList Test");
    frame.setLayout(new FlowLayout());
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    String[] selections = {"<html><img src='http:\\\\invalid\\url'>", "<html><H1>Hello</h1></html>", "orange", "dark blue"};

    JList list = new JList(selections);

    list.setSelectedIndex(1);
    System.out.println(list.getSelectedValue());

    JLabel jLabel = new JLabel("<html><h2>standard Label</h2></html>");
    frame.add(new JScrollPane(list));
    frame.add(jLabel);
    frame.pack();

    frame.setVisible(true);
}

在此处输入图像描述

运行示例

编译 Java 代理,然后运行示例:

java -javaagent:agent.jar -jar example.jar

注意: 当使用 Maven 构建代理 Jar 时,我必须将以下配置放入 POM 中设置清单:

<plugin>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
            <manifestEntries>
                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                <Can-Retransform-Classes>true</Can-Retransform-Classes>
                <Agent-Class>example.JLabelAgent</Agent-Class>
                <Premain-Class>example.JLabelAgent</Premain-Class>
                <Boot-Class-Path>byte-buddy-1.10.14.jar</Boot-Class-Path>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

Since there is no way of globally setting the html.disable property to true for each created JLabel, one hacky way (I say hacky because I'm not sure of the impact on performance, or whether such a solution could be placed on production) is to do some bytecode interception for every created JLabel instance. A library like ByteBuddy can do this. I've experimented a bit with ByteBuddy and found a way to set a Java agent that intercepts calls to the setText() method for a JLabel. This method is called when creating a JLabel with the provided text.

Agent

import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.AgentBuilder.InitializationStrategy;
import net.bytebuddy.agent.builder.AgentBuilder.Listener;
import net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy;
import net.bytebuddy.agent.builder.AgentBuilder.TypeStrategy;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.matcher.StringMatcher;

import java.io.File;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.nio.file.Files;

import static java.util.Collections.singletonMap;
import static net.bytebuddy.description.type.TypeDescription.ForLoadedType;
import static net.bytebuddy.dynamic.ClassFileLocator.ForClassLoader.read;
import static net.bytebuddy.dynamic.loading.ClassInjector.UsingInstrumentation.Target.BOOTSTRAP;
import static net.bytebuddy.matcher.ElementMatchers.*;

public class JLabelAgent {

    private static final Class<?> INTERCEPTOR_CLASS = JLabelInterceptor.class;

    private JLabelAgent() {
    }

    public static void premain(String arg, Instrumentation instrumentation) throws Exception {
        injectBootstrapClasses(instrumentation);
        new AgentBuilder.Default()
        .with(RedefinitionStrategy.RETRANSFORMATION)
        .with(InitializationStrategy.NoOp.INSTANCE)
        .with(TypeStrategy.Default.REDEFINE)
        .ignore(new AgentBuilder.RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.").or(isSynthetic()), any(), any()))
        .with(new Listener.Filtering(
                new StringMatcher("javax.swing.JLabel", StringMatcher.Mode.EQUALS_FULLY),
                Listener.StreamWriting.toSystemOut()))
        .type(named("javax.swing.JLabel"))
        .transform((builder, type, classLoader, module) ->
                builder.visit(Advice.to(INTERCEPTOR_CLASS).on(named("setText")))
        )
        .installOn(instrumentation);
    }

    private static void injectBootstrapClasses(Instrumentation instrumentation) throws IOException {
        File temp = Files.createTempDirectory("tmp").toFile();
        temp.deleteOnExit();

        ClassInjector.UsingInstrumentation.of(temp, BOOTSTRAP, instrumentation)
            .inject(singletonMap(new ForLoadedType(INTERCEPTOR_CLASS), read(INTERCEPTOR_CLASS)));
    }
}

Interceptor

import javax.swing.JComponent;

import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.Advice.Argument;
import net.bytebuddy.asm.Advice.This;

public class JLabelInterceptor {


    @Advice.OnMethodEnter()
    public static void setText(@This Object label, @Argument(0) String text) {
        ((JComponent) label).putClientProperty("html.disable", Boolean.TRUE);
        System.out.println("Label text is " + text);
    }
}

Example

public static void main(String[] args) throws Exception {
    JFrame frame = new JFrame("JList Test");
    frame.setLayout(new FlowLayout());
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    String[] selections = {"<html><img src='http:\\\\invalid\\url'>", "<html><H1>Hello</h1></html>", "orange", "dark blue"};

    JList list = new JList(selections);

    list.setSelectedIndex(1);
    System.out.println(list.getSelectedValue());

    JLabel jLabel = new JLabel("<html><h2>standard Label</h2></html>");
    frame.add(new JScrollPane(list));
    frame.add(jLabel);
    frame.pack();

    frame.setVisible(true);
}

enter image description here

Running the example

Compile the Java agent then run the example:

java -javaagent:agent.jar -jar example.jar

Note: When using Maven to build the agent Jar, I had to put the following configuration in the POM to setup the manifest:

<plugin>
    <artifactId>maven-jar-plugin</artifactId>
    <configuration>
        <archive>
            <manifestEntries>
                <Can-Redefine-Classes>true</Can-Redefine-Classes>
                <Can-Retransform-Classes>true</Can-Retransform-Classes>
                <Agent-Class>example.JLabelAgent</Agent-Class>
                <Premain-Class>example.JLabelAgent</Premain-Class>
                <Boot-Class-Path>byte-buddy-1.10.14.jar</Boot-Class-Path>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>
国际总奸 2024-09-23 06:13:20

上吊可能是最不令人不快的行为。这就是为什么数据验证如此重要。只是不允许用户输入类似的内容。

Hanging is probably the least unpleasant behavior. This is why Data Validation is so very important. Just do not allow the users to enter something like that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文