【问题标题】:How to make Delphi TButton control stay pressed?如何让 Delphi TButton 控件保持按下状态?
【发布时间】:2018-04-06 16:07:02
【问题描述】:

我见过How to make a Delphi TSpeedButton stay pressed ...,但我希望它是TButton,因为它支持绘制字形(我的意思是ImagesImageIndexHotImageIndex,...)。我知道我可以通过代码将它全部绘制出来,但我认为一定有什么技巧可以让它保持下来。

【问题讨论】:

  • 不,TButton 没有“关闭”状态
  • @DavidHeffernan 如果我知道怎么做,我不会问问题!在这里阅读 Rob 的评论后,我怀疑这是可能的:Do we have Button down property as Boolean
  • 这不是怎么做的问题。这是一个是否可能的问题,大卫告诉过你三遍,但不是。您将需要推出自己的代码来模仿这一点,因为 Windows API 本身并不支持它。您肯定不希望我们为您编写自定义控件,是吗? TSpeedButton TButton.
  • SendMessage(Button.Handle, BM_SETSTATE, BST_PUSHED, LPARAM(True)); (link)。我没有发布答案,因为我几乎可以肯定您不确定您想要什么:API 将按下的按钮绘制为突出显示,因为按钮仅在它是活动控件时才被按下,而您很可能不会希望周围有很多看起来很活跃的按钮。
  • @SertacAkyuz,但是如何通过单击来实际切换按钮状态?

标签: delphi delphi-2010 vcl


【解决方案1】:

您可以使用TCheckboxTRadioButton 来获得具有BS_PUSHLIKE 样式的按钮外观。

制作一个按钮(例如复选框、三态复选框或单选框) 按钮)的外观和行为就像一个按钮。按钮看起来凸起时 不推送或检查,推送或检查时下沉。

TCheckBoxTRadioButton 实际上都是标准 Windows BUTTON 控件的子类。 (这将提供类似于 .net CheckBox 的切换按钮行为,其中 Appearance 设置为 Button - 请参阅:Do we have Button down property as Boolean)。

type
  TButtonCheckBox = class(StdCtrls.TCheckBox)
  protected
    procedure CreateParams(var Params: TCreateParams); override;
  end;

procedure TButtonCheckBox.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.Style := Params.Style or BS_PUSHLIKE;
end;

设置Checked 属性以使其按下或不按下。

要设置图像列表,请使用 Button_SetImageList 宏(向按钮控件发送 BCM_SETIMAGELIST 消息),例如:

uses CommCtrl;
...
procedure TButtonCheckBox.SetImages(const Value: TCustomImageList);    
var
  LButtonImageList: TButtonImageList;
begin
  LButtonImageList.himl := Value.Handle;
  LButtonImageList.uAlign := BUTTON_IMAGELIST_ALIGN_LEFT;
  LButtonImageList.margin := Rect(4, 0, 0, 0);
  Button_SetImageList(Handle, LButtonImageList);
  Invalidate;
end;

注意:要使用此宏,您必须提供一个清单来指定 Comclt32.dll 6.0版

每个TButton 使用它自己的 内部图像列表 (FInternalImageList),其中包含每个按钮状态的 5 个图像(ImageIndexHotImageIndex、...)。 因此,当您分配 ImageIndexHotImageIndex 等时,它会重建该内部图像列表并使用它。如果仅存在一张图像,则将其用于所有状态。 如果需要,请参阅源代码 TCustomButton.UpdateImages 以了解它是如何完成的,并为您的 TButtonCheckBox 应用相同的逻辑。


实际上,通过使用BS_PUSHLIKE + BS_CHECKBOX 样式将其转换为“复选框”并完全省略BS_PUSHBUTTON 样式,可以轻松地将逆方法直接应用于TButton。我从TCheckBox借了一点代码,并使用了一个interposer类进行演示:

type
  TButton = class(StdCtrls.TButton)
  private
    FChecked: Boolean;
    FPushLike: Boolean;
    procedure SetPushLike(Value: Boolean);
    procedure Toggle;
    procedure CNCommand(var Message: TWMCommand); message CN_COMMAND;
  protected
    procedure SetButtonStyle(ADefault: Boolean); override;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure CreateWnd; override;

    function GetChecked: Boolean; override;
    procedure SetChecked(Value: Boolean); override;
  published
    property Checked;
    property PushLike: Boolean read FPushLike write SetPushLike;
  end;

implementation

procedure TButton.SetButtonStyle(ADefault: Boolean);
begin
  if not FPushLike then inherited;
  { Else, do nothing - avoid setting style to BS_PUSHBUTTON }
end;

procedure TButton.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  if FPushLike then
  begin
    Params.Style := Params.Style or BS_PUSHLIKE  or BS_CHECKBOX;
    Params.WindowClass.style := Params.WindowClass.style and not (CS_HREDRAW or CS_VREDRAW);
  end;
