winform中richtextbox组件设置字间距
winform中richtextbox组件设置字间距
在最近的一个项目中需要对richtextbox的内容设置字间距,但是网上搜了一圈发现richtextbox组件并不支持这个功能,而且网上也没法先很方便的实现。
但是从各种途径发现RTF规范里面是有一个关键词(\expndtw)设置字间距(以缇为单位,DIP为96情况下 15缇=1像素)的,在实际试过后在richtextbox组件里确实能够显示,于是接下来只要操作richtextbox的rtf字符串进行修改就可以了。
以下是我实现的代码,只在.net framwork4.8和4.72测试过。
private void tcbLetterSpace_SelectedIndexChanged(object sender, EventArgs e)
{
int space;
if (int.TryParse((string)tcbLetterSpace.Text, out space))
{
int SelectionStart = rtbEditor.SelectionStart;
int SelectionLength = rtbEditor.SelectionLength;
// TODO: 如果插入的位置不是字符串末尾
// 则直接插入控制语句即可
// 否则要将此次的设置保存下来
// 在输入下一个字符是才开始生效
string originRTF = (string)rtbEditor.Rtf.Clone();
string rtfStr = (string)rtbEditor.Rtf.Clone();
// 找到需要插入字符间距的起始位点
int rtfIndex = SelectionRTFIndex(rtfStr, rtbEditor.SelectionStart);
// 插入expndtw,以twips为单位,(15 twips = 1 pixel)
Console.WriteLine("rtfIndex is {0}", rtfIndex);
Console.WriteLine("Before Insert: {0}", rtfStr);
rtfStr = rtfStr.Insert(rtfIndex, $"\\expndtw{space * 15} ");
Console.WriteLine("After Insert: {0}", rtfStr);
if (SelectionLength > 0)
{
// 插入完成后调整所选内容之后的字间距
// 将所选内容的所有 \expndtw
// 清空并获得末尾字符后一个字符前的字间距
space = CleanSelectionLetterSpace(ref rtfStr, originRTF, SelectionStart, SelectionLength);
if (space >= 0)
{
rtfIndex = SelectionRTFIndex(rtfStr, SelectionStart + SelectionLength);
rtfStr = rtfStr.Insert(rtfIndex, $"\\expndtw{space} ");
}
Console.WriteLine("After Clean: {0}", rtfStr);
}
rtbEditor.Rtf = rtfStr;
rtbEditor.SelectionStart = SelectionStart + SelectionLength;
rtbEditor.Focus();
}
}
private int SelectionRTFIndex(string rtfStr, int textIndex)
{
double count = 0;
bool isTextBegin = false;
bool isSentenceBegin = false;
MemoryStream ms = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(rtfStr));
int ch;
string token;
while (true)
{
ch = ms.ReadByte();
if (ch.Equals('\\'))
{
isSentenceBegin = false;
token = RTFHelper.NextToken(ref ms);
if (token.Equals("pard"))
{
isTextBegin = true;
}
else if (token.Equals("par"))
{
if (isTextBegin)
{
isSentenceBegin = true;
// 增加数量前进行判断的原因是防止含有其它无关字符
if (count == textIndex && isSentenceBegin && isTextBegin)
{
ms.Position -= 4;
goto DONE;
}
count += 1;
}
}
else if (token.Contains("'"))
{
if (isTextBegin)
{
isSentenceBegin = true;
if (count == textIndex && isSentenceBegin && isTextBegin)
{
ms.Position -= @"\'ff".Length;
goto DONE;
}
// GB2312一个字符(非asc)由两个字节组成
count += 0.5;
// 考虑中文后面跟着其它字符的情况
if (token.Length > 3)
{
count += token.Length - 3;
}
}
}
}
else if (ch != -1)
{
if (isTextBegin)
{
if (ch.Equals(' '))
{
if (isSentenceBegin)
{
if (count == textIndex && isSentenceBegin && isTextBegin)
{
ms.Position -= 1;
goto DONE;
}
count++;
}
else
{
isSentenceBegin = true;
}
}
else if(!RTFHelper.IsDelimiter(ch))
{
if (count == textIndex && isSentenceBegin && isTextBegin)
{
ms.Position -= 1;
goto DONE;
}
count++;
}
}
}
else
{
goto DONE;
}
}
DONE:
return (int)ms.Position;
}
private int CleanSelectionLetterSpace(ref string rtfStr, string originRTF, int SelectionStart, int SelctionLength)
{
int space = 0;
int subBegin = 0, subEnd = 0;
string subStr = string.Empty;
if (SelctionLength > 1)
{
subEnd = SelectionRTFIndex(originRTF, SelectionStart + SelctionLength);
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(originRTF.Substring(0, subEnd)));
// 获得最后的 expndtw 值
int ch;
string token;
while (true)
{
ch = ms.ReadByte();
if (ch.Equals('\\'))
{
token = RTFHelper.NextToken(ref ms);
if (RTFHelper.GetNonNumericString(token).Equals("expndtw"))
{
if (!int.TryParse(RTFHelper.GetNumericString(token), out space))
{
space = -1;
goto DONE;
}
}
}
if (ch == -1)
{
goto DONE;
}
}
}
DONE:
subBegin = SelectionRTFIndex(rtfStr, SelectionStart);
subEnd = SelectionRTFIndex(rtfStr, SelectionStart + SelctionLength);
try
{
subStr = rtfStr.Substring(subBegin, subEnd - subBegin);
}
catch (ArgumentOutOfRangeException ex)
{
Console.WriteLine("出于不知名的原因,在清理字间距获得字串时出错......{0}", ex);
}
if (space != -1)
{
// 根据正则表达式删除控制语句
subStr = Regex.Replace(subStr, "\\\\expndtw\\d*\\s?", "");
rtfStr = rtfStr.Remove(subBegin, subEnd - subBegin);
rtfStr = rtfStr.Insert(subBegin, subStr);
}
return space;
}
使用方法
将以上代码复制粘贴后,将点击按钮事件设置为tcbLetterSpace_SelectedIndexChanged并设置richtextbox组件名为rtbEditor即可,或直接修改代码。注意,如果在行末设置行间距的话没有效果,因为会被自动清理掉,如有需要应该要将操作保存下来,并在下一次末尾添加字符时重新添加。