NEWS

[C#] Hiển thị line number trên Richtextbox Winform

[C#] Hiển thị line number trên Richtextbox Winform
Đăng bởi: Thảo Meo - Lượt xem: 4648 11:51:39, 08/12/2022C#   In bài viết

Xin chào các bạn, bài viết này mình tiếp tục chia sẻ các bạn cách hiển thị Line Number trên Richtextbox giống các công cụ Code Editor trên C#, Winform.

[C#] How to show line number in Richtextbox winform

Dưới đây là hình ảnh demo ứng dụng:

LineNumberRichTextBox

Vidoe demo ứng dụng:

Đầu tiền, các bạn tạo một User Control với tên LineNumberRTB.cs:

using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Globalization;

namespace LineNumberRichTextBoxDemo
{
    public partial class LineNumberRTB : UserControl
    {
        private readonly LineNumberStrip _strip;

        public LineNumberRTB()
        {
            InitializeComponent();
            _strip = new LineNumberStrip(richTextBox);
            this.Controls.Add(_strip);
            this.BorderStyle = BorderStyle.Fixed3D;
            base.BackColor = richTextBox.BackColor;
        }

        public RichTextBox RichTextBox
        {
            get { return richTextBox; }
        }

        public LineNumberStrip Strip
        {
            get { return _strip; }
        }
    }

    public enum LineNumberStyle { None, OffsetColors, Boxed };

    public class LineNumberStrip : Control
    {
        private BufferedGraphics _bufferedGraphics;
        private readonly BufferedGraphicsContext _bufferContext = BufferedGraphicsManager.Current;
        private readonly RichTextBox _richTextBox;
        private Brush _fontBrush;
        private Brush _offsetBrush = new SolidBrush(Color.DarkSlateGray);
        private LineNumberStyle _style;
        private Pen _penBoxedLine = Pens.LightGray;
        private float _fontHeight;
        private const float _FONT_MODIFIER = 0.09f;
        private bool _hideWhenNoLines, _speedBump;
        private const int _DRAWING_OFFSET = 1;
        private int _lastYPos = -1, _dragDistance, _lastLineCount;
        private int _scrollingLineIncrement = 5, _numPadding = 10;

        /// <summary>
        /// We need to pass in the MainForm so we can check the form state, Do not
        /// use mainForm.ActiveForm, this is just too dangerous
        /// </summary>
        public LineNumberStrip(RichTextBox plainTextBox)
        {
            _richTextBox = plainTextBox;
            plainTextBox.TextChanged += _richTextBox_TextChanged;
            plainTextBox.FontChanged += _richTextBox_FontChanged;
            plainTextBox.VScroll += _richTextBox_VScroll;

            this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
                ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);

            this.Size = new Size(10, 10);
            base.BackColor = System.Drawing.ColorTranslator.FromHtml("#222E33");
            base.Dock = DockStyle.Left;
            base.ForeColor = Color.LimeGreen; //System.Drawing.ColorTranslator.FromHtml("#ddd");
            this.OffsetColor =  System.Drawing.ColorTranslator.FromHtml("#222E33");
            this.Style = LineNumberStyle.Boxed;
            

            _fontBrush = new SolidBrush(base.ForeColor);

            SetFontHeight();
            UpdateBackBuffer();
            this.SendToBack();
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);

            if (e.Button.Equals(MouseButtons.Left) && _scrollingLineIncrement != 0)
            {
                _lastYPos = Cursor.Position.Y;
                this.Cursor = Cursors.NoMoveVert;
            }
        }

        protected override void OnParentChanged(EventArgs e)
        {
            base.OnParentChanged(e);
            SetControlWidth();
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            this.Cursor = Cursors.Default;
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (e.Button.Equals(MouseButtons.Left) && _scrollingLineIncrement != 0)
            {
                _dragDistance += Cursor.Position.Y - _lastYPos;

                if (_dragDistance > _fontHeight)
                {
                    int selectionStart = _richTextBox.GetFirstCharIndexFromLine(NextLineDown);
                    _richTextBox.Select(selectionStart, 0);
                    _dragDistance = 0;
                }
                else if (_dragDistance < _fontHeight * -1)
                {
                    int selectionStart = _richTextBox.GetFirstCharIndexFromLine(NextLineUp);
                    _richTextBox.Select(selectionStart, 0);
                    _dragDistance = 0;
                }

                _lastYPos = Cursor.Position.Y;
            }
        }

        #region Functions
        private void UpdateBackBuffer()
        {
            if (this.Width > 0)
            {
                _bufferContext.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);
                _bufferedGraphics = _bufferContext.Allocate(this.CreateGraphics(), this.ClientRectangle);
            }
        }

        /// <summary>
        /// This method keeps the painted text aligned with the text in the corisponding 
        /// textbox perfectly. GetFirstCharIndexFromLine will yeild -1 if line not
        /// present. GetPositionFromCharIndex will yeild an empty point to char index -1.
        /// To explicitly say that line is not present return -1.
        /// </summary>
        private int GetPositionOfRtbLine(int lineNumber)
        {
            int index = _richTextBox.GetFirstCharIndexFromLine(lineNumber);
            Point pos = _richTextBox.GetPositionFromCharIndex(index);
            return index.Equals(-1) ? -1 : pos.Y;
        }

        private void SetFontHeight()
        {
            // Shrink the font for minor compensation
            this.Font = new Font(_richTextBox.Font.FontFamily, _richTextBox.Font.Size -
                _FONT_MODIFIER, _richTextBox.Font.Style);

            _fontHeight = _bufferedGraphics.Graphics.MeasureString("123ABC", this.Font).Height;
        }

        private void SetControlWidth()
        {
            // Make the line numbers virtually invisble when no lines present
            if (_richTextBox.Lines.Length.Equals(0) && _hideWhenNoLines)
            {
                this.Width = 0;
            }
            else
            {
                this.Width = WidthOfWidestLineNumber + _numPadding * 2;
            }

            this.Invalidate(false);
        }
        #endregion

        #region Event Handlers
        private void _richTextBox_FontChanged(object sender, EventArgs e)
        {
            SetFontHeight();
            SetControlWidth();
        }

        /// <summary>
        /// Use this event to look for changes in the line count
        /// </summary>
        private void _richTextBox_TextChanged(object sender, EventArgs e)
        {
            // If word wrap is enabled do not check for line changes as new lines
            // from word wrapping will not raise the line changed event

            // Last line count is always equal to current when words are wrapped
            if (_richTextBox.WordWrap || !_lastLineCount.Equals(_richTextBox.Lines.Length))
            {
                SetControlWidth();
            }

            _lastLineCount = _richTextBox.Lines.Length;
        }

        protected override void OnForeColorChanged(EventArgs e)
        {
            base.OnForeColorChanged(e);
            _fontBrush = new SolidBrush(this.ForeColor);
        }

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            UpdateBackBuffer();
        }

        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            _bufferedGraphics.Graphics.Clear(this.BackColor);

            int firstIndex = _richTextBox.GetCharIndexFromPosition(Point.Empty);
            int firstLine = _richTextBox.GetLineFromCharIndex(firstIndex);
            Point bottomLeft = new Point(0, this.ClientRectangle.Height);
            int lastIndex = _richTextBox.GetCharIndexFromPosition(bottomLeft);
            int lastLine = _richTextBox.GetLineFromCharIndex(lastIndex);

            for (int i = firstLine; i <= lastLine + 1; i++)
            {
                int charYPos = GetPositionOfRtbLine(i);
                if (charYPos.Equals(-1)) continue;
                float yPos = GetPositionOfRtbLine(i) + _DRAWING_OFFSET;

                if (_style.Equals(LineNumberStyle.OffsetColors))
                {
                    if (i % 2 == 0)
                    {
                        _bufferedGraphics.Graphics.FillRectangle(_offsetBrush, 0, yPos, this.Width,
                            _fontHeight * _FONT_MODIFIER * 10);
                    }
                }
                else if (_style.Equals(LineNumberStyle.Boxed))
                {
                    PointF endPos = new PointF(this.Width, yPos + _fontHeight - _DRAWING_OFFSET * 3);
                    PointF startPos = new PointF(0, yPos + _fontHeight - _DRAWING_OFFSET * 3);
                    _bufferedGraphics.Graphics.DrawLine(_penBoxedLine, startPos, endPos);
                }

                PointF stringPos = new PointF(_numPadding, yPos);
                string line = (i + 1).ToString(CultureInfo.InvariantCulture);
                // i + 1 to start the line numbers at 1 instead of 0
                _bufferedGraphics.Graphics.DrawString(line, this.Font, _fontBrush, stringPos);

            }

            _bufferedGraphics.Render(pevent.Graphics);
        }

        private void _richTextBox_VScroll(object sender, EventArgs e)
        {
            // Decrease the paint calls by one half when there is more than 3000 lines
            if (_richTextBox.Lines.Length > 3000 && _speedBump)
            {
                _speedBump = !_speedBump;
                return;
            }

            this.Invalidate(false);
        }
        #endregion

        #region Properties
        private int NextLineDown
        {
            get
            {
                int yPos = _richTextBox.ClientSize.Height + (int)(_fontHeight * ScrollSpeed + 0.5f);
                Point topPos = new Point(0, yPos);
                int index = _richTextBox.GetCharIndexFromPosition(topPos);
                return _richTextBox.GetLineFromCharIndex(index);
            }
        }

        private int NextLineUp
        {
            get
            {
                Point topPos = new Point(0, (int)(_fontHeight * (ScrollSpeed * -1) + -0.5f));
                int index = _richTextBox.GetCharIndexFromPosition(topPos);
                return _richTextBox.GetLineFromCharIndex(index);
            }
        }

        /// <summary>
        /// Gets the width of the widest number on the strip
        /// </summary>
        private int WidthOfWidestLineNumber
        {
            get
            {
                if (_bufferedGraphics.Graphics != null)
                {
                    string strNumber = (_richTextBox.Lines.Length).ToString(CultureInfo.InvariantCulture);
                    SizeF size = _bufferedGraphics.Graphics.MeasureString(strNumber, _richTextBox.Font);
                    return (int)(size.Width + 0.5);
                }

                return 1;
            }
        }

        /// <summary>
        /// Make sure this is set according to the users left to right layout
        /// </summary>
        public bool DockToRight
        {
            get { return (this.Dock == DockStyle.Right); }
            set { this.Dock = (value) ? DockStyle.Right : DockStyle.Left; }
        }

        [Category("Layout")]
        [Description("Gets or sets the spacing from the left and right of the numbers to the let and right of the control")]
        public int NumberPadding
        {
            get { return _numPadding; }
            set
            {
                _numPadding = value;

                if (_richTextBox != null)
                {
                    SetControlWidth();
                }
            }
        }

        [Category("Appearance")]
        public LineNumberStyle Style
        {
            get { return _style; }
            set
            {
                _style = value;
                this.Invalidate(false);
            }
        }

        [Category("Appearance")]
        public Color BoxedLineColor
        {
            get { return _penBoxedLine.Color; }
            set
            {
                _penBoxedLine = new Pen(value);
                this.Invalidate(false);
            }
        }

        [Category("Appearance")]
        public Color OffsetColor
        {
            get { return new Pen(_offsetBrush).Color; }
            set
            {
                _offsetBrush = new SolidBrush(value);
                this.Invalidate(false);
            }
        }

        [Category("Behavior")]
        public bool HideWhenNoLines
        {
            get { return _hideWhenNoLines; }
            set { _hideWhenNoLines = value; }
        }

        /// <summary>
        /// Hide this, The right to left layout property will determine 
        /// the dock style
        /// </summary>
        [Browsable(false)]
        public override DockStyle Dock
        {
            get { return base.Dock; }
            set { base.Dock = value; }
        }

        /// <summary>
        /// Gets or sets the scrolling speed in the number of lines
        /// to increment or decrement
        /// </summary>
        [Category("Behavior")]
        public int ScrollSpeed
        {
            get { return _scrollingLineIncrement; }
            set { _scrollingLineIncrement = value; }
        }
        #endregion
    }
}

Và code sử dụng ở form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace LineNumberRichTextBoxDemo
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
           lineNumberRTB1.RichTextBox.BackColor = ColorTranslator.FromHtml("#222E33");
            lineNumberRTB1.RichTextBox.ForeColor = Color.White;
            lineNumberRTB1.RichTextBox.Font = new Font("Tahoma", 12, FontStyle.Regular);
            lineNumberRTB1.RichTextBox.BorderStyle = BorderStyle.None;
            lineNumberRTB1.BorderStyle= BorderStyle.None;

        }
    }
}

Các bạn có thể chỉnh sửa màu nên, phông chữ theo ý của bạn.

Thanks for watching!

DOWNLOAD SOURCE

THÔNG TIN TÁC GIẢ

BÀI VIẾT LIÊN QUAN

[C#] Hiển thị line number trên Richtextbox Winform
Đăng bởi: Thảo Meo - Lượt xem: 4648 11:51:39, 08/12/2022C#   In bài viết

CÁC BÀI CÙNG CHỦ ĐỀ

Đọc tiếp
.