end;

procedure TButton.CreateWnd;
begin
  inherited CreateWnd;
  if FPushLike then
    SendMessage(Handle, BM_SETCHECK, Integer(FChecked), 0);
end;

procedure TButton.CNCommand(var Message: TWMCommand);
begin
  if FPushLike and (Message.NotifyCode = BN_CLICKED) then
    Toggle
  else
    inherited;
end;

procedure TButton.Toggle;
begin
  Checked := not FChecked;
end;

function TButton.GetChecked: Boolean;
begin
  Result := FChecked;
end;

procedure TButton.SetChecked(Value: Boolean);
begin
  if FChecked <> Value then
  begin
    FChecked := Value;
    if FPushLike then
    begin
      if HandleAllocated then
        SendMessage(Handle, BM_SETCHECK, Integer(Checked), 0);
      if not ClicksDisabled then Click;
    end;
  end;
end;

procedure TButton.SetPushLike(Value: Boolean);
begin
  if Value <> FPushLike then
  begin
    FPushLike := Value;
    RecreateWnd;
  end;
end;

现在,如果您将PushLike 属性设置为True,您可以使用Checked 属性来切换按钮状态。

【讨论】:

  • 这正是我所需要的,并且在柏林 10.1 中工作得很好。明天我会在 D2010 中检查它。非常感谢您的详细回答。
【解决方案2】:

这只是对kobik's detailed answer 的修改。我添加了GroupIndex 属性以使一组按钮一起工作(在GroupIndex &lt;&gt; 0 时只让其中一个保持下来)。问题中甚至没有问到这样的设施,但我认为未来来这里的人可能很快就会需要它,就像我一样。我还删除了PushLike 属性,并默认它为True,因为毕竟我将它命名为TToggleButton

uses
  Winapi.Windows, Vcl.StdCtrls, Winapi.Messages, Vcl.Controls, Vcl.ActnList;

type
  TToggleButton = class(TButton)
  private
    FChecked: Boolean;
    FGroupIndex: Integer;
    procedure Toggle;
    procedure CNCommand(var Message: TWMCommand); message CN_COMMAND;
    procedure SetGroupIndex(const Value: Integer);
    procedure TurnSiblingsOff;
  protected
    procedure SetButtonStyle(ADefault: Boolean); override;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure CreateWnd; override;

    function GetChecked: Boolean; override;
    procedure SetChecked(Value: Boolean); override;
  published
    property Checked;
    property GroupIndex: Integer read FGroupIndex write SetGroupIndex;
  end;

implementation

 { TToggleButton}

procedure TToggleButton.SetButtonStyle(ADefault: Boolean);
begin
  { do nothing - avoid setting style to BS_PUSHBUTTON }
end;

procedure TToggleButton.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.Style := Params.Style or BS_PUSHLIKE  or BS_CHECKBOX;
  Params.WindowClass.style := Params.WindowClass.style and not (CS_HREDRAW or CS_VREDRAW);
end;

procedure TToggleButton.CreateWnd;
begin
  inherited CreateWnd;
  SendMessage(Handle, BM_SETCHECK, Integer(FChecked), 0);
end;

procedure TToggleButton.CNCommand(var Message: TWMCommand);
begin
  if Message.NotifyCode = BN_CLICKED then
    Toggle
  else
    inherited;
end;

procedure TToggleButton.Toggle;
begin
  Checked := not FChecked;
end;

function TToggleButton.GetChecked: Boolean;
begin
  Result := FChecked;
end;

procedure TToggleButton.SetChecked(Value: Boolean);
begin
  if FChecked <> Value then
  begin
    FChecked := Value;
    if HandleAllocated then
      SendMessage(Handle, BM_SETCHECK, Integer(Checked), 0);
    if Value then
      TurnSiblingsOff;
    if not ClicksDisabled then Click;
  end;
end;

procedure TToggleButton.SetGroupIndex(const Value: Integer);
begin
  FGroupIndex := Value;
  if Checked then
    TurnSiblingsOff;
end;

procedure TToggleButton.TurnSiblingsOff;
var
  I: Integer;
  Sibling: TControl;
begin
  if (Parent <> nil) and (GroupIndex <> 0) then
    with Parent do
      for I := 0 to ControlCount - 1 do
      begin
        Sibling := Controls[I];
        if (Sibling <> Self) and (Sibling is TToggleButton) then
          with TToggleButton(Sibling) do
            if GroupIndex = Self.GroupIndex then
            begin
              if Assigned(Action) and
                 (Action is TCustomAction) and
                 TCustomAction(Action).AutoCheck then
                TCustomAction(Action).Checked := False;
              SetChecked(False);
            end;
      end;
end;

TurnSiblingsOff 方法借用自TRadioButton

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-24
    • 2018-07-27
    相关资源
    最近更新 更多