(C#)RichTextBox EM_SETWORDBREAKPROC 禁則処理による改行無効方法について

nova-7651 20 評価のポイント
2025-01-21T15:10:02.7666667+00:00

C#でWindowsFormで使用するためのリッチテキストボックス開発を行っています。最終的にビルドしたdllをフォームにて読み込んで使用します。環境はVS2022、フレームワークは.Net FrameWork4.8です。

実現したいこととしてRichTextBox クラスにおけるMultiline=true,WordWrap=true時の禁則処理による改行無効を実現したいです。

下記のように、【AAAAA BBBBB CCCC DDDD】とあったとき最後のDの文字が右端に差し掛かると何もしないデフォルト状態なら禁則処理に従ってDDDDごと改行されるがSendMessageで制御できることは分かっています。

しかし、この状態で何らかの全角文字が入るとこの状態が解除されます。その後、テキストを全て削除して入力し直してもその状態は元には戻らず【AAAAA BBBBB CCCC DDDD】と入力してもDDDDごと改行されます。

禁則処理は色々ありますが、全ての禁則処理の無効の実現方法。可能であればこの半角文字例のように半角文字群が改行されることだけは有効にしたいです。
ユーザーの画像

ユーザーの画像

using System;
using System.Drawing;
using System.Linq;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using static System.Windows.Forms.VisualStyles.VisualStyleElement;
public class CustomRichTextBox : RichTextBox
{
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
    private delegate int EditWordBreakProcDelegate(IntPtr pchText, int ichCurrent, int cch, int code);
    private EditWordBreakProcDelegate wordBreakProcDelegate;
    const int EM_SETWORDBREAKPROC = 0x00D0;
    public CustomRichTextBox()
    {
        wordBreakProcDelegate = new EditWordBreakProcDelegate(EditWordBreakProc);
        IntPtr wordBreakProcPtr = Marshal.GetFunctionPointerForDelegate(wordBreakProcDelegate);
        SendMessage(this.Handle, EM_SETWORDBREAKPROC, IntPtr.Zero, wordBreakProcPtr);
    }
    private int EditWordBreakProc(IntPtr pchText, int ichCurrent, int cch, int code)
    {
        return 0;
    }
}
C#
C#
C 言語ファミリをルーツとし、コンポーネント指向プログラミングのサポートを含む、オブジェクト指向およびタイプセーフのプログラミング言語。
39 件の質問
{count} 件の投票

承認済みの回答
  1. gekka 10,821 評価のポイント MVP
    2025-01-22T14:14:17.43+00:00

    試したところ、.NetFrameworkのバージョンを4.7よりも前に戻すと想定したような文字単位の折り返しになりました。

    どうも4.7でRichTextBoxのライブラリがRichEd20.dllではなくMsftEdit.dllを利用するように変更されたらしく、挙動が変わっているみたいです。

    いちおう、互換のためのフラグがあるので、設定することで文字単位の折り返しになりました。

    app.configで設定する場合

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
        <startup> 
            
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7"/></startup>
    
        <runtime>
            <AppContextSwitchOverrides value="Switch.System.Windows.Forms.DoNotLoadLatestRichEditControl=true"/>
        </runtime>
    </configuration>
    
    

    または、コードで設定する場合

    namespace CSForm48
    {
        using System;
        using System.Drawing;
        using System.Linq;
        using System.Windows.Forms;
    
    
        public partial class Form1 : Form
        {
            public Form1()
            {
                //RichTextBoxが作成されるよりも前にフラグを設定しておく
                //RichTextBoxWrap.TrySetDoNotLoadLatestRichEditControlFlag(true);
    
                RichTextBox rtb = new RichTextBox(); //new RichTextBoxOld();
                rtb.Multiline = true;
                rtb.Dock = DockStyle.Fill;
                rtb.Font = new Font(this.Font.FontFamily, 20);
                this.Controls.Add(rtb);
    
                this.ClientSize = new Size(300, 300);
    
                rtb.Text = "あ" + string.Join(" ", "ABCDEFGHIJKLMN".Select(c => new string(c, 5)));
    
    
                RichTextBoxWrap.SetDefaultWordbreak(rtb);
    
                Timer t = new Timer();
                t.Interval = 100;
                t.Tick += (s, e) =>
                {
                    int diff = (DateTime.Now.Second < 30) ? 1 : -1;
                    this.Width = Math.Max(100, Math.Min(this.Width + diff, 600));
                };
                t.Start();
            }
    
        }
    
        class RichTextBoxWrap
        {
            /// <summary>.Net Framwork4.7以降のRichTextBoxの挙動を以前のに戻すフラグをセットする</summary>
            /// <remarks>RichTextBoxが作成されるよりも前にフラグを設定しておく必要がある</remarks>
            public static void TrySetDoNotLoadLatestRichEditControlFlag(bool enable = true)
            {
                //4.6よりも前はビルド不可
                string versionName = typeof(System.AppContext).GetProperty("TargetFrameworkName")?.GetValue(null).ToString() ?? "";
                string sver = System.Text.RegularExpressions.Regex.Match(versionName, @"\d+(\.\d+)+").Value;
                if (System.Version.TryParse(sver, out System.Version v))
                {
                    if (new System.Version(4, 7) <= v && v <= new System.Version(5, 0))
                    {
                        System.AppContext.SetSwitch("Switch.System.Windows.Forms.DoNotLoadLatestRichEditControl", enable);
                    }
                }
            }
    
            /// <summary>単語区切りの折り返しを無効にして、文字単位の折り返しにする</summary>
            /// <param name="rtb"></param>
            /// <param name="disable">true:文字単位の折り返しに</param>
            public static void SetDefaultWordbreak(System.Windows.Forms.RichTextBox rtb, bool disable = true)
            {
                if (delCallback == null)
                {
                    delCallback = new Win32.EditWordBreakProcDelegate(CallBack);
                    pCallback = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(delCallback);
                }
                if (rtb.Handle == IntPtr.Zero)
                {
                    throw new InvalidOperationException();
                }
    
                if (disable)
                {
                    Win32.SendMessageW(rtb.Handle, Win32.EM_SETWORDBREAKPROC, System.IntPtr.Zero, pCallback);
                    Win32.SendMessageW(rtb.Handle, Win32.WM_SIZE, System.IntPtr.Zero, new System.IntPtr(((uint)rtb.Width << 16) | (uint)rtb.Height));
                }
                else
                {
                    Win32.SendMessageW(rtb.Handle, Win32.EM_SETWORDBREAKPROC, System.IntPtr.Zero, System.IntPtr.Zero);
                }
    
            }
    
            private static Win32.EditWordBreakProcDelegate delCallback;
            private static System.IntPtr pCallback = System.IntPtr.Zero;
    
            //----------------------------
    
            private static int CallBack(System.IntPtr text, int ichCurrent, int cch, Win32.WB code)
            {
                return 0;
                //string s = System.Runtime.InteropServices.Marshal.PtrToStringUni(text, cch);
    
                //switch (code)
                //{
                //case Win32.WB.WB_LEFT:
                //    return System.Math.Max(0, ichCurrent - 1);
                //case Win32.WB.WB_RIGHT:
                //    return System.Math.Min(ichCurrent + 1, cch);
                //case Win32.WB.WB_ISDELIMITER:
                //    return 0;
    
                //case Win32.WB.WB_CLASSIFY:
                //    if (char.IsWhiteSpace(s, ichCurrent))
                //    {
                //        return (int)Win32.WBF.WBF_ISWHITE;
                //    }
                //    else
                //    {
                //        return (int)(Win32.WBF.WBF_BREAKAFTER | Win32.WBF.WBF_BREAKLINE);
                //    }
                //case Win32.WB.WB_MOVEWORDLEFT:
                //case Win32.WB.WB_LEFTBREAK:
                //    return System.Math.Max(0, ichCurrent - 1);
    
                //case Win32.WB.WB_MOVEWORDRIGHT:
                //case Win32.WB.WB_RIGHTBREAK:
                //    return System.Math.Min(ichCurrent + 1, cch - 1);
                //default:
                //    return 0;
                //}
            }
        }
    
        class Win32
        {
            [System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
            public extern static System.IntPtr SendMessageW(System.IntPtr hwnd, uint message, System.IntPtr wParam, System.IntPtr lParam);
    
            public const uint WM_SIZE = 5;
            public const uint EM_SETWORDBREAKPROC = 0x00D0;
            public const uint EM_GETWORDBREAKPROC = 0x00D1;
    
            public delegate int EditWordBreakProcDelegate(System.IntPtr text, int ichCurrent, int cch, WB code);
    
            public enum WB : int
            {
                WB_LEFT = 0,
                WB_RIGHT = 1,
                WB_ISDELIMITER = 2,
    
                WB_CLASSIFY = 3,
                WB_MOVEWORDLEFT = 4,
                WB_MOVEWORDRIGHT = 5,
                WB_LEFTBREAK = 6,
                WB_RIGHTBREAK = 7,
    
                //// East Asia specific flags 
                //WB_MOVEWORDPREV = 4,
                //WB_MOVEWORDNEXT = 5,
                //WB_PREVBREAK = 6,
                //WB_NEXTBREAK = 7,
            }
            public enum WBF : byte
            {
                WBF_CLASS = 0x0F,
                WBF_ISWHITE = 0x10,
                WBF_BREAKLINE = 0x20,
                WBF_BREAKAFTER = 0x40,
            }
    
            [System.Runtime.InteropServices.DllImport("kernel32.dll")]
            public static extern IntPtr LoadLibrary(string lpFileName);
        }
    
        /// <summary>
        /// 無理やり古いバージョンを使うRichTextBox
        /// </summary>
        class RichTextBoxOld : RichTextBox
        {
            static RichTextBoxOld()
            {
                string dllPath = System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.System), "riched20.dll");
                if (System.IO.File.Exists(dllPath))
                {
                    moduleHandle = Win32.LoadLibrary(dllPath);
                }
            }
    
            private bool isModleChacked = false;
            private static IntPtr moduleHandle=IntPtr.Zero;
    
            protected override CreateParams CreateParams
            {
                get
                {
                    if (moduleHandle == IntPtr.Zero)
                    {
                        return base.CreateParams;
                    }
    
                    CreateParams createParams = base.CreateParams;
                    createParams.ClassName = "RichEdit20W";
                    return createParams;
                }
            }
        }
    }
    

    ただし、.Net Core以降ではこのフラグが消されてしまったので、互換性がなくなります。
    この場合はRichEditコントロールを使わずに、RichEd20.dllを自前でロードして処理するしかないのかも。

    1 人がこの回答が役に立ったと思いました。

0 件の追加の回答

並べ替え方法: 最も役に立つ

お客様の回答

回答は、質問作成者が [承諾された回答] としてマークできます。これは、ユーザーが回答が作成者の問題を解決したことを知るのに役立ちます。