Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Math to determine item index based on col/row selection in grid

Me and 2 people have been trying to figure out what should be a simple formula. I have a grid, which can have a variable number of columns and rows. When the user clicks on one of the cells, I have to determine which 'index' was clicked based on col/row. Indexes start from 0 and go left to right, then top to bottom. There are no fixed cols/rows.

Sample of what I mean: enter image description here

So if there are 3 columns, and the user clicks on C2/R1, then it needs to resolve the index of 5. All indexes start from 0 in this example.

My current formula, which is far from working, is like this:

//I = Calculated index based on row/col (starts from 0)
//ACol = Column index user clicked (starts from 0)
//ARow = Row index user clicked (starts from 0)
//ColCount = Number of columns

I:= (C * ColCount) + (R - ColCount);

EDIT

For reference, here is my entire component unit below.

The line with the formula is marked with a comment "FORMULA HERE"

unit ImageGrid;

interface

uses
  Windows, Classes, SysUtils, Grids, Graphics, StdCtrls, ExtCtrls, Controls,
  Jpeg, PngImage;

type
  TImageGrid = class;
  TImageGridItem = class;

  TGridSizing = (gsManual, gsAuto, gsFit);

  TImageGridItem = class(TObject)
  private
    FFilename: TFilename;
    FOwner: TImageGrid;
    FBmp: TBitmap;
    procedure Event;
    procedure SetFilename(const Value: TFilename);
  public
    constructor Create(AOwner: TImageGrid);
    destructor Destroy; override;
    procedure LoadFile;
  published
    property Filename: TFilename read FFilename write SetFilename;
  end;

  TImageGrid = class(TCustomDrawGrid)
  private
    FItems: TStringList;
    FCacheDir: String;
    FRowHeight: Integer;
    FColWidth: Integer;
    FSizing: TGridSizing;
    FItemIndex: Integer;
    procedure SetCacheDir(const Value: String);
    procedure SetColWidth(const Value: Integer);
    procedure SetRowHeight(const Value: Integer);
    procedure SetSizing(const Value: TGridSizing);
    function GetItem(Index: Integer): TImageGridItem;
    procedure SetItem(Index: Integer; const Value: TImageGridItem);
    procedure SetItemIndex(const Value: Integer);
    procedure SelectCell(Sender: TObject; ACol, ARow: Longint;
      var CanSelect: Boolean);
  protected
    procedure Paint; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function Count: Integer;
    property Items[Index: Integer]: TImageGridItem
      read GetItem write SetItem; default;
    function Add: TImageGridItem;
    procedure Delete(const Index: Integer);
    procedure Clear;
  published
    property CacheDir: String read FCacheDir write SetCacheDir;
    property ColWidth: Integer read FColWidth write SetColWidth;
    property RowHeight: Integer read FRowHeight write SetRowHeight;
    property Sizing: TGridSizing read FSizing write SetSizing;
    property ItemIndex: Integer read FItemIndex write SetItemIndex;

    property Align;
    property Anchors;
    property BevelEdges;
    property BevelInner;
    property BevelKind;
    property BevelOuter;
    property BevelWidth;
    property BiDiMode;
    property BorderStyle;
    property Color;
    property ColCount;
    property Constraints;
    property Ctl3D;
    property DefaultColWidth;
    property DefaultRowHeight;
    property DefaultDrawing;
    property DoubleBuffered;
    property DragCursor;
    property DragKind;
    property DragMode;
    property DrawingStyle;
    property Enabled;
    property Font;
    property GradientEndColor;
    property GradientStartColor;
    property GridLineWidth;
    property Options;
    property ParentBiDiMode;
    property ParentColor;
    property ParentCtl3D;
    property ParentDoubleBuffered;
    property ParentFont;
    property ParentShowHint;
    property PopupMenu;
    property ScrollBars;
    property ShowHint;
    property TabOrder;
    property Touch;
    property Visible;

    property OnClick;
    property OnColumnMoved;
    property OnContextPopup;
    property OnDblClick;
    property OnDragDrop;
    property OnDragOver;
    property OnDrawCell;
    property OnEndDock;
    property OnEndDrag;
    property OnEnter;
    property OnExit;
    property OnGesture;
    property OnGetEditMask;
    property OnGetEditText;
    property OnKeyDown;
    property OnKeyPress;
    property OnKeyUp;
    property OnMouseActivate;
    property OnMouseDown;
    property OnMouseEnter;
    property OnMouseLeave;
    property OnMouseMove;
    property OnMouseUp;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
    property OnRowMoved;
    property OnSelectCell;
    property OnSetEditText;
    property OnStartDock;
    property OnStartDrag;
    property OnTopLeftChanged;
  end;

