Silverlight TextBox 行数限制的实现

Silverlight中,TextBox控件缺少一个在WPF中非常有用的属性——MaxLines。这个属性允许开发者限制用户在TextBox中输入的行数。由于Silverlight的TextBox没有这个属性,开发者需要自己实现类似的功能。本文将介绍如何在Silverlight 4中实现这一功能。

自从上周接到一个需求,需要在Silverlight4中的多行TextBox(AcceptsReturn=true)中限制用户输入的行数,就开始寻找解决方案。遗憾的是,Silverlight并没有提供这样的属性。在WPF中,TextBox控件有一个名为MaxLines的整数属性,可以设置行数限制。但在Silverlight中,由于无法确定字符串在何处进行了换行,因此实现起来比较困难。

解决方案

为了满足客户的需求,决定自己编写逻辑来模拟SilverlightTextBox中的自动换行算法。创建了一个名为XTextBox的自定义控件,它扩展了Silverlight的TextBox控件,并添加了自己的逻辑来模拟换行行为。

最有趣的部分是GetNumberOfLines()方法,它返回某个字符串在TextBox中占用的行数。首先,需要知道在哪些字符处,换行算法将字符串分割成可以换行的单词,或者在这种情况下,称之为标记(tokens)。

通过一些实验,发现TextBox的换行算法在这些字符处分割单词:

string tokenizers = @" (?<=[ /\\\$%\(\)\-\+\[\]\{\}\?])";

使用正则表达式来分割字符串,而不是String.Split(),这样它将包括分割字符,如空格。

现在知道了如何分割单词以模拟换行操作,接下来需要一种方法来测量字符串的实际宽度。这样就可以知道TextBox中的某个标记组是否适合单行。可以使用TextBlock控件来测量字符串的宽度。因为如果不显式设置TextBlock的宽度,其实际宽度将根据其内容自动调整。所以,可以使用它们来测量字符串的实际宽度。

以下是该方法的实现:

protected int GetNumberOfLines(string theText = null) { tbLineMeasurer.Text = ""; string strInput = theText == null ? Text : theText; List tokens = Regex.Split(strInput, tokenizers).ToList(); int tokensInNewLine = 0; int 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 (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++; } continue; } tbLineMeasurer.Text += token; tbLineMeasurer.InvalidateMeasure(); if (tbLineMeasurer.ActualWidth >= lineWidth) { if (tbLineMeasurer.Text != token) { tbLineMeasurer.Text = token; lineCount++; } 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 (tokensInNewLine <= 0) { tokens.Insert(i + 1, tbLineMeasurer.Text); tbLineMeasurer.Text = ""; continue; } } } if (tokensInNewLine > 0) { tbLineMeasurer.Text = string.Empty; tokensInNewLine--; } } return lineCount; }

可以优化这段代码,因为将从TextChanged事件中调用它。可以添加另一个方法来大致估计行数,然后只有在估计的行数足够接近MaxLines限制时才调用这个方法。

这个方法在最坏情况下的运行时间是O(n^2),其中n是字符串中的标记/单词数量,对于正常情况,n是字符数量。所以要小心,如果TextBox中有成千上万的单词,它可能会运行得很慢。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485