首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

Delphi 實作表單控件自由拖曳調整:游標反饋、即時縮放與版面持久化

     編輯:Delphi

在 MIS 專案(如餐廳桌位編排、工廠產線佈局)中,使用者常需於執行期自由拖曳、縮放表單上的控件。本文提供完整解決方案,包含三大核心模組:游標動態切換、WM_SYSCOMMAND 訊息驅動縮放/移動、INI 檔持久化版面資訊。所有程式碼已封裝為可復用函式,於 Delphi 7 / Windows 2000 驗證通過。

一、游標動態切換:OnMouseMove 判斷熱區

依據滑鼠座標 (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)。

二、即時縮放與移動:WM_SYSCOMMAND 訊息技巧

利用系統內建視窗命令常數($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 確保滑鼠捕捉釋放,避免殘留拖曳狀態。

三、版面持久化:元件遍歷寫入/讀取 INI

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 表單「所見即所得」的版面編輯能力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved