Delphi中无边框窗体/窗口的平滑调整大小

5
我正在尝试调整一个无边框窗体的大小,但当我使用右/下侧增加大小时,会在边框和旧客户区之间产生一个间隙,这取决于您移动鼠标的速度。当您从左边框甚至从左下角缩放时,效果更为明显,无论在哪里都很糟糕(我尝试过其他商业应用程序,也会发生这种情况)。当我改变为可调整大小的边框时,这种效果也会发生,但不像去掉窗体边框那样糟糕。
窗体布局由顶部面板执行标题栏功能(带有一些tImages和按钮),以及显示其他信息的其他面板(如备忘录、其他控件等)组成。
以下是我的代码片段,其中我捕获鼠标按钮并向Windows发送消息,但我也尝试手动操作,结果类似。
激活顶部面板的双缓冲可以避免闪烁,但调整面板的大小与调整窗体大小不同步,因此出现了间隙或部分面板消失的情况。
 procedure TOutputForm.ApplicationEvents1Message( var Msg: tagMSG;
  var Handled: Boolean );
const
  BorderBuffer = 5;
var
  X, Y: Integer;
  ClientPoint: TPoint;
  direction: integer;
begin
  Handled := false;
  case Msg.message of
    WM_LBUTTONDOWN:
      begin
        if fResizable then
        begin
          if fSides = [sTop] then
            direction := 3
          else if fSides = [sLeft] then
            direction := 1
          else if fSides = [sBottom] then
            direction := 6
          else if fSides = [sRight] then
            direction := 2
          else if fSides = [sRight, sTop] then
            direction := 5
          else if fSides = [sLeft, sTop] then
            direction := 4
          else if fSides = [sLeft, sBottom] then
            direction := 7
          else if fSides = [sRight, sBottom] then
            direction := 8;
          ReleaseCapture;
          SendMessage( Handle, WM_SYSCOMMAND, ( 61440 + direction ), 0 );
          Handled := true;
        end;
      end;
    WM_MOUSEMOVE:
      begin
        // Checks the borders and sets fResizable to true if it's in a "border" 
        // ...
      end; // mousemove
  end; // case
end;

我应该如何避免那个区域或强制窗口重新绘制?我正在使用Delphi,但通用的解决方案(或其他语言)甚至是前进的方向对我来说都可以。

提前感谢您。


1
你的意思是在调整大小期间存在“间隙”,一旦调整大小操作结束,窗体就会被正确绘制? - ain
3
如何调整没有边框的窗体大小? - NGLN
1
您无法按照您所描述的方式调整无边框窗体的大小(通过拖动调整边框)。您能否告诉我们您真正要做什么,并展示一些代码。 - David Heffernan
很抱歉回复晚了,这里的日子很忙。我使用了wm_syscommand并手动设置边界来捕获wm_mousemove消息,但结果是相同的,在鼠标移动和窗口重新绘制之间需要很长时间,我想这是正常的windows行为,因为我使用的几乎所有程序都会这样,但我想知道如何将其最小化或者如果可能的话避免这种情况。 - Jade
你需要哪部分的代码? - Jade
显示剩余6条评论
4个回答

6
上次我试图手动创建一个顶级窗口,通过WM_SYSCOMMAND和鼠标拖动来调整大小,无论涉及任何嵌套面板与否,我发现问题不仅仅局限于闪烁。
即使是没有可调整大小边框的裸TForm,添加自己的可调整大小边框并直接处理鼠标按下、鼠标移动和鼠标抬起消息也被证明过于棘手。我放弃了您在此展示的代码方法,而是找到了两种可行的方法:
  1. 采用一种方法,即接管非客户区域的绘制。这就是 Google Chrome 和许多其他完全自定义窗口所采取的方式。您仍然有一个非客户端区域,需要您自己进行绘制和处理非客户端和边框绘制。换句话说,它并不是真正的无边框,但如果您想要,它可以全部是单一颜色。请阅读这篇关于 WM_NCPAINT 消息的帮助, 以开始操作。

  2. 使用一个无边框可调整大小的窗口,即使没有其非客户区域,也会被识别为可调整大小的窗口。想象一下一个便笺小部件应用程序。 这里 是我之前提出的一个问题,在我的问题底部有一个完全有效的演示,提供了一种平滑、无闪烁的方式来拥有无边框可调整大小的窗口。答案的基本技术由 David H. 提供。


