* * *

Author Topic: How to avoid 'position range overflow' when filling a TScrollBox or TflowPanel  (Read 3904 times)

Fish

  • New member
  • *
  • Posts: 11
Hi,

I want to place a larger number of components (TPanel) on a TScrollBox and/or TFlowPanel.
If the number exceeds a certain value (depending on the height and alignment) I get 'Position range overflow in .SendMoveSizeMessages: Left=0, Top=32816' (Laz 1.8 ) or 'TWinControl.WMSize loop detected' (Laz 1.6).
So is there any way to get around this restriction? Or do I have to use other components?

Thanks in advance
Chris

jamie

  • Hero Member
  • *****
  • Posts: 785
You have something seriously wrong here.

 The TOP value is reporting in the - ranges of a small integer which would be a 16 bit.
 
 looking at the value it looks to be represented as a WORD but you are receiving an overflow which
means it is being treated as a Small INT.

 In any case you shouldn't seeing a value of that level for a TOP property of a component.

 It's my guess you are changing the size/position of the control within the OnSize event ?

 If that being the it can become tricky because it will trigger another onSize event and thus the
 OnSize you are in will be called again, again and again.

   If this is what you are doing, try to use the "SetBounds" for the control instead.


 

Fish

  • New member
  • *
  • Posts: 11
Hi jamie,

thanks for your answer.

In any case you shouldn't seeing a value of that level for a TOP property of a component.

 It's my guess you are changing the size/position of the control within the OnSize event ?

No, this error is very easy to reproduce. First example: Place a FlowPanel (autosized) on a ScrollBox. Set following code to the onClick Event of a Button

Code: Pascal  [Select]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   i: Integer;
  4.   pnl: TPanel;
  5. begin
  6.   for i := 0 to 200 do
  7.   begin
  8.     pnl := TPanel.Create(Self);
  9.     pnl.Width  := 250;
  10.     pnl.Height := 250;
  11.     pnl.Caption:= IntToStr(i);
  12.     pnl.Parent := FlowPanel1;
  13.     Form1.Caption:=inttostr(fp.Height);
  14.   end;
  15. end;
 
The program crashes after the second click.

Same result if you place your panels directly on a ScrollBox
Code: Pascal  [Select]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   i: Integer;
  4.   pnl: TPanel;
  5. begin
  6.   for i := 0 to 200 do
  7.   begin
  8.     pnl := TPanel.Create(Self);
  9.     pnl.Width  := 250;
  10.     pnl.Height := 250;
  11.     pnl.Caption:= IntToStr(i);
  12.     pnl.Parent := ScrollBox1;
  13.     pnl.Top := Tag + ScrollBox1.VertScrollBar.Size - ScrollBox1.VertScrollBar.Position;
  14.     Tag := Tag + pnl.Height;
  15.   end;
  16. end;

In these examples you would get TOPs beyond 32.767 after a number of around 130 panels in a column. Which is IMHO not that much.
I know about the limitation of a WORD value, but how can I achieve to add more than this amount of panels on a TWhatEver?

Chris

Thaddy

  • Hero Member
  • *****
  • Posts: 6529
0 to 200 is actually 201 ..
Use for Low() to High() or for in do... You might even declare a range ... Those syntax options will prevent you from overflows. And it has no cost in terms of speed at all.
Maybe I should do a write-up on defensive programming in general.
« Last Edit: February 06, 2018, 10:37:08 am by Thaddy »
Ada's daddy wrote this:"Fools are my theme, let satire be my song."

Fish

  • New member
  • *
  • Posts: 11
0 to 200 is actually 201 ..
Use for Low() to High() or for in do... You might even declare a range ... Those syntax options will prevent you from overflows. And it has no cost in terms of speed at all.
Maybe I should do a write-up on defensive programming in general.

Oh yes, you are right. I forgot to mention that in this example there are 3 panels in a row within the FlowBox (just transferred the number to the ScrollBox example).
I know how to declare a range and how to calculate a maximum of possible components. But that's definitely not the point.

My question was: is it possible to add more components to a ScrollBox/FlowPanel or do I have to live with this limitation? And are there any other possibilities to place a larger number of TPanels ordered and scrollable on a form?
Just to give you an example: If you want to view or download pictures from your camera or sdcard you would normally use a program that shows you thumbnails of these pictures. Let's say that your sdcard contains 500 pictures. I guess you would be not a little surprised if this program would tell you 'haaaay, I'm only allowed to show you no more than 201 of your 500 pictures. Just forget about the rest.'

That's what I found as answers to a Delphi related question (don't know if it's allowed to post the link) :
Quote
Subsequently, the Top value of a control, which can be negative, is limited to 15 bits and 1 sign bit, thus +-32,767. To be specific: that is where Control.ClientOrigin.X/Y is bound by. E.g. thus resolving in a maximum Top value of 32,167 for a control placed in the middle of a 1920x1200 pixel screen.

