VCL richedit,改变文字颜色很慢
VCL richedit, slow to change word colors
我在 delphi 程序(基于 VCL 的桌面应用程序)中有以下代码来遍历 richedit 中的文本行(大约 8-15 个单词之间的句子),找到所选用户的实例单词,然后为该单词 'red' 上色,如果它出现在一条线上。
问题:如果程序必须处理超过几千行,则颜色更改进行得非常缓慢(几分钟过去了)。当光标四处乱舞时,我就坐在这里了。这是造成延迟的过程:
procedure Color_Words(RE: TRichEdit; Word: String; Color: TColor);
var
i, startPos, CharPos2, nosChars: Integer;
begin
startPos := 0;
nosChars := 0;
charpos2:=0;
RE.lines.beginupdate;
for i := 0 to Pred(RE.Lines.Count) do
begin
nosChars := nosChars + Length(RE.Lines[i]);
CharPos2 := RE.FindText(word, startPos,nosChars,stmatchcase]);
startPos := CharPos2+1;
RE.SelStart := CharPos2;
RE.SelLength :=(Length(word));
RE.SelAttributes.Color := Color;
end;
RE.Lines.EndUpdate;
end;
有人可以想出一个非常非常快的程序,或者告诉我如何解决问题吗?另外,如果您能用通俗易懂的语言解释缓慢的处理过程,那就太好了。 (我只是一个爱好者)。
首先要做的是更改您的代码以使用 RichEdit 控件的 4.1 版(在 Windows XP SP1 中引入),仅此一项就可以加快速度。
"RichEdit20W"
: Riched20.dll (Windows 98)
"RICHEDIT50W"
: Msftedit.dll (Windows XP SP1)
Windows继续支持老版本的RichEdit控件,但是Delphi固执地继续使用老版本,具体见Vcl.ComCtrls.pas
:
procedure TCustomRichEdit.CreateParams(var Params: TCreateParams);
const
RichEditClassName = 'RICHEDIT20W';
begin
inherited CreateParams(Params);
CreateSubClass(Params, RichEditClassName); //<-- 'RICHEDIT20W'
//...
end;
告诉Delphi使用WindowsXP时代的RichEdit 4.1
有几种方法可以解决这个问题;最少干扰的是创建一个新单元:
MicrosoftEdit.pas
unit MicrosoftEdit;
interface
uses
Vcl.ComCtrls, Winapi.RichEdit, Vcl.Controls, Winapi.Windows, System.Classes;
type
TRichEdit = class(Vcl.ComCtrls.TRichEdit)
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
{ TMicrosoftEdit }
procedure TRichEdit.CreateParams(var Params: TCreateParams);
const
MSFTEDIT_CLASS = 'RICHEDIT50W'; //Richedit 4.1, Msftedit.dll
begin
LoadLibrary('msftedit.dll');
inherited CreateParams({var}Params);
CreateSubClass({var}Params, MSFTEDIT_CLASS); //"RICHEDIT50W"
end;
end.
然后将 MicrosoftEdit.pas
作为 last 单元包含在表单的 uses 的 interface 部分 子句。您甚至可以通过重新声明 TRichEdit
为您的新 TRichEdit
:
来加倍确定它是否有效
unit MyForm;
uses
Forms, RichEdit, MicrosoftEdit;
type
TRichEdit = MicrosoftEdit.TRichEdit; //use our own TRichEdit
TMyForm = class(TForm)
RichEdit1: TRichEdit;
private
protected
public
end;
//...
OnChange?
如果您要对 RichEdit 中的文本进行格式更改:
procedure TMyForm.Button1Click(Sender: TObject);
begin
Color_Words(RichEdit1, 'Trump', clRed);
end;
并且您有一个附加到 RichEdit 的 OnChange
处理程序,它会在每次格式更改时触发 OnChange
。你需要阻止它:
procedure TMyForm.Button1Click(Sender: TObject);
var
oldOnChange: TNotifyEvent;
begin
oldOnChange := RichEdit1.OnChange;
RichEdit1.OnChange := nil;
try
Color_Words(RichEdit1, 'Trump', clRed);
finally
RichEdit1.OnChange := oldOnChange;
end;
end;
撤销
此外,您所做的每一次颜色更改都会记录在撤消列表中!以及每次 RichEdit 重绘。停止那些:
procedure TMyForm.Button1Click(Sender: TObject);
var
oldOnChange: TNotifyEvent;
begin
oldOnChange := RichEdit1.OnChange;
RichEdit1.OnChange := nil;
try
RichEditSuspendAll(RichEdit1, True);
try
Color_Words(RichEdit1, 'Trump', clRed);
finally
RichEditSuspendAll(RichEdit1, False);
end;
finally
RichEdit1.OnChange := oldOnChange;
end;
end;
有辅助函数:
procedure RichEditSuspendAll(ARichEdit: TRichEdit; bSuspend: Boolean);
var
doc: ITextDocument;
re: IUnknown;
begin
{
http://bcbjournal.org/articles/vol3/9910/Faster_rich_edit_syntax_highlighting.htm
int eventMask = ::SendMessage(RichEdit1->Handle, EM_SETEVENTMASK, 0, 0);
SendMessage(RichEdit1->Handle, WM_SETREDRAW, false, 0);
ParseAllText(RichEdit1);
SendMessage(RichEdit1->Handle, WM_SETREDRAW, true, 0);
InvalidateRect(RichEdit1->Handle, 0, true);
SendMessage(RichEdit1->Handle, EM_SETEVENTMASK, 0, eventMask);
}
{
http://support.microsoft.com/KB/199852
How To Suspend and Resume the Undo Functionality in Richedit 3.0
If it is necessary to Undo an action that is performed before a suspend, after resuming the Undo, then,
tomFalse must be replaced with "tomSuspend" and tomTrue must be replaced with "tomResume".
This method retains the contents of the Undo buffer even when Undo is suspended.
Applications can retrieve an ITextDocument pointer from a rich edit control.
To do this, send an EM_GETOLEINTERFACE message to retrieve an IRichEditOle
object from a rich edit control. Then, call the object's
IUnknown::QueryInterface method to retrieve an ITextDocument pointer.
}
if ARichEdit = nil then
raise Exception.Create('ARichEdit is nil');
if SendMessage(ARichEdit.Handle, EM_GETOLEINTERFACE, 0, LPARAM(@re)) = 0 then
raise Exception.Create('Could not get OleInterface from RichEdit');
doc := re as ITextDocument;
doc := RichEditGetTextDocument(ARichEdit);
if bSuspend then
begin
RichEdit.Perform(WM_SETREDRAW, 0, 0); //disable all painting of the control
doc.Undo(Integer(tomSuspend)); // Suspends Undo.
end
else
begin
doc.Undo(Integer(tomResume)); // Resumes Undo.
RichEdit.Perform(WM_SETREDRAW, 0, 0); //disable all painting of the control
end;
end;
我在 delphi 程序(基于 VCL 的桌面应用程序)中有以下代码来遍历 richedit 中的文本行(大约 8-15 个单词之间的句子),找到所选用户的实例单词,然后为该单词 'red' 上色,如果它出现在一条线上。 问题:如果程序必须处理超过几千行,则颜色更改进行得非常缓慢(几分钟过去了)。当光标四处乱舞时,我就坐在这里了。这是造成延迟的过程:
procedure Color_Words(RE: TRichEdit; Word: String; Color: TColor);
var
i, startPos, CharPos2, nosChars: Integer;
begin
startPos := 0;
nosChars := 0;
charpos2:=0;
RE.lines.beginupdate;
for i := 0 to Pred(RE.Lines.Count) do
begin
nosChars := nosChars + Length(RE.Lines[i]);
CharPos2 := RE.FindText(word, startPos,nosChars,stmatchcase]);
startPos := CharPos2+1;
RE.SelStart := CharPos2;
RE.SelLength :=(Length(word));
RE.SelAttributes.Color := Color;
end;
RE.Lines.EndUpdate;
end;
有人可以想出一个非常非常快的程序,或者告诉我如何解决问题吗?另外,如果您能用通俗易懂的语言解释缓慢的处理过程,那就太好了。 (我只是一个爱好者)。
首先要做的是更改您的代码以使用 RichEdit 控件的 4.1 版(在 Windows XP SP1 中引入),仅此一项就可以加快速度。
"RichEdit20W"
: Riched20.dll (Windows 98)"RICHEDIT50W"
: Msftedit.dll (Windows XP SP1)
Windows继续支持老版本的RichEdit控件,但是Delphi固执地继续使用老版本,具体见Vcl.ComCtrls.pas
:
procedure TCustomRichEdit.CreateParams(var Params: TCreateParams);
const
RichEditClassName = 'RICHEDIT20W';
begin
inherited CreateParams(Params);
CreateSubClass(Params, RichEditClassName); //<-- 'RICHEDIT20W'
//...
end;
告诉Delphi使用WindowsXP时代的RichEdit 4.1
有几种方法可以解决这个问题;最少干扰的是创建一个新单元:
MicrosoftEdit.pas
unit MicrosoftEdit;
interface
uses
Vcl.ComCtrls, Winapi.RichEdit, Vcl.Controls, Winapi.Windows, System.Classes;
type
TRichEdit = class(Vcl.ComCtrls.TRichEdit)
protected
procedure CreateParams(var Params: TCreateParams); override;
end;
implementation
{ TMicrosoftEdit }
procedure TRichEdit.CreateParams(var Params: TCreateParams);
const
MSFTEDIT_CLASS = 'RICHEDIT50W'; //Richedit 4.1, Msftedit.dll
begin
LoadLibrary('msftedit.dll');
inherited CreateParams({var}Params);
CreateSubClass({var}Params, MSFTEDIT_CLASS); //"RICHEDIT50W"
end;
end.
然后将 MicrosoftEdit.pas
作为 last 单元包含在表单的 uses 的 interface 部分 子句。您甚至可以通过重新声明 TRichEdit
为您的新 TRichEdit
:
unit MyForm;
uses
Forms, RichEdit, MicrosoftEdit;
type
TRichEdit = MicrosoftEdit.TRichEdit; //use our own TRichEdit
TMyForm = class(TForm)
RichEdit1: TRichEdit;
private
protected
public
end;
//...
OnChange?
如果您要对 RichEdit 中的文本进行格式更改:
procedure TMyForm.Button1Click(Sender: TObject);
begin
Color_Words(RichEdit1, 'Trump', clRed);
end;
并且您有一个附加到 RichEdit 的 OnChange
处理程序,它会在每次格式更改时触发 OnChange
。你需要阻止它:
procedure TMyForm.Button1Click(Sender: TObject);
var
oldOnChange: TNotifyEvent;
begin
oldOnChange := RichEdit1.OnChange;
RichEdit1.OnChange := nil;
try
Color_Words(RichEdit1, 'Trump', clRed);
finally
RichEdit1.OnChange := oldOnChange;
end;
end;
撤销
此外,您所做的每一次颜色更改都会记录在撤消列表中!以及每次 RichEdit 重绘。停止那些:
procedure TMyForm.Button1Click(Sender: TObject);
var
oldOnChange: TNotifyEvent;
begin
oldOnChange := RichEdit1.OnChange;
RichEdit1.OnChange := nil;
try
RichEditSuspendAll(RichEdit1, True);
try
Color_Words(RichEdit1, 'Trump', clRed);
finally
RichEditSuspendAll(RichEdit1, False);
end;
finally
RichEdit1.OnChange := oldOnChange;
end;
end;
有辅助函数:
procedure RichEditSuspendAll(ARichEdit: TRichEdit; bSuspend: Boolean);
var
doc: ITextDocument;
re: IUnknown;
begin
{
http://bcbjournal.org/articles/vol3/9910/Faster_rich_edit_syntax_highlighting.htm
int eventMask = ::SendMessage(RichEdit1->Handle, EM_SETEVENTMASK, 0, 0);
SendMessage(RichEdit1->Handle, WM_SETREDRAW, false, 0);
ParseAllText(RichEdit1);
SendMessage(RichEdit1->Handle, WM_SETREDRAW, true, 0);
InvalidateRect(RichEdit1->Handle, 0, true);
SendMessage(RichEdit1->Handle, EM_SETEVENTMASK, 0, eventMask);
}
{
http://support.microsoft.com/KB/199852
How To Suspend and Resume the Undo Functionality in Richedit 3.0
If it is necessary to Undo an action that is performed before a suspend, after resuming the Undo, then,
tomFalse must be replaced with "tomSuspend" and tomTrue must be replaced with "tomResume".
This method retains the contents of the Undo buffer even when Undo is suspended.
Applications can retrieve an ITextDocument pointer from a rich edit control.
To do this, send an EM_GETOLEINTERFACE message to retrieve an IRichEditOle
object from a rich edit control. Then, call the object's
IUnknown::QueryInterface method to retrieve an ITextDocument pointer.
}
if ARichEdit = nil then
raise Exception.Create('ARichEdit is nil');
if SendMessage(ARichEdit.Handle, EM_GETOLEINTERFACE, 0, LPARAM(@re)) = 0 then
raise Exception.Create('Could not get OleInterface from RichEdit');
doc := re as ITextDocument;
doc := RichEditGetTextDocument(ARichEdit);
if bSuspend then
begin
RichEdit.Perform(WM_SETREDRAW, 0, 0); //disable all painting of the control
doc.Undo(Integer(tomSuspend)); // Suspends Undo.
end
else
begin
doc.Undo(Integer(tomResume)); // Resumes Undo.
RichEdit.Perform(WM_SETREDRAW, 0, 0); //disable all painting of the control
end;
end;