JackTrapper / DelphiBugs

Bug tracker for Delphi
7 stars 2 forks source link

WM_WindowPosChanged does not realign controls #16

Open JackTrapper opened 5 years ago

JackTrapper commented 5 years ago

The call to FOleInplaceObject.SetObjectRects causes the control to send us the WM_WINDOWPOSCHANGED message.

TWinControl does not properly handle aligning fixing during WM_WindowPosChanged like it does for SetBounds.

SetBounds calls:

procedure TControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
    UpdateAnchorRules;
    UpdateExplicitBounds;
    RequestAlign;
end;

while WM_WindowPosChanged only calls:

procedure TWinControl.WMWindowPosChanged(var Message: TWMWindowPosChanged);
begin
   //...snip
   UpdateBounds;
   //...snip
end;

TOleControl Patch

I became aware of this problem when using a OLE control; it sends WM_WindowPosChanged and Delphi doesn't handle it correctly.

We have three choices:

The first option works; though i don't know the downside of not using SetObjectRects.

Reading the documentation, it seems that it is incorrect to use SetObjectsRects to perform resizing - but that didn't stop Imprise.

The TWebBrowser happens to send WM_WindowPosChanged, and it happens to screw us up.

The person who wrote this code didn't think that calling SetObjectRects would change the position, otherwise they wouldn't have still called SetBounds.

Perhaps the ideal fix is to do all the bounds setting first, then call SetObjectsRects.

The VCL fix workaround

I decided on limiting to fix to TOleControl, and leave everyone else broken if they happen to ever receive an WM_WindowPosChanged from Windows.

procedure TOleControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
var
  LRect: TRect;
{$IFDEF WIN64}
  Temp: TPoint;
{$ENDIF}
begin
  if ((AWidth <> Width) and (Width > 0)) or ((AHeight <> Height) and (Height > 0)) then
  begin
{$IFDEF WIN64}
    Temp := Point(MulDiv(AWidth, 2540, Screen.PixelsPerInch), MulDiv(AHeight, 2540, Screen.PixelsPerInch));
    if (FMiscStatus and OLEMISC_INVISIBLEATRUNTIME <> 0) or
      ((FOleObject.SetExtent(DVASPECT_CONTENT, @Temp) <> S_OK)) then
{$ELSE}
    if (FMiscStatus and OLEMISC_INVISIBLEATRUNTIME <> 0) or
      ((FOleObject.SetExtent(DVASPECT_CONTENT, Point(
        MulDiv(AWidth, 2540, Screen.PixelsPerInch),
        MulDiv(AHeight, 2540, Screen.PixelsPerInch))) <> S_OK)) then
{$ENDIF}
    begin
      AWidth := Width;
      AHeight := Height;
     end;
     {VCL bug workaround. The call to FOleInplaceObject.SetObjectRects
        causes the control to send us back the WM_WINDOWPOSCHANGED message.
        TWinControl does not properly handle aligning fixing during WM_WindowPosChanged
        like it does for SetBounds.
        SetBounds calls:
                UpdateAnchorRules
                UpdateExplicitBounds
                RequestAlign

        while WM_WindowPosChanged only calls:
                UpdateBounds (which only calls UpdateAnchor Rules)

        We have three choices:
            - don't call OleInPlaceObject.SetObjectRects. Dephi 5 didn't do it, and it worked fine
            - add a call to RequestAlign in Vcl.Controls.TWinControl.WMPosChanged
            - override WM_WindowPosChanged here in TOleControl, call inherited first, then do our own call to RequestAlign

        The first option works; though i don't know the downside of not using SetObjectRects.
        Reading the documentation, it seems that it is incorrect to use SetObjectsRects to perform resizing.
        The TWebBrowser *happens* to send WM_WindowPosChanged, and it *happens* to screw us up.

        The person who wrote this code didn't think that calling SetObjectRects would change the position, otherwise they
        wouldnt' have still called SetBounds.

        Perhaps the ideal fix is to do all the bounds setting first, *then* call SetObjectsRects
     }
     {Removed. Call *after* inheirted SetBounds
     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 from above. We need SetBounds to happen first. Delphi's WMWindowPosChanged does not handle resizing correctly
  if FOleInplaceObject <> nil then
  begin
     LRect := Rect(Left, Top, Left+AWidth, Top+AHeight);
     FOleInplaceObject.SetObjectRects(LRect, LRect);
  end;
end;