在TListView表头列中的复选框 - 如何防止它夺取焦点?

8
这与问题“如何在TListView标题列中显示复选框?”有关。
我想使用@Sertac Akyuz这个答案中的代码。(我需要在WinXP中运行)
但是我希望使标题CheckBox不会从ListView或其他活动控件窃取焦点。
一种快速解决方法是在ListHeaderWndProc中始终将焦点设置为ListView。
...
FListHeaderChk.Checked := not FListHeaderChk.Checked;
ListView1.SetFocus;
// code that checks/clears all items

但这有点丑陋。因为CheckBox首先获得焦点,然后焦点返回到ListView。此外,如果我单击CheckBox并将鼠标拖到CheckBox外部,则无法接收BN_CLICKED消息。

我还尝试过:

TCheckBox = class(StdCtrls.TCheckBox)
  private
    procedure WMMouseActivate(var Message: TWMMouseActivate); message WM_MOUSEACTIVATE;
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  public
    procedure DefaultHandler(var Message); override;
  end;

procedure TCheckBox.WMMouseActivate(var Message: TWMMouseActivate);
begin
  Message.Result := MA_NOACTIVATE; // no effect!
end;

procedure TCheckBox.CreateParams(var Params: TCreateParams);
const
  WS_EX_NOACTIVATE = $08000000;
begin
  inherited;
  Params.ExStyle := Params.ExStyle or WS_EX_NOACTIVATE; // no effect!
end;

procedure TCheckBox.DefaultHandler(var Message);
begin
  case TMessage(Message).Msg of
    WM_SETFOCUS:
    begin
      if IsWindow(TWMSetFocus(Message).FocusedWnd) then
      begin
        TMessage(Message).Result := 1; // ???
        // inherited // ??? 
        Windows.SetFocus(TWMSetFocus(Message).FocusedWnd);
        Exit;
        // Checkbox fails to receive `BN_CLICKED` message
      end;
    end;
  end;
  inherited;
end;

什么都不起作用。我错过了什么吗?


我猜干净的方法是根本不使用复选框,而是尝试看看是否可以在列表视图中使用所有者绘制的标题项来实现效果。对于标准复选框,“单击并拖到外面”不应该生成 BN_CLICKED 事件,如果绘制复选框,则还可以更改此行为。顺便说一句,对于复选框应该有一个 TabStop:=False 的位置... - Sertac Akyuz
@SertacAkyuz,这里有一个 TabStop := False。这很明显。使用自绘制的标题是一个不错的替代方案,但这并不能回答问题,而且这有什么乐趣呢?;) - kobik
2
好的,那么请看我发布的答案。 :) - Sertac Akyuz
1个回答

7
不要处理WM_COMMAND消息的BN_CLICKED通知,否则在点击按钮后拖动到外部时,您期望的行为将无法实现。单击包括按下并释放控件上的按钮。
相反,您可以查看鼠标是否在控件内部被单击,如果是,则切换选中状态。之后可以吞噬鼠标消息,以便控件不会获得焦点。但是此操作应该在复选框的窗口过程中检查,而不是列表视图。修改后的代码:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, StdCtrls;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    CheckBox1: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FListHeaderChk: TCheckBox;
    FSaveListHeaderChkWndProc: TWndMethod;
    FListHeaderWnd: HWND;
    procedure ListHeaderChkWndProc(var Msg: TMessage);
  end;

var
  Form1: TForm1;

implementation

uses
  commctrl;

{$R *.dfm}

function GetCheckSize: TPoint;     // from checklst.pas
begin
  with TBitmap.Create do
    try
      Handle := LoadBitmap(0, PChar(OBM_CHECKBOXES));
      Result.X := Width div 4;
      Result.Y := Height div 3;
    finally
      Free;
    end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  CheckSize: TPoint;
  HeaderSize: TRect;
begin
  ListView1.HandleNeeded;
  FListHeaderWnd := ListView_GetHeader(ListView1.Handle);

  FListHeaderChk := TCheckBox.Create(nil);
  CheckSize := GetCheckSize;
  FListHeaderChk.Height := CheckSize.X;
  FListHeaderChk.Width := CheckSize.Y;

  ShowWindow(ListView1.Handle, SW_SHOWNORMAL);
  windows.GetClientRect(FListHeaderWnd, HeaderSize);
  FListHeaderChk.Top := (HeaderSize.Bottom - FListHeaderChk.Height) div 2;
  FListHeaderChk.Left := FListHeaderChk.Top;

  FListHeaderChk.Parent := Self;
  FListHeaderChk.TabStop := False;
  windows.SetParent(FListHeaderChk.Handle, FListHeaderWnd);
  FSaveListHeaderChkWndProc := FListHeaderChk.WindowProc;
  FListHeaderChk.WindowProc := ListHeaderChkWndProc;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FListHeaderChk.Free;
end;

procedure TForm1.ListHeaderChkWndProc(var Msg: TMessage);
begin
  if (Msg.Msg = WM_MOUSEACTIVATE) and (Msg.LParamLo = HTCLIENT) then begin
    Msg.Result := MA_NOACTIVATEANDEAT;
    FListHeaderChk.Checked := not FListHeaderChk.Checked;
    Exit;
  end;

  FSaveListHeaderChkWndProc(Msg);
end;

end.

3
干得好!如果可以的话,只有两个小问题:(1)在销毁时不需要恢复FListHeaderChk.WindowProc吗?(2)为什么不创建FListHeaderChk := TCheckBox.Create(Self); - kobik
3
@kobik - 谢谢!(2) - 没有问题,因为表单已经拥有了列表视图,我也不认为它拥有复选框会有任何问题。(1) 不是很确定。子类化的潜在问题是多段代码这样做并且使用错误的顺序进行恢复(或不进行恢复)。如果您完全掌控整个过程,我不认为会有问题。我在VCL代码中看到过这样的情况,但现在不记得在哪里以及是否使用了WindowProc。最好使用一个专门的复选框派生类而不需要子类化。 - Sertac Akyuz
3
我支持使用一个检查后代。因此,覆盖 WndProc 声音对我来说是更好的解决方案。谢谢! - kobik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接