Delphi Firemonkey TGrid how to update - delphi

I have a TGrid with a mixture of columns (ImageColumn and StringColumn). I can populate it using onGetValue event which works fine. My questions are:
How to force the entire grid to rebuild and cause onGetValue event?
I'm using UpdateStyle at the monent.
How can I update a single cell in the grid?

The grid updates only visible cells! Grid1.UpdateStyle force the grid to rebuild and is causing onGetValue events but its slow. Grid1.ReAlign is much faster.
As soon as cells become visible, they will be updated.
Updating 1 cell:
procedure TForm1.UpdateCell(col, row: integer);
var
cell: TStyledControl;
begin
cell := Grid1.Columns[col].CellControlByRow(row);
if Assigned(cell) then
cell.Data := 'Note: use the same datasource as OnGetValue';
end;
cell is not assigned when row never become visible.

The other option is to call Grid1.beginUpdate; make your changes and then call Grid1.endupdate; which will cause the visible grid to recalculate and redraw.

Related

Delphi StringGrid refresh

Learning Delphi (have a ways to go), using Rio.
I figured out how to use a colored background in TStringGrid rows - but it looks like I need to refresh when data in those rows changes (so as to get different colors to show up based on the data changes).
I thought that just setting the cell values to their then-existing values would cause the refresh. But it didn't. I could tell for sure that it didn't - because I had a debug breakpoint placed within the StringGrid1DrawCell procedure - and that breakpoint was not hit.
The code that I had been using to hopefully cause the refresh in TStringGrid was as follows (note: S is defined as a String):
S := StringGrid1.Cells[1, i];
StringGrid1.Cells[1, i] := S;
Is the basic assumption (that just setting/resetting values of the cell contents causes a refresh) in error?
If the idea is right, but the method is wrong: could you let me know what to do differently?
The OnDrawCell event is fired only when a given cell needs to be painted onscreen.
Setting a specific cell's value will invalidate only that cell, thus triggering a repaint of only that cell, not the cell's entire row, or the grid as a whole.
If you need to trigger a repaint of an entire row, call the grid's (protected) InvalidateRow() method, eg:
type
TStringGridAccess = class(TStringGrid)
end;
procedure TMyForm.DoSomething;
begin
...
StringGrid1.Cells[1, i] := ...;
TStringGridAccess(StringGrid1).InvalidateRow(i);
...
end;
If you need to trigger a repaint of the entire grid, call the grid's (public) Invalidate() method, eg:
StringGrid1.Cells[1, i] := ...;
StringGrid1.Invalidate;

Delphi, expand grouped by grid by default

I have this procedure for a grid in Delphi, and I need to add this property to expand all collapsed grouped by data in the grid.
procedure TProjectForm.dxDBGridEventDrawCell(
Sender: TcxCustomGridTableView; ACanvas: TcxCanvas;
AViewInfo: TcxGridTableDataCellViewInfo; var ADone: Boolean);
begin
inherited;
DrawHighlight(ACanvas);
TcxGridDbTableView.ViewData.Expand(True);
end;
I get the following error:
E2233 Property 'ViewData' inaccessible here
Help is appreciated please. And I also need to remove the collapsible button for the grouped data in this grid. Thank you
You can call cxGrid1DBTableView1.ViewData.Expand(True) without problems as long as you don't do in in a drawing event like the one in your q. However, you don't actually need to do this if you use the example below.
This works fine
procedure TDevexGroupingForm.FormCreate(Sender: TObject);
begin
cxGrid1DBTableView1.Columns[2].GroupIndex := 0; // group by the 3rd column
// NOTE: this step is only necessary if the table view has not been grouped at design-time
cxGrid1DBTableView1.DataController.Options := cxGrid1DBTableView1.DataController.Options
+ [dcoGroupsAlwaysExpanded]; // this hides the +/- buttons of the grouped nodes
cxGrid1DBTableView1.DataController.FocusedRowIndex := 0; // focuses the first group
end;
Note : This has been updated at #Nil's suggestion.
The first line, setting a column's GroupIndex is only necessary if the TableView has not already been grouped at design time.
Setting the FocusedRowIndex is optional, depending on how you want the TableView to display initially
So in fact the hiding of the +/- grouping buttons and the expansion of all the top-level group nodes can be achieved by the single step of setting the DataController Options property to include the dcoGroupsAlwaysExpanded option.
Btw Setting the DataController options to suppress the drawing of the expand/collapse buttons and is derived from the article https://www.devexpress.com/Support/Center/Question/Details/Q105527/how-do-i-hide-the-expand-button-on-a-grid

