我看到了一个关于如何让Delphi的TSpeedButton保持按下状态的问题(How to make a Delphi TSpeedButton stay pressed ...),但是由于它支持绘制图标(即Images
、ImageIndex
、HotImageIndex
等),我希望使用TButton
。我知道可以通过代码来实现所有的绘制,但我认为一定有一些技巧可以使它保持按下状态。
我看到了一个关于如何让Delphi的TSpeedButton保持按下状态的问题(How to make a Delphi TSpeedButton stay pressed ...),但是由于它支持绘制图标(即Images
、ImageIndex
、HotImageIndex
等),我希望使用TButton
。我知道可以通过代码来实现所有的绘制,但我认为一定有一些技巧可以使它保持按下状态。
TCheckbox
或TRadioButton
来实现外观类似于按钮并具有BS_PUSHLIKE
样式的效果。
使一个按钮(如复选框、三态复选框或单选按钮)看起来和行为像一个按下的按钮。当按钮未被按下或选中时,它看起来是凸起的,而当按钮被按下或选中时,它看起来是凹陷的。
TCheckBox
和TRadioButton
都是从标准的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;
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
属性来切换按钮状态。这只是对 kobik 的详细答案 的修改。我添加了 GroupIndex
属性,以便让一组按钮一起工作(当 GroupIndex <> 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
。
TButton
没有“按下”状态。 - David HeffernanTSpeedButton
<>TButton
。 - Jerry Dodge