一般的,我们做一个带行号的编辑控件,通常都使用RichTextBox,个人觉得至少有一点是RichTextBox有VScroll事件,方便重绘行号。
网络上常见的做法都是 RichTextBox + Panel 来实现。 事实上在我做这类控件时,也是用这种方法,毕竟成熟的例子很多,在网上搜索。
为 TextBox 实现带行号功能。在网上查了查,发现例子很少,通常都是考虑两个TextBox。好不容易找到一个,发现它实现的方法太不讲究... 为了让左边的TextBox显示行号,居然用循环内容行数来写行号...
更让我郁闷的是,它还专门写了两个方法:
//根据行号确定光标索引 GetCurIndex(int curRow);
//定位总行号 GetRNCount(String str)
难道不知TextBox也有 GetPositionFromCharIndex()、GetCharIndexFromPosition() 和 Lines.Length 么...
程序一执行,少量行编辑还行,行数一多... 循环写行号,你懂得。。
程序也没办法实现滚动条、鼠标滚动、鼠标选择内容上下移动等来重写行号。
鉴于此,考虑到仅是文本的TextBox 在编辑时不用考虑rtf格式问题,用TextBox实现的带行号控件还是有那么点可用价值,我自己也写了一个 仅用 TextBox 实现的带行号功能。当然需要调用Windows API来做。
大致思想是,用两个 TextBox 同步滚动,加上独立的ScrollBar来实现。
主要涉及 内容文本的 TextChanged事件、KeyDown(上下按键)、ValueChanged(Scroll滚动事件)、SizeChanged(文本框大小改变)事件。
控件事件---------------
public Example()
{
InitializeComponent();
this.txtContent.MouseWheel += new MouseEventHandler(txtContect_MouseWheel);
}
private int pageLine = 0; //当前文本框内容所能显示的行数
private bool isLeftDown = false; //鼠标左键是否点下
private void txtContect_TextChanged(object sender, EventArgs e)
{
//调用顺序不可变
SetScrollBar();
ShowRow();
ShowCursorLine();
}
//鼠标滚动
void txtContect_MouseWheel(object sender, MouseEventArgs e)
{
timer1.Enabled = true;
}
// 上、下键
private void txtContent_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyData == System.Windows.Forms.Keys.Up || e.KeyData == System.Windows.Forms.Keys.Down)
SetScrollBar();
}
private void txtContent_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyData == System.Windows.Forms.Keys.Up || e.KeyData == System.Windows.Forms.Keys.Down)
ShowCursorLine();
}
//点击滚动条
private void vScrollBar1_ValueChanged(object sender, EventArgs e)
{
int t = SetScrollPos(this.txtContent.Handle, 1, vScrollBar1.Value, true);
SendMessage(this.txtContent.Handle, WM_VSCROLL, SB_THUMBPOSITION + 0x10000 * vScrollBar1.Value, 0);
ShowRow();
}
//显示光标行
private void txtContent_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left) isLeftDown = true;
ShowCursorLine();
}
//鼠标选择内容上下移动
private void txtContent_MouseMove(object sender, MouseEventArgs e)
{
SetScrollBar();
}
private void txtContent_MouseUp(object sender, MouseEventArgs e)
{
isLeftDown = false;
}
//文本框大小改变
private void txtContent_SizeChanged(object sender, EventArgs e)
{
SCROLLINFO si = new SCROLLINFO();
si.cbSize = (uint)Marshal.SizeOf(si);
si.fMask = SIF_ALL;
int r = GetScrollInfo(this.txtContent.Handle, SB_VERT, ref si);
pageLine = (int)si.nPage;
timer1.Enabled = true;
ShowRow();
}
//行显示栏宽度自适应
private void txtRow_TextChanged(object sender, EventArgs e)
{
if (this.txtRow.Lines.Length > 0)
{
System.Drawing.SizeF s = this.txtRow.CreateGraphics().MeasureString(this.txtRow.Lines[this.txtRow.Lines.Length - 1], this.txtRow.Font);
this.txtRow.Width = (int)s.Width;
}
}
private void txtRow_SizeChanged(object sender, EventArgs e)
{
this.txtContent.Location = new Point(this.txtRow.Width, this.txtContent.Location.Y);
this.txtContent.Width = this.ClientSize.Width - this.txtRow.Width;
}
//
方法
#region Method
private void ShowCursorLine()
{
toolStripStatusLabel1.Text = "行: " + (this.txtContent.GetLineFromCharIndex(this.txtContent.SelectionStart) + 1);
}
private void timer1_Tick(object sender, EventArgs e)
{
SetScrollBar();
timer1.Enabled = false;
}
private void SetScrollBar()
{
SCROLLINFO si = new SCROLLINFO();
si.cbSize = (uint)Marshal.SizeOf(si);
si.fMask = SIF_ALL;
int r = GetScrollInfo(this.txtContent.Handle, SB_VERT, ref si);
pageLine = (int)si.nPage;
this.vScrollBar1.LargeChange = pageLine;
if (si.nMax >= si.nPage)
{
this.vScrollBar1.Visible = true;
this.vScrollBar1.Maximum = si.nMax;
this.vScrollBar1.Value = si.nPos;
}
else
this.vScrollBar1.Visible = false;
}
private void ShowRow()
{
int firstLine = txtContent.GetLineFromCharIndex(txtContent.GetCharIndexFromPosition(new Point(0, 2)));
string[] lin = new string[pageLine];
for (int i = 0; i < pageLine; i++)
{
lin[i] = (i + firstLine + 1).ToString();
}
txtRow.Lines = lin;
}
#endregion
调用 Windows API
public static uint SIF_RANGE = 0x0001;
public static uint SIF_PAGE = 0x0002;
public static uint SIF_POS = 0x0004;
public static uint SIF_TRACKPOS = 0x0010;
public static uint SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS);
public int SB_THUMBPOSITION = 4;
public int SB_VERT = 1;
public int WM_VSCROLL = 0x0115;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SCROLLINFO
{
public uint cbSize;
public uint fMask;
public int nMin;
public int nMax;
public uint nPage;
public int nPos;
public int nTrackPos;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int GetScrollInfo(IntPtr hwnd, int bar, ref SCROLLINFO si);
[DllImport("user32.dll")]
private static extern int GetScrollPos(IntPtr hwnd, int nbar);
[DllImport("user32.dll")]
public static extern int SetScrollPos(IntPtr hWnd, int nBar, int nPos, bool Rush);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
重载 TextBox 的 KeyDown 事件
protected override bool IsInputKey(System.Windows.Forms.Keys KeyData)
{
if (KeyData == System.Windows.Forms.Keys.Up || KeyData == System.Windows.Forms.Keys.Down)
return true;
return base.IsInputKey(KeyData);
}
基本上完整的实现了 > 绘制行号,包括点击滚动条、鼠标滚轮、上下按键、文本输入、鼠标选择内容上下移动、行显示宽度自适应。 但仍有个问题,就是TextBox 在 GetCharIndexFromPosition() 时,只支持 65535字符.... 所以程序目前只支持最大文本65535字符
源代码下载
提取码: b1de598c-1c1b-4a5b-be7a-ab30ceb6cb4b