Jump to content
araujoarthur

How to solve System Out of Resources in TScrollBox

Recommended Posts

Hello! I am writing a log viewer using Delphi VCL and AlmediaDev's StyleControls, and my approach to show a styled list of log entries was to add a TScrollBox (TscScrollBox actually, using AlmediaDev StyleControls, which extends the original), and dynamically fill it with frames (one frame per log entry) as the following image shows:

 

1577233014_360C7DDD-A37F-40FB-8C5B-470F28415FDB.thumb.png.03af93964552b52cc666d1d49d10995b.png

 

The Frame itself looks like this:
355790569_A36DF238-400D-4127-8C99-C4FBF3A7E800.thumb.png.0fed1e2e1c90523fd4a296f1ea46e1c4.png
 

And has this component tree:
673948159_46B12DD1-02F7-4804-9E74-D8B70E9C0E88.png.4c43a1f6ed5b83204549684ac605d715.png

 

The frames have variable width according to the window width, but the height is always fixed.

 

This approach works for smaller log files (The above screenshot was taken with 217 records) but fails with "Out of System Resources" and "Canvas Does Not Allow Drawing" with bigger files (my failed test file had 3000 entries). Although I understand why is this happening  (or atleast I believe it's because the windows won't let me draw 3.000 controls) I don't know what approach I could take to either improve the rendering to reduce resource usage or change the way it renders frames without affecting the scroll list size (otherwise I could just "remove" the frames out of sight and insert the ones that are on sight) and entry UI style (As I believe I would have to if changed for a TVirtualListView as suggested in topic/3661-how-to-detect-when-control-is-scrolled-into-view/).

Can anyone give me any direction on what should I do here? Right now it's really more a matter of "WHAT should I do" than "HOW do I do that". 

For quick reference, I'm also adding the code for the TEntryListItemTemplate and the code that creates and feed it into the scrollbox. It's also fully available here.

 

TEntryListItemTemplate

type
  TEntryListItemTemplate = class(TFrame)
    svgLogIcon: TSkSvg;
    labelDateTime: TscGPLabel;
    scGPPanel1: TscGPPanel;
    labelDescription: TscGPLabel;
    scGPPanel2: TscGPPanel;
    scGPPanel3: TscGPPanel;
    scGPGlyphContainerButton2: TscGPGlyphContainerButton;
    svgEyeIcon: TSkSvg;
  private
    { Private declarations }
    FAssociatedRecord: TLogEntry;
    FAssociatedControlIndex: Integer; // TODO: When one is removed all indexes must above must change (needs a routine)
    procedure SetupWarningIcon();
    procedure SetupErrorIcon();
    procedure SetupInfoIcon();
  public
    { Public declarations }
    constructor Create(AOwner: TComponent; ARecord: TLogEntry; AControlIndexInList: Integer); reintroduce;
  end;

const
  {
    The Following Icons are part of Google's Material Core Icon Pack
  }
  ERROR_ICON = '''
   ...
  ''';

  INFO_ICON = '''
  ...
  ''';

  WARNING_ICON = '''
  ...
  ''';

implementation

{$R *.dfm}

{ TEntryListItemTemplate }

constructor TEntryListItemTemplate.Create(AOwner: TComponent; ARecord: TLogEntry; AControlIndexInList: Integer);
begin
  inherited Create(AOwner);
  FAssociatedRecord := ARecord;
  FAssociatedControlIndex := AControlIndexInList;
  case FAssociatedRecord.Severity of
    lsUNKNOWN: SetupInfoIcon();
    lsUNIMPORTANT: SetupInfoIcon();
    lsREQUESTRECEIVED: SetupInfoIcon();
    lsINFORMATION: SetupInfoIcon();
    lsWARNING: SetupWarningIcon();
    lsERROR: SetupErrorIcon();
  end;

  labelDescription.Caption := Copy(FAssociatedRecord.Message, 0, 150);
  labelDateTime.Caption := DateTimeToStr(FAssociatedRecord.Date);

end;

