【问题标题】:How to get ellipsis in a JLabel containing HTML?如何在包含 HTML 的 JLabel 中获取省略号?
【发布时间】:2015-09-17 11:00:03
【问题描述】:

当我将 HTML 标记组合到 JLabel 文本中时,我失去了在空间太小而无法显示完整文本时显示的省略号行为。在我的具体情况下,它是一个扩展 JLabel(swing 的默认值或其他)的 TableCellRenderer。现在,当列宽太小而无法完全显示文本时,它不会显示省略号。

例如看下图: 对于左列,我使用 HTML 将文本包裹在渲染器中:setText("<html>" + "<strong>" + value.toString() + "</strong>" + "</html>");。正如您所看到的,当列宽太小而无法包含文本时,它只是被剪切了。然而,右侧列显示日期和时间并使用DefaultTableCellRenderer,当它未能包含完整文本时显示省略号。

所以我的问题是,我可以两者兼得吗?意思是,用 HTML 包装文本仍然得到省略号?

更新:

我找到了使用 HTML 时没有得到省略号的原因。我按照代码从JComponent#paintComponent(Graphics g) 一直到BasicLabelUI#layoutCL(...)。请参阅以下代码 sn-p 取自最后。如果它没有 html 属性,它只会剪切字符串(当标签文本用 html 包裹时,这是真的)。但是我不知道如何解决它:

    v = (c != null) ? (View) c.getClientProperty("html") : null;
    if (v != null) {
        textR.width = Math.min(availTextWidth,
                               (int) v.getPreferredSpan(View.X_AXIS));
        textR.height = (int) v.getPreferredSpan(View.Y_AXIS);
    } else {
        textR.width = SwingUtilities2.stringWidth(c, fm, text);
        lsb = SwingUtilities2.getLeftSideBearing(c, fm, text);
        if (lsb < 0) {
            // If lsb is negative, add it to the width and later
            // adjust the x location. This gives more space than is
            // actually needed.
            // This is done like this for two reasons:
            // 1. If we set the width to the actual bounds all
            //    callers would have to account for negative lsb
            //    (pref size calculations ONLY look at width of
            //    textR)
            // 2. You can do a drawString at the returned location
            //    and the text won't be clipped.
            textR.width -= lsb;
        }
        if (textR.width > availTextWidth) {
            text = SwingUtilities2.clipString(c, fm, text,
                                              availTextWidth);
            textR.width = SwingUtilities2.stringWidth(c, fm, text);
        }
        textR.height = fm.getHeight();
    }

【问题讨论】:

  • 为了尽快获得更好的帮助,请发布MCVE(最小完整可验证示例)或SSCCE(简短、自包含、正确示例)。
  • 如果不看具体示例就很难发表评论,但您可以为 JLabel 创建纯文本,为与 JLabel 对应的工具提示创建 HTML 文本。
  • 尝试使用pack()?如果没有看到代码并尝试它,则不能 100% 确定,但在你 setText 之后添加它可能会起作用。

标签: java swing jlabel


【解决方案1】:

我要说:不,你不能两者兼得。

我认为,如果您想要自定义样式和省略号,您将不得不在没有 HTML 和自定义 TableCellRenderer 的情况下自己完成。

如果您也想尝试吃蛋糕,您可以通过创建自己的 View 对象并使用 c.putClientProperty("html", value) 设置它来实现,但我怀疑 HTML渲染代码没有省略的概念(文本溢出是 HTML 5ish 功能),因此您必须弄清楚如何教它这样做。我怀疑这比编写自己的 TableCellRenderer 非常困难和困难。

