1
0

More than 3 years have passed since last update.

C# - いろんな対策を取り込んだ RichTextBox テンプレ - 全角空白とタブと改行を表示【自分用】

Last updated at Posted at 2021-02-28

C# - RichTextBox関連やりたいこと逆引きリファレンス【自分用】 - Qiita

上記の集大成(?)です。

画面キャプチャ

image.png

サンプルコード

全角空白だらけにすると、かなり動作が重い


using System;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;
using System.Runtime.InteropServices;
using System.Windows.Forms;


internal class MyRichTextBox:RichTextBox
{
    private const Int32 WM_PAINT = 0x000F;

    private static readonly int adjX = -2;
    private static readonly int adjY =  2;

    private int lineHeight;
    private RichTextBox dummyRichTextBox; // lineHeight算出用

    //----------------------------------------------------------------
    // Win32
    //
    private const int EM_SETTABSTOPS = 0x00CB;
    private const long IMF_DUALFONT = 0x80;
    private const int WM_SETREDRAW = 0x0008;
    private const int WM_USER = 0x0400;
    private const int EM_SETLANGOPTIONS = WM_USER + 120;
    private const int EM_GETLANGOPTIONS = WM_USER + 121;
    private const int EM_GETTEXTEX = (WM_USER + 94);
    private const int EM_GETTEXTLENGTHEX = (WM_USER + 95);

    // Flags for the GETEXTEX data structure  
    private const int GT_DEFAULT = 0;
    private const int GTL_CLOSE = 4; // Fast computation of a "close" answer 
    private const int GTL_DEFAULT = 0; // Do default (return # of chars) 

