2005. február 22., kedd

Check which column of a TListView in vsReport style has been clicked


Problem/Question/Abstract:

How can I know which column was click in a TListView? GetItemAt only works with the first column.

Answer:

Solve 1:

The method GetItemAt only provides the information about which ListItem (if any) is located at the specified coordinates passed as parameters, but only works with the first column of the TListView. The rest are ignored. If we needed to know if the user clicked on an element in another column, we can declare a new method in a derived class:

type
  TListViewX = class(TListView)
  public
    function GetItemAtX(X, Y: integer; var Col: integer): TListItem;
  end;

implementation

function TListViewX.GetItemAtX(X, Y: integer;
  var Col: integer): TListItem;
var
  i, n, RelativeX, ColStartX: Integer;
  ListItem: TlistItem;
begin
  Result := GetItemAt(X, Y);
  if Result <> nil then
  begin
    Col := 0; // First column
  end
  else if (ViewStyle = vsReport)
    and (TopItem <> nil) then
  begin
    // First, let's try to find the row
    ListItem := GetItemAt(TopItem.Position.X, Y);
    if ListItem <> nil then
    begin
      // Now let's try to find the Column
      RelativeX := X - ListItem.Position.X - BorderWidth;
      ColStartX := Columns[0].Width;
      n := Columns.Count - 1;
      for i := 1 to n do
      begin
        if RelativeX < ColStartX then
          break;
        if RelativeX <= ColStartX +
          StringWidth(ListItem.SubItems[i - 1]) then
        begin
          Result := ListItem;
          Col := i;
          break;
        end; //if
        Inc(ColStartX, Columns[i].Width);
      end; //for
    end; //if
  end; //if
end;

Casting to the new class

We don't need to intall this new component and register it in the components palette as we explained in another article ("Adding new methods and properties without registering new components"). Instead, any time we want to access this method, we can just cast the object (for example ListView1) to our new class. For example in a MouseDown event:

procedure TForm1.ListView1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
  col: integer;
  li: TListItem;
begin
  li := TListViewX.GetItemAtX(x, y, col);
  if li <> nil then
    ShowMessage('Column #' + IntToStr(col));
end;


Solve 2:

uses
  commctrl;

procedure TForm1.ListView1Click(Sender: TObject);
var
  pt: TPoint;
  col: Integer;
  pos: Integer;
begin
  GetCursorPos(pt);
  pt := Listview1.ScreenToClient(pt);
  Pos := -GetScrollPos(ListView1.Handle, SB_HORZ);
  Col := -1;
  while Pos < Pt.X do
  begin
    Inc(Col);
    Inc(Pos, ListView_GetColumnWidth(ListView1.Handle, Col));
  end;
  if Col >= ListView1.Columns.Count then
    Col := -1; {clicked past last column}
  showmessage(inttostr(col));
end;

Nincsenek megjegyzések:

Megjegyzés küldése