我有一个从TWinControl派生的可视组件。当我的组件的父控件已调整大小时,我需要在我的组件中做一些工作。一般情况下," Align"我的组件的属性是alNone。
如何捕获调整父控件大小的事件?有可能吗?
答案 0 :(得分:7)
如果更改了TWinControl(父级),则在TWinControl.Realign
处理程序中调用WM_SIZE
。这会通过TWinControl.AlignControls
冒泡到迭代所有子控件,这些控件的Align属性设置为alNone
之外的任何其他值。当设置为alCustom
时,将使用未更改的参数调用子控件的SetBounds
,即使它们的大小因锚参与而已更改或未更改。
因此,将对齐设置为alCustom
并且您有父级调整大小的通知:
TChild = class(T...Control)
private
FInternalAlign: Boolean;
function GetAlign: TAlign;
procedure ParentResized;
procedure SetAlign(Value: TAlign);
protected
procedure RequestAlign; override;
public
constructor Create(AOwner: TComponent); override;
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
published
property Align: TAlign read GetAlign write SetAlign default alCustom;
end;
constructor TChild.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Align := alCustom;
end;
function TChild.GetAlign: TAlign;
begin
Result := inherited Align;
end;
procedure TChild.ParentResized;
begin
end;
procedure TChild.RequestAlign;
begin
FInternalAlign := True;
try
inherited RequestAlign;
finally
FInternalAlign := False;
end;
end;
procedure TChild.SetAlign(Value: TAlign);
begin
if Value = alNone then
Value := alCustom;
inherited Align := Value;
end;
procedure TChild.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
if not FInternalAlign then
if (Align <> alCustom) or ((ALeft = Left) and (ATop = Top) and
(AWidth = Width) and (AHeight = Height)) then
ParentResized;
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
end;
我现在唯一能想到的缺点是Align属性永远不会是alNone
,这可能会使组件的用户感到困惑。当内部继承属性仍设置为alNone
时,很容易显示或返回alCustom
,但这不是建议,只会混淆更多。只需将alCustom
设置视为此组件的一项功能。
注意:使用这种结构,组件的用户仍然可以自己实现自定义对齐。
这是我的测试代码。也许你想为自己添加一些测试。
unit Unit1;
interface
uses
Windows, SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
TestButton: TButton;
Panel1: TPanel;
procedure FormCreate(Sender: TObject);
procedure TestButtonClick(Sender: TObject);
private
FChild: TControl;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TChild = class(TGraphicControl)
private
FInternalAlign: Boolean;
function GetAlign: TAlign;
procedure ParentResized;
procedure SetAlign(Value: TAlign);
protected
procedure Paint; override;
procedure RequestAlign; override;
public
constructor Create(AOwner: TComponent); override;
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
published
property Align: TAlign read GetAlign write SetAlign default alCustom;
end;
{ TChild }
constructor TChild.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Align := alCustom;
end;
function TChild.GetAlign: TAlign;
begin
Result := inherited Align;
end;
procedure TChild.Paint;
begin
Canvas.TextRect(ClientRect, 2, 2, 'Parent resize count = ' + IntToStr(Tag));
end;
procedure TChild.ParentResized;
begin
Tag := Tag + 1;
Invalidate;
end;
procedure TChild.RequestAlign;
begin
FInternalAlign := True;
try
inherited RequestAlign;
finally
FInternalAlign := False;
end;
end;
procedure TChild.SetAlign(Value: TAlign);
begin
if Value = alNone then
Value := alCustom;
inherited Align := Value;
end;
procedure TChild.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
if not FInternalAlign then
if (Align <> alCustom) or ((ALeft = Left) and (ATop = Top) and
(AWidth = Width) and (AHeight = Height)) then
ParentResized;
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
end;
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
FChild := TChild.Create(Self);
FChild.SetBounds(10, 10, 200, 50);
FChild.Parent := Self;
end;
procedure TForm1.TestButtonClick(Sender: TObject);
var
OldCount: Integer;
begin
OldCount := FChild.Tag;
Width := Width + 25; //1
MoveWindow(Handle, Left, Top, Width + 25, Height, True); //2
SetWindowPos(Handle, HWND_TOP, Left, Top, Width + 25, Height,
SWP_NOMOVE or SWP_NOSENDCHANGING or SWP_SHOWWINDOW); //3
FChild.Anchors := [akLeft, akTop, akRight];
Width := Width + 25; //4
MoveWindow(Handle, Left, Top, Width + 25, Height, True); //5
SetWindowPos(Handle, HWND_TOP, Left, Top, Width + 25, Height,
SWP_NOMOVE or SWP_NOSENDCHANGING or SWP_SHOWWINDOW); //6
FChild.Anchors := [akLeft, akTop];
Panel1.Anchors := [akLeft, akTop, akRight];
FChild.Parent := Panel1; //7
Width := Width + 25; //8
MoveWindow(Handle, Left, Top, Width + 25, Height, True); //9
SetWindowPos(Handle, HWND_TOP, Left, Top, Width + 25, Height,
SWP_NOMOVE or SWP_NOSENDCHANGING or SWP_SHOWWINDOW); //10
FChild.Align := alRight;
Width := Width + 25; //11
MoveWindow(Handle, Left, Top, Width + 25, Height, True); //12
SetWindowPos(Handle, HWND_TOP, Left, Top, Width + 25, Height,
SWP_NOMOVE or SWP_NOSENDCHANGING or SWP_SHOWWINDOW); //13
if FChild.Tag = OldCount + 13 then
ShowMessage('Test succeeded')
else
ShowMessage('Test unsuccessful');
end;
end.
答案 1 :(得分:1)
是的,Andrew,我认为将你的组件附加到父的消息循环(子类化它)是可行的方法。为此,您可以使用TControl.WindowProc
属性。 doc解释说你必须保存原文并在以后恢复(在组件的析构函数中),并将消息传递给原始处理程序,即你的替换应该看起来像
procedure TMyComponent.SubclassedParentWndProc(Var Msg: TMessage);
begin
FOldParentWndProc(Msg);
if(Msg.Message = WM_SIZE)then begin
...
end;
end;
如果您想以“老套”方式执行此操作,请使用GWLP_WNDPROC
API与WindowProc
,但AFAIK {{1}}的引入正是为了使其更容易子类化组件,即使用它没什么不对。
答案 2 :(得分:1)
警告:完全重写。谢谢Rob !!
使用SetWindowSubClass的示例。
unit Example;
interface
uses
Windows, Classes, Controls, StdCtrls, Messages, CommCtrl, ExtCtrls;
type
TExampleClass = class(TlistBox)
private
procedure ActivateParentWindowProc;
procedure RevertParentWindowProc;
protected
procedure SetParent(AParent: TWinControl); override;
public
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
end;
function SubClassWindowProc(hWnd: HWND; uMsg: UINT; wParam: WPARAM;
lParam: LPARAM; uIdSubclass: UINT_PTR; dwRefData: DWORD_PTR): LRESULT; stdcall;
implementation
procedure TExampleClass.ActivateParentWindowProc;
begin
SetWindowSubClass( Parent.Handle, SubClassWindowProc, NativeInt(Self), 0);
end;
procedure TExampleClass.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited Notification(AComponent, Operation);
if (Operation = opRemove) and (AComponent = Parent) then
begin
RevertParentWindowProc;
end;
end;
procedure TExampleClass.RevertParentWindowProc;
begin
RemoveWindowSubclass( Parent.Handle,
SubClassWindowProc, NativeInt(Self));
end;
procedure TExampleClass.SetParent(AParent: TWinControl);
begin
if Assigned(Parent) then
begin
RevertParentWindowProc;
end;
inherited SetParent(AParent);
if Assigned(AParent) then
begin
ActivateParentWindowProc;
end
else
begin
RevertParentWindowProc;
end;
end;
function SubClassWindowProc(hWnd: HWND; uMsg: UINT;
wParam: WPARAM; lParam: LPARAM; uIdSubclass: UINT_PTR;
dwRefData: DWORD_PTR): LRESULT;
begin
if uMsg = WM_SIZE then
begin
// ...
end;
Result := DefSubclassProc(hWnd, uMsg, wParam, lParam);
end;
end.
答案 3 :(得分:0)
我一直在寻找类似问题的解决方案。但在我的情况下,我不能对齐,并且子类化似乎有点过分(对齐的东西看起来也太过分了,现在我看着它)
所以我提出了以下想法:
SELECT
tblprocedimento.idProcedimento,
tblprocedimento.tituloProcedimento,
tblprocedimento.tipoProximoS,
tblprocedimento.numeroProximoS,
tblprocedimento.tipoProximoN,
tblprocedimento.numeroProximoN,
ap1.nomeProximo AS nomeProximoS,
ap2.nomeProximo AS nomeProximoN,
tblauxtextoprocedimento.textoProcedimento,
tblauxprocedimento.sTipificacao,
tblNivel1.nivel1,
tblNivel2.nivel2,
tblNivel3.nivel3,
tblNivel4.nivel4
FROM
tblAuxTextoProcedimento
LEFT JOIN
tblAuxProcedimento
ON
tblAuxTextoProcedimento.idTextoProcedimento = tblAuxProcedimento.sTextoProcedimento,
tblNivel4 INNER JOIN (tblNivel3
INNER JOIN (tblNivel2
INNER JOIN (tblNivel1
INNER JOIN tblAuxTipificacao
ON tblNivel1.idNivel1 = tblAuxTipificacao.sNivel1)
ON tblNivel2.idNivel2 = tblAuxTipificacao.sNivel2)
ON tblNivel3.idNivel3 = tblAuxTipificacao.sNivel3)
ON tblNivel4.idNivel4 = tblAuxTipificacao.sNivel4,
tblprocedimento,
tblauxproximo As ap1,
tblauxproximo As ap2
WHERE
ap2.idProximo = tblprocedimento.sProxN AND
ap1.idProximo = tblprocedimento.sProxS AND
tblprocedimento.idProcedimento = 1 AND
tblAuxProcedimento.sProcedimento = 1 AND
tblAuxTipificacao.idTipificacao = 130
添加或替换您跟踪的任何大小的FParentLastWidth(我只需要在父宽度更改时进行反应。您可以将其作为优化,以便不对所有类型的更改作出反应,这对您的组件没有影响)< / p>
答案 4 :(得分:-1)
以下是帮助您的示例:
procedure TForm1.Button1Click(Sender: TObject);
var newMethod: TMethod;
begin
newMethod.Code := MethodAddress('OnResizez'); //your action for parent resize
newMethod.Data := Pointer(self);
SetMethodProp(button1.Parent, 'OnResize', newMethod); //set event to button1.parent
end;
procedure TForm1.OnResizez(Sender: TObject);
begin
button1.Width := button1.Width+1; //action on resize
end;