TcxGrid Auto posting if another row is focused?

I'm using Delphi the devexpress components.
I'm running into a annoying problem where the grid seems to call a postdata if I'm inserting a new row and then accidently click on a different row.
I'm not sure the TcxGrid is doing this but I want to know if there is a property I could set to prevent this from happening?
As fas as this is not a problem of the grid, but of the dataset, the only way to intervene is preveting a post for conditions you have to define.
procedure TForm.aDatasetBeforePost(DataSet: TDataSet);
begin
if YourConditionForInvaliddata then
begin
Dataset.Cancel;
Abort;
end;
end;

How to avoid TDbgrid scrolling when returning to a previous location

In the code below, we do some operations (not deletions) on some selected rows.
However, sometimes, when finished, the top selected row has scrolled so that it is displayed 1/2 way down the grid. Is there a way to avoid this scrolling? (If my code to traverse the selected row below is improper for some unrelated reason, I welcome corrections.)
Function TForm.DoSomethingToSelectedRows;
var
KeyAtStart: Integer;
begin
Result := TRUE;
KeyAtStart := DataSet.FieldByName('Key').AsInteger;
DataSet.DisableControls;
DataSet.First;
try
while Result AND (NOT DataSet.EOF) do DataSet
begin
if DBGrid1.SelectedRows.CurrentRowSelected then
Result := ... do something ...
fMPODataTls.GetDS.Next;
end;
finally
DataSet.Locate('Key', KeyAtStart, []); // re-position where we started
DataSet.EnableControls;
end;
end;
Before looping through the dataset, you can keep note of the top row that the grid is displaying and the number of total records that is displayed. With this information, after you relocate the record, you can position the record to the exact row by moving either to the top or to the bottom and then moving back again.
You can carry out the moving by MoveBys. Unfortunately the Row and RowCount properties of TDBGrid is protected, for that you have to use what is widely known as 'protected hack'.
There's code example in this answer, together with checking bookmarks, so that you don't end up on a wrong record.
There is no guarantee that the current record in your grid is exactly positioned at the center of the visible rows, but doing a Locate when it has scrolled out of sight will make it (the new current record) the middle row of the grid, hence the apparent scrolling.
If you want to re-position the grid as it was, you have to memorize which record is at the top row.
If you are using a TClientDataset, you don't have to mess with the user's cursor, you can clone it and do your edits there.
If I have DBGrid1 linked to ClientDataSet1, and I have the cursor on row 5 of the DBGrid (and the ClientDataSet) and within view of the the first row, and delete the first row using a cloned cursor like below, I'd see the first row disappear and my selected row would go up by one row height.
Function TForm.DeleteFirstRow;
var
myCDS: TClientDataSet;
begin
myCDS := myCDS.Create(nil);
try
myCDS.CloneCursor(ClientDataSet1, False);
myCDS.First;
myCDS.Delete;
finally
myCDS.Free;
end;
If I was scrolled all the way down to row 500 or so, out of view of the first row, and executed it again, the selected row would not move at all in the grid.

Get screen coordinates of selected row in TListView

I have a TListView (actually, a custom descendant) display in ViewStyle vsReport. One row is selected. I would like to get the screen coordinates of that row (or a cell within that row). Is there any way for me to do this?
(My goal is to display a small form over the list view giving the effect that it dropped down from the selected row).
I am using Delphi 2010 for this particular application.
For a list view in vsReport style I believe the best approach is to use the LVM_GETITEMRECT and LVM_GETSUBITEMRECT messages.
The VCL does not wrap this functionality up for you but it should not be too difficult to work it out from the MSDN docs.
Whilst it is very simple to use the TListItem.Position property exposed by the VCL, as far as I can tell this does not help you obtain the row height or indeed the coordinates of sub items.
Update
As NGLN very helpfully points out, the CommCtrl unit does expose ListView_GetItemRect and ListView_GetSubItemRect which are more convenient to use than the equivalent Windows messages above.
var
sel: TListItem;
pnt: TPoint;
begin
sel := ListView1.Selected;
if not Assigned(sel) then Exit;
pnt := ListView1.ClientToScreen(Sel.Position);

Resources