    class NativeMethods
    {
        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
        public extern static IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int msg, ref GETTEXTEX wParam, StringBuilder lParam);

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessage(IntPtr hWnd, int msg, ref GETTEXTLENGTHEX wParam, IntPtr lParam);
    }

    [StructLayout(LayoutKind.Sequential)]
    struct GETTEXTEX
    {
        public int cb;
        public int flags;
        public int codepage;
        public IntPtr lpDefaultChar;
        public IntPtr lpUsedDefChar;
    }

    [StructLayout(LayoutKind.Sequential)]
    struct GETTEXTLENGTHEX
    {
        public int flags;
        public int codepage;
    }
    //
    //----------------------------------------------------------------

    private void EnsureDummyRichTextBox(bool forceSetFont)
    {
        if ( dummyRichTextBox == null ) {
            dummyRichTextBox = new RichTextBox();
            dummyRichTextBox.Text = "\n";
            dummyRichTextBox.Font = this.Font;
        }
        else if ( forceSetFont ) {
            dummyRichTextBox.Font = this.Font;
        }
    }

    void RecalcLineHeight(bool fontUpdated)
    {
        EnsureDummyRichTextBox(fontUpdated);
        int y1 = dummyRichTextBox.GetPositionFromCharIndex(dummyRichTextBox.GetFirstCharIndexFromLine(0)).Y;
        int y2 = dummyRichTextBox.GetPositionFromCharIndex(dummyRichTextBox.GetFirstCharIndexFromLine(1)).Y;
        lineHeight = y2 - y1;
    }

    protected override void OnFontChanged(EventArgs e)
    {
        RecalcLineHeight(true);
        SetFixFont();
        SetTabStop(4);
    }

    protected override void WndProc(ref System.Windows.Forms.Message m)
    {
        base.WndProc(ref m);
        if ( m.Msg == WM_PAINT ) {
            RecalcLineHeight(false);
            using ( Graphics graphic = base.CreateGraphics() ) {
                OnPaint(new PaintEventArgs(graphic, base.ClientRectangle));
            }
        }
    }

    protected override void OnPaint(PaintEventArgs pe)
    {
        Brush brush = Brushes.LightGray;
        string s = this.Text;
        int    sLen = s.Length;//this.TextLength;
        Graphics g = pe.Graphics;

        int lineStartI = GetLineFromCharIndex(GetCharIndexFromPosition(new Point(0, 0)));
        int lineEndI   = GetLineFromCharIndex(GetCharIndexFromPosition(new Point(pe.ClipRectangle.Width, pe.ClipRectangle.Height)));

        Regex regex = new Regex(@"[\t \r\n]"); // tab or 全角空白 or 改行

        // 行ごとに処理
        int nextCharI = -1;
        for ( int lineI = lineStartI; lineI <= lineEndI; lineI++ ) {
            int startCharI;
            if ( lineI == lineStartI ) {
                startCharI = GetFirstCharIndexFromLine(lineI);
            }
            else {
                startCharI = nextCharI;
            }
            nextCharI = GetFirstCharIndexFromLine(lineI+1);
            if (nextCharI < startCharI) {
                nextCharI = sLen;
                if ( startCharI == nextCharI ) {
                    break;
                }
            }
            int lenOfLine = nextCharI - startCharI;

            Match match = regex.Match(s, startCharI, lenOfLine);

            while ( match.Success ) {
                Point point = this.GetPositionFromCharIndex(match.Index);
                //Point point2 = this.GetPositionFromCharIndex(match.Index+match.Length);
                //g.FillRectangle(brush, point.X, point.Y, point2.X - point.X, lineHeight);
                if ( s[match.Index] == '\t' ) {
                    g.DrawString(">", this.Font, brush, adjX+point.X, adjY+point.Y, new StringFormat());
                }
                else if ( s[match.Index] == ' ' ) {//全角空白
                    g.DrawString("□", this.Font, brush, adjX+point.X, adjY+point.Y, new StringFormat());
                }
                else if ( s[match.Index] == '\r' ) {
                    g.DrawString(@"\r", this.Font, brush, adjX+point.X, adjY+point.Y, new StringFormat());
                }
                match = match.NextMatch();
            }

            /*
            // ちらつきと残骸が残る。実用レベルではない
            //
            // drawstringでtabの描画で調整がやっかいなので、1文字ずつGetPositionFromCharIndexで場所を取得して描画する
            Point pointNext = new Point(0,0);
            for ( int i=startCharI; i<startCharI+lenOfLine; i++ ) {
                Point point;
                if ( i == startCharI ) {
                    point = this.GetPositionFromCharIndex(i);
                }
                else {
                    point = pointNext;
                }
                pointNext = this.GetPositionFromCharIndex(i+1);
                if ( pointNext.X < 0 ) {
                    continue;
                }
                if ( point.X >= this.Width ) {
                    break;
                }
                g.DrawString(new string(s[i],1), this.Font, Brushes.Blue, adjX+point.X, adjY+point.Y, new StringFormat());
            }
            */

        }
    }


    public override string Text
    {
        get {
            var getLength = new GETTEXTLENGTHEX();
            getLength.flags = GTL_CLOSE; //get buffer size
            getLength.codepage = 1200; //Unicode
            int textLength = (int)(NativeMethods.SendMessage(this.Handle, EM_GETTEXTLENGTHEX, ref getLength, IntPtr.Zero).ToInt64());
            var getText = new GETTEXTEX();
            getText.cb = textLength + 2; //add space for null terminator
            if ( getText.cb < 2 ) { return ""; } // overflow
            getText.flags = GT_DEFAULT;
            getText.codepage = 1200; //Unicode
            var sb = new StringBuilder(getText.cb);
            NativeMethods.SendMessage(this.Handle, EM_GETTEXTEX, ref getText, sb);
            return sb.ToString();
        }
        set { base.Text = value; }
    }

    public override int TextLength
    {
        get {
            var getLength = new GETTEXTLENGTHEX();
            getLength.flags = GTL_DEFAULT; //Returns the number of characters
            getLength.codepage = 1200; //Unicode
            int textLength = (int)(NativeMethods.SendMessage(this.Handle, EM_GETTEXTLENGTHEX, ref getLength, IntPtr.Zero).ToInt64());
            if ( textLength < 0 ) { return 0; } // overflow
            return textLength;
        }
    }

    private void SetFixFont()
    {
        IntPtr lParam = NativeMethods.SendMessage(this.Handle, EM_GETLANGOPTIONS, new IntPtr(0), new IntPtr(0));
        lParam = new IntPtr( ((long)lParam) & (~IMF_DUALFONT));
        NativeMethods.SendMessage(this.Handle, EM_SETLANGOPTIONS, new IntPtr(0), lParam);
    }

    private void SetTabStop(int tabSize)
    {
        int[] tabarray = new int[] { tabSize*4 };
        int wparam = tabarray.Length;

        IntPtr parray = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * tabarray.Length);
        Marshal.Copy(tabarray, 0, parray, tabarray.Length);
        NativeMethods.SendMessage(this.Handle, EM_SETTABSTOPS, new IntPtr(wparam), parray);
    }
}


class RichTextBoxSample : Form
{
    MyRichTextBox txt;

    RichTextBoxSample()
    {
        ClientSize = new Size(600, 250);

        Controls.Add(txt = new MyRichTextBox(){
            Dock = DockStyle.Fill,
            Multiline = true,
            WordWrap = false,
            AcceptsTab = true,
            ScrollBars = RichTextBoxScrollBars.Both,
        });

        Font baseFont = new Font("MS ゴシック", 10);
        txt.Font = baseFont;
    }

    [STAThread]
    static void Main()
    {
        Application.Run(new RichTextBoxSample());
    }
}
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0