Icon View Thread

The following is the text of the current message along with any replies.
Messages 1 to 6 of 6 total
Thread How to changed font/background of a TGrid cell based on a value outside of the cell?
Fri, Oct 8 2021 2:37 PMPermanent Link

Gyro Cosmic

I'm looking for a way to change the font/background of a cell in a TGrid based on a value outside of the cell. For example, I'd like to change the background of a cell based on the cell in the row above it in the grid or a cell to the left in the current row. (I was assuming I'd be able to do this in the OnCellUpdate event but I can't figure out how to get "outside" of the cell in order to look around.)

Has any one been able to or know of a way to do this?

Thanks,
GCR
Sun, Oct 17 2021 10:23 PMPermanent Link

Richard Harding

Wise Nutrition Coaching

Gyro

I had a similar problem. On Tim's suggestion, I created a "Shadow Table". "esStockSummary3" is the shadow table that determines if the row is a "HEADER".

The customer stores stock items in a database. Stock items with the same identity are equivalent and can be substituted. The first item that has the same identity is a "header" that needs to be highlighted. The Delphi version of the program did this. The customer considered this essential. It works - the customer is happy.

Each cell of the grid calls OnCellUpdate. When there is activity on the grid, there is about 5 or 6 "rounds" of OnCellUpdate called.

The first "round" or "group" of OnCellUpdate events does not have ACell.Data defined so you need to check that ACellData is assigned.

In the AfterLoad event for the lookup dataset (or the "shadow table"), you need to add Grid1.DataSet.First. This will force another set of OnCellUpdate events that will perform the formatting. Otherwise, the cells will not be formatted until you scroll the items in the grid.

Let me know if it does not make any sense.

Richard

//==================================================================================

procedure TfmMenu.GridColumn5CellUpdate(Sender: TObject; ACell: TGridCell);
//Called for the first column of the grid
var
  isHeader: boolean;
  Number:   integer;
  H1:       integer;

begin
  if (ACell.Data > '') then
  begin
    if grStockItems.DataSet = esStockSummary2 then
    begin
       if esStockSummary3.Find(['StockNumber'], [ACell.Data], false, true, true)then
       begin
          number := esStockSummary3.Columns['Number'].AsInteger;
          H1 := esStockSummary3.Columns['H1'].AsInteger;
          isHeader := (Number = H1);
       end else
       begin
         isHeader := false;
       end;
    end else
    begin
       isHeader := false;
    end;

    if grStockItems.DataSet = esStockSummary then
    begin
      ACell.Font.Style.Bold := false;
      ACell.Font.Color := clBlack;
      ACell.Background.Fill.Color := clWhiteSmoke;
    end else if (isHeader) then
    begin
      ACell.Font.Style.Bold := true;
      ACell.Font.Color := clBlack;
      ACell.Background.Fill.Color := clLightGray;
    end else
    begin
      ACell.Font.Style.Bold := false;
      ACell.Font.Color := clBlack;
      ACell.Background.Fill.Color := clWhiteSmoke;
    end;
  end;          // if assigned
end;

procedure TfmMenu.GridColumn6CellUpdate(Sender: TObject; ACell: TGridCell);
//Called for all columns of the grid except for the first
begin
  if (ACell.index mod 2 = 0) and
      (SameText(ACell.InterfaceState, NORMAL_STATE_NAME)) then
  begin
     ACell.Font.Color := clBlack;
     ACell.Background.Fill.Color := clWhite;

  end else if (SameText(ACell.InterfaceState, NORMAL_STATE_NAME)) then
  begin
     ACell.Font.Color := clBlack;
     ACell.Background.Fill.Color := clGhostWhite;
  end;
end;

//==============================================================================



Attachments: Stock Grid with Headers.jpg
Wed, Oct 20 2021 5:13 PMPermanent Link

Gyro Cosmic

Hi Richard ... thanks for the reply.

In my case this won't work. But I like the approach and I'm going to try the shadow table for another use: to flatten out a row oriented DataTable into columns. I'll switch the shadow table and live table though as I want the flattened table to be associated with the grid. We'll see how it works out.

The problem I have, which you don't, is that my ACell.Data wasn't unique and I can't tie it back to a particular row in the grid's datatable. The cell's data may appear in many different rows. After an email from Tim it turns out there is a solution. The ACell.Index value is a relative row but that combined with the Grids RowOffset property can get me to the grid row. Then with a bit of manipulation I can get to other data to figure things out. It's not intuitive but not too bad. I'll work up a generic example and post in in a follow-up so others can see it if they're searching the forum.

Again, thanks for the reply. The shadow table method does look useful.
--GCR
Wed, Oct 20 2021 6:30 PMPermanent Link

