如何获取 TStatusPanel (StatusBar - Delphi) 的索引?

How to get index of a TStatusPanel (StatusBar - Delphi)?

我正在尝试在 OnDblClick 事件中获取 TStatusPanelTStatusBar 的面板)的索引以将其与 ShowMessage() 一起使用,例如, 但我不知道如何获取索引。

我知道像 OnDrawPanel 这样的事件有一个 Panel: TStatusPanel 参数,但我在 OnDblClick 中需要同样的东西,但只有一个参数,Sender: TObject

没有像if StatusBar.Panel = 1这样的命令。我可以使用 StatusBar.Panels[0],但我不知道如何比较点击显示我的消息的索引。

嗯,这就是我需要的简单方法:

if StatusBar.Panel = 0 then
  showmessage('0')
else if StatusBar.Panel = 1 then
  showmessage('1');

我知道上面的代码不起作用,这只是一个例子。它应该是这样的:

if StatusBar.Panels[0].'SOMETHING' = 0 then
  showmessage('0')
else if StatusBar.Panels[0].'SOMETHING' = 1 then
  showmessage('1');

您可以在 OnDblClick 事件处理程序中使用 GetMessagePos 来在检索到触发双击处理程序的 WM_LBUTTONDBLCLK 消息时获取鼠标位置并转换为客户端坐标。然后您可以遍历状态栏的面板以定位鼠标所在的部分。示例:

procedure TForm1.StatusBar1DblClick(Sender: TObject);
var
  Pt: TPoint;
  i, w: Integer;
begin
  Pt := SmallPointToPoint(TSmallPoint(DWORD(GetMessagePos)));
  MapWindowPoints(HWND_DESKTOP, StatusBar1.Handle, Pt, 1);
  w := 0;
  for i := 0 to StatusBar1.Panels.Count - 1 do begin
    w := w + StatusBar1.Panels[i].Width;
    if Pt.X < w then begin
      ShowMessage(IntToStr(i));
      Break;
    end;
  end;
end;

您也可以选择使用 OnMouseDown 事件处理程序,鼠标点击位置的客户端坐标已经传递,并在事件处理程序中测试双击,然后定位面板。使用 OnMouseDown 处理程序没有副作用,因为它是在双击时从同一个 WM_LBUTTONDBLCLK 触发的。

procedure TForm1.StatusBar1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
  i, w: Integer;
begin
  if (Button = mbLeft) and (ssDouble in Shift) then begin
    w := 0;
    for i := 0 to StatusBar1.Panels.Count - 1 do begin
      w := w + StatusBar1.Panels[i].Width;
      if X < w then begin
        ShowMessage(IntToStr(i));
        Break;
      end;
    end;
  end;
end;

几乎相同的逻辑,但它从 StatusBar 获取实际的面板边界。换句话说,如果您单击面板之间的分隔符,它将 return -1。

uses 
  Winapi.CommCtrl;

procedure TForm1.StatusBar1DblClick(Sender: TObject);
var
  LClickPos: TPoint;
  LIndex: Integer;

  function GetPanelIndex: Integer;
  var
    I: Integer;
    LRect: TRect;
  begin
    for I := 0 to StatusBar1.Panels.Count - 1 do
    begin
      if SendMessage(StatusBar1.Handle, SB_GETRECT, I, LPARAM(@LRect)) <> 0 then
      begin
        if PtInRect(LRect, LClickPos) then
          Exit(I);
      end;
    end;

    Result := -1;
  end;

begin
  LClickPos := SmallPointToPoint(TSmallPoint(GetMessagePos()));
  LClickPos := StatusBar1.ScreenToClient(LClickPos);
  LIndex := GetPanelIndex;

  Application.MessageBox(PChar(Format('Panel %d', [LIndex])), 'Test');
end;

我建议使用 SB_GETPARTS instead of SB_GETRECT。这样,您向 TStatusBar:

发送的消息就会减少
uses
  ..., Winapi.CommCtrl;

function GetStatusPanelAt(StatusBar: TStatusBar; X, Y: Integer): TStatusPanel; overload;
function GetStatusPanelAt(StatusBar: TStatusBar; const P: TPoint): TStatusPanel; overload;

...

