2024/02/19 更新
VCL 最厲害的地方是將 Windows 完美的封裝成易用的框架,又保留 Windows Message
事件機制的處理方式。在 2012 年我還在使用 C++ Builder 就曾為了學習 SendMessage
技巧而吃了很多苦頭。2024
年再回頭看這份【Delphi中SendMessage使用技巧】文件,發現原文來源網址已失效。在網路上搜尋到的資料都沒有標記來源,盜文嚴重程度可見一般。這份文件到現在仍然可以使用,作為
Windows API 的入門仍然很有價值,我整理這份文件的重點如下。
使用 SendMessage 向元件發送 Message
VCL 容器元件如 ListBox, ComboBox
等,其寬度屬於靜度設定。有時畫面在排列時需要將容器元件寬度調得很窄,但呼叫下拉清單時則寬度需要隨
item 最大長度配合調整。這種情況就很適合 SendMessage 處理。
先取得下拉清單項目的最大長度,再使用 SendMessage 對下拉清單傳遞
CB_SETDROPPEDWIDTH 及寬度值,就可以滿足預期顯示的效果。如下圖顯示。
左邊是原始 VCL 元件效果,右邊是搭配 SendMessage 傳遞寬度值的效果 |
部份程式碼如下所示
begin i := 0; // 清單計數器 MaxWidth := 0; //讀 LastName 清單到 ComboBox.Items ComboBox1.Clear; Table1.First; while not Table1.Eof do begin ComboBox1.Items.add(Table1LastName.AsString); LWidth := ComboBox1.Font.Size * Length(ComboBox1.Items[i]); if LWidth > MaxWidth then MaxWidth := LWidth; //找出最大值 Table1.Next; i := i + 1; end; ComboBox1.Text := ComboBox1.Items[0]; //傳遞 Message 以改變顯示區域的寬度 SendMessage(ComboBox1.Handle, CB_SETDROPPEDWIDTH, MaxWidth, 0); end;
把 Button 變成 RadioButton
利用 SendMessage API 還可以實現一些有趣效果,例如在 Button 的 Click 事件添加下列指令,就可以在按下按鈕後看到按鈕 UI 的變化。
SendMessage(TButton(Sender).Handle, BM_SETSTYLE, BS_RADIOBUTTON, 1);
【同場加映】自定接收 Message 方法
對元件的操作大多是不會經過 TForm 傳遞的,而是直接對該元件直接觸發,Windows 這樣的設計目的在於程式操作的體驗會比較好。以 ListBox 元件為例,它沒有封裝清單滾動事件,但內部仍然有滾動事件的處理,此時就可以干涉元件內部的滾動事件處理。步驟如下:
1. 繼承 TListBox 元件為 TMyListBox,並重載 WndProc 方法
TMyListBox=class(TListBox) private procedure WndProc(var Msg: TMessage); override; //重載 WndProc,處理所有傳遞到元件的 Message end;
TMessage 為 record 型別,包含 Message Code 和 Param
2. WndProc 事件加入對滾動事件的處理
procedure TMyListBox.WndProc(var Msg: TMessage); begin if (Msg.Msg = WM_VSCROLL) and (Msg.WParamLo = SB_ENDSCROLL) then begin //獲得滑鼠位置對應的列 ItemIndex := ItemAtPos(LPoint, True); Form1.Edit1.Text := IntToStr(ItemIndex); end; inherited; end;
程式接收到 WM_VSCROLL Message,且 WParamLo 參數為 SB_ENDSCROLL 時,表示 TMyListBox 已停止滾動,接著就可以用 ItemAtPos 方法確定滑鼠位置所對應的 ItemIndex。ItemAtPos 方法的 Point 參數是一個 TPoint 類別的全域變數,用於儲存滑鼠的位置。
3. 滑鼠移動時,將當前位置儲存在 TPoint 裡
procedure TForm1.ListBoxMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin LPoint.X := X; LPoint.Y := Y; end;
4. 建立及初始化 TMyListBox
var List: TMyListBox; i: Integer; begin LPoint.X := 0; LPoint.Y := 0; //創建自定義列表框 List := TMyListBox.Create(Form1); List.Parent := Form1; List.Left := 250; List.Top := 200; List.Width := 150; List.Height := 200; for i := 0 to 300 do begin List.Items.Add(inttostr(i)); //初始化 end; //指定處理MouseMove事件的方法 List.OnMouseMove := ListBoxMouseMove; end;
See also
- 攝影師:Lukas: https://www.pexels.com/zh-tw/photo/684318/
Delphi中SendMessage使用技巧
东南大学 梁云
i:=0; //計數 MaxWidth:=0; Query1.SQL.Clear; Query1.SQL.Add('select Company from Customer'); Query1.Open; //讀客戶列表到下拉框 while not Query1.Eof do begin ComboBox1.Items.add(Query1.FieldByName('Company').AsString); Width := ComboBox1.Font.Size * Length(ComboBox1.Items[i]); if Width>MaxWidth then MaxWidth:=Width; //找出最大值 Query1.Next; i:=i+1; end; Query1.Close; ComboBox1.Text:=ComboBox1.Items[0]; //發送消息以確定顯示區域的寬度 SendMessage(ComboBox1.Handle, CB_SETDROPPEDWIDTH, MaxWidth, 0);利用SendMessage函数还可以实现一些有趣的效果,例如在按钮的Click事件中加入如下语句:
SendMessage(Button.Handle, BM_SETSTYLE, BS_RADIOBUTTON, 1);
type TMyListBox=class(TListBox) private procedure WndProc(var Msg: TMessage); override; //重載WndProc,處理發送到控件的消息 public end;
if (Msg.Msg=WM_VSCROLL) and (Msg.WParamLo=SB_ENDSCROLL) then begin //獲得鼠標位置對應的列 ItemIndex:=ItemAtPos(Point,true); Form1.Edit1.Text:=inttostr(ItemIndex); end; inherited;
procedure TForm1.ListBoxMouseMove(Sender: TObject; Shift: TShiftState; X,Y: Integer); begin Point.X:=X; Point.Y:=Y; end;
begin Point.X:=0; Point.Y:=0; //創建自定義列表框 List:=TMyListBox.Create(Form1); List.Parent:=Form1; List.Left:=5; List.Top:=30; List.Width:=150; List.Height:=200; for i:=0 to 300 do begin List.Items.Add(inttostr(i)); //初始化 end; //指定處理MouseMove事件的方法 List.OnMouseMove := ListBoxMouseMove; end;(计算机世界报 第26期 C13)
沒有留言:
張貼留言