本文算是副產品,正品是利用 FFmpeg 從任意視頻中生成GIF片段的小程序,寫完了就發。
V2G 正品已出爐,雖然不大像樣,但好歹是能用,請見:用 Delphi 7 實現基於 FFMS2 的視頻轉 GIF 工具。
因為要對視頻畫面進行框選,再生成 GIF,所以得有個框選的控件,可 Delphi 里沒有啊,只好自己寫一個了。
聲明
本文參考的是盒子網的 RectTracker,原作者署名 xwwaw,發布於2007年5月28日。主要的修改之處是增加了邊框檢測,因為我覺得讓選框超出父控件是不合邏輯的。
最開始參考的是 Anthony Scott 的 TStretchHandle,可是怎么改都不好用,遂放棄。以下是 TStretchHandle 的網站介紹截圖:
是的,你沒看錯!TStretchHandle v.2.0 在 Windows 3.1 和 Windows 95 下測試通過!看到這2個詞,我瞬間石化。頓時想起了畢業前去光盤市場淘了張 Windows 95 的預覽版,想着去了工作單位也許能用的上。結果上了班才發現,干活的是 Sco Unix,辦公還都是 Windows 3.2,而且品牌機全都自帶操作系統。
什么是 RectTracker
直譯是“橡皮筋”,竊以為不好理解,還是稱其為框選控件,說白了就是在屏幕上畫個虛線框供選中區域,8 個方向都有可以拉伸的控制柄,類似 QQ 的屏幕截圖功能。在 MFC 里有個CRectTracker
可用,可參考 CRectTracker源碼學習筆記。
在微軟官方的文檔 GetHandleMask里,8 個控制柄是有編號的:
當然我們就不必這么講究了。
主要思路
- 覆蓋
Paint
方法:畫框,包括畫 8 個控制柄(小黑塊) - 響應
WMMouseMove
消息:修改光標形狀,邊界檢測(不論移動還是拉伸都不超出父控件),最小尺寸檢測 - 響應
WMLButtonDown
消息:開始拖動 - 響應
WMLButtonUp
消息:停止拖動
常量
const
DefaultSize=65; //默認的控件大小
DefaultHandleSize=5; //默認的控制柄大小
DefaultBorderWidth=1;//默認的邊框寬度(暫時沒用,因為超過 1 就畫不出虛線框)
主要成員變量
TDXRectTracker = class(TGraphicControl)
private
FDragging: boolean; //是否處於拖動狀態(鼠標左鍵保持按下)
FHandleSize: integer; //控制柄大小
FBorderWidth: integer; //邊框寬度(暫時沒用)
FMinSize: integer; //控件最小尺寸
FTrackerType: TMousePosType; //當前控件拖動類型
FX,FY: integer; //當前光標位置(相對於本控件,在拖動狀態下可能是負值)
Paint 方法
一圖解千惑:
繪制8個控制柄和虛線框還是簡單的。但是有一點,如果Pen.Width
>1,是無法繪制出虛線的,不知哪位高人能解。
WMLButtonDown 消息處理
在收到鼠標左鍵按下的消息時,表示要啟動拖動狀態,為后續的WMMouseMove
消息處理做准備。
FDragging:= true; //啟動拖動狀態
Fx:= Message.XPos; //記錄光標當前橫位置
Fy:= Message.YPos; //記錄光標當前縱位置
FTrackerType:= GetMousePos(Fx, Fy); //根據光標位置設置鼠標光標類型
inherited;
本控件全部區域都是可拖動范圍,所以鼠標左鍵按下即表示要開始拖動。如果鼠標位於控制柄上,表示要拉伸邊框;如果鼠標位於控件內部,表示要移動整個控件;如果鼠標位於控件之外,則不會接收到鼠標左鍵按下事件。
WMLButtonUp 消息處理
在收到鼠標左鍵抬起的消息時,表示拖動狀態結束,做狀態清理:
FDragging:= false;
Fx:= -1;
Fy:= -1;
FTrackerType:= mpOutBox;
inherited;
WMMouseMove 消息
本控件最“重”的處理就是在MouseMove
消息上了。為了能在鼠標拖動邊框或整個控件時,能實時顯示位置,必須計算出目標位置。
- 根據
WMLButtonDown
消息處理時記錄的光標初始值(Fx, Fy)計算偏移量(dx, dy); - 根據
WMLButtonDown
消息處理時記錄的拖動類型(FTrackerType)計算控件外框相對於父控件的坐標值(x1, x2, y1, y2); - 修正控件外框坐標,將控件限制在父控件的Client區域內部,拖動或者拉伸均不能越界。且拉伸也不能小於最小尺寸(FMinSize);
- 根據當前光標位置,設置鼠標光標形狀。
以下是最關鍵的計算控件外框坐標的代碼:
case FTrackerType of
mpLeft:
begin
inc(x1, dx);
end;
mpRight:
begin
inc(x2, dx);
Fx:= Message.XPos;
end;
mpTop:
begin
inc(y1, dy);
end;
mpBottom:
begin
inc(y2, dy);
Fy:= Message.YPos;
end;
mpLeftTop:
begin
inc(x1, dx);
inc(y1, dy);
end;
mpRightBottom:
begin
inc(x2, dx);
inc(y2, dy);
Fx:= Message.XPos;
Fy:= Message.YPos;
end;
mpLeftBottom:
begin
inc(x1, dx);
inc(y2, dy);
Fy:= message.YPos;
end;
mpRightTop:
begin
inc(x2, dx);
inc(y1, dy);
Fx:= message.XPos;
end;
mpInBox: //只是移動,不做拉伸
begin
inc(x1, dx);
inc(y1, dy);
inc(x2, dx);
inc(y2, dy);
end;
end;
請注意,WMMouseMove
消息帶入的是相對於父控件的坐標,光標坐標(message.XPos
, message.YPos
)可能會小於0,也可能會大於當前控件的Width
及Height
值。因為在鼠標保持按下狀態時,即使光標位置移出了當前控件的邊界,控件仍然會接收到WMMouseMove
消息。向左向上移出,坐標就會出現負值。向下向右移出,坐標則會大於當前控件的Width及Height值。以下是示意圖:
中間是子控件,外圍是父控件。