Quote
So (just) before reaching the magic limit, fool Windows by scrolling the scroll box

Quote
Instead of TPanel (a TWinControl), use TControl derivatives to bypass API calls to SetWindowPos

Quote
Use a virtual approach, like TDBControlGrid does, showing only a few panels while giving the impression of having a lot

Unfortunately I haven't yet found a way to implement one of these suggestions.

Chris

jamie

  • Hero Member
  • *****
  • Posts: 785
You are over complicating your program..

 Use a TDrawGrid and a TImagelist..

 First load an iconic view of all your images in the TimageList and also populate a TstringList of all the
file names for each image, keep them all in the order of the TimageList so that a single index value can be used.

 With the TDrawGrid, implement the OnDrawCell and draw the iconic image from the TimageList to the cell.

 Once note with the Timagelist, you'll need to load each image from file into a common image component, like
for example the TPicture and then from there do a StretchDraw to the ImageList of the base size indicated in the
imagelist.

 You really don't want to try and load all of those images in memory..

 If you need more help on the concept just ask.

JuhaManninen

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3412
  • I like bugs.
I want to place a larger number of components (TPanel) on a TScrollBox and/or TFlowPanel.
If the number exceeds a certain value (depending on the height and alignment) I get 'Position range overflow in .SendMoveSizeMessages: Left=0, Top=32816' (Laz 1.8 )
I added the overflow test and exception in TWinControl.SendMoveSizeMessages because there were random errors in Lazarus itself and in some user projects.
The value is finally assigned to a SmallInt which inherently limits the range.

Quote
So is there any way to get around this restriction? Or do I have to use other components?
In your example the Top positions are calculated in TCustomFlowPanel.AlignControls.
Panel heights are summed which leads to overflow. I don't think there is a way around this restriction, although it should not raise an exception. How to handle the situation better? Ideas?

Note: with your example it was predictable, more like a feature than a bug.
There is still a bug lurking in the layout code which causes those overflows (semi-)randomly.

Fish

  • New member
  • *
  • Posts: 11
Hi jamie,

thanks for your constructive answer.

Quote
You are over complicating your program..

Until now I considered it as very easy ;)

Quote
Use a TDrawGrid and a TImagelist..

I never worked with DrawGrids, but will surely have a look at it.

May be I should give you a more detailed information about what I have in mind.
Two years ago I wrote a program for an older family member, who always struggled with getting her pictures from the camera and sending them per email. So my prog starts as soon as she inserts her sdcard, copies new pictures to a defined folder, Exif-rotates them and shows the thumbnails with the newest on top. They can be selected without the need of holding ctrl or shift by checking a checkbox and a simple click starts the email program with the selected pictures attached to a new message.

The code consist of a class TThumbViewer derived from TScrollBox and a TThumb derived from TPanel. This panel contains a TImage, a TLabel and a TCheckBox. A load procedure gives the ThumbViewer a StringList containing the paths to the pictures. This procedure loads the TThumbs into a TScrollBox, positions them and makes them visible. Finally a TThread is started that uses a fast loading and scaling algorithm to draw the thumbs into the TImages.   
That's all :)

This program runs without major issues, maybe because of a low number of pictures. But know I started a new project with the need of larger 'thumbs', that results in the mentioned error. 

So to return to the DrawGrid I would assume that I would have to create a canvas containing the image, the label and the checkbox and creating this canvas again every time the thumb is selected or checked. Isn't that more complicated than my solution?

Chris


Fish

  • New member
  • *
  • Posts: 11
Hi JuhaManninen,

Quote
In your example the Top positions are calculated in TCustomFlowPanel.AlignControls.
Panel heights are summed which leads to overflow. I don't think there is a way around this restriction, although it should not raise an exception. How to handle the situation better? Ideas?

in the mentioned Delphi post it seems as if Delphi would simply stack those 'bad' panels at the last possible position. At least that wouldn't crash the program.
Quote
Note: with your example it was predictable, more like a feature than a bug.

Sorry, didn't get that.

Chris

JuhaManninen

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3412
  • I like bugs.
in the mentioned Delphi post it seems as if Delphi would simply stack those 'bad' panels at the last possible position. At least that wouldn't crash the program.
Yes, it is a good idea. I patch would be welcome. Otherwise I can look at the issue later this week again.

Quote
Quote
Note: with your example it was predictable, more like a feature than a bug.
Sorry, didn't get that.
I meant your case was not one of the random overflows which inspired me to add the test and exception.
Or, maybe the same code in TCustomFlowPanel.AlignControls caused them in some rare corner case situations, dunno.

Fish

  • New member
  • *
  • Posts: 11
