Search code examples
delphi-xe6tstringgrid

Change color of text in a TStringGrid cell


How can I change the color of text in a TStringGrid cell depending on certain conditions?

I am using TStringGrid to display a monthly calendar view on a form and I'm populating the TStringGrid with days of the month in certain rows and columns, with days of the week as column headings. I'm also populating the TStringGrid with job work orders for certain dates that are based on entries in a database. So I'm using the DrawCell event to display the content in the TStringGrid. Certain jobs are recurring jobs and other jobs are one offs. I'd like the recurring jobs to appear in one color and the one offs in another.

Is this possible, and/or should I be using a different component to accomplish this task? I assume it's not possible to have two different text colors in the same cell.

type
  TCalendarView2 = class(TForm)
    CalViewStringGrid: TStringGrid;
    NextBtn: TButton;
    PrevBtn: TButton;
    MonthLabel1: TLabel;
    CloseBtn: TButton;
    procedure OnShow(Sender: TObject);
    procedure CalViewStringGridDrawCell(Sender: TObject; ACol, ARow: Integer;
      Rect: TRect; State: TGridDrawState);
    procedure NextBtnClick(Sender: TObject);
    procedure PrevBtnClick(Sender: TObject);
    procedure CloseBtnClick(Sender: TObject);
  private
    { Private declarations }
FDateTime: TDateTime;
    FDay: Word;
    EndDate, StartDay: TDateTime;  // selected date so we know what month the calendar is for
     iNumDays, iDay: Integer;  // Holds the number of days for a given month
    procedure FillWithWorkOrders;
    procedure UpdateRowHeights;
  public
    { Public declarations }
     MyDate : TDateTime;
  end;

var
  CalendarView2: TCalendarView2;

implementation

{$R *.dfm}

uses POEData;

procedure TCalendarView2.OnShow(Sender: TObject);
var
  wYear, wMonth: Word;
begin
  FDateTime := Date;

  // Extract the month, day and year for the current date
  DecodeDate (FDateTime, wYear, wMonth, FDay);
  MonthLabel1.Caption := FormatSettings.LongMonthNames[wMonth] + ' ' + IntToStr(wYear);

  FillWithWorkOrders;
end;

procedure TCalendarView2.CloseBtnClick(Sender: TObject);
begin
  CalendarView2.Close;
end;

procedure TCalendarView2.CalViewStringGridDrawCell(Sender: TObject; ACol,
  ARow: Integer; Rect: TRect; State: TGridDrawState);
var
  s, ds, sDay, WorkOrder, WorkOrders: string;
  dd, idx: integer;
  dtDate: TDateTime;
  SerType, WoNum, ETips: string;
  bIsToday: boolean;
