RichTextBox repainting artifacts

G

Guest

I have created a Control that extends RichTextBox to do syntax-hilighting. My
strategy is to have a timer that restarts every time a user types a key so
that it can wait until the user pauses for at least 500 milliseconds, then
perform its work. The hilighting simply goes through a list of keywords,
finding each instance in the RichTextBox, and selecting it in order to change
its color. I figured out how to remember where the cursor is and put it back
where the user left it. I even bracketed all my activity with a
SuspendLayout/ResumeLayout. But I cannot get around my problem: the RTB has
an annoying flash/scroll/jump each time my hilighting occurs (if there are
more lines in the box than the height of the box). How can I avoid this?

Here is the code basics:
==================================
private void SyntaxHilightTextBox_TextChanged(object sender, EventArgs e)
{
if (timer.Tag.Equals(1)) { return; }
if (timer.Enabled) { timer.Stop(); }
timer.Start(); // restart it
}

private void timer_Tick(object sender, EventArgs e)
{
timer.Stop();
timer.Tag = 1; // disable TextChanged so our changes are transparent
SuspendLayout();
Colorize();
ResumeLayout();
timer.Tag = 0; // re-enable TextChanged
}

private void Colorize()
{
int saveCaret = CaretPosition();

// Restore all text to black.
// Prevents terminal hilighting from bleeding into new typing.
SelectAll();
SelectionColor = Color.Black;

ColorWords(myWordList);

SelectionStart = saveCaret; // restore caret
SelectionLength = 0;
}

public void ColorWords(string[] text)
{
foreach (string s in text) { ColorWord(s); }
}

public void ColorWord(string text)
{
RichTextBoxFinds options =
(RichTextBoxFinds.WholeWord | RichTextBoxFinds.NoHighlight);
int start = Find(text, options);
bool done = (start < 0);
while (!done)
{
SelectionStart = start;
SelectionLength = text.Length;
SelectionColor = hilightColor;
int lastStart = start;
start = Find(text, start + text.Length, options);
done = (start <= lastStart);
}
}

==================================
 
G

Guest

Additional note:
If I disable the auto-hilight and put it on a button, the button works great
(i.e. no annoying flash/scroll). Is this possibly because the timer tick
occurs on a different thread? I tried adding some debug lines to identify
thread differences, but if I instrumented that correctly it is on the same,
main thread. Any suggestions from this point?
 
L

Linda Liu [MSFT]

Hi Michael,

I performed a test based on your sample code and did see the problem on my
side. But I don't think the problem is caused by the timer tick occurring
on a thread other than the UI thread. It is UI thread that executes the
timer's Tick event handler when the timer's Tick event occurs.

I think the reason of the problem is that you're attempting to
select/unselect the text in the RichTextBox while it gets focused, which
causes the window flash/scroll.

I suggest that you remove the timer in your project and only check the
words near the current input cursor to see if they match the key words, if
yes, select them and change their color. In this way, you needn't always
re-check ALL the text and re-hilight those key words.

The following is a sample. It requires that you add a RichTextBox control
on the form.

