DOBON.NET DOBON.NETプログラミング掲示板過去ログ

RichTextBoxで指定した文字列の背景色を着色前に戻す方法

環境/言語:[VB2013]
分類:[.NET]

RichTextBoxで選択した文字の背景色を変更したのち
初期状態(着色前のRichTextBox.Backcolorと同期がとれている状態)に
戻すことは可能でしょうか?

もし、可能であればその方法を教えていただきたく、よろしくお願いします。

NG例1:
「RichTextBox1.SelectionBackColor = Color.Empty」や
「RichTextBox1.SelectionBackColor = Color.Transparent」では
文字列の背景色は白となってしまいました。

NG例2:
「RichTextBox1.SelectionBackColor = RichTextBox1.BackColor」では
一時的にRichTextBox1のBackColorと同一になりますが
RichTextBox1のBackColorを変更した場合、同期はしません。
一般論として状態を変えたものを元に戻すためには、元の状態を覚えておく必要があります。
それがライブラリ・コンポーネントが用意するプロパティを使えるのか、Undo を使えるのかは状況によります。

■No32694に返信(くるりさんの記事)
> RichTextBoxで選択した文字の背景色を変更したのち

これはプログラムで自動的に背景色を変えているということでしょうか?


> 初期状態(着色前のRichTextBox.Backcolorと同期がとれている状態)に
> 戻すことは可能でしょうか?
>
> もし、可能であればその方法を教えていただきたく、よろしくお願いします。

候補として考えられるのは以下のものでしょうか。
ただ、前提条件などが書かれていないので、適切かどうかはわかりかねます。

(1)リッチテキストで代入しなおす(Rtf プロパティ or SelectedRtf プロパティ)
(2)部分的な色変更情報を自分で覚えておいてそのテーブルからうまく復元する

後者の場合はテキストの位置情報とかもうまく覚える必要がありそうなのでうまく自分でコーディングすることになります。
Feedbackにもあがってないみたいだけど、WinFormのバグですね。
操作としてはColor.Emptyの代入で正しいのですが、SelectionBackColor内の処理でミスしているようです。
http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/RichTextBox.cs,d0f4a24face1b591,references
Color.Emptyのとき
> cf2.dwEffects = RichTextBoxConstants.CFE_AUTOBACKCOLOR;
だけ記述しているけど、
> cf2.dwMask = RichTextBoxConstants.CFM_BACKCOLOR;
こっちの設定*も*必要。

CHARFORMAT2AとSendMessageとRichTextBoxConstantsを再定義して、SelectionBackColorを設定する代わりにdwEffectsとdwMaskを設定したCHARFORMAT2AをSendMessageするようにしてやれば、背景色をクリアできるかと思います。
// C#が分からないのであれば、ウェブ上でC#/VBコンバータを探してください。
Azuleanさん
ご回答ありがとうございます。

> 一般論として状態を変えたものを元に戻すためには、元の状態を覚えておく必要があります。

その様な一般論があるんですね。

私の場合、初期値を保持するロジックを明示的にコーディングしなくても
初期化を行う手法が用意されている場合があるのではないか という考えが先に浮かびます。

> これはプログラムで自動的に背景色を変えているということでしょうか?

・プログラムで文字列の背景色を変える場合があります。
・ペーストした文字列に背景色がセットされている場合があります。
・RTF形式のテキストをRichTextBox1.Rtfにセットした際に文字列の背景色がセットされる場合があります。

> (1)リッチテキストで代入しなおす (Rtf プロパティ or SelectedRtf プロパティ)
> (2)部分的な色変更情報を自分で覚えておいてそのテーブルからうまく復元する

他のコントロールであればBackcolorの値をEnptyにすることで初期化が可能です。
それと同様の手法がRichTextBoxでも使えないか模索しています。

現状、Hongliangさんから頂いた内容が目的としている機能に近い様に考えられるので、そちらを試してみようと思います。
Hongliangさん
ご回答ありがとうございます。

Color.Empty でクリアされないので不思議に思っていましたが
バグでしたか…。
とりあえず、考え方は間違っておらず、.netのバグが原因らしい事がわかり
その点はすっきりしました。ありがとうございます。

RichTextBoxの背景色クリアについては
教えていただいた方法を参考に対応を試みてみようと思いますが
現時点では理解できていない部分も多いため、対応には
時間がかかるかもしれません。

結果はまた後日、報告したいと思います。
■No32698に返信(くるりさんの記事)
> RichTextBoxの背景色クリアについては
> 教えていただいた方法を参考に対応を試みてみようと思いますが
> 現時点では理解できていない部分も多いため、対応には
> 時間がかかるかもしれません。

Hongliangさんのコードで実装してみました。

Imports System.Runtime.InteropServices
Imports System.Runtime.CompilerServices

Public Class Form1

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Button1.Text = " No32694 "
        Button2.Text = " No32696 "

        CheckBox1.Checked = True

        RichTextBox1.Text = <![CDATA[あああああ
いいいいい
ううううう
えええええ
おおおおお]]>.Value

        RichTextBox1.Select(6, 5)
        RichTextBox1.SelectionBackColor = Color.YellowGreen
        RichTextBox1.Select(18, 5)
        RichTextBox1.SelectionBackColor = Color.Cyan
        RichTextBox1.Select(0, 0)
    End Sub

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        'RichTextBox1.SelectionBackColor = Color.Transparent
        RichTextBox1.SelectionBackColor = Color.Empty
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        RichTextBox1.ResetSelectionBackColor()
    End Sub

    Private Sub CheckBox1_CheckedChanged(sender As Object, e As EventArgs) Handles CheckBox1.CheckedChanged
        RichTextBox1.BackColor = If(CheckBox1.Checked, Color.Khaki, Color.LightPink)
    End Sub
End Class

Public Module HongliangAnswer
    <Extension()> _
    Public Sub ResetSelectionBackColor(rtb As RichTextBox)
        If rtb.IsHandleCreated Then
            Const CFM_BACKCOLOR As Integer = &H4000000
            Const CFE_AUTOBACKCOLOR As Integer = CFM_BACKCOLOR
            Const EM_SETCHARFORMAT As Integer = (&H400 + 68)
            Const SCF_SELECTION As Integer = &H1

            Dim cf2 As New CHARFORMAT2A() With {.dwMask = CFM_BACKCOLOR, .dwEffects = CFE_AUTOBACKCOLOR}
            SendMessage(New HandleRef(rtb, rtb.Handle), EM_SETCHARFORMAT, SCF_SELECTION, cf2)
        End If
    End Sub

    <DllImport("User32", CharSet:=CharSet.Ansi)> _
    Private Function SendMessage(
        hWnd As HandleRef, msg As Integer, wParam As Integer, _
        <[In](), [Out](), MarshalAs(UnmanagedType.LPStruct)> lParam As CHARFORMAT2A _
    ) As IntPtr
    End Function

    <StructLayout(LayoutKind.Sequential, Pack:=4)> _
    Private Class CHARFORMAT2A
        Public cbSize As Integer = Marshal.SizeOf(GetType(CHARFORMAT2A))
        Public dwMask As Integer = 0
        Public dwEffects As Integer = 0
        Public yHeight As Integer = 0
        Public yOffset As Integer = 0
        Public crTextColor As Integer = 0
        Public bCharSet As Byte = 0
        Public bPitchAndFamily As Byte = 0
        <MarshalAs(UnmanagedType.ByValArray, SizeConst:=32)> _
        Public szFaceName() As Byte = New Byte(31) {}
        Public wWeight As Short = 0
        Public sSpacing As Short = 0
        Public crBackColor As Integer = 0
        Public lcid As Integer = 0
        Public dwReserved As Integer = 0
        Public sStyle As Short = 0
        Public wKerning As Short = 0
        Public bUnderlineType As Byte = 0
        Public bAnimation As Byte = 0
        Public bRevAuthor As Byte = 0
    End Class
End Module
魔界の仮面弁士さん
ご回答ありがとうございます。

コードまで書いていただいて、恐縮です。
結果は、完璧でした。

恥ずかしい話、現在の私のスキルでは
このコードまでたどり着けたかどうか…かなり怪しいところです。
いつかは、自力でこのようなコードをかける様、技術を磨いてゆこうと思います。

非常に助かるとともに、勉強になりました。

魔界の仮面弁士さん、Hongliangさん、Azuleanさん
ありがとうございました。
解決済み!

DOBON.NET | プログラミング道 | プログラミング掲示板