begin
  s := CalViewStringGrid.Cells[ACol, ARow];
  Inc(Rect.Left, 2);
  Inc(Rect.Top, 2);

  if (gdFixed in State) then
  begin
    CalViewStringGrid.Canvas.Brush.Color := CalViewStringGrid.FixedColor;
    CalViewStringGrid.Canvas.Font.Color := CalViewStringGrid.Font.Color;
    CalViewStringGrid.Canvas.FillRect(Rect);
    CalViewStringGrid.Canvas.TextRect(Rect, Rect.Left, Rect.Top, s);
    Exit;
  end;

  idx := Pos(#10, s);
  if idx <> 0 then
  begin
    sDay := Copy(s, 1, idx-1);
    WorkOrders := Copy(s, idx+1, MaxInt);
  end else
  begin
    ds := s;
    WorkOrders := '';
  end;

  if sDay <> '' then
  begin
    dd := StrToIntDef(sDay, 0);
    dtDate := Date;
    bIsToday := (MonthOf(dtDate) = MonthOf(FDateTime)) and (DayOf(dtDate) = dd);
  end else begin
    bIsToday := False;
  end;

  if bIsToday then
  begin
    CalViewStringGrid.Canvas.Brush.Color := clSkyBlue;
    CalViewStringGrid.Canvas.Font.Color := clBlue;
  end;
  begin
    CalViewStringGrid.Canvas.Brush.Color := CalViewStringGrid.Color;
    CalViewStringGrid.Canvas.Font.Color := CalViewStringGrid.Font.Color;
  end;
  CalViewStringGrid.Canvas.FillRect(Rect);
  CalViewStringGrid.Canvas.TextRect(Rect, Rect.Left, Rect.Top, sDay);

  if (WorkOrders = '') then Exit;

  Inc(Rect.Top, CalViewStringGrid.Canvas.TextHeight(sDay) + 2);
  repeat
    idx := Pos(#10, WorkOrders);
    if idx <> 0 then
    begin
      WorkOrder := Copy(WorkOrders, 1, idx-1);
      WorkOrders := Copy(WorkOrders, idx+1, MaxInt);
    end else
    begin
      WorkOrder := WorkOrders;
      WorkOrders := '';
    end;

    s := WorkOrder;
    idx := Pos('-', s);
    ETips := Copy(s, 1, idx-1);
    s := Copy(s, idx+1, MaxInt);
    idx := Pos('-', s);
    SerType := Copy(s, 1, idx-1);
    s := Copy(s, idx+1, MaxInt);
    WoNum := s;

   if bIsToday then
    begin
      CalViewStringGrid.Canvas.Brush.Color := clSkyBlue;
      //CalViewStringGrid.Font.Color := clBlue;
    end
    else if SerType = 'R' then
    begin
      CalViewStringGrid.Canvas.Font.Color := clRed;
    end
    else if SerType = 'P' then
    begin
      CalViewStringGrid.Canvas.Font.Color := clBlue;
    end
    else if SerType = 'S' then
    begin
      CalViewStringGrid.Canvas.Font.Color := clGreen;
    end
    else if SerType = 'N' then
    begin
      CalViewStringGrid.Canvas.Font.Color := clBlack;
    end;
    begin
      CalViewStringGrid.Canvas.Font.Color := CalViewStringGrid.Font.Color;
    end;
    CalViewStringGrid.Canvas.TextRect(Rect, Rect.Left, Rect.Top, WorkOrder);
    Inc(Rect.Top, CalViewStringGrid.Canvas.TextHeight(WorkOrder) + 2);
  until WorkOrders = '';
  // CalViewStringGrid.Canvas.Font.Color := clBlack;
end;

procedure TCalendarView2.FillWithWorkOrders;
const
  days: array[0..6] of String = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
var
  X, Y, i, DateSW, RotType, PurType, SheType, SW, iNumDays: Integer;
  dtTime, StartDay, EndDate: TDateTime;
  SerType, WoNum, CoName, SCity, ETips, s: string;
  wDay: Word;
  WorkOrders: array[1..31] of String;
begin
  RotType := 0;
  PurType := 0;
  SheType := 0;
  SW := 0;

  // This section displays the abbreviated day of the week in each cell in the first row,
  // and clears out cell info just in case any data was left over from before
  for i := 0 to 6 do
  begin
    CalViewStringGrid.Cells[i, 0] := days[i];
    CalViewStringGrid.Cells[i, 1] := '';
    CalViewStringGrid.Cells[i, 2] := '';
    CalViewStringGrid.Cells[i, 3] := '';
    CalViewStringGrid.Cells[i, 4] := '';
    CalViewStringGrid.Cells[i, 5] := '';
    CalViewStringGrid.Cells[i, 6] := '';
  end;

  // Gets the number of days for the current month
  iNumDays := DaysInMonth(FDateTime);

  // The next two lines initialize the variables the first time through
  if DateSW = 0 then
  begin
    StartDay := FDateTime - FDay;
    EndDate := EndOfTheMonth(FDateTime);
  end;
  DateSW := 1;

  //Generate and open the ToBeSchedGrid Query
  POE_Data.ToBeSchedGrid.Close;
  POE_Data.ToBeSchedGrid.Sql.Clear;
  POE_Data.ToBeSchedGrid.Sql.Add('SELECT DISTINCT D.WorkOrder, D.CustID, D.OpID, D.EnteredDate, D.EnteredTime, D.EstServiceDate, D.Status, D.EstBoxes, D.Truck, D.EstTips, D.ServiceDesc, D.Zone, D1.CompanyName, D1.Contact, D1.SContact1, D1.SPhone1, D1.SCity');
  POE_Data.ToBeSchedGrid.Sql.Add('FROM ":Shred:WorkOrdersIn.DB" D, ":Shred:Customer.DB" D1');
  POE_Data.ToBeSchedGrid.Sql.Add('WHERE (D.EstServiceDate > "' + DateToStr(StartDay) + '")');
  POE_Data.ToBeSchedGrid.Sql.Add('AND (D.EstServiceDate <= "' + DateToStr(EndDate) + '")');
  POE_Data.ToBeSchedGrid.Sql.Add('AND (D1.CustID = D.CustID)');
  POE_Data.ToBeSchedGrid.Sql.Add('AND (D.Status <> "Cancelled")');
  POE_Data.ToBeSchedGrid.Sql.Add('ORDER BY D.EstServiceDate');
  // Save this Query to a text file for debugging purposes
  POE_Data.ToBeSchedGrid.Sql.SaveToFile('c:\PolarQBE\WorkOrdersIn.txt');
  POE_Data.ToBeSchedGrid.Open;

  // populate each day's Work Orders
  While NOT POE_Data.ToBeSchedGrid.EOF do
  begin
    dtTime := POE_Data.ToBeSchedGridEstServiceDate.AsDateTime;
    SerType := POE_Data.ToBeSchedGridServiceDesc.AsString;
    WoNum := POE_Data.ToBeSchedGridWorkOrder.AsString;
    SCity := POE_Data.ToBeSchedGridSCity.AsString;
    ETips := POE_Data.ToBeSchedGridEstTips.AsString;
    if ETips = '' then ETips := '0';
    CoName := POE_Data.ToBeSchedGridCompanyName.AsString;

    if SerType = 'Route' then
      Inc(RotType);
    if SerType = 'Purge' then
      Inc(PurType);
    if SerType = 'Shred Event' then
      Inc(SheType);

    //wDay := DayOfTheMonth(FDateTime);
    wDay := DayOfTheMonth(dtTime);
    //WorkOrders[wDay] := WorkOrders[wDay] + ETips + '-' + Copy(CoName,1,11) + '-' + Copy(SCity,1,8) + '-' + Copy(SerType,1,1) + '-' + WoNum + #10;
    WorkOrders[wDay] := WorkOrders[wDay] + ETips + '-' + Copy(SerType,1,1) + '-' + WoNum + #10;

    POE_Data.ToBeSchedGrid.Next;
  end;

  // Initialize the Row and Column counters
  Y := 1;
  X := DayOfWeek(StartOfTheMonth(FDateTime)- 1);
  if X > 6 then X := (X div 6) - 1;

  for i := 1 to iNumDays do
  begin
    s := IntToStr(i);
    if WorkOrders[i] <> '' then begin
      s := s + #10 + WorkOrders[i];
    end;
    CalViewStringGrid.Cells[X, Y] := s;
    // increment the column counter
    Inc(X);
    // if the column counter is greater than 6 reset back to 0.
    if X > 6 then
    begin
      X := 0;
      Inc(Y);
    end;
  end;

  UpdateRowHeights;
end;

procedure TCalendarView2.UpdateRowHeights;
var
  X, Y, TxtHeight: Integer;
  MaxHeight: Integer;
  R: TRect;
begin
  // This next line seems to really control the height of the rows
  CalViewStringGrid.Canvas.Font.Size := 8;
  for Y := CalViewStringGrid.FixedRows to CalViewStringGrid.RowCount - 1 do
  begin
    MaxHeight := CalViewStringGrid.DefaultRowHeight - 4;
    for X := CalViewStringGrid.FixedCols to CalViewStringGrid.ColCount - 1 do
    begin
      R := Rect(0, 0, CalViewStringGrid.ColWidths[X] - 4, 0);
      TxtHeight := DrawText(CalViewStringGrid.Canvas.Handle,
        PChar(CalViewStringGrid.Cells[X, Y]), -1, R, DT_WORDBREAK or DT_CALCRECT);
      if TxtHeight > MaxHeight then
        MaxHeight := TxtHeight;
    end;
    // 11/18/2015 - was = AGrid.RowHeights[Y] := MaxHeight + 4;
    CalViewStringGrid.RowHeights[Y] := MaxHeight + 1;
  end;
end;

Solution

  • Yes, it is possible to use multiple colors in a single cell. Since you are already using the TStringGrid.OnDrawCell event to draw the cells yourself, simply extend your drawing logic to include per-job text colors. All you have to do is assign the TStringGrid.Canvas.Font.Color property before drawing a job's text onto the TStringGrid.Canvas. You just need to expose a way for your OnDrawCell handler to know when a given job is recurring or not, so it can assign the appropriate color before drawing that job's text.

    Update: Try something more like this instead:

    type
      TCalViewForm = class(TForm)
        CalViewStringGrid: TStringGrid;
        procedure OnShow(Sender: TObject);
        procedure CalViewStringGridDrawCell(Sender: TObject; ACol,
      private
        FDateTime: TDateTime;
        FDay: Word;
        procedure FillWithWorkOrders;
        procedure UpdateRowHeights;
      end;
    
    ...
    
    procedure TCalViewForm.OnShow(Sender: TObject);
    var
      wYear, wMonth: Word;
    begin
      FDateTime := Date;
    
      // Extract the month, day and year for the current date
      DecodeDate (FDateTime, wYear, wMonth, FDay);
      MonthLabel.Caption := FormatSettings.LongMonthNames[wMonth] + ' ' + IntToStr(wYear);
    
      FillWithWorkOrders;
    end;
    
    procedure TCalViewForm.FillWithWorkOrders;
    const
      days: array[0..6] = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
    var
      X, Y, i, DateSW: Integer;
      dtTime: TDateTime;
      SerType, WoNum, CoName, SCity, ETips, s: string;
      wDay: Word;
      WorkOrders: array[1..31] of String;
    begin
      RotType := 0;
      PurType := 0;
      SheType := 0;
      SW := 0;
    
      // This section displays the abbreviated day of the week in each cell in the first row,
      // and clears out cell info just in case any data was left over from before
      for i := 0 to 6 do
      begin
        CalViewStringGrid.Cells[i, 0] := days[i];
        CalViewStringGrid.Cells[i, 1] := '';
        CalViewStringGrid.Cells[i, 2] := '';
        CalViewStringGrid.Cells[i, 3] := '';
        CalViewStringGrid.Cells[i, 4] := '';
        CalViewStringGrid.Cells[i, 5] := '';
        CalViewStringGrid.Cells[i, 6] := '';
      end;
    
      // Gets the number of days for the current month
      iNumDays := DaysInMonth(FDateTime);
    
      // The next two lines initialize the variables the first time through
      if DateSW = 0 then
      begin
        StartDay := FDateTime - FDay;
        EndDate := EndOfTheMonth(FDateTime);
      end;
      DateSW := 1;
    
      //Generate and open the ToBeSchedGrid Query
      POE_Data.ToBeSchedGrid.Close;
      POE_Data.ToBeSchedGrid.Sql.Clear;
      POE_Data.ToBeSchedGrid.Sql.Add('SELECT DISTINCT D.WorkOrder, D.CustID, D.OpID, D.EnteredDate, D.EnteredTime, D.EstServiceDate, D.Status, D.EstBoxes, D.Truck, D.EstTips, D.ServiceDesc, D.Zone, D1.CompanyName, D1.Contact, D1.SContact1, D1.SPhone1, D1.SCity');
      POE_Data.ToBeSchedGrid.Sql.Add('FROM ":Shred:WorkOrdersIn.DB" D, ":Shred:Customer.DB" D1');
      POE_Data.ToBeSchedGrid.Sql.Add('WHERE (D.EstServiceDate > "' + DateToStr(StartDay) + '")');
      POE_Data.ToBeSchedGrid.Sql.Add('AND (D.EstServiceDate <= "' + DateToStr(EndDate) + '")');
      POE_Data.ToBeSchedGrid.Sql.Add('AND (D1.CustID = D.CustID)');
      POE_Data.ToBeSchedGrid.Sql.Add('AND (D.Status <> "Cancelled")');
      POE_Data.ToBeSchedGrid.Sql.Add('ORDER BY D.EstServiceDate');
      // Save this Query to a text file for debugging purposes
      POE_Data.ToBeSchedGrid.Sql.SaveToFile('c:\PolarQBE\WorkOrdersIn.txt');
      POE_Data.ToBeSchedGrid.Open;
    
      // populate each day's Work Orders
      While NOT POE_Data.ToBeSchedGrid.EOF do
      begin
        dtTime := POE_Data.ToBeSchedGridEstServiceDate.AsDateTime;
        SerType := POE_Data.ToBeSchedGridServiceDesc.AsString;
        WoNum := POE_Data.ToBeSchedGridWorkOrder.AsString;
        SCity := POE_Data.ToBeSchedGridSCity.AsString;
        ETips := POE_Data.ToBeSchedGridEstTips.AsString;
        if ETips = '' then ETips := '0';
        CoName := POE_Data.ToBeSchedGridCompanyName.AsString;
    
        if SerType = 'Route' then
          Inc(RotType);
        if SerType = 'Purge' then
          Inc(PurType);
        if SerType = 'Shred Event' then
          Inc(SheType);
    
        wDay := DayOfTheMonth(dtTime);
        //WorkOrders[wDay] := WorkOrders[wDay] + ETips + '-' + Copy(CoName,1,11) + '-' + Copy(SCity,1,8) + '-' + Copy(SerType,1,1) + '-' + WoNum + #10;
        WorkOrders[wDay] := WorkOrders[wDay] + ETips + '-' + Copy(SerType,1,1) + '-' + WoNum + #10;
    
        POE_Data.ToBeSchedGrid.Next;
      end;
    
      // Initialize the Row and Column counters
      Y := 1;
      X := DayOfWeek(StartOfTheMonth(FDateTime)- 1);
      if X > 6 then X := (X div 6) - 1;
    
      for i := 1 to iNumDays do
      begin
        s := IntToStr(i);
        if WorkOrders[i] <> '' then begin
          s := s + #10 + WorkOrders[i];
        end;
        CalViewStringGrid.Cells[X, Y] := s;
        // increment the column counter
        Inc(X);
        // if the column counter is greater than 6 reset back to 0.
        if X > 6 then
        begin
          X := 0;
          Inc(Y);
        end;
      end;
    
      UpdateRowHeights;
    end;
    
    procedure TCalViewForm.CalViewStringGridDrawCell(Sender: TObject; ACol,
      ARow: Integer; Rect: TRect; State: TGridDrawState);
    var
      s, sDay, WorkOrder, WorkOrders: string;
      dd, idx: integer;
      dtDate: TDateTime;
      SerType, WoNum, ETips: string;
      bIsToday: boolean;
    begin
      s := CalViewStringGrid.Cells[ACol, ARow];
      Inc(Rect.Left, 2);
      Inc(Rect.Top, 2);
    
      if (gdFixed in State) then
      begin
        CalViewStringGrid.Canvas.Brush.Color := CalViewStringGrid.FixedColor;
        CalViewStringGrid.Canvas.Font.Color := CalViewStringGrid.Font.Color;
        CalViewStringGrid.Canvas.FillRect(Rect);
        CalViewStringGrid.Canvas.TextRect(Rect, Rect.Left, Rect.Top, s);
        Exit;
      end;
    
      idx := Pos(#10, s);
      if idx <> 0 then
      begin
        sDay := Copy(s, 1, idx-1);
        WorkOrders := Copy(s, idx+1, MaxInt);
      end else
      begin
        sDay := s;
        WorkOrders := '';
      end;
    
      if sDay <> '' then
      begin
        dd := StrToIntDef(sDay, 0);
        dtDate := Date;
        bIsToday := (MonthOf(dtDate) = MonthOf(FDateTime)) and (DayOf(dtDate) = dd);
      end else begin
        bIsToday := False;
      end;
    
      if bIsToday then
      begin
        CalViewStringGrid.Canvas.Brush.Color := clSkyBlue;
        CalViewStringGrid.Canvas.Font.Color := clBlue;
      end
      begin
        CalViewStringGrid.Canvas.Brush.Color := CalViewStringGrid.Color;
        CalViewStringGrid.Canvas.Font.Color := CalViewStringGrid.Font.Color;
      end;
      CalViewStringGrid.Canvas.FillRect(Rect);
      CalViewStringGrid.Canvas.TextRect(Rect, Rect.Left, Rect.Top, sDay);
    
      if (WorkOrders = '') then Exit;
    
      Inc(Rect.Top, CalViewStringGrid.Canvas.TextHeight(sDay) + 2);
      repeat
        idx := Pos(#10, WorkOrders);
        if idx <> 0 then
        begin
          WorkOrder := Copy(WorkOrders, 1, idx-1);
          WorkOrders := Copy(WorkOrders, idx+1, MaxInt);
        end else
        begin
          WorkOrder := WorkOrders;
          WorkOrders := '';
        end;
    
        s := WorkOrder;
        idx := Pos('-', s);
        ETips := Copy(s, 1, idx-1);
        s := Copy(s, idx+1, MaxInt);
        idx := Pos('-', s);
        SerType := Copy(s, 1, idx-1);
        s := Copy(s, idx+1, MaxInt);
        WoNum := s;
    
        if SerType = 'R' then
        begin
          CalViewStringGrid.Canvas.Font.Color := clRed;
        end
        else if SerType = 'P' then
        begin
          CalViewStringGrid.Canvas.Font.Color := clBlue;
        end
        else if SerType = 'S' then
        begin
          CalViewStringGrid.Canvas.Font.Color := clGreen;
        end
        else if bIsToday then
        begin
          CalViewStringGrid.Canvas.Font.Color := clBlue;
        end
        begin
          CalViewStringGrid.Canvas.Font.Color := CalViewStringGrid.Font.Color;
        end;
    
        CalViewStringGrid.Canvas.TextRect(Rect, Rect.Left, Rect.Top, WorkOrder);
        Inc(Rect.Top, CalViewStringGrid.Canvas.TextHeight(WorkOrder) + 2);
      until WorkOrders = '';
    end;
    
    procedure TCalViewForm.UpdateRowHeights;
    var
      X, Y, TxtHeight: Integer;
      MaxHeight: Integer;
      R: TRect;
    begin
      // This next line seems to really control the height of the rows
      CalViewStringGrid.Canvas.Font.Size := 9;
      for Y := CalViewStringGrid.FixedRows to CalViewStringGrid.RowCount - 1 do
      begin
        MaxHeight := CalViewStringGrid.DefaultRowHeight - 4;
        for X := CalViewStringGrid.FixedCols to CalViewStringGrid.ColCount - 1 do
        begin
          R := Rect(0, 0, CalViewStringGrid.ColWidths[X] - 4, 0);
          TxtHeight := DrawText(CalViewStringGrid.Canvas.Handle,
            PChar(CalViewStringGrid.Cells[X, Y]), -1, R, DT_WORDBREAK or DT_CALCRECT);
          if TxtHeight > MaxHeight then
            MaxHeight := TxtHeight;
        end;
        // 11/18/2015 - was = AGrid.RowHeights[Y] := MaxHeight + 4;
        CalViewStringGrid.RowHeights[Y] := MaxHeight + 1;
      end;
    end;