Link to home
Start Free TrialLog in
Avatar of MasamuneXGP
MasamuneXGP

asked on

RichEd20 Problem: RTF Text insertion too slow

Hello all.  I'm currently writing a code editor that I'm trying to make as close to the VB IDE as possible.  One thing it will have is colored keywords, comments, and strings.  I already have a function that goes through the text, determines what needs to be colored, and spits out the appropriate RTF code.  For my tests, I'm using 5000 lines, each with 4 color words.  The line checking goes through all of them and returns one big string containing all 5000 lines with an RTF header and tags in the appropriate places.

Here's were the bottleneck happens.  I can't seem to get this RTF text into the box without it taking WAY too long.  At first I was using the standard RichTextBox control and inserting via .SelRTF.  Later, I ditched that for vbAccelerator's RichEdit control and tried using the EM_STREAMIN method.  The gain was minimal at best.  The single line that inserts the text takes approximately 19 seconds to execute on my P4 1.7GHz machine.  This is unacceptable.

VB's IDE is able to color the same 5000 lines in the blink of an eye... There has to be a faster way.  Does anyone know how I can speed this up?
Avatar of ameba
ameba
Flag of Croatia image

Hi MasamuneXGP,
> The line checking goes through all of them and returns one big string
> containing all 5000 lines with an RTF header and tags in the appropriate places.

Concatenation in VB isn't optimized, producing a string using '&' will be very slow.  There are few solutions for faster string operations, e.g. there is a StringBuilder class by Karl E. Peterson, which uses byte array internally, but for your purpose Join() will work:

    S2RTF = rtfhdr & Join(sLines(), vbCrLf & "\par ") & rtftail

where sLines() is array containing rtf code for each line.
That should work well for cases when editor is opening a file, or user is pasting a big block to editor.

Cheers!
Avatar of MasamuneXGP
MasamuneXGP

ASKER

Erm... let me clarify.  It is not my function that is lagging.  I am well aware of the & operator's suckiness, and am using some pretty advanced techniques to generate the string quickly (the SafeArray hack for example).  The lagging comes when I try to actually insert the text.  It is this one line, where RTFBuild is the variable containing the string:

(VB's Rich TextBox)
.SelRTF = RTFBuild

(vbAccel's RichEdit)
.InsertContents SF_RTF, RTFBuild

Both of those take upwards of 19 seconds to execute.  Hense my problem.
I apologize for doubleposting, but this may be relevent.  Here is what RTFBuild looks like before I insert it.  Keep in mind it's just for testing purposes, so don't expect it to make sense or anything =p  As you can see, each line has four color words in it, and that's definately what's slowing it down.  Removing the color words has the text inserted much quicker (although still a tad laggy).  I know my test is a little above the standard, but I'd really not like to compromise, as I do expect large amounts of code to be entered into it.  I am a bit of a performance freak, and it bugs me to see the rest of my program so heavily optimized but slowed down by this one huge bottleneck... Anyway, here it is:

{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Courier New;}}
{\colortbl ;\red0\green0\blue128;\red0\green128\blue0;\red128\green0\blue0;\red255\green0\blue0;}
\viewkind4\uc1\pard\cf0\lang1033\f0\fs20
\cf1 For\cf0  x = 2 + 1 \cf1 To\cf0  Len(\cf3 "Testing"\cf0 ) - 1 ^ 2 \cf1 Mod\cf0  1 + 2\cf2  'Comment\cf0
\par \cf1 For\cf0  x = 2 + 1 \cf1 To\cf0  Len(\cf3 "Testing"\cf0 ) - 1 ^ 2 \cf1 Mod\cf0  1 + 2\cf2  'Comment\cf0
\par \cf1 For\cf0  x = 2 + 1 \cf1 To\cf0  Len(\cf3 "Testing"\cf0 ) - 1 ^ 2 \cf1 Mod\cf0  1 + 2\cf2  'Comment\cf0
\par \cf1 For\cf0  x = 2 + 1 \cf1 To\cf0  Len(\cf3 "Testing"\cf0 ) - 1 ^ 2 \cf1 Mod\cf0  1 + 2\cf2  'Comment\cf0
....5000 of these....
\par \cf1 For\cf0  x = 2 + 1 \cf1 To\cf0  Len(\cf3 "Testing"\cf0 ) - 1 ^ 2 \cf1 Mod\cf0  1 + 2\cf2  'Comment\cf0
\par \cf1 For\cf0  x = 2 + 1 \cf1 To\cf0  Len(\cf3 "Testing"\cf0 ) - 1 ^ 2 \cf1 Mod\cf0  1 + 2\cf2  'Comment\cf0
}
If you are opening file, you'll use .TextRTF not .SelRtf; you shouldn't use multiple .SelRtf calls to insert multiple lines.
Any insertion will be followed by:
- internal optimization/reduction of rtf code - to avoid this, plan everything, no duplicate colors etc.
- formatting - it depends on right margin property and length (line length) of inserted text.  Make sure there is no wrapping (like in VB code editor).
To check if there was some internal optimization check rtf code after the insertion - if cf5 is cf4, control did something.
Maybe you have code in SelChange or Change event which is executed during insertion. Use flags to exit those events:
    ' set flags
    bIgnoreInChange = True
    bIgnoreInSelChange = True
    resetundo
   
    rtbText.TextRTF = S2RTF(x, True)
   
    ' reset flags
    bIgnoreInChange = False
    bIgnoreInSelChange = False
