在 Silverlight TextBox 中模拟 MaxLines 属性





0/5 (0投票)
如何在 Silverlight TextBox 中模拟 MaxLines 属性。
引言
您肯定已经注意到 Silverlight TextBox
控件缺少臭名昭著的 MaxLines
属性,而该属性在 WPF TextBox
中非常有用,这就是您阅读本文的原因。
自从上周以来,我一直有这个需求,即限制您可以在 Silverlight 4 的多行 (AcceptsReturn=true
) TextBox
中写入的行数,并且失望地发现目前没有像在 WPF 中那样做的方法。在 WPF 中,TextBox
控件有一个名为 MaxLines
的整数属性,您可以设置它来限制行数。在 Silverlight 中,似乎没有办法,因为您不可能知道在字符串中的何时何地发生了自动换行拆分。如果使用 Courier/CourierNew 等固定宽度字体,那将很简单,因为您可以简单地计算出一行可以容纳的字符数,但在我的例子中,它是 Comic Sans。客户坚持要这个功能,所以我决定编写自己的逻辑来模拟 Silverlight TextBox
中使用的自动换行算法。我别无选择,只能接受这个挑战。
使用代码
附加的代码包含一个 Visual Studio 解决方案,演示了文本框的行为。我将新控件称为 XTextBox
,代表“扩展的文本框”。
第二个附件 XTextBoxControl 包含 XTextBox
控件的源代码。
您可以从此处查看 TextBox 的演示: http://briggs69.blogspot.com/2011/08/solution-maxlines-property-in.html。
关注点
我所做的是扩展了 Silverlight TextBox
控件,并添加了我自己的逻辑来模拟自动换行的行为。 这是它的工作方式。
源代码中最有趣的部分是 GetNumberOfLines()
方法。 此方法返回 TextBox
中特定字符串所占用的行数。
首先,我们需要知道自动换行算法在哪些字符上将字符串拆分为“可换行”的单词,或者在本例中我称它们为标记。 通过一些实验,我发现 TextBox
自动换行算法在这些字符上拆分单词
//split string into tokens/words separated by these symbols
string tokenizers = @"(?<=[ /\\\$%\(\)\-\+\[\]\{\}\?])";
List<string> tokens = Regex.Split(strNoBreaks, tokenizers).ToList();
这里我们使用正则表达式来拆分字符串,而不是 String.Split()
,这样它将包含像空格这样的拆分字符。
现在我们知道了在哪里拆分单词以模拟自动换行操作,接下来我们需要一种方法来测量字符串的实际宽度。 这样我们才能知道文本框中的特定标记组是否适合 TextBox
中的单行。 我们可以使用 TextBlock
控件来测量字符串的宽度。 因为对于 TextBlock
,如果您没有显式设置宽度,则实际宽度将根据其内容自动调整。 因此,我们可以使用它们来测量字符串的实际宽度。
这是该方法的样子
protected int GetNumberOfLines(string theText = null)
{
tbLineMeasurer.Text = "";
string strInput = theText == null ? Text : theText;
//split string into tokens/words separated by these symbols
string tokenizers = @"(?<=[ /\\\$%\(\)\-\+\[\]\{\}\?])";
List<string> tokens = Regex.Split(strInput, tokenizers).ToList();
int tokensInNewLine = 0;
lineCount = 1;
for (var i = 0; i < tokens.Count; i++)
{
string token = tokens[i];
if (token == string.Empty)
{
if (tokensInNewLine > 0)
{
tbLineMeasurer.Text = string.Empty;
tokensInNewLine--;
}
continue;
}
//if the word contains a line break, add line count
//then split the token by linebreak into multiple tokens
//then insert each token to the list of tokens and set line count
if (token.Contains("\r"))
{
string[] s = token.Split('\r');
lineCount += s.Count() - 1;
tokensInNewLine = s.Count() - 1;
int j = i + 1;
foreach (var tok in s)
{
tokens.Insert(j, tok);
j++;
}
//Skip this token, proceed to next token.
continue;
}
//append the current token to the measuring TextBlock
tbLineMeasurer.Text += token;
tbLineMeasurer.InvalidateMeasure();
//if the length of the current line's string
//plus the new token exceeds the line width,
//increment line count, then carry
//over the current token to the next line.
if (tbLineMeasurer.ActualWidth >= lineWidth)
{
if (tbLineMeasurer.Text != token)
//Increment line count only
//if the token is NOT the very first token
{
//carry over this token to the next line
tbLineMeasurer.Text = token;
lineCount++; //increment line count
}
//If the current token's width exceeds
//the line width, simulate token split
//then insert the last remaining unsplitted
//string to the list of tokens for the next iteration
if (tbLineMeasurer.ActualWidth >= lineWidth)
{
char[] arrToken = tbLineMeasurer.Text.ToArray();
tbLineMeasurer.Text = "";
foreach (var ch in arrToken)
{
tbLineMeasurer.Text += ch;
if (tbLineMeasurer.ActualWidth > lineWidth)
{
lineCount++;
tbLineMeasurer.Text = new String(ch, 1);
}
}
//If this current token wraps to a new line,
//clear the measurer then insert the token to the list
//because this token will be in a new line
if (tokensInNewLine <= 0)
{
tokens.Insert(i + 1, tbLineMeasurer.Text);
tbLineMeasurer.Text = "";
continue;
}
}
}
//If the following tokens are part of a token
//with linebreaks, clear the measurer
//as if the new token is in a new line.
if (tokensInNewLine > 0)
{
tbLineMeasurer.Text = string.Empty;
tokensInNewLine--;
}
}
//System.Diagnostics.Debug.WriteLine(
// string.Format("Line Count = {0}" ,lineCount));
return lineCount;
}
但是,我们可以优化代码,因为您将从 TextChanged
事件调用它。 您可以添加另一个方法来粗略估计行数,然后仅在估计的行数足够接近 MaxLines
限制时才调用此方法。 对于正常情况,此方法的运行时间的最佳情况为 O(n^2),其中 n 是字符串中的标记/单词的数量,对于最坏的情况,n 是字符的数量。 所以要小心,如果你的文本框中有成千上万个单词,这可能会运行得很慢。
历史
- 2011 年 8 月 18 日:创建了文章。
- 2011 年 8 月 31 日:添加了指向演示的链接。 更新了算法。 修复了错误。