【讨论】:

    【解决方案2】:

    只要 HTML 内容简单,就像您的问题一样,省略号显示可以使用定制的 JLabel 来完成。这是一个工作示例。 只需调整窗口大小,您就会看到省略号出现和消失,并且随着标签随窗口大小调整而适当地剪切文本。

    import java.awt.Font;
    import java.awt.FontMetrics;
    import java.awt.event.ComponentAdapter;
    import java.awt.event.ComponentEvent;
    import javax.swing.JLabel;
    import javax.swing.border.Border;
    
    public class SimpleHTMLJLabel extends JLabel
    {
        private static final long serialVersionUID = -1799635451172963826L;
    
        private String textproper;
        private String ellipsis = "...";
        private int textproperwidth;
        private FontMetrics fontMetrics;
        private int ellipsisWidth;
        private int insetsHorizontal;
        private int borderHorizontal;
    
        public SimpleHTMLJLabel(String textstart, String textproper, String textend)
        {
            super(textstart + textproper + textend);
            this.textproper = textproper;
            insetsHorizontal = getInsets().left + getInsets().right;
            fontMetrics = getFontMetrics(getFont());
            calculateWidths();
            addComponentListener(new ComponentAdapter() {
                public void componentResized(ComponentEvent e)
                {
                    int availablewidth = getWidth();
                    if (textproperwidth > availablewidth - (insetsHorizontal + borderHorizontal)) 
                    {
                        String clippedtextproper = textproper;
                        while (clippedtextproper.length() > 0 
                               && fontMetrics.stringWidth(clippedtextproper) + ellipsisWidth > availablewidth - (insetsHorizontal + borderHorizontal)) 
                        {
                            clippedtextproper = clipText(clippedtextproper);
                        }
                        setText(textstart + clippedtextproper + ellipsis + textend);
                    } else 
                    {
                        setText(textstart + textproper + textend);
                    }
                }
            });
        }
    
        private void calculateWidths()
        {
            if (textproper != null) 
            {
                textproperwidth = fontMetrics.stringWidth(textproper);
            }
    
            if (ellipsis != null)
            {
                ellipsisWidth = fontMetrics.stringWidth(ellipsis);
            }
        }
    
        @Override
        public void setFont(Font font)
        {
            super.setFont(font);
            fontMetrics = getFontMetrics(getFont());
            calculateWidths();
        }
    
        private String clipText(String clippedtextproper)
        {
            return clippedtextproper.substring(0, clippedtextproper.length() - 1);
        }
    
        @Override
        public void setBorder(Border border)
        {
            super.setBorder(border);
            borderHorizontal = border.getBorderInsets(this).left + border.getBorderInsets(this).right;
        }
    }
    

    主要

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Component;
    import javax.swing.BorderFactory;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class Main
    {
        public static void main(String[] args)
        {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run()
                {
                    JFrame window = new JFrame();
                    window.setResizable(true);
                    window.setTitle("Label Test");
                    window.getContentPane().add(getContent());
                    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    window.setSize(400, 200);
                    window.setLocationRelativeTo(null);
                    window.setVisible(true);
                }
            });
        }
    
        protected static Component getContent()
        {
            JPanel panel = new JPanel(new BorderLayout());
            SimpleHTMLJLabel label = new SimpleHTMLJLabel("<html><strong>", "TEST1test2TEST3test4TEST5test6TEST7test8TEST", "</strong></html>");
            label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.BLUE, 5),
            BorderFactory.createEmptyBorder(10, 10, 10, 10)));
            label.setFont(label.getFont().deriveFont(20F));
            panel.add(label, BorderLayout.CENTER);
            return panel;
        }
    }
    

    【讨论】:

      【解决方案3】:

      这是使用上述代码的 SimpleHTMLJLabel 的修改版本

      import java.awt.Font;
      import java.awt.FontMetrics;
      import java.awt.event.ComponentAdapter;
      import java.awt.event.ComponentEvent;
      import java.io.IOException;
      import java.io.StringReader;
      import java.util.AbstractMap;
      import java.util.ArrayList;
      import java.util.HashSet;
      import java.util.List;
      import java.util.Map.Entry;
      import java.util.stream.Collectors;
      
      import javax.swing.JLabel;
      import javax.swing.border.Border;
      import javax.swing.text.MutableAttributeSet;
      import javax.swing.text.html.HTML.Tag;
      import javax.swing.text.html.HTMLEditorKit.ParserCallback;
      
      public class SimpleHTMLJLabel extends JLabel {
      
          private static final String ellipsis = "...";
          private static final String Set = null;
          private int textproperwidth;
          private FontMetrics fontMetrics;
          private int ellipsisWidth;
          private int insetsHorizontal;
          private int borderHorizontal;
      
          private List<Entry<String, String>> lines;
          static String HTML = "<HTML>";
      
          public SimpleHTMLJLabel() {
              insetsHorizontal = getInsets().left + getInsets().right;
              fontMetrics = getFontMetrics(getFont());
              calculateWidths();
              addComponentListener(new ComponentAdapter() {
                  @Override
                  public void componentResized(ComponentEvent e) {
                      int availablewidth = getWidth();
                      renderHtml(availablewidth);
                  }
              });
          }
      
          public SimpleHTMLJLabel(String text) {
              this();
              setText(text);
          }
      
          public SimpleHTMLJLabel(List<Entry<String, String>> lines) {
              this();
              this.lines = lines;
              calculateWidths();
              super.setText(HTML + toHtml(lines));
          }
      
          @Override
          public void setText(String text) {
              if (text.toUpperCase().startsWith(HTML)) {
                  this.lines = parseHtml(text);
                  calculateWidths();
                  super.setText(HTML + toHtml(lines));
                  return;
              }
              super.setText(text);
          }
      
          private List<Entry<String, String>> parseHtml(String text) {
              List<Entry<String, String>> ret = new ArrayList<>();
              java.util.Map<Tag, MutableAttributeSet> tags = new HashMap<>();
              try {
                  (new javax.swing.text.html.parser.ParserDelegator()).parse(new StringReader(text), new ParserCallback() {
                      @Override
                      public void handleEndTag(Tag t, int pos) {
                          //TODO clean handle MutableAttributeSet a
                          tags.remove(t);
                      }
      
                      @Override
                      public void handleStartTag(javax.swing.text.html.HTML.Tag t, MutableAttributeSet a, int pos) {
                          if (t == Tag.HTML) return;
                          if (t == Tag.P) return;
                          if (t == Tag.BR) return;
                          if (t == Tag.BODY) return;
                          tags.put(t,a);
                      }
      
                      @Override
                      public void handleText(char[] data, int pos) {
      
                          String formats = tags.entrySet().stream().map(t -> "<" + t.getKey() + getAttrib(t.getValue) + ">").collect(Collectors.joining());
                          ret.add(new AbstractMap.SimpleEntry<>(formats, new String(data)));
                      }
      
                      private String getAttrib(MutableAttributeSet t) {
                          // TODO Auto-generated method stub
                          //return " style='color:red'";
                          return " " + t;
                      }
                  }, false);
              } catch (IOException e) {
                  e.printStackTrace();
              }
      
              return ret;
          }
      
          private static String toEndTag(String s) {
              return s.replace("<", "</");
          }
      
          private static String toHtml(List<Entry<String, String>> lines) {
              return lines.stream().map(s -> s.getKey() + s.getValue() + toEndTag(s.getKey())).collect(Collectors.joining());
          }
      
          private static String toPlain(List<Entry<String, String>> lines) {
              return lines.stream().map(s -> s.getValue()).collect(Collectors.joining("  "));
          }
      
          static private List<Entry<String, String>> clipText(List<Entry<String, String>> properList) {
              Entry<String, String> last = properList.get(properList.size() - 1);
              List<Entry<String, String>> ret = properList.subList(0, properList.size() - 1);
              String newlastValue = truncate(last.getValue());
              if (newlastValue.isEmpty()) {
                  return ret;
              }
              List<Entry<String, String>> retNew = new ArrayList<>();
              retNew.addAll(ret);
              retNew.add(new AbstractMap.SimpleEntry<>(last.getKey(), newlastValue));
              return retNew;
          }
      
          static private String truncate(String newlastValue) {
              newlastValue = newlastValue.substring(0, newlastValue.length() - 1);
              while (newlastValue.endsWith(" ")) {
                  newlastValue = newlastValue.substring(0, newlastValue.length() - 1);
              }
              return newlastValue;
          }
      
          private void calculateWidths() {
              if (lines != null) {
                  textproperwidth = fontMetrics.stringWidth(toPlain(lines));
              }
              ellipsisWidth = fontMetrics.stringWidth(ellipsis);
          }
      
          @Override
          public void setFont(Font font) {
              super.setFont(font);
              fontMetrics = getFontMetrics(getFont());
              calculateWidths();
          }
      
          @Override
          public void setBorder(Border border) {
              super.setBorder(border);
              borderHorizontal = border.getBorderInsets(this).left + border.getBorderInsets(this).right;
          }
      }
      

      在 TableCellRenderer 中使用我的代码时,您需要在构造函数时立即调整大小,但是当您没有列的大小时:

          public SimpleHTMLJLabel() {
      ...
          addComponentListener(new ComponentAdapter() {
              @Override
              public void componentResized(ComponentEvent e) {
                  int availablewidth = getWidth();
                  renderHtml(availablewidth);
              }
          });
      }
      
      protected void renderHtml(int availablewidth) {
          if (lines == null || availablewidth == 0) return;
          System.out.println("renderHtml " + textproperwidth + ">" + availablewidth);
          if (textproperwidth > availablewidth - (insetsHorizontal + borderHorizontal)) {
              List<Entry<String, String>> properList = clipText(lines);
              while (properList.size() > 0 && fontMetrics.stringWidth(toPlain(properList)) + ellipsisWidth > availablewidth - (insetsHorizontal + borderHorizontal)) {
                  properList = clipText(properList);
              }
              SimpleHTMLJLabel.super.setText(HTML + toHtml(properList) + ellipsis);
          } else {
              SimpleHTMLJLabel.super.setText(HTML + toHtml(lines));
          }
      }
      @Override
      public void reshape(int x, int y, int w, int h) {
          if (w > 0) renderHtml(w - 5);
          super.reshape(x, y, w, h);
      }
      

      在 JTable 中

          table = new JTable(model) {
              @Override
              public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
                  Component c = super.prepareRenderer(renderer, row, column);
                  TableColumn col = table.getColumnModel().getColumn(column);
                  javax.swing.table.DefaultTableCellRenderer.UIResource csss;
                  SimpleHTMLJLabel lab = new SimpleHTMLJLabel(((JLabel) 
                                  //lab.setXXX( c.getXXX)); for font bcolor, color, border, etc
                                  lab.setText(c.getText());
                  lab.renderHtml(col.getWidth() - 5);
                  return lab;
              }
          };
      

      可以复用html-labell组件来节省GC

      【讨论】:

      • 在 TableCellRenderer 中使用我的代码时,您需要在构造函数时立即调整大小,但是当您没有列大小时:
      • 当 Oracle 修复 HTML 渲染以使用 style='white-space:nowrap;overflow:hidden;text-overflow:ellipsis' 时,只需要 JLable 或其他 cComponent 来制作“... “就像在流行的浏览器中一样。特别适用于多行表格行和可变字体大小+图标等
      猜你喜欢
      • 2017-06-19
      • 2013-03-15
      • 2019-06-28
      • 1970-01-01
      • 2011-08-02
      • 2020-10-01
      • 2013-03-24
      • 2013-10-07
      • 2012-10-05
      相关资源
      最近更新 更多