本文算是副產品,正品是利用FFmpeg從任意視頻中生成GIF片段的小程式,寫完了就發。 因為要對視頻畫面進行框選,再生成GIF,所以得有個框選的控制項,可Delphi里沒有啊,只好自己寫一個了。 聲明 本文參考的是盒子網的 "RectTracker" ,原作者署名xwwaw,發佈於2007年5月28日 ...
本文算是副產品,正品是利用FFmpeg從任意視頻中生成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值。以下是示意圖:
中間是子控制項,外圍是父控制項。