在进行文本处理时,经常需要将一行文本自动换行,以适应特定的显示宽度。本文将介绍如何在C#中实现这一功能,包括使用LINQ和非LINQ的实现方法,并通过WinForms应用程序展示代码的实际应用。
基本思想是将一行文本(字符串)自动换行,以适应给定的宽度,仅在单词边界处换行,这里定义为“空格分隔”。然而,如果一个单词本身的长度超过了给定的宽度,这种情况是模糊的。即,(a) 将单词分割以适应,或 (b) 允许单个单词超过宽度。决定提供一个可选参数来选择所需的行为。
使用string.Split()
可以简化代码,特别是在允许超宽单词的简单情况下,并且避免了在分割时逐字符重新处理的复杂性。此外,实现不依赖于WinForms的TextRenderer
和Font
类,而是采用一个委托(Predicate
),用于测试字符串是否“合适”。调用者可以自由地通过任何所需的方式来确定是否合适。
以下是使用LINQ实现的代码示例:
string input = "这是测试字符串,包含一些非常长的单词!";
int wrapWidth = 160;
// 字体从某个来源确定
Predicate stringFits = s => TextRenderer.MeasureText(s, font).Width < wrapWidth;
string Ret = input.WordWrap(stringFits, false);
// 不允许超宽单词
如果总是允许超宽单词,则不需要布尔参数:
string Ret = input.WordWrap(stringFits);
// 允许超宽单词
有WordWrapNonLinq()
方法实现相同的功能,但不使用LINQ的.Aggregate()
方法。(这展示了.Aggregate()
实际上在做什么。)
初始版本。
将单词换行功能作为扩展方法放在一个静态类中,可以提高代码的可重用性和可维护性。
以下是不使用LINQ的实现方法:
public static class StringExtensions
{
public static string WordWrap(this string input, Predicate stringFits, bool allowOversizedWords = false)
{
var words = input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var wrapped = new StringBuilder();
var currentLine = new StringBuilder();
foreach (var word in words)
{
if (currentLine.Length > 0)
{
currentLine.Append(" ");
}
string testLine = currentLine.ToString() + word;
if (stringFits(testLine) || (allowOversizedWords && testLine.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Length == 1))
{
currentLine.Append(word);
}
else
{
wrapped.AppendLine(currentLine.ToString());
currentLine.Clear();
currentLine.Append(word);
}
}
if (currentLine.Length > 0)
{
wrapped.AppendLine(currentLine.ToString());
}
return wrapped.ToString();
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
string input = "这是测试字符串,包含一些非常长的单词!";
int wrapWidth = 160;
// 字体从某个来源确定
Predicate stringFits = s => TextRenderer.MeasureText(s, this.Font).Width < wrapWidth;
string wrappedText = input.WordWrap(stringFits, false);
textBox1.Text = wrappedText;
}
}