15

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

要重现此问题,只需按如下方式创建 JLabel

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

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

4

4 回答 4

9

如果您创建自己的外观和感觉,有一种方法。
我不确定它的性能如何,但它确实有效。假设您将扩展“经典 Windows”L&F。您至少需要 2 个类一个是外观本身,我们称之为 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 类来管理所有 JLabel 并设置属性:

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

你会看到类似的东西

替代文字

于 2010-08-27T19:24:25.940 回答
5

对于一个简单的 JLabel,可以调用 JComponent 方法

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

在要禁用 HTML 呈现的标签上。

参考:不可能在 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 自定义渲染器

于 2010-08-27T12:58:53.943 回答
5

由于无法将html.disable每个 created 的属性全局设置为 true JLabel,因此一种 hacky 方式(我说 hacky 是因为我不确定对性能的影响,或者这种解决方案是否可以用于生产)是做一些每个创建的JLabel实例的字节码拦截。像 ByteBuddy 这样的库可以做到这一点。我已经对 ByteBuddy 进行了一些试验,并找到了一种设置Java 代理setText()的方法,该代理拦截对JLabel. 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>
于 2021-01-30T13:26:03.383 回答
3

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

于 2010-08-27T14:51:00.587 回答