在您提到的线程中,第二个使用SetWindowRgn的答案似乎很好,除了当您最大化窗口时...标题栏的空间会显示为透明矩形(该空间被保留但不在要可视化的区域内)。我尝试在CreateForm方法中使用SetWindowLong(Handle,GWL_STYLE,GetWindowLong(Handle,GWL_STYLE) and not WS_CAPTION); ClientHeight := Height;来移除标题栏,但是它完全无用:S - Jade
1
我因为你提到的同样原因而放弃了那个。换句话说,要么和WM_NCPAINT成为朋友,要么就忘了它! :-) 只有在您永远不让主窗口冻结时,WM_NCPAINT才仍然可行。否则,默认的框架/边框/非客户端绘画将对用户可见,破坏您应用程序的外观。 - Warren P
救了我一命!非常感谢你! - karliwson

2
好的,Warren P已经给出了一个非常有说服力的方向,但我会尝试回答你的问题。或者不是真正的回答。
您的编辑现在使问题非常清晰:
不仅其他商业应用程序,而且每个操作系统窗口都显示此效果。拉伸资源管理器窗口的顶部也会“隐藏”和“展开”状态栏或底部面板。我相当确定它无法被击败。
对于无边框表单来说,它可能看起来更糟,但我认为这只是光学上的欺骗。
如果我必须猜测解释这种效果,那么我会说在调整大小操作期间,左侧和顶部的更新优先于宽度和高度的更新,这导致两者没有同等更新次数。也许与显卡相关。或者,...该死,我在说什么?这超出了我的能力范围。
虽然如此,我仍然无法重现它以调整表单的右侧和/或底部。如果控件的数量或(组合)其对齐和锚定属性是问题,则可以考虑暂时完全禁用对齐,但我几乎确定您也不想要那个。以下是我的测试代码,从问题中复制,稍作修改,当然还加上了Sertac的常量:
function TForm1.ResizableAt(X, Y: Integer): Boolean;
const
  BorderBuffer = 5;
var
  R: TRect;
  C: TCursor;
begin
  SetRect(R, 0, 0, Width, Height);
  InflateRect(R, -BorderBuffer, -BorderBuffer);
  Result := not PtInRect(R, Point(X, Y));
  if Result then
  begin
    FSides := [];
    if X < R.Left then
      Include(FSides, sLeft)
    else if X > R.Right then
      Include(FSides, sRight);
    if Y < R.Top then
      Include(FSides, sTop)
    else if Y > R.Bottom then
      Include(FSides, sBottom);
  end;
end;

function TForm1.SidesToCursor: TCursor;
begin
  if (FSides = [sleft, sTop]) or (FSides = [sRight, sBottom]) then
    Result := crSizeNWSE
  else if (FSides = [sRight, sTop]) or (FSides = [sLeft, sBottom]) then
    Result := crSizeNESW
  else if (sLeft in FSides) or (sRight in FSides) then
    Result := crSizeWE
  else if (sTop in FSides) or (sBottom in FSides) then
    Result := crSizeNS
  else
    Result := crNone;
end;

procedure TForm1.ApplicationEventsMessage(var Msg: tagMSG;
  var Handled: Boolean);
var
  CommandType: WPARAM;
begin
  case Msg.message of
    WM_LBUTTONDOWN:
      if FResizable then
      begin
        CommandType := SC_SIZE;
        if sLeft in FSides then
          Inc(CommandType, WMSZ_LEFT)
        else if sRight in FSides then
          Inc(CommandType, WMSZ_RIGHT);
        if sTop in FSides then
          Inc(CommandType, WMSZ_TOP)
        else if sBottom in FSides then
          Inc(CommandType, WMSZ_BOTTOM);
        ReleaseCapture;
        DisableAlign;
        PostMessage(Handle, WM_SYSCOMMAND, CommandType, 0);
        Handled := True;
      end;
    WM_MOUSEMOVE:
      with ScreenToClient(Msg.pt) do
      begin
        FResizable := ResizableAt(X, Y);
        if FResizable then
          Screen.Cursor := SidesToCursor
        else
          Screen.Cursor := Cursor;
        if AlignDisabled then
          EnableAlign;
      end;
  end;
end;

关于您的顶部对齐面板:尝试设置 Align = alCustomAnchors = [akLeft, akTop, akRight],但增强效果可能取决于面板与窗体的颜色不同,或者可能是我被视觉欺骗了。;)


我会尝试Warren和你们的建议,但我认为像你一样,这是Windows的问题(或者说是一个特性 :))。非常感谢你们两个,我会让你们知道我的进展:D - Jade

0
你尝试过将表单设置为 DoubleBuffered := True 吗?