erickengelke

Richard Harding wrote:


> I had a similar problem. On Tim's suggestion, I created a "Shadow Table". "esStockSummary3" is the shadow
> table that determines if the row is a "HEADER".

I've solved this in a couple of ways on different occaisions.

A shadow table works... until someone resorts the rows and they are out of order.

A second solution I've used on other occasions is to add an invisible character chr(1), chr(2), etc. to the data field.
EWB doesn't print the first 31 of them if I recall correctly, and you can have multiple characters.

eg.  chr(1)+chr(31) + data.

first digit is foreground, second digit is background code.

Then in your on cell draw handler, you look at the first few characters and set the font or background appropriately.

It would be nice if EWB let gave you a cell's rowID and columnID, or had a Tag integer you could use. But this hack/solution lets you create very complex solutions and nobody knows better.

Erick
EWB Programming Books and Component Library
http://www.erickengelke.com
Wed, Oct 20 2021 6:36 PMPermanent Link

erickengelke

erickengelke wrote:

>A second solution I've used on other occasions is to add an invisible character chr(1), chr(2), etc. to the data
>field.EWB doesn't print the first 31 of them if I recall correctly, and you can have multiple characters.

It works well if you declare a 'virtual field'. By that I mean, suppose you have a DataSet with a field named Userid,
Make a second VUserid of char, then after reading the table, set Userid =  chr(1)+chr(31) + data
and display VUserid, processing the whole table.

Then have an OnCellDraw handler which looks at the first two characters to determine the foreground and background color or italics or whatever.
.
Good luck,
Erick

Erick
EWB Programming Books and Component Library
http://www.erickengelke.com
EWB Programming Books and Component Library
http://www.erickengelke.com
Thu, Oct 21 2021 7:56 AMPermanent Link

Gyro Cosmic

Based on Tim's comments (via email) here's the solution it looks like I'm going to use.

Within the OnCellUpdate event handler You can get a TGrid's database row number by using the ACell.Index value. This returns the relative ROW number from the first row being shown in the grid. The index value is not a column number as I'd first thought. To get the absolute row number you can use the following:

rowNo := TGridColumn(Sender).ParentGrid.RowOffset + ACell.Index + 1;

You can get column information by TGridColumn(Sender).TheWantedProperty.

Using this here's sample code to set a cell's background color based on data in the same dataset row.

procedure TMyForm.colMyCellUpdate(Sender: TObject; ACell: TGridCell);
var
 gridRowNo: Integer; // the DataTable row the grid is looking at.
 ds: TDataSet;
begin
 // This is where the grid is looking into the DataTable.
 // Not necessarily the active DataTable row.
 gridRowNo := TGridColumn(Sender).ParentGrid.RowOffset + ACell.Index + 1;
 
 ds := TGrid(TGridColumn(Sender).ParentGrid).DataSet; // the grid's dataset
 ds.SaveBookmark;
 try
   ds.RowNo := gridRowNo; // make sure you're on the expected row!

   // put your test/evaluation and cell formatting code here:
   if ds.Columns['AColumn'].AsBoolean then
   begin
     ACell.Background.Fill.Color := clRed;
   end;

 finally
   ds.GotoBookmark;
 end;
end;

And here's a more complex example for checking a value from the previous row.

procedure TMyForm.colMyCellUpdate(Sender: TObject; ACell: TGridCell);
var
 gridRowNo: Integer; // the DataTable row the grid is looking at.
 ds: TDataSet;
 currRowValue, aboveRowValue: string;
begin
 // is appropriate, exit if the cell hasn't been filled yet.
 // (event may be triggered before data is populated)
 if ACell.Data = '' then exit;

 // This is where the grid is looking into the DataTable.
 // Not necessarily the active DataTable row.
 gridRowNo := TGridColumn(Sender).ParentGrid.RowOffset + ACell.Index + 1;
 
 if gridRowNo = 1 then exit; // skip if checking "above row" and on first row
 
 ds := TGrid(TGridColumn(Sender).ParentGrid).DataSet;
 ds.SaveBookmark;
 try
   ds.RowNo := gridRowNo-1; // move to the above row
   aboveRowValue := ds.Columns['AColumn'].AsString;
   ds.RowNo := gridRowNo; // move to the current row
   currRowValue := ds.Columns['AColumn'].AsString;;
   if currRowValue = aboveRowValue then
   begin
     ACell.Background.Fill.Color := clRed;
   end;
 finally
   ds.GotoBookmark;
 end;
end;

Note that there are a number of edge cases you may have to code around. For example if you allow grid inserts or column sorting.
Image