Delphi/Firemonkey make Listboxses sync scroll better - delphi

I have a very dirty way of having 7 listboxses scroll in sync, they all have the exact same height so when I move the position of any one of them the other 6 should also move to that position
What I do is Assume Listbox1 is scrolled and ViewportPositionChange() is fired I say
Listbox2.ViewportPosition := NewViewportPosition;
Listbox3.ViewportPosition := NewViewportPosition;
And this all works and I just add this code to each Listboxses ViewportPositionChange() event, but its terribly jerky and too slow on Andriod, I still need to be able to scroll from any one listbox but is there not a way to improve the performace of the scroll?
Ok to be honest it isn't terribly slow but you can feel its not exactly snappy, and also each listbox can have lots of listboxses and each listbox can also have a few controls and more controls in those controls so alot of repainting is done I think and this is what is making it less snappy.
Sample code
Firstly place a Gridpanel layout on your form, Add 6 columns And add a listbox to each, each Listbox represents a day, where Listbox 1 is Monday, Also Align these to client.
Global variables
private Glistbox : array[0..6] of TListBox;
On form Create
Glistbox[0] := ListBox1;
Glistbox[1] := ListBox2;
Glistbox[2] := ListBox3;
Glistbox[3] := ListBox4;
Glistbox[4] := ListBox5;
Glistbox[5] := ListBox6;
Glistbox[6] := ListBox7;
And now on each listboxses OnViewportPositionChange
//Note that this would be for Listbox1 so for listbox 2 it needs to be changed acordingly
ListBox2.ViewportPosition := NewViewportPosition;
ListBox3.ViewportPosition := NewViewportPosition;
ListBox4.ViewportPosition := NewViewportPosition;
ListBox5.ViewportPosition := NewViewportPosition;
ListBox6.ViewportPosition := NewViewportPosition;
ListBox7.ViewportPosition := NewViewportPosition;
Then add a button with the following code :
var
mainlayout,item1,item2 : Tlayout;
listboxitem : TListBoxItem;
myrec,myrec2 : TRectangle;
lbl1,lbl2 : TLabel;
I: Integer;
begin
for I := 0 to 6 do
begin
listboxitem := TListBoxItem.Create(nil);
mainlayout := TLayout.Create(nil);
mainlayout.Align := TAlignLayout.Client;
mainlayout.Parent:= listboxitem;
item1 := TLayout.Create(nil);
item1.Align:= TAlignLayout.Left;
item1.Parent := mainlayout;
item2 := TLayout.Create(nil);
item2.Align:= TAlignLayout.Right;
item2.Parent := mainlayout;
myrec := TRectangle.Create(nil);
myrec.Align := TAlignLayout.Client;
myrec.Parent := item1;
myrec2 := TRectangle.Create(nil);
myrec2.Align := TAlignLayout.Client;
myrec2.Parent := item2;
lbl1 := TLabel.Create(nil);
lbl1.Align := TAlignLayout.Client;
lbl1.TextAlign := TTextAlign.Center;
lbl1.Text := '1';
myrec.AddObject(lbl1);
lbl2 := TLabel.Create(nil);
lbl2.Align := TAlignLayout.Client;
lbl2.TextAlign := TTextAlign.Center;
lbl2.Text := '2';
myrec2.AddObject(lbl2);
Glistbox[I].AddObject(listboxitem);
item2.Width:= mainlayout.Width/2;
end;
There could sometimes be more controls in a listboxitem but generally I think this would be the norm, Now add a few items and try the scroll

The FMX TListBox has a lot of overhead because every listbox item basically is a styled component that is inserted into a scrollbox. This is good for flexibility, but bad for speed.
In Windows and OSX there is functions like ScrollWindowEx or NSView.scrollRect + NSView.setNeedsDisplayInRect that can help improve performance by scrolling a part of the window without issuing paint messages. So you'd only have to invalidate a part of the listbox. I could imagine that Android has something similar. But you'll likely have to patch FMX in order to use that. This can improve performance a bit, but is still not a magic switch for great performance. A similar approach would be caching the content of the scrollbox in a bitmap and then copy parts of the bitmap to the screen while scrolling.
If you need really good listbox performance then you'll need to develop your own listbox that is optimized for speed. This is what I had to do for the OSX version of my application in order to get the performance I needed.