我尝试过,但就我所记得的而言,它避免了重新对齐闪烁,但并没有解决边距和客户区之间的空间问题。我必须进一步研究这种可能性,但目前我宁愿不使用它:D - Jade
那么你承认你的问题不仅仅是闪烁了! :-) - Warren P
@Warren,OP的客户区在ClipRect中没有重绘。据我记得,我曾经见过这种情况,但我现在无法再现。 - NGLN
@Warren P和@NGLN,对我来说,当您快速从左边缘调整大小时(如果使用左下角更容易注意到),它很容易再现,但不,面板中没有闪烁,正如我在帖子名称中所述,我没有得到平滑的调整大小效果,并且在我调整大小时在新区域中什么也没有(直到Windows完成重新绘制先前正在进行的更新为止)。我必须尝试不同的方法来最小化这些影响,并且在回答之前阅读您友好建议的帖子。 - Jade

-1

我知道这个帖子已经相当老了,但仍然是人们仍在苦苦挣扎的一个问题。

答案很简单。问题在于尝试调整大小会让你想使用正在调整大小的表单作为参考。 不要那样做。

使用另一个表单。

这里是一个TForm的完整源代码,可以帮助您。确保此表单具有BorderStyle = bsNone。您可能还希望确保它不可见。

unit UResize;
{
  Copyright 2014 Michael Thomas Greer
  Distributed under the Boost Software License, Version 1.0
  (See accompanying file LICENSE.txt or copy
   at http://www.boost.org/LICENSE_1_0.txt )
}

//////////////////////////////////////////////////////////////////////////////
interface
//////////////////////////////////////////////////////////////////////////////

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

const
  ResizeMaskLeft   = $1;
  ResizeMaskTop    = $2;
  ResizeMaskWidth  = $4;
  ResizeMaskHeight = $8;

type
  TResizeForm = class( TForm )
    procedure FormMouseMove( Sender: TObject;      Shift: TShiftState; X, Y: Integer );
    procedure FormMouseUp(   Sender: TObject;
                             Button: TMouseButton; Shift: TShiftState; X, Y: Integer );
  private
    anchor_g: TRect;
    anchor_c: TPoint;
    form_ref: TForm;
    resize_m: cardinal;

  public
    procedure SetMouseDown( AForm: TForm; ResizeMask: cardinal );
  end;

var
  ResizeForm: TResizeForm;


//////////////////////////////////////////////////////////////////////////////
implementation
//////////////////////////////////////////////////////////////////////////////

{$R *.DFM}

//----------------------------------------------------------------------------
procedure TResizeForm.SetMouseDown( AForm: TForm; ResizeMask: cardinal );
  begin
  anchor_g.Left   := AForm.Left;
  anchor_g.Top    := AForm.Top;
  anchor_g.Right  := AForm.Width;
  anchor_g.Bottom := AForm.Height;
  anchor_c        := Mouse.CursorPos;
  form_ref        := AForm;
  resize_m        := ResizeMask;
  SetCapture( Handle )
  end;

//----------------------------------------------------------------------------
procedure TResizeForm.FormMouseMove(
  Sender: TObject;
  Shift:  TShiftState;
  X, Y:   Integer
  );
  var
    p: TPoint;
    r: TRect;
  begin
  if Assigned( form_ref ) and (ssLeft in Shift)
    then begin
         p := Mouse.CursorPos;
         Dec( p.x, anchor_c.x );
         Dec( p.y, anchor_c.y );

         r.Left   := form_ref.Left;
         r.Top    := form_ref.Top;
         r.Right  := form_ref.Width;
         r.Bottom := form_ref.Height;

         if (resize_m and ResizeMaskLeft)   <> 0 then begin r.Left   := anchor_g.Left   + p.x;  p.x := -p.x end;
         if (resize_m and ResizeMaskTop)    <> 0 then begin r.Top    := anchor_g.Top    + p.y;  p.y := -p.y end;
         if (resize_m and ResizeMaskWidth)  <> 0 then       r.Right  := anchor_g.Right  + p.x;
         if (resize_m and ResizeMaskHeight) <> 0 then       r.Bottom := anchor_g.Bottom + p.y;

         with r do form_ref.SetBounds( Left, Top, Right, Bottom )
         end
  end;

//----------------------------------------------------------------------------
procedure TResizeForm.FormMouseUp(
  Sender: TObject;
  Button: TMouseButton;
  Shift:  TShiftState;
  X, Y:   Integer
  );
  begin
  ReleaseCapture;
  form_ref := nil
  end;

end.

现在您的应用程序中的任何无边框窗体都可以通过简单地挂钩 ResizeForm 来平滑地调整大小。
ResizeForm.SetMouseDown( self, (sender as TComponent).Tag );

一个好的位置是在你用来跟踪无边框窗体边缘的组件的MouseDown事件中放置它。(注意如何使用Tag属性指示您希望拖动/调整大小的窗体的哪个边缘)。
哦,还要将您的窗体设置为DoubleBuffered = true,以消除任何剩余的闪烁。
这只是我能给你的小小幸福。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接