Recent

Author Topic: Clip stringgrid components from fixed row  (Read 8427 times)

Gary Randall

  • Jr. Member
  • **
  • Posts: 70
Clip stringgrid components from fixed row
« on: March 07, 2017, 04:14:04 pm »
I have a large stringgrid requiring scrollbars.  The first row has text titles and is fixed.  I place custom graphic components on the stringgrid at runtime which don't scroll automatically.  They are scrolled using stringgrid.ScrollBy(DeltaX,DeltaY) in the OnTopLeftChanged event.  This works fine except the components can scroll over the fixed row, unlike cell contents which are clipped.  I've tried setting a canvas cliprect but either I don't understand clipping (most likely) or clipping doesn't work with ScrollBy.  Can anyone show me the way?  The components are larger than one cell and need to be clipped.  Note that I'm using Version 1.4.0 because several features in my code won't compile with later versions, and I can't take the time to solve those problems.

Version 1.4.0
Date 2015-04-18
FPC Version 2.6.4
SVN Revision: 48774
i386-win32-win32/win64

Windows 7 Home Premium 64 bit - SP 1
Lazarus Version #: 1.8.0; FPC Version: 3.0.4
SVN Revision 56594
i386-win32-win32/win64

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: Clip stringgrid components from fixed row
« Reply #1 on: March 07, 2017, 07:13:20 pm »
I don't think that there's a straightforward way to solve this.

Maybe try this: Do not add TImage components to the grid at all, but keep a list of the bitmaps and the cells to which the top-left image corner is anchored. Inherit a new class from TStringGrid and override the DrawAllCells method. At the end of this method, all the painting is finished and you can set the clip rect as you need and then paint the images over the grid; get the position of the images from the image list. Intersect the non-fixed cells rect with the image rect to decide whether an image is off-screen.

Try the code in the attachment, it is working for me. You may have to modify the path to the Lazarus cheetah loaded when the program starts (const IMAGE_FILE). A button-click opens a file dialog to load other images to be anchored at the currently selected cell.

[EDIT]
Rereading your first post more carefully, I notice that my answer maybe is a bit off-topic since you are talking of embedded "graphic components", not of "images" as I assume here.
« Last Edit: March 08, 2017, 04:38:21 pm by wp »

Gary Randall

  • Jr. Member
  • **
  • Posts: 70
Re: Clip stringgrid components from fixed row
« Reply #2 on: March 08, 2017, 06:09:19 am »
Thanks WP, this looks interesting.  It will probably take me a couple of days to digest and try your solution.  I don't really understand how the component scrolling works but this should be a good lesson.

Windows 7 Home Premium 64 bit - SP 1
Lazarus Version #: 1.8.0; FPC Version: 3.0.4
SVN Revision 56594
i386-win32-win32/win64

tk

  • Sr. Member
  • ****
  • Posts: 361
Re: Clip stringgrid components from fixed row
« Reply #3 on: March 08, 2017, 08:25:39 am »
I place custom graphic components on the stringgrid at runtime which don't scroll automatically.  They are scrolled using stringgrid.ScrollBy(DeltaX,DeltaY) in the OnTopLeftChanged event.

This is very bad practice. You should paint your graphics in OnDrawCell.
This would work in normal stringgrid.
Plus in KGrid you can use cell clipping and it also fully supports images in cells, out of the box.

Gary Randall

  • Jr. Member
  • **
  • Posts: 70
Re: Clip stringgrid components from fixed row
« Reply #4 on: March 08, 2017, 07:38:42 pm »
Thank you TK.  I briefly looked at KGrid in the beginning but decided to use standard Lazarus features so I would understand their shortcomings before looking further.  Sounds like this may be the time.  A big concern is cross-platform coding.  My custom graphic components are bitmaps dropped on the grid from a context menu and then edited and moved as needed.  Their top/lt position is initially the center of the selected cell.  Each cell represents a day of the week.   The program is essentially a CAD program to draft schedules.  Once placed on the grid the components are scrolled separately using the stringgrid's ScrollBy command.  Since they only need to be redrawn when the grid's top/left cell changes I do it there.  Wouldn't redrawing them in the OnDrawCell event increase overhead? 

