araujoarthur 0 Posted Thursday at 08:53 PM 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: The Frame itself looks like this: And has this component tree: 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
stijnsanders 36 Posted Thursday at 09:51 PM (edited) 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 Thursday at 09:52 PM by stijnsanders Share this post Link to post
Vincent Parrett 762 Posted Friday at 01:33 AM 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
Remy Lebeau 1429 Posted Friday at 03:32 PM (edited) 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 Friday at 03:36 PM by Remy Lebeau Share this post Link to post
ToddFrankson 2 Posted Friday at 07:28 PM 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