Saturday, January 21, 2017

Delphi Source Code: DBGrids.pas enhancement for automatic alternating grid row colors / highlights

DELPHI Class Enhancement: DBGrids.pas Delphi source-code modification to implement alternating grid row colors

NOTE: This Delphi DBGrids.pas source code modification makes use of my GetAlternateColor Delphi function (source code here) for alternating grid-row-colors calculations. See comments within this Delphi source code (below) for where to insert that function (search for "GetAlternateColor").

Have you ever wanted to create a (Borland, CodeGear, Embarcadero) Delphi-based DBgrid with alternating row-colors / highlight-colors, like what is shown in these images:

Delphi Grid Control using this Alternating-Row-Color / Highlight logic ("Classic" look)

Delphi Grid Control using this Alternating-Row-Color / Highlight logic ("Aero" / modern look)

A requirement for such "green-bar" effects (alternating row-colors or row-highlight-colors in a DBGrid) came up a lot for various applications I have developed, and I was honestly frustrated by the fact that Delphi did not included this functionality to begin with, especially after so many releases. With every new Delphi release... Delphi 7, Delphi 2005, Delphi 2006, Delphi 2009, and Delphi 2010, I just kept hoping for this to just be included, but it was not.

My solution was to modify the source code provided with Delphi, since the DBGrid.pas DrawCell (TCustomDBGrid.DrawCell method) provided no simple way to extend this routine. The code that follows (below) will hopefully guide you through where to "hack" the existing Delphi DBGrid source code if you choose to. Next, I simply move the modified Delphi DBGrids.pas file into the directory with the rest of my project source-code and compile it in (thus, overriding the existing outdated-looking Grid).

See the somewhat detailed comments within the source-code modifications for why I made that changes that I did. I do not just alternate the grid-row colors, I also do some other things like modifying how bookmarks work and selected-rows work and such.

Even if this does not do exactly what you want, it should provide you with enough guidance to be able to modify DBGRID.pas quite easily to meet your specific requirements.

This procedure has been tested within Delphi version from Delphi 7 through Delphi 2010.

Delphi Function Source Code

--********************************************************************************
--This source code is Copyright (c) 2007-2017
--     Author: Mike Eberhart
--
--I hereby release this code under the terms of the MIT License (for freeware).
--
--Permission is hereby granted, free of charge, to any person obtaining a copy
--of this software and associated documentation files (the "Software"), to deal
--in the Software without restriction, including without limitation the rights
--to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
--copies of the Software, and to permit persons to whom the Software is
--furnished to do so, subject to the following conditions:
--
--The above copyright notice and this permission notice shall be included in
--all copies or substantial portions of the Software.
--
--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
--IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
--FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
--AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
--LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
--OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
--THE SOFTWARE.
--********************************************************************************

procedure TCustomDBGrid.DrawCell(ACol, ARow: Longint; ARect: TRect; AState: TGridDrawState);
...
...

//NOTE: I simply PLACED MY GetAlternateColor method here; get the source code from my other blog entry at:
//https://suretalent.blogspot.com/2017/01/delphi-source-code-function-alternating-highlight-row-color-algorithm.html


//Now, look for the following lines of code (at the start of the DrawCell method body)
var
  OldActive: Integer;
  Indicator: Integer;
  Value: string;
  DrawColumn: TColumn;
  MultiSelected: Boolean;
  ALeft: Integer;

  {*******************************************************************************
  I placed my constants here... ADDITIONS BEGIN
  *******************************************************************************}
  const
  //                        BBGGRR
  clLightTan    = TColor($00CCEEEE);
  clMidTan      = TColor($00B6D4D4);
  clDarkTan     = TColor($00A0BABA);
  {*******************************************************************************
  ADDITIONS - END
  *******************************************************************************}

begin
  if csLoading in ComponentState then
  begin
    Canvas.Brush.Color := Color;
    Canvas.FillRect(ARect);
    Exit;
  end;

  ...
  ... (about 70 lines of code here...;  look for the following...)
  ...
  
    if ARow < 0 then
      DrawTitleCell(ACol, ARow + FTitleOffset, DrawColumn, AState)
    else if (FDataLink = nil) or not FDataLink.Active then
      FillRect(ARect)
    else
    begin
      Value := '';
      OldActive := FDataLink.ActiveRecord;
      try
        FDataLink.ActiveRecord := ARow;
        if Assigned(DrawColumn.Field) then
          Value := DrawColumn.Field.DisplayText;
        if HighlightCell(ACol, ARow, Value, AState) and DefaultDrawing then
          DrawCellHighlight(ARect, AState, ACol, ARow);
        if not Enabled then
          Font.Color := clGrayText;
        if FDefaultDrawing then
          WriteText(Canvas, ARect, 3, 2, Value, DrawColumn.Alignment,
            UseRightToLeftAlignmentForField(DrawColumn.Field, DrawColumn.Alignment));
        if Columns.State = csDefault then
          DrawDataCell(ARect, DrawColumn.Field, AState);
  