I do have other issues getting the non-visible custom components onto a full size printer.canvas with transparency handled correctly.  Your OnDrawCell  suggestion may be the answer.  Are you saying the components would scroll like cell contents with clipping?  The grid is very large and any scrolling requires redrawing the non-visible custom components.  Any guidance will be appreciated.

Attached is an example of my grid.  Most of the column headings are merged cell rectangles.  The graphic cell contents are from a custom font.  The circle and triangle are my custom graphic components.
   
Windows 7 Home Premium 64 bit - SP 1
Lazarus Version #: 1.8.0; FPC Version: 3.0.4
SVN Revision 56594
i386-win32-win32/win64

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: Clip stringgrid components from fixed row
« Reply #5 on: March 08, 2017, 10:05:10 pm »
OnDrawCell would only draw an image within a single cell. In order to have an image extend over several cells you must repaint it in tiles cell by cell, with the correct offset applied. And I am not sure about the grid lines - I think they are not included within the cliprect passed to the OnDrawEvent and will be excluded when painting the tiled image. Just painting the images over the completed grid is much easier and requires only a few lines of code. Use a png with transparency to see the grid background around the circle and the triangle (the attached modified project can handle this). And since images can also be drawn on a printer canvas I don't see too many difficulties with printing.

The attached project is an extended version of the one posted above
  • Can handle several image formats
  • Draws png with transparency correctly
  • Allow offset of image with respect to anchor cell in grid
  • Some simplification here and there
« Last Edit: March 08, 2017, 10:26:15 pm by wp »

tk

  • Sr. Member
  • ****
  • Posts: 361
Re: Clip stringgrid components from fixed row
« Reply #6 on: March 09, 2017, 09:13:07 am »
Actually, painting those large images (or whatever else) over many stringgrid cells is another bad practice, for many reasons.
Try to find another layout. You can put those triangles or circles into another column and make the rows taller so they fit into one cell.
Or remove them entirely and use another indicator, outside of the grid, custom hint etc.


wp

  • Hero Member
  • *****
  • Posts: 11923
Re: Clip stringgrid components from fixed row
« Reply #7 on: March 09, 2017, 09:31:05 am »
Actually, painting those large images (or whatever else) over many stringgrid cells is another bad practice, for many reasons.
Why? The only reasons I could imagine are
  • flicker because the same screen area is painted twice, first as a grid and then and as an image. But I did not see any flicker in my tests. How do you solve it in kgrid (you were saying that it supports images)?
  • the possibility to select a cell underneath the grid and enter data there. But this can be prevented by some extra code. And BTW, Excel has the same issue.

tk

  • Sr. Member
  • ****
  • Posts: 361
Re: Clip stringgrid components from fixed row
« Reply #8 on: March 09, 2017, 08:57:47 pm »
Why?

Correct way to paint such countours in a grid would be to create a merged cell and paint it there. This is how I would make it for example in kgrid.
Painting over cells needs additional code (cell selecting issue you mentioned, grid scrollbox sizing issue might arise in case the shape overlaps last column or row, problems with synchronous positioning, clipping issues etc.).
Unless he creates a new spreadsheet with overlapped objects (like Excel does it), he should IMO avoid such things.
It is a mess in Excel too - how many times I moved the embedded chart to see if the cells behind were empty.
But in a spreadsheet I cannot imagine another approach because the cells behind the shape must be ready to hold values or formulas.

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: Clip stringgrid components from fixed row
« Reply #9 on: March 10, 2017, 06:02:34 pm »
In the attachment there's a new version of the demo program. Now a click onto an image is no longer passed to the grid, but  is converted to the new event OnImageClick. Similarly, if an image has been clicked and now the mouse is dragged over the grid, there is a new event OnImageMove which allows to move the image with the mouse to a new location. The event gets parameters to decide if dragging should occur or not. The demo works such that the CTRL key must be held down during dragging in order to move an image. Note that it is only the enclosing rectangle which is checked for a mouse click, i.e. transparent regions of the png images are considered to be part of the image which may be a bit confusing here and there.

I am planning to add this demo to the examples/gridexamples folder of Lazarus.

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: Clip stringgrid components from fixed row
« Reply #10 on: March 10, 2017, 06:59:17 pm »
And since images can also be drawn on a printer canvas I don't see too many difficulties with printing.
No, I am wrong here: the idea of painting images over the grid does not work for printing because the already-printed grid will shine through the image printed later. Maybe the way to go is to paint tiles of the printout to a bitmap first and then to print the bitmap.

Gary Randall

  • Jr. Member
  • **
  • Posts: 70
Re: Clip stringgrid components from fixed row
« Reply #11 on: March 27, 2017, 09:56:43 pm »
Thanks everyone for your input.  I had to take some time off from the project plus review wp's code.  I learned a lot and almost understand all of it.  My biggest problems are that the grid's design (cell sizes, control placement, etc.) can't be changed, and cross-platform coding (Windows, Linux, Mac) is a must.

Attached is the code I use to scroll my custom bitmap controls which works nicely. The controls simply draw a bitmap on the screen.  The bitmaps are not saved but it would be easy to save them in a separate list.  TStringGrid.ScrollBy just changes the anchor positions of all the controls (visible components).  When done it invalidates the grid, forcing the OnDrawCell event to redraw everything.  My OnDrawCell does not set a clipping value but the cell contents are clipped when scrolled over a a fixed row.  The controls are clipped at the grid's limits, not the fixed row.  It seems to me that any control clipping must be done by the OnDrawCell event, and it should be simple to set the clipping bounds there.  However, I haven't figured out how to do that if it can be done.

I'd be interested in anyone's suggestions on whether and how control clipping can be done in the OnDrawCell event.  If it can't be done is this a doable feature request?