implementation

//Register procedure will come later...

{ TImageGrid }

function TImageGrid.Add: TImageGridItem;
begin
  Result:= TImageGridItem.Create(Self);
  FItems.AddObject('', Result);
end;

procedure TImageGrid.Clear;
begin
  while Count > 0 do
    Delete(0);
end;

function TImageGrid.Count: Integer;
begin
  Result:= FItems.Count;
end;

constructor TImageGrid.Create(AOwner: TComponent);
begin
  inherited;
  Options:= [goFixedVertLine,goFixedHorzLine,goVertLine,goHorzLine,
    goRangeSelect,goThumbTracking];
  Parent:= TWinControl(AOwner);
  FItems:= TStringList.Create;
  FixedCols:= 0;
  FixedRows:= 0;
  RowCount:= 1;
  ColCount:= 1;
  FColWidth:= 100;
  FRowHeight:= 100;
  ColWidths[0]:= FColWidth;
  RowHeights[0]:= FRowHeight;
  FSizing:= gsManual;
  FItemIndex:= -1;
  OnSelectCell:= SelectCell;

  Invalidate;
end;

procedure TImageGrid.Delete(const Index: Integer);
begin
  if (Index >= 0) and (Index < FItems.Count) then begin
    TImageGridItem(FItems.Objects[Index]).Free;
    FItems.Delete(Index);
  end else begin
    raise Exception.Create('List index out of bounds ('+IntToStr(Index)+')');
  end;
end;

destructor TImageGrid.Destroy;
begin
  FItems.Free;
  inherited;
end;

function TImageGrid.GetItem(Index: Integer): TImageGridItem;
begin
  if (Index >= 0) and (Index < FItems.Count) then begin
    Result:= TImageGridItem(FItems.Objects[Index]);
  end else begin
    Result:= nil;
    raise Exception.Create('List index out of bounds ('+IntToStr(Index)+')');
  end;
end;

procedure TImageGrid.Paint;
var
  Bmp: TBitmap;
  C: Integer;
  RC: Integer;
  CC: Integer;
  X, Y: Integer;
  I: TImageGridItem;
  R: TRect;

  procedure DrawImage(B: TBitmap; const R: TRect);
  begin
    Canvas.StretchDraw(R, B);
  end;

begin
  Canvas.Brush.Style:= bsSolid;
  Canvas.Pen.Style:= psClear;
  Canvas.Brush.Color:= clWhite;
  Canvas.FillRect(Canvas.ClipRect);
  Bmp:= TBitmap.Create;
  try
    if Count > 0 then begin
      case FSizing of
        gsManual: begin
          //Draw like regular grid with variable sizes - expand rows as needed
          inherited;

        end;
        gsAuto: begin
          //Calculate image width based on col count

        end;
        gsFit: begin
          //Calculate col count based on image width
          CC:= Trunc(ClientWidth / FColWidth);
          RC:= Trunc(Count / CC);
          ColCount:= CC;
          RowCount:= RC;
          for X := 0 to ColCount - 1 do
            ColWidths[X]:= FColWidth;
          for X := 0 to RowCount - 1 do
            RowHeights[X]:= FRowHeight;
          Canvas.Brush.Style:= bsSolid;
          Canvas.Pen.Style:= psSolid;
          Canvas.Brush.Color:= clWhite;
          Canvas.Pen.Width:= 1;
          C:= 0; //C = Count of items to show
          for X := 0 to ColCount - 1 do begin
            for Y := 0 to RowCount - 1 do begin
              I:= Self.Items[C];
              if C = FItemIndex then begin
                Canvas.Pen.Color:= clRed;
                Canvas.Pen.Width:= 2;
              end else begin
                Canvas.Pen.Color:= clNavy;
                Canvas.Pen.Width:= 1;
              end;
              R:= CellRect(X,Y);
              Canvas.Rectangle(R);
              InflateRect(R, -4, -4);
              Canvas.StretchDraw(R, I.FBmp);
              C:= C + 1;
            end;
          end;
        end;
      end;
    end else begin
      ColCount:= 1;
      RowCount:= 1;
    end;
  finally
    Bmp.Free;
  end;
end;

procedure TImageGrid.SelectCell(Sender: TObject; ACol, ARow: Integer;
  var CanSelect: Boolean);