There's also TListView for Firemonkey which is better choice than TListBox when all items share the same visual structure. There's a tutorial by Ray Konopka (from 2014) explaining what each one is suited for:
https://www.youtube.com/watch?time_continue=934&v=XHYtwAu5fl4
For adjusting scrolling behaviour (in case it helps) see:
https://docwiki.embarcadero.com/RADStudio/Alexandria/en/FireMonkey_Layouts_Strategies#Smooth_Inertial_Scrolling

Related

TChart with TKnobGauge creates shifted labels at runtime

Following code creates knob with labels shifted to the right:
procedure TForm1.FormShow(Sender: TObject);
begin
_chart := TChart.Create(Self);
_chart.Parent := Self;
_chart.Align := alClient;
_knob := TKnobGauge.Create(Self);
_knob.ParentChart := _chart;
_knob.RotateLabels := False;
_knob.RotationAngle := 180;
end;
The same code as DFM produces the right knob.
What could be wrong?
TeeChart Pro v2015.16.150901 32bit VCL
Delphi 10
There is a bug in TChart. When I set
_chart.Title.Text.Text := 'Some title';
labels are on their places.
When I do
_chart.Title.Text.Text := '';
or
_chart.Title.Visible := False;
they are shifted.
The reason why the same code in DFM produced the right knob is that the visual designer extends my minimal chart declaration by adding several properties automatically. Among these properties was a chart title too. It is automatically filled by "TChart" text.
Sounds as exactly what is described in the ticket #1547, initially reported here.
Please, give a try at the workaround described in the ticket:
I can only workaround by having a small title with only a blank in it.

Best strategy for too many Elements in FireMoneky TListView Item [duplicate]

This question already has an answer here:
Create a customized Item Appearance for ListView Delphi XE7
(1 answer)
Closed 7 years ago.
Believe me I did my homework before reaching out for help. I spent last 3 days searching and reading but I couldn't come to a solution. So any help will be highly appreciated.
My task is to have a ListView connected to a Dataset where the ListView Item is of the following structure:
Bear in mind that
Elements 4, 6, & 8 are of fixed values & Color (i.e. labels)
Colors of Elements 1 & 10 depends on values of Elements 5, 7, & 9
Best what I got is references to Delphi Standard Example that shifts with Embarcadero Delphi Examples directory: ListViewMultiDetailAppearance.
This solution offers to create our own class for MutliDetailItemAppearance and register as many details as we need (in my case I need additional 8 I think).
Now my questions:
Is this the best approach?
if not, what is the better approach?
if it is, how adding additional 8 details will affect the
performance?
and most important how to reach custom coloring for elements for
each List View Item based on the values?
and finally how to reach this sections borders? and List item bottom
borders (the green line)?
Thank you very much for your thoughts in advance.
I am not sure that my way was correct, but I was using TListbox for alike purpose in my fmx project. The structure of its items was formed in the following way during filling from DataSource by LiveBindings.
procedure THMICD10Fr.LinkListControlToField1FillingListItem(Sender: TObject;
const AEditor: IBindListEditorItem);
begin
if (Assigned(AEditor)) and (HDM2.FDQicd_detail_for_TreeView.Active) then
try
if (AEditor.CurrentObject as TMetropolisUIListBoxItem).ChildrenCount = 2
then
begin
with TPanel.Create(AEditor.CurrentObject as TMetropolisUIListBoxItem) do
begin
Parent := (AEditor.CurrentObject as TMetropolisUIListBoxItem);
Align := TAlignLayout.alRight;
Width := 45;
Margins.Bottom := 1;
Margins.Top := 1;
end;
with TLabel.Create((AEditor.CurrentObject as TMetropolisUIListBoxItem)
.Children.Items[2] as TPanel) do
begin
Parent := (AEditor.CurrentObject as TMetropolisUIListBoxItem)
.Children.Items[2] as TPanel;
Text := '↓';
VertTextAlign := TTextAlign.taCenter;
TextAlign := TTextAlign.taCenter;
Align := TAlignLayout.alClient;
HitTest := true;
AutoSize := false;
StyledSettings := StyledSettings - [TStyledSetting.ssStyle];
Font.Style := Font.Style + [TFontStyle.fsBold];
Tag := HDM2.FDQicd_detail_for_TreeView.FieldByName('id').AsInteger;
TagString := HDM2.FDQicd_detail_for_TreeView.FieldByName
('category_etiology').AsString;
OnClick := LabelInListBox1Click;
end;
end;
except
end;
end;
This code gave me the following appearence:
You can create and nest all necessary TLayouts, TLabels etc. inside the Item and set all the necessary settings using the logics from inside the LiveBindings event handler.