Quote
Yes, it is a good idea. I patch would be welcome. Otherwise I can look at the issue later this week again.
Sorry, but my knowledge is far from being enough to write patches.

Okay, seems as if I found a possible solution.
According to a answer of NGLN at stackoverflow.com:
Quote
So (just) before reaching the magic limit, fool Windows by scrolling the scroll box:

Code: Pascal  [Select]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   I: Integer;
  4.   J: Integer;
  5.   P: TPanel;
  6. begin
  7.   ScrollBox1.DisableAlign;
  8.   try
  9.     ScrollBox1.VertScrollBar.Range := 400 * 202; // 80,800 ! ;-)
  10.     for I := 0 to 3 do
  11.     begin
  12.       ScrollBox1.VertScrollBar.Position := I * 100 * 202;
  13.       for J := 0 to 99 do
  14.       begin
  15.         P := TPanel.Create(Self);
  16.         P.SetBounds(0, J * 202, 100, 200);
  17.         P.Align := alCustom;
  18.         P.Caption := IntToStr(I * 100 + J);
  19.         P.ParentBackground := False;
  20.         P.Color := Random(clWhite);
  21.         P.Parent := ScrollBox1;
  22.       end;
  23.     end;
  24.   finally
  25.     ScrollBox1.VertScrollBar.Position := 0;
  26.     ScrollBox1.EnableAlign;
  27.   end;
  28. end;

I did some test and finally removed the loop that scrolls the ScrollBox.
To my surprise it worked.

Code: Pascal  [Select]
  1. procedure TForm1.Button2Click(Sender: TObject);
  2. var
  3.   J, t: Integer;
  4.   P: TPanel;
  5. begin
  6.   t := 0;
  7.   ScrollBox1.DisableAlign;
  8.   try
  9.     for J := 0 to 999 do
  10.     begin
  11.       P := TPanel.Create(Self);
  12.       P.SetBounds(0, J * 202, 100, 200);
  13.       P.Align := alCustom;
  14.       P.Color := Random(clWhite);
  15.       P.Parent := ScrollBox1;
  16.       P.Caption := IntToStr(J) + '/' + IntToStr(p.Top);
  17.       t += 202;
  18.     end;
  19.   finally
  20.     ScrollBox1.EnableAlign;
  21.   end;
  22. end;
  23.  

This adds 1000 panels to a ScrollBox where the TOP of the last panel is 201798 (!)
So for some unknown reason setting the position of a component with .top fails while using SetBounds is the solution (ehem, as jamie already suggested in his first post)

Thanks again
Chris

jamie

  • Hero Member
  • *****
  • Posts: 785
I suspect the reason why that may have worked is due to the fact that when setting the
Top, left one at a time, processing is taking place, could even be a ProcessMessages call being
made there somewhere..

 Using SetBounds gets all of the size all at once so that Resize is called only once and only if any
values differ from what is already there.

Fish

  • New member
  • *
  • Posts: 11
Cheered too soon.
Setting positions by SetBound works, but it only works when you add controls to a ScrollBox. As soon as you use the same code in the OnResize event the program crashes under Laz1.9Trunk showing the '.SendMoveSizeMessages' error. There's no crash or error under Laz1.6, but the controls are positioned in a strange way.
Attached are two screenshots. P1 shows 1000 panels added without any problems to a ScrollBox with properly scaled ScrollBar and adjusted range. P2 shows the same panels after resizing the form (Laz1.6). Note that parts of the panels are stacked. Panels with a higher Top value (behind the slash) lying above the ones with lower values.
I have no glue about the internal differences between filling a ScrollBox and resizing a ScrollBox but the current situation is weird and unsatisfying, not only because you are limited to static unresizable forms. 
For those you want to test for themselves I attached the demo project.

Chris

josh

  • Hero Member
  • *****
  • Posts: 602
Hi

Even in your button click event, your trying to create panels that top value exceeds the limits of smallint.
I have added in a check in your button click event to show the error, and stop creating more panels out of range.
Code: Pascal  [Select]
  1. procedure TForm1.Button1Click(Sender: TObject);
  2. var
  3.   i: Integer;
  4.   PanelsPerRow, CurrRow, CurrPos: Integer;
  5.   p: TPanel;
  6. begin
  7.   l := 0;        //Left
  8.   t := 0;        //Top
  9.   PanelsPerRow := ScrollBox1.ClientWidth DIV (w + s);
  10.   ScrollBox1.DisableAlign;
  11.   try
  12.     for i := 0 to c do
  13.     begin
  14.       CurrRow := i DIV PanelsPerRow;
  15.       CurrPos := i MOD PanelsPerRow;
  16.       t := CurrRow * (h + s);
  17.       l := (w + s) * CurrPos;
  18.       if (l < Low(Smallint)) or (l> High(Smallint)) or (t< Low(Smallint)) or (t> High(Smallint)) then
  19.       begin
  20.         showmessage('Create Component No:'+inttostr(i)+' co-ordinates out of range of SmallINT'+slinebreak+
  21.                      'Left :'+IntToSTr(L)+slinebreak+'Top  :'+IntToStr(T));
  22.         Exit;
  23.       end;
  24.       p := TPanel.Create(Self);
  25.       p.SetBounds(l, t, w, h);
  26.       p.Caption := Format('%d (%d/%d)', [i+1, l, t]);
  27.       p.Color   := Random(clWhite);
  28.       p.Parent  := ScrollBox1;
  29.     end;
  30.   finally
  31.     ScrollBox1.EnableAlign;
  32.     Form1.Caption := Format('Scrollbar Range: %d', [ScrollBox1.VertScrollBar.Range]);
  33.   end;
  34. end;                    
  35.  

Similarly a change to your re_size routine to stop moving a panel an out of bounds area.

Code: Pascal  [Select]
  1. procedure TForm1.FormResize(Sender: TObject);
  2. var
  3.   i: Integer;
  4.   PanelsPerRow, CurrRow, CurrPos: Integer;
  5. begin
  6.   RadioButton1.Checked:=Not RadioButton1.Checked;
  7.   ScrollBox1.DisableAlign;
  8.   PanelsPerRow := ScrollBox1.ClientWidth DIV (w + s);
  9.   try
  10.     for i := 0 to ScrollBox1.ControlCount-1 do
  11.     begin
  12.       CurrRow := i DIV PanelsPerRow;
  13.       CurrPos := i MOD PanelsPerRow;
  14.       t := CurrRow * (h + s);
  15.       l := (w + s) * CurrPos;
  16.       if (l < Low(Smallint)) or (l> High(Smallint)) or (t< Low(Smallint)) or (t> High(Smallint)) then
  17.       begin
  18.         showmessage('Resize Component No:'+inttostr(i)+' co-ordinates out of range of SmallINT'+slinebreak+
  19.                      'Left :'+IntToSTr(L)+slinebreak+'Top  :'+IntToStr(T));
  20.         Exit;
  21.       end;
  22.       TPanel(ScrollBox1.Controls[i]).Caption := Format('%d (%d/%d)', [i+1, l, t]);
  23.       TPanel(ScrollBox1.Controls[i]).SetBounds(l, t, w, h);
  24.     end;
  25.     //ScrollBox1.VertScrollBar.Range := t + h;
  26.     Form1.Caption := Format('Scrollbar Range: %d', [ScrollBox1.VertScrollBar.Range]);
  27.   finally
  28.     ScrollBox1.EnableAlign;
  29.   end;
  30. end;              
  31.  

I appreciate this does not solve your problem, but it shows what is causing your design to fail.

This article I just googled may help
https://stackoverflow.com/questions/8064678/windows-forms-panel-32767-size-limit
« Last Edit: February 10, 2018, 03:41:25 pm by josh »
Development Installation Lazarus 1.3, FPC 2.7.1,Windows 7/8 32/64, OSX, *nix

Test Environment Lazarus & FPC Trunk on Windows and OSX (Cocoa Mainly on OSX). Testing also Crosscompile windows to OSX.. 
Any posts made from 2015 will be based on Lazarus Trunk.

jamie

  • Hero Member
  • *****
  • Posts: 785
yes you are obviously over doing the client scrolling area..

The best solution and most practiced is to use a DrawGrid or in your case since you are
already miles deep into your methods of coding is to limit the actual number of panels made.
you only need panels to display your images, these panels only need to be created to a point where
you have enough to cover a couple of scrolls of a screen..

 For example:
   
  Lets assume you have 1000 images and you want to scroll them vertically in a single column, what you would
do is create no more than lets say 50 panels, enough to cover scroll up and down visual voids.  use those sets of
panels only and select a chunk of images in a list somewhere to view and each panel can have some sort of
title you update also to reflect it.

  Just think of a these 50 panels sliding up/down a list of images. You simply set the first panel plus an OFFSET
value to a list of images. The offset would be anywhere in a list of images that far exceed the 50 count and from
there you simply populate your 50 panels starting from that offset and move onwards.

 This is the most logical way of doing such an feat. I do think the TdrawGrid does the same, it simply only has enough
cells to make sure it covers the whole client area.

 In your case however, you can disable the native scrollbar and use a scrollbar control or manipulate the current
scrollbar position,

   its all about design.. I suppose the limit could be fixed if the issues for the reason the limit was put there in the first place are corrected.

 

Recent

Get Lazarus at SourceForge.net. Fast, secure and Free Open Source software downloads Open Hub project report for Lazarus