简体   繁体   English

如何让Delphi TButton控件保持按下状态?

[英]How to make Delphi TButton control stay pressed?

I've seen How to make a Delphi TSpeedButton stay pressed ... , but I want it to be TButton because of the way it supports drawing glyph (I mean Images , ImageIndex , HotImageIndex , ...). 我已经看过如何使Delphi TSpeedButton保持按下... ,但我希望它是TButton因为它支持绘制字形的方式(我的意思是ImagesImageIndexHotImageIndex ,...)。 I know that I can draw it all by code, but I thought there must be some trick that makes it stay down. 我知道我可以通过代码绘制所有内容,但我认为必须有一些技巧可以让它保持原状。

You can use a TCheckbox or a TRadioButton to have an appearance of a Button with the BS_PUSHLIKE style. 您可以使用TCheckboxTRadioButton来显示具有BS_PUSHLIKE样式的Button。

Makes a button (such as a check box, three-state check box, or radio button) look and act like a push button. 使按钮(例如复选框,三态复选框或单选按钮)看起来像按钮一样。 The button looks raised when it isn't pushed or checked, and sunken when it is pushed or checked. 未按下或检查按钮时,按钮会凸起,按下或检查时,按钮会凹陷。

Both TCheckBox and TRadioButton are actually sub-classed from the standard Windows BUTTON control. 无论TCheckBoxTRadioButton实际上是子类从标准的Windows BUTTON控制。 (This will give a toggle button behavior similar to .net CheckBox with Appearance set to Button - see: Do we have Button down property as Boolean ). (这将提供类似于.net CheckBox的切换按钮行为,其Appearance设置为Button - 请参阅: 我们将Button down属性设置为布尔值 )。

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;

Set the Checked property to make it pressed or not. 设置Checked属性以使其按下。

To set an image list use Button_SetImageList macro (which sends a BCM_SETIMAGELIST message to the button control) eg: 要设置图像列表,请使用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;

Note: To use this macro, you must provide a manifest specifying Comclt32.dll version 6.0 注意:要使用此宏,必须提供指定Comclt32.dll版本6.0的清单

Each TButton uses it's own internal image list ( FInternalImageList ) that holds 5 images for each button state ( ImageIndex , HotImageIndex , ...). 每个TButton使用它自己的内部图像列表( FInternalImageList ),每个按钮状态( ImageIndexHotImageIndex ,...)保存5个图像。 So when you assign an ImageIndex or HotImageIndex etc, it rebuilds that internal image list, and uses that. 因此,当您分配ImageIndexHotImageIndex等时,它会重建该内部图像列表,并使用它。 If only one image is present, it is used for all states. 如果仅存在一个图像,则将其用于所有状态。 If needed, see source TCustomButton.UpdateImages to learn how it's done, and apply the same logic for your TButtonCheckBox . 如果需要,请参阅源TCustomButton.UpdateImages以了解它是如何完成的,并为TButtonCheckBox应用相同的逻辑。


Actually the inverse method could be easily applied directly to a TButton by turning it into a "check box" using BS_PUSHLIKE + BS_CHECKBOX styles, and omitting the BS_PUSHBUTTON style completely. 实际上,反演方法可以很容易地直接应用于TButton将其变成一个“复选框”使用BS_PUSHLIKE + BS_CHECKBOX风格,并省略BS_PUSHBUTTON完全风格。 I borrowed a bit of code from TCheckBox and used an interposer class for demo: 我从TCheckBox借了一些代码并使用了一个插入器类进行演示:

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;

Now if you set PushLike property to True , you can use the Checked property to toggle the button state. 现在,如果将PushLike属性设置为True ,则可以使用Checked属性切换按钮状态。

This is just a modification to kobik's detailed answer . 这只是对kobik详细解答的修改。 I added GroupIndex property to make a group of buttons work together (let only one of them stay down at a time when GroupIndex <> 0 ). 我添加了GroupIndex属性以使一组按钮一起工作(当GroupIndex <> 0时,只允许其中一个按钮保持GroupIndex <> 0 )。 Such facility was not even asked in the question, but I thought people who come here in the future may need it soon after, just like I did. 在问题中甚至没有问过这样的设施,但我认为将来很快就会有人这样做,就像我一样。 I also removed PushLike property and assumed it to be True by default, since I named it the TToggleButton after all. 我还删除了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;

The TurnSiblingsOff method is borrowed from TRadioButton . TurnSiblingsOff方法来自TRadioButton

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM