{通常,我们拖动对话框窗口的标题栏来移动窗口,但有时候,我们想通过鼠标在客户区上拖动来移动窗口。</p><p>拖动对话框窗口的标题栏来移动窗口的方案是,处理鼠标消息WM_LBUTTONDOWN和WM_LBUTTONUP。在OnLButtonUp函数中计算鼠标位置的变化,调用MoveWindow实现窗口的移动。注意,拖动标题栏移动窗口的时候,会出现一个矩形框,它提示了窗口移动的当前位置。当鼠标左键放开的时候,窗口就移动到矩形框所在位置}
unit frm_main;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm1 = class(TForm)
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
private
procedure Magnetize(var nl, nt: Integer);
{ Private declarations }
public
{ Public declarations }
end;
{ “ 磁性 ” 窗口
Winamp的用户都知道 , Winamp的播放列表或均衡器在被移动的时候 , 仿佛会受到一股磁力 ,每当靠近主窗口时就一下子被 “ 吸附 ” 过去 ,自动沿边对齐 。我想让我的Winamp插件也具备这种奇妙特性 , 于是琢磨出了一种 “ 磁化 ” 窗口的方法 。该法适用于Delphi的各个版本 。为了演示这种技术 , 请随我来制作一个会被Winamp “ 吸引 ” 的样板程序 。
先新建一应用程序项目 , 把主窗口Form1适当改小些 ,并将BorderStyle设为bsNone 。放一个按钮元件 , 双击它并在OnClick事件中写 “ Close;” 。待会儿就按它来结束程序 。 现在切换到代码编辑区 , 定义几个全局变量 。 }
var
Form1: TForm1; // “磁性”窗口
LastX, LastY: Integer; // 记录前一次的坐标
WinampRect: Trect; // 保存Winamp窗口的矩形区域
hwnd_Winamp: HWND; // Winamp窗口的控制句柄
implementation
{$R *.dfm}
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
const
ClassName = 'TWinamp'; // Winamp主窗口的类名
// 如果改成ClassName=‘TAppBuilder’,你就会发现连Delphi也有引力啦!
begin
// 记录当前坐标
LastX := X;
LastY := Y;
// 查找Winamp
hwnd_Winamp := FindWindow(ClassName, 'Winamp v1.X');
if hwnd_Winamp > 0 then // 找到的话,记录其窗口区域
GetWindowRect(hwnd_Winamp, WinampRect);
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
var
nLeft, nTop: Integer; // 记录新位置的临时变量
begin
// 检查鼠标左键是否按下
if HiWord(GetAsyncKeyState(VK_LBUTTON)) > 0 then
begin
// 计算新坐标
nLeft := Left + (X - LastX); // 括号里面的是移动的..
nTop := Top + (Y - LastY);
// 如果找到Winamp,就修正以上坐标,产生“磁化”效果
if hwnd_Winamp > 0 then
Magnetize(nLeft, nTop);
// 重设窗口位置
SetBounds(nLeft, nTop, width, height);
end;
end;
{别急着,看Magnetize()过程,先来了解一下修正坐标的原理。根据对Winamp实现效果的观察,我斗胆给所谓“磁化”下一个简单的定义,就是“在原窗口与目标窗口接近到某种预定程度,通过修正原窗口的坐标,使两窗口处于同一平面且具有公共边的过程”。依此定义,我设计了以下的“磁化”步骤。
第一步,判断目标窗口(即Winamp)和我们的Form1在水平及垂直方向上的投影线是否重叠。“某方向投影线有重叠”是“需要进行坐标修正”的必要非充分条件。判断依据是两投影线段最右与最左边界的差减去它们宽度和的值的正负。
第二步,判断两窗口对应边界是否靠得足够近了。肯定的话就让它们合拢。好了,下面便是“神秘”的Magnetize过程了……}
procedure TForm1.Magnetize(var nl, nt: Integer);
// 内嵌两个比大小的函数
function Min(a, b: Integer): Integer;
begin
if a > b then
result := b
else
result := a;
end;
function Max(a, b: Integer): Integer;
begin
if a > b then
result := a
else
result := b;
end;
var
H_Overlapped, V_Overlapped: boolean; // 记录投影线是否重叠
tw, ww, wh: Integer; // 临时变量
const
MagneticForce: Integer = 50; // “磁力”的大小。
// 准确的说,就是控制窗口边缘至多相距多少像素时需要修正坐标
// 为了演示,这里用一个比较夸张的数字――50。
// 一般可以用20左右,那样比较接近Winamp的效果
begin
OutputDebugString('Magnetize in');
// 判断水平方向是否有重叠投影
ww := WinampRect.Right - WinampRect.Left;
tw := Max(WinampRect.Right, nl + width) - Min(WinampRect.Left, nl);
H_Overlapped := tw <= (width + ww);
// 再判断垂直方向
wh := WinampRect.Bottom - WinampRect.Top;
tw := Max(WinampRect.Bottom, nt + height) - Min(WinampRect.Top, nt);
V_Overlapped := tw <= (height + wh);
// 足够接近的话就调整坐标
if H_Overlapped then
begin
if Abs(WinampRect.Bottom - nt) < MagneticForce then
begin
nt := WinampRect.Top;
nl := WinampRect.Right;
end
else if Abs(nt + height - WinampRect.Top) < MagneticForce then
begin
nl := WinampRect.Right;
nt := WinampRect.Top;
end;
end;
if V_Overlapped then
begin
if Abs(WinampRect.Right - nl) < MagneticForce then
begin
nt := WinampRect.Top;
nl := WinampRect.Right;
end
else if Abs(nl + width - WinampRect.Left) < MagneticForce then
begin
nt := WinampRect.Top;
nl := WinampRect.Right;
end;
end;
end;
end.