本文将向您展示如何通过编写数个消息处理函数来实现当把窗口移动到屏幕边缘的时"snap"的效果.
当然,Win API中没有任何内建的功能允许窗口在屏幕边缘停靠 - 我们必须自己处理 Windows 消息. 正如我们已经知道的, Delphi 能让消息处理非常容易的通过它的事件; 事件通常在应答某个发送给应用程序的 Windows 消息时产生.
即使许多 Windows 消息都被 Delphi 事件处理了, 但是仍然有一些消息忽略了. 例如, 我们知道, 如果一个窗口被 Resized 的话, 我们可以得到 Resize 事件 (OnResize) - Delphi 处理了 WM_SIZE 消息, 但是我们如何知道窗口被移动了呢? Delphi 窗体能获知该消息, 但是却并没有对它做任何处理.
用户在移动窗口的时候, WM_MOVING 消息将被发送到目标窗口. 通过处理这个消息, 应用程序可以监视拖曳矩形的 Size 和 Position 的变化, (如果需要)还能改变它的 Size 和 Position.
当调用 SetWindowPos 函数或者其它窗口管理函数的时候, WM_WINDOWPOSCHANGING 消息将会作为上述函数产生的结果发送给目标窗口, 其中包含了窗口的 Size, Position 以及 Z Order 中的位置等信息.
大多数时候一个简单的消息是不够的, 我们可以通过消息携带的参数得到更多信息. 例如, WM_MOVE 消息告诉我们, 我们的窗口已经改变了位置, 并且 LPARAM 参数给出了我们需要的 X 和 Y 轴的位置信息.
使用 WM_WINDOWPOSCHANGING 消息仅给我们提供了一个参数 - 一个指向 WindowPos 结构的一个指针, 该结构包含 Windows 的新 Size 和 Position 信息. 这是 WindowPos 结构的定义:
TWindowPos = packed record
hwnd: HWND; {Window 标识.}
hwndInsertAfter: HWND; {此窗口上面的 Window}
x: Integer; {Window 的左边界}
y: Integer; {Window 的右边界}
cx: Integer; {Window 宽度}
cy: Integer; {Window 高度}
flags: UINT; {Window-positioning 选项.}
end;
我们的任务很简单:
在设定的屏幕边缘的距离(假设20个象素)之内, 当 Delphi 窗口靠近的时候, 让它吸附上去.
新建一个 Delphi 窗口, 然后添加一个 TLabel, 一个 TEdit 和 4 个 TCheckBox 控件. 将 TEdit 控件的名称改为 edStrickAt. 将 TCheckBox 控件的名称改为 chkLeft, chkTop... 依此类推. 我们用edStickAt 设定一个象素数, 当我们的窗口距离屏幕边缘的距离小余这个象素的时候, 让它吸附上去. 窗口效果图如下:
我们唯一感兴趣的消息就是 WM_WINDOWPOSCHANGING. 在我们窗口的声明的 private 部分做声明这个消息的处理函数. 我将在这里给出 "sticking" 过程的全部代码以及代码描述. 注意, 您可以取消有关 TCheckBox 的勾选来防止某个方向的吸附.
使用 SPI_GETWORKAREA 作为 SystemParametersInfo 函数第一个参数, 可以得到工作(桌面)区域的大小信息. 我们用它减去 appbars, 任务栏, IE 工具栏等的大小来确定屏幕的区域.
//...
private
procedure WMWINDOWPOSCHANGING
(Var Msg: TWMWINDOWPOSCHANGING);
message WM_WINDOWPOSCHANGING;
//...
procedure TfrMain.WMWINDOWPOSCHANGING
(var Msg: TWMWINDOWPOSCHANGING);
const
Docked: Boolean = FALSE;
var
rWorkArea: TRect;
StickAt : Word;
begin
StickAt := StrToInt(edStickAt.Text);
SystemParametersInfo
(SPI_GETWORKAREA, 0, @rWorkArea, 0);
with Msg.WindowPos^ do begin
if chkLeft.Checked then
if x <= rWorkArea.Left + StickAt then begin
x := rWorkArea.Left;
Docked := TRUE;
end;
if chkRight.Checked then
if x + cx >= rWorkArea.Right - StickAt then begin
x := rWorkArea.Right - cx;
Docked := TRUE;
end;
if chkTop.Checked then
if y <= rWorkArea.Top + StickAt then begin
y := rWorkArea.Top;
Docked := TRUE;
end;
if chkBottom.Checked then
if y + cy >= rWorkArea.Bottom - StickAt then begin
y := rWorkArea.Bottom - cy;
Docked := TRUE;
end;
if Docked then begin
with rWorkArea do begin
// no moving out of the screen
if x < Left then x := Left;
if x + cx > Right then x := Right - cx;
if y < Top then y := Top;
if y + cy > Bottom then y := Bottom - cy;
end; {with rWorkArea}
end; {if Docked}
end; {with Msg.WindowPos^}
inherited;
end;
end.
现在,运行项目,并且把窗口移动到屏幕的任意边缘,就可看到它停靠过去了.