procedure TEntryListItemTemplate.SetupErrorIcon;
begin
  svgLogIcon.Svg.OverrideColor := $FFFF2D2D;
  svgLogIcon.Svg.Source := ERROR_ICON;
end;

procedure TEntryListItemTemplate.SetupInfoIcon;
begin
  svgLogIcon.Svg.OverrideColor := $FF2D4DFF;
  svgLogIcon.Svg.Source := INFO_ICON;
end;

procedure TEntryListItemTemplate.SetupWarningIcon;
begin
  svgLogIcon.Svg.OverrideColor := $FFFFAB2D;
  svgLogIcon.Svg.Source := WARNING_ICON;
end;

 

Creation and Insertion in TscScrollBox:

procedure TfrmMain.Open1Click(Sender: TObject);
var
  OpenedFile: TLogFile;
begin
  OpenedFile := ActionUtils.OpenFile;
  if OpenedFile <> nil then
  begin
    gStateHolder.CurrentFile := OpenedFile;
    gStateHolder.HasOpenFile := True;

    var Progress: Extended := 0;
    var Increment: Extended := 100/Length(gStateHolder.CurrentFile.FLogEntries);
    ProgressBarInc(0);

    {
      I decided to make invisible while I add the entries so the it doesn't have to draw every single entry on each insert.
      By setting it to Visible := False before the loop and True after the loop I got it to render all at once, which
      **Improved significantly the Load time for 217 records**. Still fails for 3.000
    }
    contentScrollBox.Visible := False;
    for var I := Length(gStateHolder.CurrentFile.FLogEntries) - 1 downto 0 do
    begin
      var rec := gStateHolder.CurrentFile.FLogEntries[I];
      var frame := TEntryListItemTemplate.Create(contentScrollBox, rec, contentScrollBox.ControlCount);
      frame.Name := TypeUtils.GenerateFrameName();
      contentScrollBox.InsertControl(frame);
      Progress := Progress + Increment;
      ProgressBarInc(Round(Progress));
    end;

    contentScrollBox.Visible := True;  // System out of Resources. How to deal with?

  end;
end;


Thank you for your time!

 

Share this post


Link to post

If you have a potentially really long list, and you create components in memory (and with GUI elements), but only have a few of them at once on screen, that's a lot of wasted resources, so the 'out of resources' error you get may be actually correct.

 

If I recall correctly, what things like VirtualTreeList do, and you can do this yourself with TListView by switching property Style to dlOwnerDrawFixed or lbOwnerDrawVariable and using the OnDrawItem event, is actually just draw the information of the items when they are on screen. By setting the numer of items, the scrollbars show as if there are that many items, but it is in fact a kind of illusion.

 

I wish I had a nice example to show you, but I only have this one I once wrote for a complete code-comparing tool. I hope you can find more examples if you look for them on the net.

Edited by stijnsanders

Share this post


Link to post

Using a TScrollbox with thousands of child controls is a terrible idea, apart from the memory overhead the performance would not be greate. As others have said, using a virtual list control is much better solution. 

 

This is what I use for these scenarios - you do have to wite the paint code yourself but it's very lightweight

 

https://github.com/VSoftTechnologies/VSoft.VirtualListView - supports XE2 - D12 and vcl themes - although I have only tested themes within an IDE plugin

 

There is a demo app and you can also see it in use here 

 

https://github.com/DelphiPackageManager/DPM/blob/master/Source/IDE/EditorView/DPM.IDE.EditorViewFrame.pas

 

Share this post


Link to post

For my custom log viewer, I used a standard TListView in virtual mode with owner-drawn items, and it handles millions of items (GB-sized log files) just fine with low overhead. The hardest part was implementing a caching mechanism for the on-screen items, as I also filter and search items so don't want to keep the whole file in memory at one time.

Edited by Remy Lebeau

Share this post


Link to post

I use an In memory table (TFDMemtable) for somethings I am working on, assigning only the records that will be visible on the screen at any given time.  So if I will see 20 -30 items, I reuse the controls, just change the data in them as someone "scrolls" .  It's an illusion, but visually it seems extremely fast, and low memory overhead due to only a set number of visual components 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×