function GetStatusPanelAt(StatusBar: TStatusBar; X, Y: Integer): TStatusPanel;
begin
  Result := GetStatusPanelAt(StatusBar, Point(X, Y));
end;

function GetStatusPanelAt(StatusBar: TStatusBar; const P: TPoint): TStatusPanel;
var
  index: Integer;
  arr: array of Integer;
  Panel: TStatusPanel;
begin
  Result := nil;

  if not PtInRect(StatusBar.ClientRect, P) then
    Exit;

  SetLength(arr, SendMessage(StatusBar.Handle, SB_GETPARTS, 0, 0));
  SendMessage(StatusBar.Handle, SB_GETPARTS, Length(arr), LPARAM(PInteger(arr)));

  index := 0;
  while index < Length(arr) do
  begin
    if (P.X <= arr[index]) or (arr[index] = -1) then
    begin
      Result := StatusBar.Panels[index];
      Exit;
    end;
    Inc(index);
  end;
end;

那么你可以这样做:

uses
  ..., System.Types, Winapi.Windows;

procedure TForm5.StatusBar1DblClick(Sender: TObject);
var
  Pt: TPoint;
  Panel: TStatusPanel;
begin
  Pt := SmallPointToPoint(TSmallPoint(GetMessagePos()));
  Pt := StatusBar1.ScreenToClient(Pt);

  Panel := GetStatusPanelAt(StatusBar1, Pt);
  if Panel <> nil then
    ShowMessage('Click on Panel ' + IntToStr(Panel.Index))
  else
    ShowMessage('No click on a Panel');
end;

或者,如果您使用的是 D2006 或更高版本,则可以将逻辑包装到 class 帮助程序中:

uses
  ..., Winapi.CommCtrl;

type
  TStatusBarHelper = class helper for TStatusBar
  public
    function GetPanelAt(X, Y: Integer): TStatusPanel; overload;
    function GetPanelAt(const P: TPoint): TStatusPanel; overload;
  end;

...

function TStatusBarHelper.GetPanelAt(X, Y: Integer): TStatusPanel;
begin
  Result := GetPanelAt(Point(X, Y));
end;

function TStatusBarHelper.GetPanelAt(const P: TPoint): TStatusPanel;
var
  index: Integer;
  arr: array of Integer;
  Panel: TStatusPanel;
begin
  Result := nil;

  if not PtInRect(Self.ClientRect, P) then
    Exit;

  SetLength(arr, SendMessage(Self.Handle, SB_GETPARTS, 0, 0));
  SendMessage(Self.Handle, SB_GETPARTS, Length(arr), LPARAM(PInteger(arr)));

  index := 0;
  while index < Length(arr) do
  begin
    if (P.X <= arr[index]) or (arr[index] = -1) then
    begin
      Result := Self.Panels[index];
      Exit;
    end;
    Inc(index);
  end;
end;

uses
  ..., System.Types, Winapi.Windows;

procedure TForm5.StatusBar1DblClick(Sender: TObject);
var
  Pt: TPoint;
  Panel: TStatusPanel;
begin
  Pt := SmallPointToPoint(TSmallPoint(GetMessagePos()));
  Pt := StatusBar1.ScreenToClient(Pt);

  Panel := StatusBar1.GetPanelAt(Pt);
  if Panel <> nil then
    ShowMessage('Click on Panel ' + IntToStr(Panel.Index))
  else
    ShowMessage('No click on a Panel');
end;

我使用了这个解决方案并且它对我有用。简单明了!

首先,为 StatusBar 的 OnMouseDown 事件编写处理程序

procedure TfrmMain.StatusBarMouseDown(Sender: TObject; Button: TMouseButton; Shift:
  TShiftState; X, Y: Integer);
begin
  TComponent(Sender).Tag := X;
end;

然后为 StatusBar 的 OnDoublClick 事件编写处理程序

procedure TfrmMain.StatusBarDblClick(Sender: TObject);
var
  AccumelatedWidth, i : Integer;
begin
  AccumelatedWidth := 0;
  for i := 0 to StatusBar.Panels.Count - 1 do
    begin
      AccumelatedWidth := AccumelatedWidth + StatusBar.Panels[i].Width;
      if StatusBar.Tag < AccumelatedWidth then
        begin
          ShowMessage ('You clicked panel ' + i.ToString);
          Break;
        end;
    end;
end;