var
  I: Integer;
  C, R: Integer;
begin
  //Determine item index based on col/row

  C:= ACol;
  R:= ARow;

  //I = Calculated index based on row/col (starts from 0)
  //ACol = Column index user clicked (starts from 0)
  //ARow = Row index user clicked (starts from 0)
  //ColCount = Number of columns

  I:= (R * ColCount) + C;    //       <<----- FORMULA HERE

  if I < Count - 1 then begin
    FItemIndex:= I;
    CanSelect:= True;
  end else begin
    FItemIndex:= -1;
    CanSelect:= False;
  end;
  Invalidate;
end;

procedure TImageGrid.SetCacheDir(const Value: String);
begin
  if Value <> FCacheDir then begin
    FCacheDir := Value;
    Invalidate;
  end;
end;

procedure TImageGrid.SetColWidth(const Value: Integer);
begin
  if Value <> FColWidth then begin
    FColWidth := Value;
    Invalidate;
  end;
end;

procedure TImageGrid.SetItem(Index: Integer; const Value: TImageGridItem);
begin
  if (Index >= 0) and (Index < FItems.Count) then begin
    FItems.Objects[Index]:= Value;
    Invalidate;
  end else begin
    raise Exception.Create('List index out of bounds ('+IntToStr(Index)+')');
  end;
end;

procedure TImageGrid.SetItemIndex(const Value: Integer);
begin
  if Value <> FItemIndex then begin
    FItemIndex := Value;
    Invalidate;
  end;
end;

procedure TImageGrid.SetRowHeight(const Value: Integer);
begin
  if Value <> FRowHeight then begin
    FRowHeight := Value;
    Invalidate;
  end;
end;

procedure TImageGrid.SetSizing(const Value: TGridSizing);
begin
  if Value <> FSizing then begin
    FSizing := Value;
    Invalidate;
  end;
end;

{ TImageGridItem }

constructor TImageGridItem.Create(AOwner: TImageGrid);
begin
  FOwner:= AOwner;
  FBmp:= TBitmap.Create;
end;

destructor TImageGridItem.Destroy;
begin
  FBmp.Free;
  inherited;
end;

procedure TImageGridItem.Event;
begin
  FOwner.Invalidate;
end;

procedure TImageGridItem.LoadFile;
  //Determine file type and load accordingly
  function GetImage(const Filename: String; B: TBitmap): Bool;
  var
    E: String;
    IJ: TJpegImage;
    IP: TPngObject;
  begin
    Result:= False;
    if FileExists(Filename) then begin
      E:= UpperCase(ExtractFileExt(Filename));
      if E = '.BMP' then begin
        B.LoadFromFile(Filename);
        Result:= True;
      end else
      if (E = '.JPG') or (E = '.JPEG') then begin
        IJ:= TJpegImage.Create;
        try
          IJ.LoadFromFile(Filename);
          B.Assign(IJ);
          Result:= True;
        finally
          IJ.Free;
        end;
      end else
      if E = '.PNG' then begin
        IP:= TPngObject.Create;
        try
          IP.LoadFromFile(Filename);
          B.Assign(IP);
          Result:= True;
        finally
          IP.Free;
        end;
      end else begin
        raise Exception.Create('Invalid file extension ('+E+')');
      end;
    end;
  end;

begin
  GetImage(FFilename, FBmp);
end;

procedure TImageGridItem.SetFilename(const Value: TFilename);
begin
  if Value <> FFilename then begin
    FFilename := Value;
    LoadFile;
    Event;
  end;
end;

end.

RESOLVED

Thanks to the answer below, I got it fixed. I thought the formula was wrong at first, but after scoping more of my code, I found that in another place, it was improperly looping through the rows and columns. Below is how it should work...

for Y := 0 to RowCount - 1 do begin
  for X := 0 to ColCount - 1 do begin

But what I had before was...

for X := 0 to ColCount - 1 do begin
  for Y := 0 to RowCount - 1 do begin
like image 759
Jerry Dodge Avatar asked Jan 15 '12 19:01

Jerry Dodge


1 Answers

Following should do the trick

(R * ColCount) + C

For your examples, this would become

(2 * 3) + 2 = 8

and ...if there are 3 columns, and the user clicks on C2/R1, then it needs to resolve the index of 5

(1 * 3) + 2 = 5
like image 107
Lieven Keersmaekers Avatar answered Nov 09 '22 21:11

Lieven Keersmaekers