65.9K
CodeProject 正在变化。 阅读更多。
Home

在 ASP.NET 中截断文本字符串以适应给定的像素宽度

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (8投票s)

2010年9月8日

CPOL

4分钟阅读

viewsIcon

44981

downloadIcon

331

如何在 ASP.NET 中截断文本字符串,使其适应以像素为单位指定的给定宽度。

引言

有时在开发 Web 应用程序时,您需要字符串适应一定的像素宽度(通常是容器的宽度,如 div、p、td 等)。 对于链接来说,这尤其常见,因为您有时需要每个链接都适应一行,并且不会换行到下一行(如果链接的文本太宽而无法适应容器的宽度,就会发生这种情况)。 或者用视觉方式表达,您可能有一个看起来像这样的链接

wrapping.gif

并且想要让它看起来像这样

singleline.gif

客户端 vs. 服务器端

这可以在客户端(使用 CSS 或 JavaScript)或服务器端完成。 对于客户端解决方案,CSS3 text-overflow 样式(text-overflow: ellipsis OR text-overflow: ellipsis-word)是最简单也是最标准的方法,但不幸的是,它仍然没有被所有浏览器完全支持。 目前有一些 JavaScript 解决方案可以使用,直到 text-overflow 得到完全支持; 不过,我更喜欢服务器端解决方案,因为它

  1. 节省带宽(如果文本太长并且需要更长的时间才能下载,尤其是在用户连接速度较慢的情况下,这对用户体验也有好处)。
  2. 保证在所有浏览器(包括较旧的浏览器)中都能正常工作,无论它们是否启用了 JavaScript,甚至是否支持它。

代码

我将截断所需的代码包装到一个类 `TextTruncator` 中,您可以在此处看到(也包含在下载文件中,以及一个演示项目)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace aspnet_TextTruncator
{
    public static class TextTruncator
    {
        // Private Properties
        //

        private static Dictionary<string, int[]> _fontWidthDic;
        private static Dictionary<string, int[]> FontWidthDic
        {
            get
            {
                if (_fontWidthDic == null)
                {
                    _fontWidthDic = new Dictionary<string, int[]>();
                }
                return _fontWidthDic;
            }
        }

        //
        // Public Methods
        //

        public static string TruncateText(string text, int textMaxWidth, 
               string fontName, int fontSizeInPixels)
        {
            return TruncateText(text, textMaxWidth, fontName, 
                                fontSizeInPixels, false);
        }

        public static string TruncateText(string text, int textMaxWidth, 
               string fontName, int fontSizeInPixels, bool isFontBold)
        {
            if (string.IsNullOrEmpty(text))
                return text;

            // Check
            //
            if (textMaxWidth < 1 ||
                string.IsNullOrEmpty(fontName) ||
                fontSizeInPixels < 1)
            {
                throw new ArgumentException();
            }

            int[] fontWidthArray = GetFontWidthArray(fontName, 
                                          fontSizeInPixels, isFontBold);
            int ellipsisWidth = fontWidthArray['.'] * 3;
            int totalCharCount = text.Length;
            int textWidth = 0;
            int charIndex = 0;
            for (int i = 0; i < totalCharCount; i++)
            {
                textWidth += fontWidthArray[text[i]];
                if (textWidth > textMaxWidth)
                {
                    return text.Substring(0, charIndex) + "...";
                }
                else if (textWidth + ellipsisWidth <= textMaxWidth)
                {
                    charIndex = i;
                }
            }
            return text;
        }

        //
        // Private Methods
        //

        private static int[] GetFontWidthArray(string fontName, 
                             int fontSizeInPixels, bool isFontBold)
        {
            string fontEntryName = fontName.ToLower() + "_" + 
                   fontSizeInPixels.ToString() + "px" + 
                   (isFontBold ? "_bold" : "");
            int[] fontWidthArray;
            if (!FontWidthDic.TryGetValue(fontEntryName, out fontWidthArray))
            {
                fontWidthArray = CreateFontWidthArray(new Font(fontName, 
                    fontSizeInPixels, isFontBold ? FontStyle.Bold : 
                    FontStyle.Regular, GraphicsUnit.Pixel));
                FontWidthDic[fontEntryName] = fontWidthArray;
            }

            return fontWidthArray;
        }

        private static int[] CreateFontWidthArray(Font font)
        {
            int[] fontWidthArray = new int[256];
            for (int i = 32; i < 256; i++)
            {
                char c = (char)i;
                fontWidthArray[i] = 
                  IsIllegalCharacter(c, false) ? 0 : GetCharWidth(c, font);
            }
            return fontWidthArray;
        }

        private static int GetCharWidth(char c, Font font)
        {
            // Note1: For typography related reasons,
            // TextRenderer.MeasureText() doesn't return the correct
            // width of the character in pixels, hence the need
            // to use this hack (with the '<' & '>'
            // characters and the subtraction). Note that <'
            // and '>' were chosen randomly, other characters 
            // can be used.
            //

            // Note2: As the TextRenderer class is intended
            // to be used with Windows Forms Applications, it has a 
            // special use for the ampersand character (used for Mnemonics).
            // Therefore, we need to check for the 
            // ampersand character and replace it with '&&'
            // to escape it (TextRenderer.MeasureText() will treat 
            // it as one ampersand character)
            //

            return
                TextRenderer.MeasureText("<" + (c == '&' ? "&&" : 
                                         c.ToString()) + ">", font).Width -
                TextRenderer.MeasureText("<>", font).Width;
        }

        private static bool ContainsIllegalCharacters(string text, 
                            bool excludeLineBreaks)
        {
            if (!string.IsNullOrEmpty(text))
            {
                foreach (char c in text)
                {
                    if (IsIllegalCharacter(c, excludeLineBreaks))
                        return true;
                }
            }

            return false;
        }

        private static bool IsIllegalCharacter(char c, bool excludeLineBreaks)
        {
            // See the Windows-1252 encoding
            // (we use ISO-8859-1, but all browsers, or at least
            // IE, FF, Opera, Chrome and Safari,
            // interpret ISO-8859-1 as Windows-1252).
            // For more information,
            // see http://en.wikipedia.org/wiki/ISO/
            //        IEC_8859-1#ISO-8859-1_and_Windows-1252_confusion
            //

            return
                (c < 32 && (!excludeLineBreaks || c != '\n')) ||
                c > 255 ||
                c == 127 ||
                c == 129 ||
                c == 141 ||
                c == 143 ||
                c == 144 ||
                c == 157;
        }
    }
}

Using the Code

该类只有一个重载的公共方法 `TruncateText()`,您应该调用该方法来截断文本。

使用示例

在您的 * .aspx * 页面中

<%= TextTruncator.TruncateText("Some text that will " + 
      "likely come from your database, an XML file, or another source", 
      300, "Verdana", 12) %>

关于代码的说明

  • 为了测量文本宽度,我使用 `TextRenderer.MeasureText()`,它实际上是为 Windows Forms 应用程序设计的,但仍然可以与 ASP.NET 一起使用。 出于性能原因,我缓存了字母的宽度,这样我就不必再次调用 `TextRenderer.MeasureText()`。 这样做是有充分理由的,因为 `TextRenderer.MeasureText()` 可能会非常慢(在我的测试中,这个改变将一百万个字符串的截断时间从 8 分钟缩短到 2 秒!)。
  • 因为我选择缓存字母的宽度,所以我不得不将字符集限制为 ISO-8859-1(对于基于拉丁语的语言),这对于我自己的目的来说效果很好。 如果您需要此代码与 Unicode 一起使用,您需要删除字母宽度缓存机制,并在每次要测量文本宽度时调用 `TextRenderer.MeasureText()`(这会稍微减慢速度,但除非您计划在非常高的流量网站和速度不够快的机器上使用它,否则应该不会很明显;您需要进行自己的测试才能确定)。 您还可以更改字母宽度缓存机制以使用字典而不是数组,并且仅在实际使用字母时才将它们添加到字典中。
  • 您可能会注意到,在 `TruncateText()` 中,我传递字体名称、像素大小以及是否粗体,而不是传递字体对象。 我这样做是为了方便,但如果您想传递 `Font` 对象,这应该很容易更改(可能还要使用其他字体样式,如斜体,但您还需要更改字母宽度的缓存机制)。
  • 您需要在项目中添加对 `System.Windows.Forms` 和 `System.Drawing` 程序集的引用,以便此代码能够正常工作。

关于 Web 浏览器的注意事项

在网络上的许多讨论中,您会读到您不应该依赖文本宽度(以像素为单位),因为不能保证不同的浏览器会以相同的方式显示文本(即,文本的宽度可能因浏览器而异)。 虽然不能保证,但在所有流行的浏览器(以及不太流行的浏览器)中的实际测试中,我发现在所有浏览器中,浏览器中显示的文本的宽度从未超过我在 `TruncateText()` 中为文本指定的最大宽度; 有时它略小(仅几个像素 - 那是在旧版本的 Safari on Windows 中),但同样,它从未更大。 如果您像我一样偏执,请始终使用“安全边距”。 例如,如果文本容器(div、p、td 或任何元素)的宽度为 500px,则使最大宽度(传递给 `TruncateText()`)减少 10px 或 20px,即 490 或 480。

© . All rights reserved.