官术网_书友最值得收藏!

Bulk updates

Another aspect of the same problem of overabundance of messages occurs when you want to add or modify multiple lines in a TListBox or TMemo. The demonstration program, BeginUpdate, demonstrates the problem and shows possible solutions.

Let's say we have a listbox and we want to populate it with lots of lines. The demo program displays 10,000 lines, which is enough to show the problem.

A naive program would solve the problem in two lines:

for i := 1 to CNumLines do
ListBox1.Items.Add('Line ' + IntToStr(i));

This, of course, works. It is also unexpectedly slow. 10,000 lines is really not much for modern computers so we would expect this code to execute very quickly. In reality, it takes 1.4 seconds on my test machine!

We could do the same with a TMemo. The result, however, is a lot more terrible:

for i := 1 to CNumLines do
Memo1.Lines.Add('Line ' + IntToStr(i));

With the memo we can see lines appear one by one. The total execution time on my computer is a whopping 12 seconds!

This would make both listbox and memo unusable for displaying a large amount of information. Luckily, this is something that the original VCL designers anticipated, so they provided a solution.

If you look into the code, you'll see that both TListBox.Items and TMemo.Lines are of the same type, TStrings. This class is used as a base class for TStringList and also for all graphical controls that display multiple lines of text.

The TStrings class also provides a solution. It implements methods BeginUpdate and EndUpdate, which turn visual updating of a control on and off. While we are in update mode (after BeginUpdate was called), visual control is not updated. Only when we call EndUpdate will Windows redraw the control to the new state. 

BeginUpdate and EndUpdate calls can be nested. Control will only be updated when every BeginUpdate is paired with an EndUpdate:

ListBox1.Items.Add('1');  // immediate redraw
ListBox1.BeginUpdate; // disables updating visual control
ListBox1.Items.Add('2'); // display is not updated
ListBox1.BeginUpdate; // does nothing, we are already in *update* mode
ListBox1.Items.Add('3;); // display is not updated
ListBox1.EndUpdate; // does nothing, BeginUpdate was called twice
ListBox1.Items.Add('4'); // display is not updated
ListBox1.EndUpdate; // exits update mode, changes to ListBox1 are
// displayed on the screen

Adding BeginUpdate/EndUpdate to existing code is very simple. We just have to wrap them around existing operations:

ListBox1.Items.BeginUpdate;
for i := 1 to CNumLines do
ListBox1.Items.Add('Line ' + IntToStr(i));
ListBox1.Items.EndUpdate;

Memo1.Lines.BeginUpdate;
for i := 1 to CNumLines do
Memo1.Lines.Add('Line ' + IntToStr(i));
Memo1.Lines.EndUpdate;

If you click on the second button in the demo program you'll see that the program reacts much faster. Execution times on my computer were 48 ms for TListBox and 671 ms for TMemo. This second number still seems suspicious. Why does TMemo need 0.7 seconds to add 10,000 lines if changes are not painted on the screen?

To find an answer to that we have to dig into VCL code, into the TStrings.Add method:

function TStrings.Add(const S: string): Integer;
begin
Result := GetCount;
Insert(Result, S);
end;

Firstly, this method calls GetCount so that it can return the proper index of appended elements. Concrete implementation in TMemoStrings.GetCount sends two Windows messages even when the control is in the updating mode: 

Result := SendMessage(Memo.Handle, EM_GETLINECOUNT, 0, 0);
if SendMessage(Memo.Handle, EM_LINELENGTH, SendMessage(Memo.Handle,
EM_LINEINDEX, Result - 1, 0), 0) = 0 then Dec(Result);

After that, TMemoStrings.Insert sends three messages to update the current selection:

if Index >= 0 then
begin
SelStart := SendMessage(Memo.Handle, EM_LINEINDEX, Index, 0);
// some code skipped ... it is not executed in our case
SendMessage(Memo.Handle, EM_SETSEL, SelStart, SelStart);
SendTextMessage(Memo.Handle, EM_REPLACESEL, 0, Line);
end;

All that causes five Windows messages to be sent for each appended line and that slows the program down. Can we do it better? Sure!

To speed up TMemo, you have to collect all updates in some secondary storage, for example in a TStringList. At the end, just assign the new memo state to its Text property and it will be updated in one massive operation.

The third button in the demo program does just that:

sl := TStringList.Create;
for i := 1 to CNumLines do
sl.Add('Line ' + IntToStr(i));
Memo1.Text := sl.Text;
FreeAndNil(sl);

This change brings execution speed closer to the listbox. My computer needed only 75 ms to display 10,000 lines in a memo with this code.

An interesting comparison can be made by executing the same code in the FireMonkey framework. Graphical controls in FireMonkey are not based directly on Windows controls, so effects of BeginUpdate/EndUpdate may be different.

The program BeginUpdateFMX in the code archive does just that. I will not go through the whole process again, but just present the measurements. All times are in milliseconds:

We can see that BeginUpdate/EndUpdate are also useful in the FireMonkey framework.

If a class implements BeginUpdate/ EndUpdate, use it when doing bulk updates.

In the next part, we'll see how we can replace a TListBox with an even faster solution if we are programming with the VCL framework.

主站蜘蛛池模板: 澳门| 山西省| 马公市| 平邑县| 大余县| 河池市| 漳浦县| 镇沅| 阳山县| 新兴县| 同江市| 瓦房店市| 赤壁市| 东乌珠穆沁旗| 荥阳市| 唐海县| 浦北县| 新密市| 天柱县| 漳州市| 大姚县| 武宁县| 于田县| 乌鲁木齐市| 新龙县| 河曲县| 德保县| 大姚县| 尉犁县| 开封市| 丰城市| 江西省| 礼泉县| 大港区| 漳浦县| 分宜县| 普兰店市| 玉田县| 祁东县| 齐河县| 金门县|