画像ファイル
画像ファイル RichTextBoxの不思議
RichTextBox関係
1RichTextBoxの不思議
2テキスト色付け高速化計画
3VB.NET RichTextBox1
4VB.NET RichTextBox 2

VB.NET C#全般
1羊の皮を着た狼 VB.NET
2Form1、Form2の相互参照
3Form1、Form2の相互参照 2
4VB.NET C# データ型の基本
5VB.NET C# 文字列
6VB.NET タイマー精度
7BackgroundWorkerの魅力1..
8BackgroundWorkerの魅力2..
9VB6のタイマー
10コントロールの配列をインデクサ..
11コントロールの配列はジェネリク..
12インデクサ(C#、VB.NET)
13インデクサでBit操作
14Unicode 入門
15デリゲート入門
16マルチスレッド入門
17イベント入門
18デリゲートとイベント
18インターフェースの基本

RS-232C関係
1RS-232Cの基礎
2RS-232Cの何が変わった..
3SerialPortクラス
4Unicode(ユニコード)の壁
5マルチスレッドの壁
6RS-232C サンプルコード
7RS-232CのHEXモニタ
8RS-232C 送信モジュール
9RS-232Cのループテスト
10RS-232Cのピンチェンジ..

Socket通信
1C#、VB2005 でSocket通信
2サーバー 複数接続

プロセス間通信
1プロセス間通信(送信側)
2プロセス間通信(受信側)


質問、意見はこちらに
画像ファイル


RichTextBoxとは
「HTML忠太」のHTMLテキストエディターの部分はVisualBasicのRichTextBoxで出来ています。
なぜRichTextBoxなのかというと、理由は2つ有ります。
まづ第一はテキスト文の長さが標準のTextBoxを使うと長すぎて使用できない場合があるからです。
それでは標準のTextBoxはどの位までの長さのテキストが表示可能かというと、 vbの「プログラミングガイド」にいわく
「Visual Basic 32ビット版では、各テキストボックスコントロールのTextプロパティの内容は、
Windows95では最大64KBまで、Windows NTではメモリーの空き容量に制限されています。」
となっています。
VisualBasicのヘルプを見ると。
「テキスト ボックス (TextBox) コントロールの Text プロパティに設定する文字列は、 MultiLine プロパティが真 (True) の場合は最大で約 32KB まで、偽 (False) の場合は最大で 2,048 文字 (半角の場合) までに、それぞれ制限されています。」
いったいどちらが正しいのか? 私のOSはWindows2000だけど最大文字数は幾つなんだろうか?
そこでフォームの上にテキストボックスとラベルを一つずつ置いて次のプログラムを実行してみました。

普通のTextBox最大文字を調べるサンプル
VB6のコード  最大文字を調べるサンプル
Dim t As Long
Dim s As String
s= String(100000, "a") '10万字を入れる
Text1.Text = s
t= Len(Text1.Text)
Label1.Caption = Str(t)  'Text1の文字列の長さをLabelに表示させる
テキストの長さは65535を示した。
次にs = String(100000, "a")の"a"を"亜"につまり一バイト文字から2バイト文字に変えて実行して見た。
その結果やはりテキストの長さは65535を示した。
そうかTextBoxの文字の上限は、半角、全角を問わず65535までなのだ!。
又MulitiLineをTrueにしたり、Falseに変えたりしてみたけれど結果は同じでした。
ちなみに64KBと表現されるのは、
64*1024=65536バイトであるから64KBまでというの64KB未満ということなのでしょう。
本題を離れたけれど、通常のTextBoxを使用しない2つ目の理由は、
テキスト文の一部を色分けできない為である。
「HTML忠太」はHTMLのソースのタグと文章と変数を色分けしている、
普通のTextBoxではこれは出来ないのである。
ところがRichTextBoxを使うと文字数の制限がない(多分?or 2Gbyte? )テキストの色分けが自由に出来る、
こんなうまいものを使わない手はない。
この安易な決断が次から次へと問題を引き起こすのである。

問題その1 遅い
遅いとにかく遅い、RichTexBoxに32KB位のHTMLテキストデーターを読ませて、
そのタグの部分を色分けしてみたのである。
どうやったかというと、
タグ色付け通常のコード
VB6のコード タグ色付け通常のコード
Dim s As String
Dim n As Long
Dim t As Long
s=RichTextBox1.Text
For n = 1 To Len(s)
If Mid(s, n, 1) = "<" Then    '<を見つける
RichTextBox1.SelStart = n   'タグの始め
t = n
ElseIf Mid(s, n, 1) = ">" Then  'タグの終わり
RichTextBox1.SelLength = n - t - 1 'セレクトの長さを決める。
RichTextBox1.SelColor = &HFF '文字を赤
End If
Next n
(注意)HTMLのテキストは改行なら<BR> のように<と>との間に色の指定や様々な命令を書くのである。
つまり<****>の中を選択して色をつけたのである。
そして実行、最初のうちは、ぱらぱらと画面上で実行していたがそのうちに固まってしまったのでは ないかと思う位遅くなるのである。

TextRTFとは
実はRichTextBoxというのはテキストが2重なっているのである。
RichtextBox1.TextというのとRichTextBox1.TextRTF
というのがセットになっているのである、というか、文字の色や大きさフォントの種類等などは RichTextBox1.TextRTFが保持しているのである。
つまり上のソフトは、実はRichTextBox1.TextRTFを書き変えているのである。
「直接TextRTFを書き換えればいいじゃん。」普通はこう考えるのである。
「ところでRichTextってどんなフォーマットなんねん」
そこでフォームの上に2つRichTextBoxを並べて片方にもう一方のTextRIFを書かせたのである。

VB6のコード TextRTFを調べるコード
Dim s As String
Dim n As Long
Dim t As Long
s = "ABCDEFG<bbb>HIJKLMN"
RichTextBox1.Text = s
RichTextBox1.SelStart = 0 'まず全体の文字をBlueにする
RichTextBox1.SelLength = Len(s)
RichTextBox1.SelColor = &HFF0000
For n = 1 To Len(s)
If Mid(s, n, 1) = "<" Then
RichTextBox1.SelStart = n 'タグの始め
 t = n
ElseIf Mid(s, n, 1) = ">" Then
RichTextBox1.SelLength = n - t - 1 'タグの中を赤い文字にする
RichTextBox1.SelColor = &HFF '文字を赤く
End If
Next n
RichTextBox2.Text = RichTextBox1.TextRTF '一番目のRichTextBox1のRTFを書き出す
こんな感じで。
そして表示されたのが次のテキストである。
{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset128 \'82\'6c\'82\'72 \'82\'6f\'83\'53\'83\'56\'83\'62\'83\'4e;}}
{\colortbl ;\red0\green0\blue255;\red255\green0\blue0;}
\viewkind4\uc1\pard\cf1\lang1041\f0\fs18 ABCDEFG <> HIJKLMN\cf0 \par }
よく見てほしい上の赤いところがフォントのカラーテーブルではないかちゃんと\colortblと書いてある。
そして青いところがタグの中すなわち赤く表示したいところである。
"\cf2っつてカラーテーブルの中のカラー番号?"要するに"変えたい文字をカラーテーブルで挟めばいいのである。
色々やってみるとどうも\cf2の後は半角スペースがいるようである。
これで文字の色を直接変える準備は出来た、それではさっきの32KBのテキストの色を変えてみよう。
なんと、さっきは15分(15秒では無いぞ)もかかっていた色変換が5秒くらいで終わったのである。
これではまだ満足ではない5秒は結構長いテキストの編集長うに文字を変えるたびに 5秒もかかっていたのでは仕事にならない。
何でこんなに時間がかかるのか?
TextRTFを直接書き換えているからもうこれはRichTextBoxの問題ではなく文字列操作の問題なのだ。

長い文字列の連結は時間がかかる
更なる時間短縮を目指して、わたしは次の実験をしてみたのである。

時間を計るタイマーを作る
まずVBでフォームを一つ置き精密な時間を計るようにする。
VB6 のコード  API時間の取得DLL
Private Declare Function timeGetTime Lib "winmm.dll" () As Long
と宣言をする。
MSDNのヘルプによると
"The timeGetTime function retrieves the system time, in milliseconds.
The system time is the time elapsed since Windows was started."ということである。
すなわちtimeGetTimeはWindowsが起動してからの時間を1/1000秒単位で返すのである。
Dim Static t1 as Long
Dim Static t2 as Long
t1=timeGetTime '時間の測定のスタート
t2=timeGetTime-t1 '時間測定の終わり
とやれば1/1000秒単位で時間が測定できるのである。
この関数は便利で私は次のような関数を作って使用している。
VB6のコード  WaitTimer
Private Sub TimerWaitTimer(ByVal WaitTime As Long)
Dim t1 As Long
t1 = timeGetTime
Do
DoEvents
Loop While (timeGetTime < (t1 + waittime))
End Sub
この関数は引数の時間だけプログラムの実行を待つものである。
1秒以下の場合は DoEventsはいらないでしょう。
さてこのAPIを使って時間を測定してみる。

文字連結の時間測定
VB6 のコード  文字列の連結時間を計る
Dim s1 As String
Dim s2 As String
Dim t1 As Long
Dim t2 As Long
s1 = String(100000, "a")
t1 = timeGetTime
For n = 1 To Len(s1)
s2 = s2 + Mid(s1, n, 1)
Next n
Label1.Caption = Str(timeGetTime - t1) 'かかった時間を表示する
こんなプログラムを作ってみた、10万個の"a"を頭から一つずつ切り出してs2に足していくだけのプログラムである。
時間はLabe1に表示される、結果は23.063秒である。

文字の長さによる処理時間の違い
そこでフォームに更にリストボックスを置き1000個切り出すたびに時間をリストボックスに書かせてみた。
VB6のコード  時間表示コード
Dim s1 As String
Dim s2 As String
Dim t1 As Long
Dim t2 As Long
s1 = String(100000, "a")
t1 = timeGetTime
For n = 1 To Len(s1)
s2 = s2 + Mid(s1, n, 1)
If n \ 1000 = n / 1000 Then '1000の倍数の時一致する
List1.AddItem Str(timeGetTime - t1) 'リストに時間を表示
t1 = timeGetTime
End If
Next n
結果は、始めのうちは10/1000秒位であるが最後のほうは516/1000かかっている、
つまり文字列が長くなればなるほど操作に時間がかかるのである。

長い文字は分割して処理する
文字が長くなれば長くなるほど処理時間がかかる事が解った。
それでは長い文字列は分割して処理したら良いのではないか。
VB6のコード 改良板
Dim s1 As String
Dim s2 As String
Dim s3 As String
Dim t1 As Long
Dim t2 As Long
s1 = String(100000, "a")
t1 = timeGetTime
For n = 1 To Len(s1)
s3 = s3 + Mid(s1, n, 1)
If n \ 1000 = n / 1000 Then '1000回に一回書きこむ
s2 = s2 + s3
s3 = ""
End If
Next n
s2 = s2 + s3
Label1.Caption = Str(timeGetTime - t1)
つまりs3という文字列を用意してs1からs3に一文字ずつ切り出しs3が1000文字になったら、
s2に足しこむのである、こうすると長い文字列の操作は1000回に一回で、
後は短い文字列の操作になるのである。
そして結果は、なんと266/1000秒である始めの1/86の時間である。
15分位かかった処理がたった0.2秒である。恐ろしきかな "藤吉郎の割り普請"
これなら使えると思ったのである。しかしこの喜びが次トラブルの布石とは...

VB6のコード 問題その2 RichText.Textが化ける
(注意)
(この記事は以前に書かれたものを多少手直しして載せてあります、以下のトラブルは、
最新のサービスパックをあてて以来発生していないようです。)
RichTextの処理速度が向上した喜もつかの間、次の問題が起こった。
長いテキストで終わりの方のタグを選択して書き換えたり、消したり、
又文字を挿入したりすると文字の場所がずれるのである。たとえば
 <aaa>というタグをRichText.Textの中から探して<bbb>に書き換えようとする。
VB6のコード InStr関数サンプルコード
RichTextBox1.SelStart = InStr(RichTextBox1.Text, "<aaa>")  '<aaa>を探す
RichTextBox1.SelLength = Len("<aaa>")
RichTextBox1.SelText = "<bbb>" '<bbb>に置き換える
こんな具合に、InStr(s1,s2)はs1の中からs2を探し最初の番号を返す関数である。
テキストが短かったり <aaa>が最初の方に有るうちは正確に<bbb>に書き換わるのであるが、
テキストが長くなったり、<aaa>が終わりの方にある場合は置き換わる文字が変わってしまうのである。
しかも必ず変えたい文字より後の文字が置き換わるのである。
「途中に何か違う文字が挿入されている」そんな感じである。
試しに普通のTextBoxを一つ置いて、
VB6のコード 原因を調べるコード
Text1.Text = RichTextBox1.Text
Label1.Caption = Str(Len(Text1.Text))
Label2.Caption = Str(Len(RichTextBox1.Text))
とやってみたつまり、RichText1.textをText1.textに代入してその長さを比べたのである。
驚いたことに長さが違うのである。"使えんジャン" 思わず叫んでしまった。
そしてこれからが大変である、どこに何が挿入されているか探すのである。
テキストを短くすると無くなってしまうから始末に悪いのである。
苦心惨憺色々試してみるうちに、CHR(13)が勝手に挿入されていることが判った。
それも CHR(13),CHR(10)つまりキャリッジリターンの前にCHR(13)が追加されるのである。
なぜか判らない結果的に追加されるのである。
つまり CHR(13),CHR(13),CHR(10)となってしまうのである。
そこで
VB6のコード とりあえず対策方法のコード
Do
t1 = InStr(RichTextBox1.Text, Chr(13) & Chr(13))
If t1 = 0 Then
Exit Do
End If
RichTextBox1.SelStart = t1
RichTextBox1.SelLength = 1
RichTextBox1.SelText = ""
Loop
としたのである、つまりChr(13)が連続したら最初のChr(13)を消したのである。
このプログラムでInstrが間違わないのは、頭からChr(13)の連続を消している為である。
この処理を所々に挿入することによってようやく正しい置き換えの処理が可能となったのである。

再現出来るバグ
最後に誰にでも再現出来るRichTextBoxのバグを一つ。
RichTextBoxを一つ置いて次のコードを実行してみて欲しい。
VB6のコード RichTextBoxの誤動作するプログラム
'誤動作するプログラム
RichTextBox1.Text = Chr(-32386) & vbCrLf & "abcdef"
RichTextBox1.SelStart = 3
Debug.Print RichTextBox1.SelStart
RichTextBox1.SelLength = 1
Debug.Print RichTextBox1.SelLength
RichTextBox1.SetFocus
私は『a』が選択されることを期待した、ところが選択された文字はなんと、
『bc』である、また吐き出された数字は4と2であった。
これは、私の確認した範囲ではChr(-32387)でも発生する、この文字を『あ』や
『a』等に変えると発生しない。
SelStart とSelLengthが狂ってしまうので、運が悪いとメモリーエラーとなり
プログラムがターミネートされるのである。
Chr(-32386)は全角の『×』で有り、Chr(-32387)は『±』である、RichTextBoxの中に
一つでもこの文字があれば、これ以降の RichTextBox1.SelStartは全て違う場所を指すことになる。
この不具合は、VB.NET VB2005では解消されている。

訪問者数カウンター

画像ファイル