public partial class Form1 : Form
{
string[] myWordList = new string[] { "the", "a", "an" };
Color hilightColor = Color.Orange;

public Form2()
{
InitializeComponent();
this.richTextBox1.TextChanged += new
EventHandler(richTextBox1_TextChanged);
}

void richTextBox1_TextChanged(object sender, EventArgs e)
{
if (this.richTextBox1.Text.Trim() != "")
{
if (this.GetCharBeforeSelectionStart() == 0)
{
this.ProcessNextWord(this.richTextBox1.SelectionStart);
}
if (this.GetCharBeforeSelectionStart() == ' ' ||
this.GetCharBeforeSelectionStart() == '\n')
{
this.ProcessPreWord(this.richTextBox1.SelectionStart);
this.ProcessNextWord(this.richTextBox1.SelectionStart);
}
else
{

this.ProcessCurrentWord(this.richTextBox1.SelectionStart);
}
}
}

private char GetCharBeforeSelectionStart()
{
if (this.richTextBox1.SelectionStart == 0)
{
return '0';
}
else
{
return
this.richTextBox1.Text[this.richTextBox1.SelectionStart - 1];
}
}
private bool IsMatchKeyword(string text)
{
int i = 0;
for (; i < myWordList.Length; i++)
{
if (myWordList == text)
{
break;
}
}
if (i < myWordList.Length)
return true;
else
return false;
}

private void ProcessCurrentWord(int carsetIndex)
{
int wordstart = carsetIndex -1;
while ((wordstart - 1 >= 0) &&
(this.richTextBox1.Text[wordstart - 1] != ' ' &&
this.richTextBox1.Text[wordstart-1]!='\n'))
{
wordstart--;
}
int wordend = carsetIndex - 1;
while ((wordend + 1 < this.richTextBox1.TextLength) &&
this.richTextBox1.Text[wordend + 1] != ' ' &&
this.richTextBox1.Text[wordend +1] != '\n')
{
wordend++;
}
string wordtext = this.richTextBox1.Text.Substring(wordstart,
wordend - wordstart + 1);
this.richTextBox1.SelectionStart = wordstart;
this.richTextBox1.SelectionLength = wordend - wordstart + 1;

if (IsMatchKeyword(wordtext))
{
this.richTextBox1.SelectionColor = this.hilightColor;
}
else
{
this.richTextBox1.SelectionColor =
this.richTextBox1.ForeColor;
}
this.richTextBox1.SelectionStart = carsetIndex;
this.richTextBox1.SelectionLength = 0;
}

private void ProcessPreWord(int carsetIndex)
{
int wordend = carsetIndex -1;
while (wordend >= 0 && (this.richTextBox1.Text[wordend] == ' '
|| this.richTextBox1.Text[wordend] == '\n'))
{
wordend--;
}
int wordstart = wordend;
if (wordstart > 0)
{
while ((wordstart - 1 >= 0) &&
this.richTextBox1.Text[wordstart-1] != ' ' &&
this.richTextBox1.Text[wordstart-1]!='\n')
{
wordstart--;
}
}
if (wordstart >= 0)
{
this.richTextBox1.SelectionStart = wordstart;
this.richTextBox1.SelectionLength = wordend - wordstart + 1;

string wordtext =
this.richTextBox1.Text.Substring(wordstart, wordend - wordstart + 1);
if (IsMatchKeyword(wordtext))
{
this.richTextBox1.SelectionColor = this.hilightColor;
}
else
{
this.richTextBox1.SelectionColor =
this.richTextBox1.ForeColor;
}
this.richTextBox1.SelectionStart = carsetIndex;
this.richTextBox1.SelectionLength = 0;
}
}

private void ProcessNextWord(int carsetIndex)
{
int wordstart = carsetIndex -1;
while ((wordstart < this.richTextBox1.TextLength) &&
(this.richTextBox1.Text[wordstart] == ' ' ||
this.richTextBox1.Text[wordstart]=='\n'))
{
wordstart++;
}
int wordend = wordstart;
if (wordend < this.richTextBox1.TextLength - 1)
{
while (wordend + 1 < this.richTextBox1.TextLength &&
this.richTextBox1.Text[wordend +1]!= ' '&&
this.richTextBox1.Text[wordend+1]!='\n')
{
wordend++;
}
}
if (wordstart < this.richTextBox1.TextLength)
{
this.richTextBox1.SelectionStart = wordstart;
this.richTextBox1.SelectionLength = wordend - wordstart + 1;
string wordtext = this.richTextBox1.Text.Substring
(wordstart,wordend - wordstart +1);
if (IsMatchKeyword(wordtext))
{
this.richTextBox1.SelectionColor = this.hilightColor;
}
else
{
this.richTextBox1.SelectionColor =
this.richTextBox1.ForeColor;
}
this.richTextBox1.SelectionStart = carsetIndex;
this.richTextBox1.SelectionLength = 0;
}
}
}

Hope this helps.
If you have anything unclear, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
G

Guest

While I appreciate your industriousness in providing a good bit of code, it
seems like a lot of code to workaround a simple repaint issue. Also, it will
handle typing but I do not think it could deal with cut-and-paste very well.

I do agree that it is not a thread issue and that it is related to the
selection/focus issue. So is there not a simple way to suspend the UI
activity while doing some processing? That is what I was hoping the
SuspendLayout/ResumeLayout bracketing would do, but it did not.
 
L

Linda Liu [MSFT]

Hi Michael,

Thank you for your reply.

Yes, you're right that my code doesn't work well when the user pastes a
block of text into the RichTextBox.

The key point of your solution is that the RichTextBox get focused when
you're trying to select its text. So a better way solve the problem is to
make the RichTextBox lose the keyboard focus before we're trying to select
the text and restore the focus after we finish the task, based on your
original code.

To make a control lose focus, we could use the Win32API function
'SendMessage' to to send a 'WM_KILLFOCUS' message to the RichTextBox.

The following is a sample.

using System.Runtime.InteropServices;
class API
{
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd,uint msg, IntPtr
wParam, IntPtr lParam);
}

uint WM_KILLFOCUS = 0x0008;
// this is your original method
void timer1_Tick(object sender, EventArgs e)
{
timer1.Stop();
timer1.Tag = 1; // disable TextChanged so our changes are
transparent
// SuspendLayout();
API.SendMessage(this.richTextBox1.Handle, WM_KILLFOCUS,
IntPtr.Zero, IntPtr.Zero);

Colorize();
// note that we need to get the form focused first
this.Focus();
this.richTextBox1.Focus();

// ResumeLayout();
timer1.Tag = 0; // re-enable TextChanged
}

Please try my suggestion and let me know the result.

Sincerely,
Linda Liu
Microsoft Online Community Support
 
G

Guest

That works great--thank you!!

Just a couple followup questions (marginally related to your solution
details):

(1) I had already been using SendMessage in the same class for another
purpose with this signature:
private static extern IntPtr
SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);

That differs subtlely from the signature you provided:
public static extern int SendMessage(IntPtr hWnd,uint msg, IntPtr
wParam, IntPtr lParam);

However, I was able to alter the arguments you provided to fit my existing
signature and it worked fine:
const int WM_KILLFOCUS = 0x0008;
HandleRef hr = new HandleRef(this, base.Handle);
SendMessage(hr, WM_KILLFOCUS, 0, 0);

My question: Is one of these SendMessage signatures more "correct" ? My
knowledge of the Win32API is very minimal.

(2) Could you provide a URL to the Win32API documentation root? In the
future when I want to tinker with a control I would like to be able to dig
through the documentation to see if there is a command to do what I might
need.
 
L

Linda Liu [MSFT]

Hi Michael,

Thank you for your prompt feedback.

The parameters of the SendMessage function may differ for different Windows
message it is going to send. It is OK to use your existing signature of
SendMessage to send the WM_KILLFOCUS message.

MSDN has provided a full reference for Win32API. You may visit the
following link for it:

'Windows API'
http://msdn2.microsoft.com/en-us/library/aa383750.aspx

Hope this helps.
If you have any question, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support
 
L

Linda Liu [MSFT]

Hi Michael,

How about the problem now?

If you have anything unclear, please feel free to let me know.

Thank you for using our MSDN Managed Newsgroup Support Service!

Sincerely,
Linda Liu
Microsoft Online Community Support
 
G

Guest

As I indicated on my 4/18 post, your information at that point solved my
problem (and I marked your post with a checkmark indicating it answered the
question).
The follow-up questions were just for future reference.

Thanks again.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top