As you can see from my last post, I am indeed inserting them all in one fell swoop and with the best color optimization.  And I have the RightMargin prop set to the max possible value, so word wrapping isn't an issue either.
Try TextRtf it should be faster than SelRtf.
Alrighty, upon analyzing the RTF code output, it appears that my color tags are indeed being reordered.  After much tweaking, I finally got the expression (RTFBuild = .TextRTF) to equal True.  The end result: it saved me about 2.5 seconds.  So now we're down to around 16 seconds instead of 19.  Yay~~ =p

That event locking thing I am already doing, and I am using LockWindowUpdate to prevent it to refreshing as well.  Experimenting shows .TextRTF to be no faster than .SelRTF.  Since it appears this question isn't as easy to answer as I was hoping it would be, this is now 500 point question.
' Form1, add MS RichTextbox, do not change any property
Option Explicit
Private Declare Function GetTickCount Lib "kernel32" () As Long

Private Sub Form_Click()
    Dim hdr As String, sLines() As String, tail As String, i As Long, tim0 As Long
   
    ' start timer
    tim0 = GetTickCount
   
    hdr = "{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Courier New;}}" & vbCrLf _
        & "{\colortbl ;\red0\green0\blue128;\red0\green128\blue0;\red128\green0\blue0;\red255\green0\blue0;}" & vbCrLf _
        & "\viewkind4\uc1\pard\cf0\lang1033\f0\fs20" & vbCrLf
    tail = "}"
   
    ' fill array of lines
    ReDim sLines(0 To 5000)
    For i = 0 To UBound(sLines)
        sLines(i) = "\cf1 For\cf0  x = 2 + 1 \cf1 To\cf0  Len(\cf3 ""Testing""\cf0 ) - 1 ^ 2 \cf1 Mod\cf0  1 + 2\cf2  'Comment\cf0"
    Next
   
    Me.RichTextBox1.RightMargin = 60000
    Me.RichTextBox1.TextRTF = hdr & Join(sLines, vbCrLf & "\par ") & tail
   
    ' show time
    Caption = (GetTickCount - tim0) / 1000 & " seconds, length=" & Len(Me.RichTextBox1.Text)
End Sub
I started a new project, added an RTB, copied and pasted this exact code, compiled, ran, clicked the form.  After finishing, the titlebar read "17.172 seconds, length=300058"

Have you tried this code on your own system?  If so what processer speed do you have?
0.484 seconds on old 666 MHz Celeron, MS Rich Textbox 6.0 (SP4). If I increase window size, or change some properties I can make it 2-3 times slower.  I'll put a temporary zip with .exe http://www.geocities.com/ameba_vb/temp/rtftest/rtftest.html
Maybe you can try different header:
    hdr = "{\rtf1\ansi\deff0\deftab480{\fonttbl{\f0\fnil MS Sans Serif;}{\f1\fswiss\fcharset238 Fixedsys;}}" & _
        "{\colortbl\red0\green0\blue0;\red192\green192\blue192;\red0\green143\blue0;\red255\green0\blue0;\red0\green0\blue127;\red170\green0\blue0;}" & vbCrLf _
        & "\deflang1050\pard\plain\f1\fs18 "
It takes 17 seconds on my P4 1.7GHz machine, 11 seconds on my P4 2.4GHz machine, and 48 seconds on my PIII 650MHz laptop.

Seeing as I am also using MS Rich Textbox 6.0 (SP4), I guess it must be something to do with file versions....  Would you mind sending me whatever copy of RichTx32.ocx and RichEd20.dll you have on that 666 Mhz machine?
Tried the new header.  Font was different, time was the same =\
ASKER CERTIFIED SOLUTION
Avatar of ameba
ameba
Flag of Croatia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
You can find info on versions installed (to system32 directory) at http://support.microsoft.com/default.aspx?scid=/servicedesks/fileversion/dllinfo.asp&SD=MSDN&FR=0
type dll name: RichEd20.dll
*GASP* It worked!  It was indeed RichEd20.dll that was slowing it down.  The confusing part is... the RichEd20.dll in my sys32 folder is actually a newer version than the one you sent me... but who cares, I'm just happy it finally works!  One last question though: I'm going to be distributing this program.  Should I just have the installer place this copy of RichEd20.dll in the installation folder?  Or does it have to be regsvr32'd or something?
I suggest placing to installation directory - that's what installer does.
If you replaced newer version with older one in your System dir, that might not be good idea.  Maybe MS has the reason, e.g. it is more secure, or something.
If you are sure your dll was debug version for some beta product, I guess manually removing it and replacing/registering is OK.
Thank you very much for your help ameba!  Enjoy your 500 points =)
Thanks :-)