BLOG zzy.my

合抱之木, 生于毫末; 九层之台, 起于累土; 千里之行, 始于足下。

C#.net TextBox 同步滚动 实现行号

     一般的,我们做一个带行号的编辑控件,通常都使用RichTextBox,个人觉得至少有一点是RichTextBoxVScroll事件,方便重绘行号。

    

    网络上常见的做法都是 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);
}

 

 

 

基本上完整的实现了 >  绘制行号,包括点击滚动条、鼠标滚轮、上下按键、文本输入、鼠标选择内容上下移动、行显示宽度自适应。 但仍有个问题,就是TextBoxGetCharIndexFromPosition() 时,只支持 65535字符....  所以程序目前只支持最大文本65535字符

 

 

 源代码下载
提取码: b1de598c-1c1b-4a5b-be7a-ab30ceb6cb4b

评论 (2) -

  • ntcat

    2014-09-21 21:58:55 | 回复

    thx very much!

  • 刘建珍

    2014-03-13 06:07:28 | 回复

    做的很好,如果是实现横格信纸的richtextbox怎么写.

Loading