CnEdit.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. {******************************************************************************}
  2. { CnPack For Delphi/C++Builder }
  3. { 中国人自己的开放源码第三方开发包 }
  4. { (C)Copyright 2001-2018 CnPack 开发组 }
  5. { ------------------------------------ }
  6. { }
  7. { 本开发包是开源的自由软件,您可以遵照 CnPack 的发布协议来修 }
  8. { 改和重新发布这一程序。 }
  9. { }
  10. { 发布这一开发包的目的是希望它有用,但没有任何担保。甚至没有 }
  11. { 适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。 }
  12. { }
  13. { 您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果 }
  14. { 还没有,可访问我们的网站: }
  15. { }
  16. { 网站地址:http://www.cnpack.org }
  17. { 电子邮件:master@cnpack.org }
  18. { }
  19. {******************************************************************************}
  20. unit CnEdit;
  21. {* |<PRE>
  22. ================================================================================
  23. * 软件名称:界面控件包
  24. * 单元名称:CnEdit控件单元
  25. * 单元作者:盛小青 chbsxq@163.com QQ:822154
  26. * jAmEs_
  27. * 备 注:-使CnEdit带有一个按钮,按钮拥有单击事件.
  28. * -LinkStyle属性可以设置为lsNone, lsEllipsis, lsDropDown
  29. *
  30. * -CnEdit的Text类型可以为整形,浮点型,普通文字型,标识符型
  31. * -TextType属性可以设置为NormalText, IntegerText, FloatText
  32. * -TextType设置为IntegerText,CnEdit只接受数字键,Backspace键,其它按键无效.
  33. * 同样设为FloatText只比IntegerText多能接受'.',但如果CnEdit.text包含'.',则不能再输入.
  34. * -对负号'-',只能在开头输入.
  35. * -FloatText类型时,输入'0.'或者'.0'这样的情况会自动修正为'0.0'
  36. * -CnEdit失去焦点时会检查Text,如果不符合TextType的设置,则清空或置0
  37. * 这样避免了粘贴进来.
  38. *
  39. * -具备回车键替换成tab键
  40. * -设置属性EnterAsTab为True,则在CnEdit控件中按回车键,则自动跳动下一控件.
  41. *
  42. * 开发平台:PWinXP + Delphi 6.0
  43. * 兼容测试:PWin9X/2000/XP + Delphi 6.0
  44. * 本 地 化:该单元中的字符串均符合本地化处理方式
  45. * 单元标识:$Id$
  46. * 修改记录:2009.07.04 V1.3
  47. * 修正tArightJustify时绘制不正确的问题,感谢jAmEs_
  48. * 2008.06.05 V1.2
  49. * 处理粘贴时的限制内容
  50. * 2007.08.02 V1.1
  51. * jAmEs_ 加入 Value 属性,加入标识符型、文字过滤和负数控制功能
  52. * 2004.04.24 V1.0
  53. * 创建单元
  54. ================================================================================
  55. |</PRE>}
  56. interface
  57. {$I CnPack.inc}
  58. uses
  59. Windows, Messages, SysUtils, Classes, Controls, StdCtrls, Forms, Graphics,
  60. Clipbrd;
  61. type
  62. TLinkStyle = (lsNone, lsEllipsis, lsDropDown); //是否出现按钮以及按钮类型
  63. TTextType = (NormalText, IntegerText, FloatText, IdentText); //文本类型
  64. // 普通文本、 整数、 小数、 标识符
  65. TCnEdit = class(TEdit)
  66. private
  67. { Private declarations }
  68. FButtonWidth: Integer;
  69. FCanvas: TControlCanvas;
  70. FLinkStyle: TLinkStyle;
  71. FAlignment: TAlignment;
  72. FPressed: Boolean;
  73. FTracking: Boolean;
  74. FOnButtonClick: TNotifyEvent;
  75. FTextType: TTextType; //文本类型 整形,浮点,文字
  76. FEnterAsTab: Boolean; //回车做为tab
  77. FAcceptNegative: Boolean;
  78. FAcceptCharList: string;
  79. FButtonCursor: TCursor;
  80. procedure SetLinkStyle(Value: TLinkStyle); //设置是否显示按钮
  81. procedure TrackButton(X, Y: Integer); //跟踪鼠标按下按钮移开又回来的情况,按下触发
  82. procedure StopTracking; //同上 up触发
  83. procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
  84. procedure WMPaste(var Message: TWMPaste); message WM_PASTE;
  85. function GetTextMArgins: TPoint; //text编辑区边上的空白
  86. procedure WMSetCursor(var Msg: TWMSetCursor); message WM_SETCURSOR; //设置鼠标在按钮上的箭头
  87. function GetValue: Variant;
  88. procedure SetButtonCursor(const Value: TCursor);
  89. protected
  90. { Protected declarations }
  91. procedure EditButtonClick; //单击事件
  92. procedure BoundSChanged;
  93. procedure CreateParams(var Params: TCreateParams); override; //这个非常有用
  94. procedure DoEnter; override; //获取焦点时选择全部文字
  95. procedure DoExit; override;
  96. procedure KeyDown(var Key: Word; Shift: TShiftState); override; //快捷键
  97. procedure KeyPress(var Key: Char); override; //必须屏蔽回车键,因为是多行方式,但不允许换行
  98. //在MouseUp触发按钮事件, 其它事件画按钮状态
  99. procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  100. procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
  101. procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
  102. public
  103. { Public declarations }
  104. constructor Create(AOwner: TComponent); override;
  105. destructor Destroy; override;
  106. property Value: Variant read GetValue;
  107. published
  108. { Published declarations }
  109. property OnButtonClick: TNotifyEvent read FOnButtonClick write FOnButtonClick;
  110. property LinkStyle: TLinkStyle read FLinkStyle write SetLinkStyle default lsNone;
  111. property ButtonCursor: TCursor read FButtonCursor write SetButtonCursor default crDefault;
  112. property Alignment: TAlignment read FAlignment write FAlignment default TaLeftJustify;
  113. property TextType: TTextType read FTextType write FTextType default NormalText;
  114. property EnterAsTab: Boolean read FEnterAsTab write FEnterAsTab default False;
  115. property AcceptNegative: Boolean read FAcceptNegative write FAcceptNegative default True;
  116. property AcceptCharList: string read FAcceptCharList write FAcceptCharList;
  117. end;
  118. implementation
  119. uses CnCommon;
  120. { TCnEdit }
  121. procedure TCnEdit.DoExit;
  122. var
  123. Ri, Code: Integer;
  124. Rs: Single;
  125. begin
  126. inherited;
  127. //如果为整形,为空时自动为0
  128. if FTextType = IntegerText then
  129. begin
  130. if Text = '' then
  131. Text := '0';
  132. Val(Text, Ri, Code);
  133. if Code <> 0 then
  134. Text := '0';
  135. Code := Ri; // 避免编译警告
  136. end;
  137. //如果为浮点,检查.前后是否为空,为空加0
  138. if FTextType = FloatText then
  139. begin
  140. if Pos('.', Text) = 1 then
  141. Text := '0' + Text;
  142. if Pos('.', Text) = Length(Text) then
  143. Text := Text + '0';
  144. Val(Text, Rs, Code);
  145. if Code <> 0 then
  146. Text := '0';
  147. Code := Round(Rs); // 避免编译警告
  148. end;
  149. Invalidate;
  150. end;
  151. procedure TCnEdit.BoundSChanged;
  152. var
  153. R: TRect;
  154. begin
  155. SetRect(R, 0, 0, ClientWidth - 2, ClientHeight + 1); // +1 is workAround for Windows paint bug
  156. if (FLinkStyle <> lsNone) then
  157. Dec(R.Right, FButtonWidth);
  158. SendMessage(Handle, EM_SETRECT, 0, LongInt(@R));
  159. Repaint;
  160. end;
  161. constructor TCnEdit.Create(AOwner: TComponent);
  162. begin
  163. inherited Create(AOwner);
  164. FButtonWidth := GetSystemMetrics(SM_CXVScROLL);
  165. FAcceptNegative := True;
  166. end;
  167. procedure TCnEdit.CreateParams(var Params: TCreateParams);
  168. begin
  169. inherited CreateParams(Params);
  170. with Params do
  171. begin
  172. Style := Style or ES_MULTILINE;
  173. end;
  174. end;
  175. destructor TCnEdit.Destroy;
  176. begin
  177. inherited Destroy;
  178. FCanvas.Free;
  179. end;
  180. procedure TCnEdit.DoEnter;
  181. begin
  182. if (FLinkStyle <> lsNone) then
  183. BoundSChanged;
  184. inherited DoEnter;
  185. if AutoSelect then
  186. SelectAll;
  187. end;
  188. procedure TCnEdit.EditButtonClick;
  189. begin
  190. if Assigned(FOnButtonClick) then
  191. FOnButtonClick(Self);
  192. end;
  193. function TCnEdit.GetTextMArgins: TPoint;
  194. var
  195. DC: HDC;
  196. SaveFont: HFont;
  197. I: Integer;
  198. SysMetrics, Metrics: TTextMetric;
  199. begin
  200. if NewStyleControls then
  201. begin
  202. if BOrderStyle = bsNone then
  203. I := 0
  204. else if Ctl3D then
  205. I := 1
  206. else
  207. I := 2;
  208. Result.X := SendMessage(Handle, EM_GETMArGINS, 0, 0) and $0000FFFF + I;
  209. Result.Y := I;
  210. end
  211. else
  212. begin
  213. if BOrderStyle = bsNone then
  214. I := 0
  215. else
  216. begin
  217. DC := GetDC(0);
  218. GetTextMetrics(DC, SysMetrics);
  219. SaveFont := SelectObject(DC, Font.Handle);
  220. GetTextMetrics(DC, Metrics);
  221. SelectObject(DC, SaveFont);
  222. ReleaseDC(0, DC);
  223. I := SysMetrics.tmHeight;
  224. if I > Metrics.tmHeight then
  225. I := Metrics.tmHeight;
  226. I := I div 4;
  227. end;
  228. Result.X := I;
  229. Result.Y := I;
  230. end;
  231. end;
  232. procedure TCnEdit.KeyDown(var Key: Word; Shift: TShiftState);
  233. var
  234. Msg: TMsg;
  235. begin
  236. if (FLinkStyle in [lsEllipsis, lsDropDown]) and (Key = VK_RETURN) and (Shift = [sSCtrl]) then
  237. begin
  238. EditButtonClick;
  239. PeekMessage(Msg, Handle, WM_CHAR, WM_CHAR, PM_REMOVE);
  240. end
  241. else
  242. inherited KeyDown(Key, Shift);
  243. end;
  244. procedure TCnEdit.KeyPress(var Key: ChAr);
  245. var
  246. AParent: TControl;
  247. begin
  248. if Key = #13 then
  249. begin
  250. if FEnterAsTab then
  251. begin
  252. AParent := Parent;
  253. if AParent <> nil then
  254. while AParent.Parent <> nil do
  255. AParent := AParent.Parent;
  256. if (AParent <> nil) and (AParent is TControl) then
  257. (AParent as TControl).Perform(WM_NEXTDLGCTL, 0, 0);
  258. Key := #0;
  259. end
  260. else
  261. MessageBeep(0);
  262. end
  263. else
  264. begin
  265. if not CharInSet(Key, [Chr(VK_BACK), Chr(VK_RETURN), #01, #03, #08, #22, #24, #26]) then // Ctrl+A/C/BK/V/X/Z
  266. begin
  267. if FTextType = IntegerText then
  268. begin
  269. if not FAcceptNegative and (Key = '-') then
  270. Key := #0
  271. else if not CharInSet(Key, ['0'..'9', '-']) then
  272. Key := #0
  273. else
  274. begin
  275. if (Key = '-') and ((Pos('-', Text) > 0) or (Self.SelStart > 0)) then Key := #0;
  276. end;
  277. end
  278. else if FTextType = FloatText then
  279. begin
  280. if not FAcceptNegative and (Key = '-') then
  281. Key := #0
  282. else if not CharInSet(Key, ['0'..'9', '.', '-']) then
  283. Key := #0
  284. else
  285. begin
  286. if (Key = '-') and ((Pos('-', Text) > 0) or (Self.SelStart > 0)) then
  287. Key := #0;
  288. if (Pos('.', Text) > 0) and (Key = '.') then
  289. Key := #0;
  290. end;
  291. end
  292. else if (FTextType = IdentText) and (not IsValidIdentChar(Key, SelStart = 0)) then
  293. Key := #0
  294. else if FAcceptCharList <> '' then
  295. begin
  296. if Pos(Key, FAcceptCharList) = 0 then
  297. Key := #0;
  298. end;
  299. end;
  300. end;
  301. inherited KeyPress(Key);
  302. end;
  303. procedure TCnEdit.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  304. begin
  305. if (Button = mbLeft) and (FLinkStyle <> lsNone)
  306. and PtInRect(Rect(Width - FButtonWidth, 0, Width, Height), Point(X, Y)) then
  307. begin
  308. MouseCapture := True;
  309. FTracking := True;
  310. TrackButton(X, Y);
  311. end;
  312. inherited MouseDown(Button, Shift, X, Y);
  313. end;
  314. procedure TCnEdit.MouseMove(Shift: TShiftState; X, Y: Integer);
  315. begin
  316. if FTracking then
  317. TrackButton(X, Y);
  318. inherited MouseMove(Shift, X, Y);
  319. end;
  320. procedure TCnEdit.MouseUp(Button: TMouseButton; Shift: TShiftState; X,
  321. Y: Integer);
  322. var
  323. WasPressed: Boolean;
  324. begin
  325. WasPressed := FPressed;
  326. StopTracking;
  327. if (Button = mbLeft) and (FLinkStyle in [lsEllipsis, lsDropDown]) and WasPressed then
  328. EditButtonClick;
  329. inherited MouseUp(Button, Shift, X, Y);
  330. end;
  331. procedure TCnEdit.SetLinkStyle(Value: TLinkStyle);
  332. begin
  333. if Value = FLinkStyle then
  334. Exit;
  335. FLinkStyle := Value;
  336. if not HandleAllocated then
  337. Exit;
  338. BoundSChanged;
  339. end;
  340. procedure TCnEdit.StopTracking;
  341. begin
  342. if FTracking then
  343. begin
  344. TrackButton(-1, -1);
  345. FTracking := False;
  346. MouseCApture := False;
  347. end;
  348. end;
  349. procedure TCnEdit.TrackButton(X, Y: Integer);
  350. var
  351. NewState: Boolean;
  352. R: TRect;
  353. begin
  354. SetRect(R, ClientWidth - FButtonWidth, 0, ClientWidth, ClientHeight);
  355. NewState := PtInRect(R, Point(X, Y));
  356. if FPressed <> NewState then
  357. begin
  358. FPressed := NewState;
  359. InvalidateRect(Handle, @R, False);
  360. end;
  361. end;
  362. procedure TCnEdit.WMPaint(var Message: TWMPaint);
  363. var
  364. Left: Integer;
  365. MArgins: TPoint;
  366. R: TRect;
  367. DC: HDC;
  368. PS: TPaintStruct;
  369. S: string;
  370. Flags: Integer;
  371. W: Integer;
  372. I: Integer;
  373. begin
  374. if FCanvas = nil then
  375. begin
  376. FCanvas := TControlCanvas.Create;
  377. FCanvas.Control := Self;
  378. end;
  379. DC := Message.DC;
  380. if DC = 0 then
  381. DC := BeginPaint(Handle, PS);
  382. FCanvas.Handle := DC;
  383. try
  384. FCanvas.Font := Font;
  385. with FCanvas do
  386. begin
  387. //设置控件的范围
  388. if (FLinkStyle <> lsNone) then
  389. SetRect(R, ClientWidth - FButtonWidth, 0, ClientWidth, ClientHeight)
  390. else
  391. begin
  392. R := ClientRect;
  393. if not (NewStyleControls and Ctl3D) and (BOrderStyle = bsSinGle) then
  394. begin
  395. Brush.Color := clWindowFrame;
  396. FrameRect(R);
  397. InflateRect(R, -1, -1);
  398. end;
  399. Brush.Color := Color;
  400. end;
  401. //是否是密码型
  402. S := Text;
  403. if PasswordChAr <> #0 then
  404. FillChAr(S[1], Length(S), PasswordChAr);
  405. //画文字
  406. MArgins := GetTextMArgins;
  407. if Focused then
  408. begin
  409. Left := MArgins.X;
  410. end
  411. else
  412. begin
  413. case FAlignment of
  414. taLeftJustify: Left := MArgins.X;
  415. tArightJustify: Left := ClientWidth - TextWidth(S) - MArgins.X - 1;
  416. else
  417. Left := (ClientWidth - TextWidth(S)) div 2;
  418. end;
  419. end;
  420. TextRect(R, Left, MArgins.Y, S);
  421. if (FLinkStyle <> lsNone) then //画按钮
  422. begin
  423. Flags := 0;
  424. if FPressed then
  425. Flags := BF_FLAT;
  426. DrawEdge(DC, R, EDGE_RAISED, BF_RECT or BF_MIDDLE or Flags);
  427. Flags := ((R.Right - R.Left) shr 1) - 1 + Ord(FPressed);
  428. if FLinkStyle = lsEllipsis then
  429. begin
  430. W := 2;
  431. PatBlt(DC, R.Left + Flags, R.Top + Round(ClientHeight / 2) - 1, W, W, BLACKNESS);
  432. PatBlt(DC, R.Left + Flags - (W * 2), R.Top + Round(ClientHeight / 2) - 1, W, W, BLACKNESS);
  433. PatBlt(DC, R.Left + Flags + (W * 2), R.Top + Round(ClientHeight / 2) - 1, W, W, BLACKNESS);
  434. end
  435. else if FLinkStyle = lsDropDown then
  436. begin
  437. for I := 0 to 3 do // 画下拉箭头
  438. begin
  439. Windows.MoveToEx(DC, R.Left + 4 + I, R.Top + 7 + I, nil);
  440. Windows.LineTo(DC, R.Left + 4 + 7 - I, R.Top + 7 + I);
  441. end;
  442. end;
  443. ExcludeClipRect(DC, R.Left, R.Top, R.Right, R.Bottom);
  444. PaintWindow(DC);
  445. end;
  446. end;
  447. finally
  448. FCanvas.Handle := 0;
  449. if Message.DC = 0 then
  450. EndPaint(Handle, PS);
  451. end;
  452. end;
  453. procedure TCnEdit.WMSetCursor(var Msg: TWMSetCursor);
  454. var
  455. P: TPoint;
  456. begin
  457. GetCursorPos(P);
  458. if (FLinkStyle <> lsNone) and
  459. PtInRect(Rect(Width - FButtonWidth - 4, 0, ClientWidth,
  460. ClientHeight), ScreenToClient(P)) then
  461. Windows.SetCursor(Screen.Cursors[FButtonCursor])
  462. else
  463. inherited;
  464. end;
  465. function TCnEdit.GetValue: Variant;
  466. begin
  467. case FTextType of
  468. IntegerText: Result := StrToInt(Text);
  469. FloatText: Result := StrToFloat(Text);
  470. else
  471. Result := Text;
  472. end;
  473. end;
  474. procedure TCnEdit.SetButtonCursor(const Value: TCursor);
  475. begin
  476. if FButtonCursor <> Value then
  477. begin
  478. FButtonCursor := Value;
  479. Perform(WM_SETCURSOR, 0, 0);
  480. end;
  481. end;
  482. procedure TCnEdit.WMPaste(var Message: TWMPaste);
  483. var
  484. I: Integer;
  485. S: string;
  486. begin
  487. // 处理粘贴消息,删除不需要的字符,副作用是会影响剪贴板内容
  488. if Clipboard.AsText = '' then
  489. Exit;
  490. S := Clipboard.AsText;
  491. I := Length(S);
  492. case FTextType of
  493. IntegerText:
  494. begin
  495. while I > 0 do
  496. begin
  497. if not CharInSet(S[I], ['0'..'9', '-']) then
  498. Delete(S, I, 1);
  499. Dec(I);
  500. end;
  501. end;
  502. FloatText:
  503. begin
  504. while I > 0 do
  505. begin
  506. if not CharInSet(S[I], ['0'..'9', '-', '.']) then
  507. Delete(S, I, 1);
  508. Dec(I);
  509. end;
  510. end;
  511. IdentText:
  512. begin
  513. while I > 0 do
  514. begin
  515. if not IsValidIdentChar(S[I], I = 1) then
  516. Delete(S, I, 1);
  517. Dec(I);
  518. end;
  519. end;
  520. else
  521. ;
  522. end;
  523. Clipboard.AsText := S;
  524. inherited;
  525. end;
  526. end.