tags:

views:

62

answers:

2

Hi.
I want to make a string grid to display some kind of vertical cursor to highlight the current selected column. So, in MouseDown I call setCurPos, then I call InvalidateCol to invalidate current column. This calls the DrawCell. DrawCell paints the cursor on current column.

The problem is this: if I have more rows in grid then it can display some of them will not be visible (of course) so grid's vertical scroll bar will automatically appear. When I scroll down to see the rows at the bottom of the grid, the cursor is not painted in these rows. It looks like the number of bottom rows (now visible on screen) in which the cursor is NOT painted is proportional with the number of invisible rows in the top of the grid.

If I minimize and restore the application, the cursor is nicely painted. So, obviously the invalidateColumn() is not working.

procedure TmyGrid.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
VAR aCol, aRow: Integer;
begin
 MouseToCell(X, Y, ACol, ARow);
 ...                                                                  
    inherited MouseDown(Button, Shift, X, Y); 
    CursorPosFocus:= ACol;                          
end;


procedure TmyGrid.setCurPos(CONST NewColumn: Integer);                 
VAR OldPos: Integer;
begin
 ...
 OldPos:= CursorPos;
 FCursorPos:= NewColumn;    
 ...
 //- This is not working:
 //InvalidateCol(OldPos);
 //InvalidateCol(NewColumn);    
 //Update;

 //- THIS WORKS:
 InvalidateGrid; 
end;


procedure TmyGrid.DrawCell(ACol, ARow: integer; ARect: TRect; AState: TGridDrawState);
Var TempRect: TRect;
begin
 inherited;
  ...

 {DRAW CURSOR}
 if CursorPos= ACol then
  begin
   TempRect.Top   := 0;
   TempRect.Left  := ARect.Left;
   TempRect.Right := ARect.Right;
   TempRect.Bottom:= ClientHeight-2;     
   Frame3D(Canvas, TempRect, $909090, $808080, 1);       
  end;
end;

Delphi 7, Win XP

A: 

Solution:
After a lot of experimenting I realized that however InvalidateGrid is working.

I am happy with this solution but I don't really agree with accidental programming. I would still like to know what I am doing wrong. Why the column is not invalidated by InvalidateCol().

Altar
+3  A: 

You are doing nothing wrong, you just got caught by a bug in the VCL grid implementation that has been in the Delphi 4 VCL (I don't have any earlier CD here to check, but it might even have been in the 16 bit Delphi VCL already) and is still with us in Delphi 2009.

Both methods to invalidate a whole row or column do this by calculating an area of cells that is passed to the internal InvalidateRect() method. This area always starts with the column / row 0, and extends to the first completely invisible row / column. It's quite obvious that this will only ever work correctly for an unscrolled client area. What the code should do instead is invalidate to the last column / row, and let the code in the InvalidateRect() helper figure out which cells are indeed visible and calculate the client area that needs to be invalidated from that.

Since you are writing your own class you could easily implement your own methods to invalidate the correct range of cells; I did the same many years ago, together with more methods to invalidate multiple columns, multiple rows and whole blocks of cells. Since InvalidateRect() is private (great thinking there too) you need to use the Windows API function with the same name, and calculate the rectangle to be invalidated using either the CellRect() or BoxRect() method.

While InvalidateGrid() does work for you it is really kind of a sledge hammer - it invalidates the whole grid, which I think is not what you wanted when you started using InvalidateCol().

For your experiments you should make paint cycles for each cell easily visible. Causing the background colour of the cells to change with each update is a simple way to check that you really only do minimum screen redraws. Something like

StringGrid1.Canvas.Brush.Color := RGB(Random(256), Random(256), Random(256));
StringGrid1.Canvas.FillRect(Rect);

in the OnDrawCell event handler works fine.

mghie
Thanks a lot for your answer. I hate when bugs are there for years. This is why I stopped long time ago to report bugs on Borland's web site. It is such a waste of time. I will use the InvalidateGrid for the moment. But in the future I plan to use it with large grids so I would have to apply your solution.
Altar
I agree with your sentiments re Delphi bugs. Regarding grid size - it doesn't really matter whether a grid is large or small, as only the visible cells are affected by `InvalidateXXX()`, and for the painting performance it's irrelevant whether the visible area is 50% or 0.05% of the whole grid. But if you don't need maximum painting performance, simply use `InvalidateGrid()` or even `Invalidate()`. Otherwise use a custom `InvalidateCol()`, and call `Update()` between multiple calls, to keep Windows from combining multiple non-adjacent regions into a single larger one.
mghie