RichTextBoxのTextを変更していないのにTextChangedイベントが発生してハマったので調べてみた (C#)

# 1. 結論

書式設定をするだけでも`TextChanged`イベントが発生する。



# 2. 実験

## 2.1 実験用コード

```csharp:RichTextBoxTest.cs

using System;
using System.Drawing;
using System.Windows.Forms;

class RichTextBoxTest:Form
{
    RichTextBox rtxt;

    RichTextBoxTest()
    {
        ClientSize = new Size(400,300);

        Controls.Add( rtxt = new RichTextBox() {
            Multiline = true,
            Location = new Point(0,0),
            Size = new Size(400,270),
        });

        Button btn;
        Controls.Add( btn = new Button() {
            Location = new Point(0,270),
            Size = new Size(400,30),
            Text = "SetColor",
        });

        rtxt.TextChanged += Rtxt_TextChanged;

        btn.Click += (s,e)=>{SetColor();};
    }

    void Rtxt_TextChanged(object sender, EventArgs e)
    {
        Console.WriteLine("TextChanged called.");
    }


    void SetColor()
    {
        Console.WriteLine("setting SelectionBackColor.");
        rtxt.SelectionBackColor = Color.Yellow;

        Console.WriteLine("setting SelectionColor.");
        rtxt.SelectionColor = Color.Blue;

        Font fontForTest = new Font("ＭＳ ゴシック", 12);
        Console.WriteLine("setting SelectionFont.");
        rtxt.SelectionFont = fontForTest;

        Console.WriteLine("end of UpdateColor.");
    }

    [STAThread]
    public static void Main(string[] args)
    {
        Application.Run(new RichTextBoxTest());
    }
}
```

## 2.2. 実行結果

RichTextBoxにテキストを1文字入力して、全選択し、ボタンを押した結果。

```text:コンソール出力

TextChanged called.
setting SelectionBackColor.
TextChanged called.
setting SelectionColor.
TextChanged called.
setting SelectionFont.
TextChanged called.
end of UpdateColor.
```

RichTextBoxの以下のいずれのプロパティに対しても、設定時に `TextChanged` イベントが発生しています。

- `SelectionBackColor`
- `SelectionColor`
- `SelectionFont`


# 3. 内部仕様調査
## 3.1. 仕様記載

仕様記載上は、そのようには読み取れない。

[Control.TextChanged Event (System.Windows.Forms) | Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.control.textchanged?view=netframework-4.8)

> Occurs when the Text property value changes.

## 3.2. 内部コード

dotnet451\Source\ndp\fx\src\WinForms\Managed\System\WinForms

```csharp:RichTextBox.csより抜粋

public Color SelectionColor {
    get {
        省略
    }
    set {
        ForceHandleCreate();
        NativeMethods.CHARFORMATA cf = GetCharFormat(true);
        cf.dwMask = RichTextBoxConstants.CFM_COLOR;
        cf.dwEffects = 0;
        cf.crTextColor = ColorTranslator.ToWin32(value);

        // set the format information
        UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_SETCHARFORMAT, RichTextBoxConstants.SCF_SELECTION, cf);
    }
}
```

明示的にTextChangedイベントを発火させている処理は見当たらない。
`SendMessage`が要因っぽい予感。

WindowsのMessageを処理して`TextChanged`イベントを発火しているのは、継承元の`TextBoxBase`クラスのよう。

```csharp:TextBoxBase.csより抜粋

private void WmReflectCommand(ref Message m) {
    if (!textBoxFlags[codeUpdateText] && !textBoxFlags[creatingHandle]) {
        if (NativeMethods.Util.HIWORD(m.WParam) == NativeMethods.EN_CHANGE && CanRaiseTextChangedEvent) {
            OnTextChanged(EventArgs.Empty);
        }
        省略
    }
}
```

確証が持てないが、
`UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_SETCHARFORMAT, RichTextBoxConstants.SCF_SELECTION, cf);`
が
`NativeMethods.Util.HIWORD(m.WParam) == NativeMethods.EN_CHANGE`を満たすメッセージを発火しているのではないか。

## 3.3. Window Messageを監視してみる

### 3.3.1. テストコード

`RichTextBox`を、下記に置き換えて`RichTextBox`のWindowメッセージを監視してみた。

```csharp:テストコード(C#)

public class MyRichTextBox:RichTextBox
{
    protected override void WndProc(ref Message m)
    {
        long wparam = m.WParam.ToInt64();
        long lparam = m.LParam.ToInt64();

        Console.Write( "Msg:0x"    +  m.Msg.ToString("X08"));
        Console.Write(" Wparam:0x" + wparam.ToString("X08"));
        Console.Write(" Lparam:0x" + lparam.ToString("X08"));
        Console.WriteLine();

        base.WndProc(ref m);
    }
}
```

### 3.3.2. 結果

```text:コンソール出力

setting SelectionBackColor.
Msg:0x00000444 Wparam:0x00000001 Lparam:0x00D3E460
Msg:0x00002111 Wparam:0x030007CC Lparam:0x000707CC
TextChanged called.
setting SelectionColor.
Msg:0x0000043A Wparam:0x00000001 Lparam:0x00D3E440
Msg:0x00000444 Wparam:0x00000001 Lparam:0x00D3E4B0
Msg:0x00002111 Wparam:0x030007CC Lparam:0x000707CC
TextChanged called.
setting SelectionFont.
Msg:0x00000444 Wparam:0x00000001 Lparam:0x00D3E470
Msg:0x00002111 Wparam:0x030007CC Lparam:0x000707CC
TextChanged called.
end of UpdateColor.
```

> `UnsafeNativeMethods.SendMessage(new HandleRef(this, Handle), RichTextBoxConstants.EM_SETCHARFORMAT, RichTextBoxConstants.SCF_SELECTION, cf);`
が
`NativeMethods.Util.HIWORD(m.WParam) == NativeMethods.EN_CHANGE`を満たすメッセージを発火しているのではないか。

内部コードをみると、

- `EM_SETCHARFORMAT` は `0x444` ．
- `EN_CHANGE` は `0x0300` ．

に対して、結果として、

`Msg:0x00000444 Wparam:.......... Lparam:........`
`Msg:.......... Wparam:0x0300.... Lparam:........`

が得られているため、推測の裏どりが取れた。

# 参考サイト

 - [List Of Windows Messages - WineHQ Wiki](https://wiki.winehq.org/List_Of_Windows_Messages)