Delphi - How to add tabs in TMEMO?

As shown like here.
pic: tabs with memo
Currently, my TMEMO displays bunch of different data, like this:
Data #1
Paragraphs
Data #2
Paragraphs
Data #N
Paragraphs
So to avoid scrolling, I want to add tabs to the Nth number.
So what components do I need and how should I intiate the process?
you need to use a combination of a TMemo and TTabControl.
Do not know how you get your paragraphs but you'll have to iterate through them, creating a TabSheet and a Memo for each.
procedure TfrmMemo.CreateTabsWithMemo;
var
pgControl: TPageControl;
TabSheet: TTabSheet;
Memo: TMemo;
begin
pgControl := TPageControl.Create(self);
pgControl.Parent := Self;
pgControl.Align := alClient;
//Do this for each paragraph
TabSheet := TTabSheet.Create(pgControl);
TabSheet.PageControl := pgControl;
TabSheet.Caption := Format('Tab %d', [pgControl.PageCount]);
Memo := TMemo.Create(TabSheet);
Memo.Parent := TabSheet;
Memo.Align := alClient;
Memo.Lines.Text := 'Your Paragraph here'
///
end;
Use TPageControl and TTabSheet. Place a TMemo component on each TTabSheet.
You can drage the TPageControl onto the form to get started.

How to temporarily stop a control from being painted?

