该问题是由两个回归引起的。
- 一进一进TWinControl.AlignControls
- 另一个是由更改引起的TOleControl.SetBounds,虽然实际的错误是在TWinControl.WMWindowPosChanged.
“永远不会自动调整大小”错误
我在 Stackoverflow 问题中详细介绍了第一个错误包含 TPanel 时 TPanel 不自动调整大小 https://stackoverflow.com/a/29421315/12597:
procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);
begin
//...snip
// Apply any constraints
if Showing and ((sfWidth in FScalingFlags) or (sfHeight in FScalingFlags)) then
DoAdjustSize;
//...snip
end;
这里的错误是它不会调用DoAdjustSize
除非sfWidth or sfHeight存在缩放标志。
解决办法是不要试图超越自己,并且DoAdjustSize
不管:
procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);
begin
//...snip
// Apply any constraints
//QC125995: Don't look to scaling flags to decide if we should adjust size
if Showing {and ((sfWidth in FScalingFlags) or (sfHeight in FScalingFlags))} then
DoAdjustSize;
//...snip
end;
“调整大小时不自动调整大小”错误
先前的修复使面板在包含子项时自动调整大小TControl or 双控。但是当面板包含一个时还有另一个错误远程控制。该错误是在 Delphi XE 中引入的。与上面的错误不同,这个错误是由某人认为自己很聪明引起的,而这个错误要微妙得多。
When a 远程控制调整大小后,其设置边界方法被调用。这是原始的功能代码:
procedure TOleControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
if ((AWidth <> Width) and (Width > 0)) or ((AHeight <> Height) and (Height > 0)) then
begin
//...snip: perhaps tweak AWidth and AHeight
end;
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
end;
在 XE2 时间范围内,代码已更改为以便通知底层Ole控制它的边界即将改变:
procedure TOleControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
var
LRect: TRect;
begin
if ((AWidth <> Width) and (Width > 0)) or ((AHeight <> Height) and (Height > 0)) then
begin
//...snip: perhaps tweak AWidth and AHeight
//Notify the underlying Ole control that its bounds are about to change
if FOleInplaceObject <> nil then
begin
LRect := Rect(Left, Top, Left+AWidth, Top+AHeight);
FOleInplaceObject.SetObjectRects(LRect, LRect);
end;
end;
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
end;
作者不知道的是,这暴露了一个错误双控。打电话的问题是 Ole 控件(例如 Internet Explorer)转身并发送WM_WindowPosChanged https://msdn.microsoft.com/en-us/library/windows/desktop/ms632652(v=vs.85).aspx信息。这WM_WindowPoschanged处理程序在双控不能正确处理消息。
虽然常规的SetBounds
方法正确调用:
procedure SetBounds;
begin
UpdateAnchorRules;
UpdateExplicitBounds;
RequestAlign; //the important one we need
end;
The WMWindowPosChanged
方法仅调用:
procedure WMWindowPosChanged;
begin
UpdateBounds; //which only calls UpdateAnchorRules
end;
这意味着WinControl调整其大小;但其父级永远不会重新调整以处理新的自动尺寸。
The Fix
修复方法是:
- 不要打电话
IOleInPlaceObject.SetObjectRects
完全来自 SetBounds。 Delphi 5没有做到,而且运行得很好
-
更改 WM_WindowPosChanged 以便它也被调用请求对齐:
procedure TWinControl.WMWindowPosChanged;
begin
UpdateBounds;
RequestAlign; //don't forget to autosize our parent since we're changing our size behind our backs (e.g. TOleControl)
end;
-
更改 UpdateBounds 以同时调用请求对齐:
procedure TWinControl.UpdateBounds;
begin
UpdateAnchorRules;
//UpdateExplicitBounds; SetBounds calls this; why are we not calling it?
RequestAlign; //in response to WM_WindowPosChanged
end;
我选择了第四种解决方案;一个完整保留错误的方法,但对我来说已经足够修复它了。
错误在于:
-
WM_WindowPosChanged不能正确处理尺寸变化
- but 设置边界 does
所以让我们使用设置边界 first.
利用(大部分)正确的代码设置边界完成所有自动调整大小。然后我们可以调用SetObjectRects
. When WM_WindowPosChanged收到它的WM_WindowPosChanging
消息,它不会做任何事情 - 因此不会做任何错误的事情。
tl;dr
procedure TOleControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
var
LRect: TRect;
begin
if ((AWidth <> Width) and (Width > 0)) or ((AHeight <> Height) and (Height > 0)) then
begin
//...snip: perhaps fiddle with AWidth or AHeight
{Removed. Call *after* inheirted SetBounds
//Notify the underlying Ole control that its bounds are about to change
if FOleInplaceObject <> nil then
begin
LRect := Rect(Left, Top, Left+AWidth, Top+AHeight);
FOleInplaceObject.SetObjectRects(LRect, LRect);
end;}
end;
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
//moved to call *after* SetBounds, we need SetBounds to happen first.
//TWinControl's WMWindowPosChanged does not handle autosizing correctly
//while SetBounds does.
//Notify the underlying Ole control that its bounds are already about to change
if FOleInplaceObject <> nil then
begin
LRect := Rect(Left, Top, Left+AWidth, Top+AHeight);
FOleInplaceObject.SetObjectRects(LRect, LRect);
end;
end;
Note:发布到公共领域的任何代码。无需归属。