Code: Pascal  [Select][+][-]
  1. procedure TMainForm.sgMainTopLeftChanged(Sender: TObject);
  2. var
  3.   DeltaX, DeltaY: integer;
  4.   PrevTopLtRect, CurTopLtRect: TRect;
  5. begin
  6.   with sender as TStringGrid do begin;
  7.     {Save new topleft col/row position in pushdown array.  VisTopLtPos is a TPoint
  8.      array declared in forms's private vars and initialized in OnFormCreate}
  9.     VisTopLtPos[1] := VisTopLtPos[0];
  10.     VisTopLtPos[0] := Point(sgMain.LeftCol, sgMain.TopRow);
  11.     if (sgMain.ControlCount > 0) then begin
  12.       {Calculate control's scroll coordinates (pixels)}
  13.       PrevTopLtRect := sgMain.CellRect(VisTopLtPos[1].x, VisTopLtPos[1].y);
  14.       CurTopLtRect := sgMain.CellRect(VisTopLtPos[0].x, VisTopLtPos[0].y);
  15.       DeltaX := PrevTopLtRect.Left - CurTopLtRect.Left;
  16.       DeltaY := PrevTopLtRect.Top - CurTopLtRect.Top;
  17.       sgMain.ScrollBy(DeltaX, DeltaY);
  18.       {All controls' anchor positions have been revised by DeltaX and DeltaY,
  19.        and the grid has been invalidated and redrawn by sgMainDrawCell}
  20.     end;
  21.   end;
  22. end;
  23.  


Windows 7 Home Premium 64 bit - SP 1
Lazarus Version #: 1.8.0; FPC Version: 3.0.4
SVN Revision 56594
i386-win32-win32/win64

Gary Randall

  • Jr. Member
  • **
  • Posts: 70
Re: Clip stringgrid components from fixed row
« Reply #12 on: March 30, 2017, 04:17:27 pm »
Fooling around with this I've changed my mind about clipping controls in the OnDrawEvent.  I'm beginning to see why WP said "I don't think that there's a straightforward way to solve this".  In the OnDrawCell event the ClipRect is read-only and has the area of the current cell. 

I believe WP's DrawAllRows procedure replaces my use of TStringGrid.ScrollBy(DeltaX, DeltaY).  Rather than display all of a control's image it clips out any part that is over a  fixed  row or column.  Any subsequent calls to OnDrawCell should not touch the image unless the underlying cell content is changed.  Is that right?  If so, I should be able to either replace/modify ScrollBy with code to clip the control's image, or modify my control to do the clipping before it draws its image to the grid's canvas.  It seems to me that the latter method may be the proper way since the controls are being redrawn whenever the large grid's layout is changed, which can be caused by intentional scrolling, adding/removing rows, or editing/moving a control.  Typically, controls are not placed over cells with data.

Here is the code for the ScrollBy procedure:

  // scroll inner controls
  for i := 0 to ControlCount - 1 do
    with Controls do
      SetBounds(Left + DeltaX, Top + DeltaY, Width, Height);

Please, I welcome all suggestions. 
Windows 7 Home Premium 64 bit - SP 1
Lazarus Version #: 1.8.0; FPC Version: 3.0.4
SVN Revision 56594
i386-win32-win32/win64

wp

  • Hero Member
  • *****
  • Posts: 11923
Re: Clip stringgrid components from fixed row
« Reply #13 on: March 30, 2017, 04:51:42 pm »
My feeling is that adding controls to the grid makes things very complicated - but ok: my solution is not easy either... In the Paint procedure the grid paints the cells, fixed cells and grid lines - this is where the OnDrawCell event originates. Then, since the grid has children now, it paints the children. In this second phase the clip rect is defined by the grid's bounds, not by the fixed cells - therefore your controls are painted over the fixed cells. You cannot intercept OnDrawCell since this already has been called when the controls are painted. The only thing I could imagine would be to intercept the Paint method (you need a descendant stringgrid to get access to it!) and after calling inherited you must redefine the cliprect to the rectangle without the fixed cells. Then, the controls hopefully will respect the new, smaller cliprect to be cut off at the fixed cells.

I don't know if this works, and I don't know if this is simpler than my method of just painting images over the completed grid. But you'll defininitely also need a new class inherited from TStringGrid, similar to my solution.

OnDrawCell (which would work within the standard StringGrid) might work if - like in my approach - you paint the images over the grid. You must store a list of the image bitmaps and their positions, i.e. the cells where they are anchored plus their offset from the anchor coordinates (again, like in my solution). Then, in the OnDrawCell event you iterate through the image list and calculate the intersection of the cell rectangle (known as a parameter of the OnDrawCell event) and the rectangle occupied by each image. The image position must be calculated to be relative to the cell, i.e. it must take care of the Top/Left scrolling offset. Due to the cliprect of the OnDrawCell event only the part of the image being inside the cell rect will be painted. If the image reaches into the adjacent cell the procedure will be repeated for that new section if the image.

There are two disadvantages:
- If the image is large it will be painted as often as there are cells into which it is reaching
- Since the cell border is drawn after painting the cell content you will still see the cell border lines as if they were painted on top of the image.

Gary Randall

  • Jr. Member
  • **
  • Posts: 70
Re: Clip stringgrid components from fixed row
« Reply #14 on: April 01, 2017, 05:56:27 pm »
Thanks wp!  I'm beginning to understand stringgrid drawing from your explanations.  I see now that OnDrawCell is not the way to go for the reasons you stated.  I've got a lot of time invested in developing control classes which work just the way I want except for the clipping and printing problems.  I think I've solved the printing problem. 

Is there any reason a control's OnPaint event can't just clip the bitmap image before invoking Canvas.Draw so it doesn't overlap fixed rows/cols?  Typically the OnPaint event is called initially at least three times.  Basically anytime a control's parameters are changed (position, text, grid changes, restoration, etc.).  It seems to me that a stringgrid should automatically clip a control if it touches a fixed row/col.  Just as it does when scrolled off the grid's boundary.

Could you explain this code in your example? I'm really confused by ColRowToOffset:
Code: Pascal  [Select][+][-]
  1.   // Calculate the clip area, i.e. the rectangle enclosing the non-fixed cells...
  2.   clipArea := Canvas.ClipRect;
  3.   ColRowToOffset(true, false, FixedCols, clipArea.Left, tmp);
  4.   ColRowToOffset(false, false, FixedRows, clipArea.Top, tmp);
  5.  
     
« Last Edit: April 01, 2017, 06:04:57 pm by Gary Randall »
Windows 7 Home Premium 64 bit - SP 1
Lazarus Version #: 1.8.0; FPC Version: 3.0.4
SVN Revision 56594
i386-win32-win32/win64

 

TinyPortal © 2005-2018