We have a win control object which moves its clients to some other coordiantes. The problem is, when there are too many children - for example 500 controls - the code is really slow.
It must be because of each control being repainted each time I set Left and Top property. So, I want to tell the WinControl object stop being repainted, and after moving all objects to their new positions, it may be painted again (Something like BeginUpdate for memo and list objects). How can I do this?
Here's the code of moving the objects; it's quite simple:
for I := 0 to Length(Objects) - 1 do begin
with Objects[I].Client do begin
Left := Left + DX;
Top := Top + DY;
end;
end;
As Cosmin Prund explains, the cause for the long duration is not an effect of repainting but of VCL's realignment requisites at control movement. (If it really should take as long as it does, then you might even need to request immediate repaints).
To temporarily prevent realignment and all checks and work for anchors, align settings and Z-order, use DisableAlign and EnableAlign. And halve the count of calls to SetBounds by called it directly:
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
Control: TControl;
begin
for I := 0 to 499 do
begin
Control := TButton.Create(Self);
Control.SetBounds((I mod 10) * 40, (I div 10) * 20, 40, 20);
Control.Parent := Panel1;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
C: TControl;
begin
// Disable Panel1 paint
SendMessage(Panel1.Handle, WM_SETREDRAW, Integer(False), 0);
Panel1.DisableAlign;
try
for I := 0 to Panel1.ControlCount - 1 do
begin
C := Panel1.Controls[I];
C.SetBounds(C.Left + 10, C.Top + 5, C.Width, C.Height);
end;
finally
Panel1.EnableAlign;
// Enable Panel1 paint
SendMessage(Panel1.Handle, WM_SETREDRAW, Integer(True), 0);
// Update client area
RedrawWindow(Panel1.Handle, nil, 0, RDW_INVALIDATE or RDW_UPDATENOW or RDW_ALLCHILDREN);
end;
end;
Your assumption that the slowness comes from re-painting controls is probably true, but not the whole story. The default Delphi code that handles moving controls would delay painting until the next WM_PAINT message is received, and that would happen when the message queue is pumped, after you complete moving all the controls. Unfortunately there are a lot of things involved in this, that default behavior can be altered in many places, including Delphi and Windows itself. I've used the following code to test what happens when you move a control at runtime:
var i: Integer;
begin
for i:=1 to 100 do
begin
Panel1.Left := Panel1.Left + 1;
Sleep(10); // Simulate slow code.
end;
end;
The behaviour depends on the control! A TControl (example: TLabel) is going to behave according to Delphi's rules, but a TWinControl depends on too many factors. A simple TPanel is not repainted until after the loop, in the case of TButton on my machine only the background is re-painted, while a TCheckBox is fully repainted. On David's machine the TButton is also fully repainted, proving this depends on many factors. In the case of TButton the most likely factor is the Windows version: I tested on Windows 8, David tested on Windows 7.
AlignControl Avalanche
Anyhow, there's an other really important factor to be taken into account. When you move a control at runtime, all the rules for alignment and anchoring for all the controls need to be taken into account. This likely causes an avalanche of AlignControls / AlignControl / UpdateAnchorRules calls. Since all those calls end up requiring recursive invocations of the same, the number of calls will be exponential (hence your observation that moving lots of objects on a TWinControl is slow).
The simplest solution is, as David suggests, placing everything on a Panel and moving the panel as one. If that's not possible, and all your controls are actually TWinControl (ie: they have a Window Handle), you could use:
BeginDeferWindowPos, DeferWindowPos, EndDeferWindowPos
I would put all the controls in a panel, and then move the panel rather than the controls. That way you perform the shift in a one single operation.
If you would rather move the controls within their container then you can use TWinControl.ScrollBy.
For what it is worth, it is more efficient to use SetBounds than to modify Left and Top in separate lines of code.
SetBounds(Left+DX, Top+DY, Width, Height);
To speed up you should set the Visible property of you WinControl to False during child movement to avoid repainting.
Together with SetBounds you will get the best from moving the child controls.
procedure TForm1.MoveControls( AWinControl : TWinControl; ADX, ADY : Integer );
var
LIdx : Integer;
begin
AWinControl.Visible := False;
try
for LIdx := 0 to Pred( AWinControl.ControlCount ) do
with AWinControl.Controls[LIdx] do
begin
SetBounds( Left + ADX, Top + ADY, Width, Height );
end;
finally
AWinControl.Visible := True;
end;
end;
BTW As David suggested, moving the parent is much faster than each child.

Problem with adding graphics to TLabel

I'm trying to create with Delphi a component inherited from TLabel, with some custom graphics added to it on TLabel.Paint. I want the graphics to be on left side of text, so I overrode GetClientRect:
function TMyComponent.GetClientRect: TRect;
begin
result := inherited GetClientRect;
result.Left := 20;
end;
This solution has major problem I'd like to solve: It's not possible to click on the "graphics area" of the control, only label area. If the caption is empty string, it's not possible to select the component in designer by clicking it at all. Any ideas?
First excuse-me for my bad English.
I think it is not a good idea change the ClientRect of the component. This property is used for many internal methods and procedures so you can accidentally change the functionality/operation of that component.
I think that you can change the point to write the text (20 pixels in the DoDrawText procedure -for example-) and the component can respond on events in the graphic area.
procedure TGrlabel.DoDrawText(var Rect: TRect; Flags: Integer);
begin
Rect.Left := 20;
inherited;
end;
procedure TGrlabel.Paint;
begin
inherited;
Canvas.Brush.Color := clRed;
Canvas.Pen.Color := clRed;
Canvas.pen.Width := 3;
Canvas.MoveTo(5,5);
Canvas.LineTo(15,8);
end;
What methods/functionality are you getting from TLabel that you need this component to do?
Would you perhaps be better making a descendent of (say, TImage) and draw your text as part of it's paint method?
If it's really got to be a TLabel descendant (with all that this entails) then I think you'll be stuck with this design-time issue, as doesn't TLabel have this problem anyway when the caption is empty?
I'll be interested in the other answers you get! :-)

Resources