有没有一种方法可以使Delphi VCL表单可调整大小,而不改变边框样式?

9
我刚刚花了相当长的时间尝试通过GExperts使Delphi 6/7 IDE的Tools/Environment Options对话框可调整大小。一切似乎都很顺利,直到我发现将表单的BorderStyle更改为bsSizable会关闭并重新创建表单的句柄,在此过程中丢失了调色板配置列表框的内容。(之后Items属性为空。)
通过设置高度和宽度来改变表单的大小可以正常工作,但允许用户调整大小会遇到上述问题。
有没有办法使一个Delphi表单可调整大小而不改变BorderStyle?

你不能早些时候设置那个属性吗? - David Heffernan
在此过程中,调色板配置的列表框内容会丢失。我不确定这是什么意思,但我敢打赌它与在应该绘制时没有进行绘制有关 - 绘制到控件画布上只能在Windows发送的WM_PAINT消息时进行。 - Jerry Dodge
很遗憾,@david,在窗体构造完成并设置为活动窗体后,我才能获取对该窗体的引用。但是那时已经太晚了,因为它已经可见了。 - dummzeuch
@jerry 不是那个问题。之后Items属性为空。这实际上是VCL已知的问题。 - dummzeuch
处理 WM_NCHITTEST 是正确的方法。 - Dalija Prasnikar
显示剩余6条评论
2个回答

8

“Wnd”是对话框句柄,您可以通过调整大小的边框将对话框转换为重叠窗口:

SetWindowLong(Wnd, GWL_STYLE,
    GetWindowLong(Wnd, GWL_STYLE) and not WS_POPUP or WS_THICKFRAME);

去除对话框的边框:

SetWindowLong(Wnd, GWL_EXSTYLE,
    GetWindowLong(Wnd, GWL_EXSTYLE) and not WS_EX_DLGMODALFRAME);

然后附加适当的系统菜单项,以便处理大小调整消息:

AppendMenu(GetSystemMenu(Wnd, False), MF_STRING, SC_SIZE, 'Size');

并绘制新框架:

SetWindowPos(Wnd, 0, 0, 0, 0, 0,
    SWP_NOSIZE or SWP_NOMOVE or SWP_NOZORDER or SWP_FRAMECHANGED);

有趣。这本不应该起作用,因为WS_THICKFRAME样式被记录为无法在窗口创建后更改。但它似乎确实可以,尽管对话框标题栏中存在丑陋的初始故障(没有系统菜单和小工具放错位置),直到实际调整表单大小才得以解决,此外还会在对话框系统菜单上添加一个相当奇怪的额外“大小”项目(在已经存在但禁用的情况下-例如BorderStyle = bsSingle)。 - Deltics
@Sertac:https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms632600(v=vs.85).aspx WS_THICKFRAME 的注释中没有任何内容表明它可以在创建后进行修改(相比之下,例如 WS_GROUP 就被注明为可在创建后更改)。 - Deltics
事实上,SetWindowLong特别提到了改变框架样式。 - Sertac Akyuz
1
我不太明白这个讨论。这个答案看起来是正确的。+1 - David Heffernan
@Deltics,你在这里的评论反映了你想要为自己辩护而不是面对事实的愿望。 - David Heffernan
显示剩余7条评论

5

通常情况下,您可以通过实现对WM_NCHITTEST的响应并设置一个指示窗口框架中一个调整大小“区域”的结果来赋予窗口调整大小的行为。

例如:

procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST;

...

procedure TForm2.WMNCHitTest(var Message: TWMNCHitTest);
const
  EDGEDETECT = 7;  //adjust as required
var
  deltaRect: TRect;  //not used as a rect, just a convenient structure
begin
  inherited;

  with Message, deltaRect do 
  begin
    Left   := XPos - BoundsRect.Left;
    Right  := BoundsRect.Right - XPos;
    Top    := YPos - BoundsRect.Top;
    Bottom := BoundsRect.Bottom - YPos;

    if (Top<EDGEDETECT)and(Left<EDGEDETECT) then
      Result := HTTOPLEFT
    else if (Top<EDGEDETECT)and(Right<EDGEDETECT) then
      Result := HTTOPRIGHT
    else if (Bottom<EDGEDETECT)and(Left<EDGEDETECT) then
      Result := HTBOTTOMLEFT
    else if (Bottom<EDGEDETECT)and(Right<EDGEDETECT) then
      Result := HTBOTTOMRIGHT
    else if (Top<EDGEDETECT) then
      Result := HTTOP
    else if (Left<EDGEDETECT) then
      Result := HTLEFT
    else if (Bottom<EDGEDETECT) then
      Result := HTBOTTOM
    else if (Right<EDGEDETECT) then
      Result := HTRIGHT
  end;
end;

上面的代码对于这种情况来说相当基础,但是为了节省时间,我从这里选了一个特定的例子。如果应用于现有窗口/表单,则需要调整此内容以适配 WndProc hook use case。
存在一个问题...
如果被钩住的表单具有bsDialog或bsSingle(可能还有其他)BorderStyle,并且该表单也拥有系统菜单(BorderIcons中设置了biSysMenu),那么如果强制更改BorderIcons属性,就会导致窗口重新创建,形成与原先相同的局面。
然而,在 Delphi 7 的"工具>环境"选项对话框中检查过之后,这似乎没有系统菜单,因此在WndProc hook中添加WM_NCHITTEST处理程序以钩住该对话框应该会产生期望的效果。

谢谢,如果Sertac Akyuz的建议不起作用,我会尝试你的建议。 - dummzeuch
正如我在对Sertac答案的评论中所指出的那样,他的方法本来不应该起作用,但它似乎确实起作用了。然而,在我的Windows 7上测试中,它仍然存在一些令人讨厌的视觉故障。虽然这比挂钩窗口处理程序要简单一些,但如果你可以忍受这些故障和依赖于某些本不应该起作用的东西的不安感,那么这可能是一个可行的方法。 :) - Deltics
是的。这里有一个MSDN博客示例,可以切换窗口框架。请注意,WS_OVERLAPPEDWINDOW包括WS_THICKFRAME。 - Sertac Akyuz

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