在 MIS 專案(如餐廳桌位編排、工廠產線佈局)中,使用者常需於執行期自由拖曳、縮放表單上的控件。本文提供完整解決方案,包含三大核心模組:游標動態切換、WM_SYSCOMMAND 訊息驅動縮放/移動、INI 檔持久化版面資訊。所有程式碼已封裝為可復用函式,於 Delphi 7 / Windows 2000 驗證通過。
依據滑鼠座標 (X, Y) 相對控件邊緣 Prec(預設 5 像素)判斷所在區域,設定 Control.Cursor:
procedure CursorControl(Control: TControl; Shift: TShiftState; X, Y, Prec: Integer);
begin
// 左/右邊緣 → 水平縮放
if (X <= Prec) and (Y > Prec) and (Y < Control.Height - Prec) then
Control.Cursor := crSizeWE
else if (X >= Control.Width - Prec) and (Y > Prec) and (Y < Control.Height - Prec) then
Control.Cursor := crSizeWE
// 上/下邊緣 → 垂直縮放
else if (X > Prec) and (X < Control.Width - Prec) and (Y <= Prec) then
Control.Cursor := crSizeNS
else if (X > Prec) and (X < Control.Width - Prec) and (Y >= Control.Height - Prec) then
Control.Cursor := crSizeNS
// 四角 → 對角縮放
else if (X <= Prec) and (Y <= Prec) then
Control.Cursor := crSizeNWSE
else if (X >= Control.Width - Prec) and (Y <= Prec) then
Control.Cursor := crSizeNESW
else if (X <= Prec) and (Y >= Control.Height - Prec) then
Control.Cursor := crSizeNESW
else if (X >= Control.Width - Prec) and (Y >= Control.Height - Prec) then
Control.Cursor := crSizeNWSE
// 客戶區 → 移動整個控件
else if (X > 5) and (Y > 5) and (X < Control.Width - 5) and (Y < Control.Height - 5) then
Control.Cursor := crSizeAll
else
Control.Cursor := crDefault;
end;
於每個可編排控件的 OnMouseMove 呼叫 CursorControl(Self, Shift, X, Y, 5)。
利用系統內建視窗命令常數($F001~$F009),發送 WM_SYSCOMMAND 讓 Windows 自行處理縮放/移動邏輯,無需自寫座標運算:
procedure ManipulateControl(Control: TControl; Shift: TShiftState; X, Y, Prec: Integer);
var
SC_MANIPULATE: Word;
begin
// 依游標熱區決定 SC_* 常數
if (X <= Prec) and (Y > Prec) and (Y < Control.Height - Prec) then SC_MANIPULATE := $F001 // 左
else if (X >= Control.Width - Prec) and (Y > Prec) and (Y < Control.Height - Prec) then SC_MANIPULATE := $F002 // 右
else if (X > Prec) and (X < Control.Width - Prec) and (Y <= Prec) then SC_MANIPULATE := $F003 // 上
else if (X <= Prec) and (Y <= Prec) then SC_MANIPULATE := $F004 // 左上
else if (X >= Control.Width - Prec) and (Y <= Prec) then SC_MANIPULATE := $F005 // 右上
else if (X > Prec) and (X < Control.Width - Prec) and (Y >= Control.Height - Prec) then SC_MANIPULATE := $F006 // 下
else if (X <= Prec) and (Y >= Control.Height - Prec) then SC_MANIPULATE := $F007 // 左下
else if (X >= Control.Width - Prec) and (Y >= Control.Height - Prec) then SC_MANIPULATE := $F008 // 右下
else if (X > 5) and (Y > 5) and (X < Control.Width - 5) and (Y < Control.Height - 5) then SC_MANIPULATE := $F009 // 移動
else SC_MANIPULATE := $F000;
if Shift = [ssLeft] then begin
ReleaseCapture;
Control.Perform(WM_SYSCOMMAND, SC_MANIPULATE, 0);
end;
end;
於控件 OnMouseDown 呼叫 ManipulateControl(Self, Shift, X, Y, 5)。ReleaseCapture 確保滑鼠捕捉釋放,避免殘留拖曳狀態。
uses IniFiles;
procedure SaveLayout(Form: TForm; const IniFileName: string);
var
Ini: TIniFile;
i: Integer;
Ctrl: TControl;
begin
Ini := TIniFile.Create(IniFileName);
try
for i := 0 to Form.ControlCount - 1 do begin
Ctrl := Form.Controls[i];
Ini.WriteInteger('Layout', Ctrl.Name + '_Left', Ctrl.Left);
Ini.WriteInteger('Layout', Ctrl.Name + '_Top', Ctrl.Top);
Ini.WriteInteger('Layout', Ctrl.Name + '_Width', Ctrl.Width);
Ini.WriteInteger('Layout', Ctrl.Name + '_Height', Ctrl.Height);
end;
finally
Ini.Free;
end;
end;
procedure LoadLayout(Form: TForm; const IniFileName: string);
var
Ini: TIniFile;
i: Integer;
Ctrl: TControl;
begin
if not FileExists(IniFileName) then Exit;
Ini := TIniFile.Create(IniFileName);
try
for i := 0 to Form.ControlCount - 1 do begin
Ctrl := Form.Controls[i];
Ctrl.Left := Ini.ReadInteger('Layout', Ctrl.Name + '_Left', Ctrl.Left);
Ctrl.Top := Ini.ReadInteger('Layout', Ctrl.Name + '_Top', Ctrl.Top);
Ctrl.Width := Ini.ReadInteger('Layout', Ctrl.Name + '_Width', Ctrl.Width);
Ctrl.Height := Ini.ReadInteger('Layout', Ctrl.Name + '_Height', Ctrl.Height);
end;
finally
Ini.Free;
end;
end;
於 FormClose 呼叫 SaveLayout,FormCreate 呼叫 LoadLayout。亦可使用現成元件(如 TFormStorage 或第三方 JvFormStorage)零程式碼實現。
TPanel 或 TImage,Tag 儲存桌號,雙擊彈出訂位視窗OnContextPopup 選單提供「置頂/置底、對齊、鎖定」等編排輔助功能此方案零外部依賴、核心邏輯不足 100 行,即可賦予任何 VCL 表單「所見即所得」的版面編輯能力。