//>> LOOK FOR THE PREVIOUS DELPHI DBGRID SOURCE CODE LINES ABOVE (appearning near the END of DrawCell method body)
//>> and place this provided custom code right below it...

        {*******************************************************************************
        ADDITIONS - BEGIN

        This section of code is required to accomplish a few particular GUI
        goals for the DBGrid that the standard DBGrid does not provide:
          1) Alternate colors between each row in grid to make it visually
             simple to quickly scan a row's data from left-to-right.
          2) maintain Bookmarks, even when I just want to allow ONLY one-row to
             be selected, since my applications regularly depend on Bookmarks
             to return to a particular row in the grid.
             The DBGrid normally only maintains bookmarks if dgMultiSelect
             option is True/Enabled, and in that state, DBGrid (as expected)
             allows the user to select as many rows as they want.
             My alterations make dgMultiSelect truly mean MULTI-SELECT ONLY WHEN
             the dgIndicator Option is True/Enabled at the same time
             dgMultiSelect is True/Enabled.
             When dgMultiSelect is used WITHOUT dgIndicator, only ONE ROW at
             a time (i.e., one row maximum) can be set to "selected" state.
             This allows for TRUE multiselect as well as my ONE-ROW-ONLY
             "multiselect" (where I am relying on multi-select just to set the
             bookmark on my one selected row).
          4) Highlight "selected" row(s) in a color/theme that makes the selection
             quickly apparent, whether just one row is selected or many rows are selected.
          5) Only show "Selected" rows when the grid has Focus, OR when the
             dgAlwaysShowSelection option is True (to be consistent with normal
             DBGrid behaviour).

        CODE COMMENTS:
        Make sure only ONE record selected UNLESS dgIndicator is TRUE.
        Set Current "Active" Row to "Selected" row if allowing only one row to be selected.
        Only do this if we have focus on the grid, or if AlwaysShowSelection is ON.
        For all other situations, color the alternating lines in the grid or
        any other selected-rows (if TRUE MULTI-select is in effect).
        *******************************************************************************}
        if ((DataLink.ActiveRecord = Row - 1) and ((dgAlwaysShowSelection in Options) or Focused)) or
           ((dgAlwaysShowSelection in Options) and SelectedRows.CurrentRowSelected and not (dgIndicator in Options)) then
        begin
          if (ACol = 0) then  //only need to set "Selected" once per row - do so when painting Column Zero, lest flicker ensue
          begin
            if not (dgIndicator in Options) then   //use the showing INDICATOR to mean ALLOW TRUE MULTI-SELECT!
              if SelectedRows.Count > 1 then       //Remove stragglers from MultiSelect
                SelectedRows.Clear;

            SelectedRows.CurrentRowSelected := True;
          end;

          //NOTE: for "classic" look only, replace following 3 lines with: Canvas.Brush.Color := clSelectedRow;
          Canvas.Brush.Color  := clHighlight;
          Canvas.Brush.Style  := bsClear;
          Canvas.Font.Color   := IfThen(DrawingStyle <> gdsClassic, clWindowText, clHighlightText); //set optimal text-color per draw-style
        end
        else  //Logic for coloring other rows.
        begin
          Font.Color            := clWindowText;

          //This only kicks in for the multi-selection (TRUE multiselection that is, which requires dgIndicator to be on too),
          //since above logic only highlights the SINGLE ACTIVE/SELECTED ROW (this catches other multi-select rows)
          if SelectedRows.CurrentRowSelected then
          begin
            //NOTE: for "classic" look only, replace following 3 lines with: Canvas.Brush.Color := clSelectedRow;
            Canvas.Brush.Color  := clHighlight;
            Canvas.Brush.Style  := bsClear;
            Canvas.Font.Color   := IfThen(DrawingStyle <> gdsClassic, clWindowText, clHighlightText); //set optimal text-color per draw-style
          end
          else
            if ((Columns[ACol].Field.DataSet.RecNo mod 2) =1) then  //Is it an "alternating-line" to have "green-bar paper" effect?
              Canvas.Brush.Color    := GetAlternateColor(Columns[ACol].Color)
            else
              Canvas.Brush.Color    := Columns[ACol].Color;   //TPrevent weird line-coloration if focused moved off grid after a row is selected
        end;

        DefaultDrawColumnCell( ARect, ACol, Columns[ACol], AState );  //Perform our chosen coloration
        
        {*******************************************************************************
        ADDITIONS - END
        *******************************************************************************}        

//>> LOOK FOR THE FOLLOWING DELPHI DBGRID.pas SOURCE CODE (near the END of DrawCell method body)
//>> and place provided custom code right above it
           
        DrawColumnCell(ARect, ACol, DrawColumn, AState);
      finally
        FDataLink.ActiveRecord := OldActive;
      end;
      Canvas.Brush.Style := bsSolid;
      if FDefaultDrawing and (gdSelected in AState)
        and ((dgAlwaysShowSelection in Options) or Focused)
        and not (csDesigning in ComponentState)
        and not (dgRowSelect in Options)
        and (UpdateLock = 0)
        and (ValidParentForm(Self).ActiveControl = Self) then
      begin
        if (FInternalDrawingStyle = gdsThemed) and (Win32MajorVersion >= 6) then
          InflateRect(ARect, -1, -1);
        Windows.DrawFocusRect(Handle, ARect);
      end;
    end;
  end;
  if (gdFixed in AState) and ([dgRowLines, dgColLines] * Options =
     [dgRowLines, dgColLines]) and (FInternalDrawingStyle = gdsClassic) and
     not (gdPressed in AState) then
  begin
    InflateRect(ARect, 1, 1);
    DrawEdge(Canvas.Handle, ARect, BDR_RAISEDINNER, BF_BOTTOMRIGHT);
    DrawEdge(Canvas.Handle, ARect, BDR_RAISEDINNER, BF_TOPLEFT);
  end;
end;



Continue to read this Software Development and Technology Blog for computer programming, software development, and technology Techniques, How-To's, Fixes, Reviews, and News — focused on Dart Language, Delphi, SQL Server, Nvidia CUDA, VMware, TypeScript, SVG, other technology tips and how-to's, and my varied political and economic opinions.

No comments: