UpDownEdit.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. unit UpDownEdit;
  2. interface
  3. uses
  4. Windows, ComCtrls, Controls, ExtCtrls, Classes, Graphics, Messages, Forms,
  5. StdCtrls, Menus, SysUtils;
  6. { TUpDownEdit }
  7. type
  8. TValueType = (vtInt, vtFloat, vtHex);
  9. TUpDownEditGetValue = procedure(Sender: TObject; Text: string;
  10. var Value: Extended; var Handled: Boolean) of object;
  11. TUpDownEditSetValue = procedure(Sender: TObject; Value: Extended;
  12. var Text: string; var Handled: Boolean) of object;
  13. TUpDownEdit = class(TCustomEdit)
  14. private
  15. FAlignment: TAlignment;
  16. FMinValue: Extended;
  17. FMaxValue: Extended;
  18. FIncrement: Extended;
  19. FDecimal: Byte;
  20. FChanging: Boolean;
  21. FEditorEnabled: Boolean;
  22. FValueType: TValueType;
  23. FArrowKeys: Boolean;
  24. FButtonsVisible: Boolean;
  25. FOnTopClick: TNotifyEvent;
  26. FOnBottomClick: TNotifyEvent;
  27. FUpDown: TCustomUpDown;
  28. FOnGetValue: TUpDownEditGetValue;
  29. FOnSetValue: TUpDownEditSetValue;
  30. procedure UpDownClick(Sender: TObject; Button: TUDBtnType);
  31. function GetMinHeight: Integer;
  32. procedure GetTextHeight(var SysHeight, Height: Integer);
  33. function GetValue: Extended;
  34. function CheckValue(NewValue: Extended): Extended;
  35. function GetAsInteger: Longint;
  36. function IsIncrementStored: Boolean;
  37. function IsMaxStored: Boolean;
  38. function IsMinStored: Boolean;
  39. function IsValueStored: Boolean;
  40. procedure SetArrowKeys(Value: Boolean);
  41. procedure SetAsInteger(NewValue: Longint);
  42. procedure SetValue(NewValue: Extended);
  43. procedure SetValueType(NewType: TValueType);
  44. procedure SetDecimal(NewValue: Byte);
  45. function GetButtonWidth: Integer;
  46. procedure RecreateButton;
  47. procedure ResizeButton;
  48. procedure SetEditRect;
  49. procedure SetAlignment(Value: TAlignment);
  50. procedure SetButtonsVisible(Value: Boolean);
  51. procedure WMSize(var Message: TWMSize); message WM_SIZE;
  52. procedure CMEnter(var Message: TMessage); message CM_ENTER;
  53. procedure CMExit(var Message: TCMExit); message CM_EXIT;
  54. procedure WMPaste(var Message: TWMPaste); message WM_PASTE;
  55. procedure WMCut(var Message: TWMCut); message WM_CUT;
  56. procedure CMCtl3DChanged(var Message: TMessage); message CM_CTL3DCHANGED;
  57. procedure CMEnabledChanged(var Message: TMessage); message CM_ENABLEDCHANGED;
  58. procedure CMFontChanged(var Message: TMessage); message CM_FONTCHANGED;
  59. procedure CMBiDiModeChanged(var Message: TMessage); message CM_BIDIMODECHANGED;
  60. protected
  61. procedure Change; override;
  62. function IsValidChar(Key: Char): Boolean; virtual;
  63. procedure UpClick(Sender: TObject); virtual;
  64. procedure DownClick(Sender: TObject); virtual;
  65. procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  66. procedure KeyPress(var Key: Char); override;
  67. procedure CreateParams(var Params: TCreateParams); override;
  68. procedure CreateWnd; override;
  69. public
  70. constructor Create(AOwner: TComponent); override;
  71. destructor Destroy; override;
  72. property AsInteger: Longint read GetAsInteger write SetAsInteger default 0;
  73. property Text;
  74. published
  75. property Alignment: TAlignment read FAlignment write SetAlignment
  76. default taLeftJustify;
  77. property ArrowKeys: Boolean read FArrowKeys write SetArrowKeys default True;
  78. property Decimal: Byte read FDecimal write SetDecimal default 2;
  79. property EditorEnabled: Boolean read FEditorEnabled write FEditorEnabled default True;
  80. property Increment: Extended read FIncrement write FIncrement stored IsIncrementStored;
  81. property MaxValue: Extended read FMaxValue write FMaxValue stored IsMaxStored;
  82. property MinValue: Extended read FMinValue write FMinValue stored IsMinStored;
  83. property ValueType: TValueType read FValueType write SetValueType default vtInt;
  84. property Value: Extended read GetValue write SetValue stored IsValueStored;
  85. property ButtonsVisible: Boolean read FButtonsVisible write SetButtonsVisible default True;
  86. property AutoSelect;
  87. property AutoSize;
  88. property BorderStyle;
  89. property Color;
  90. property Ctl3D;
  91. property DragCursor;
  92. property DragMode;
  93. property Enabled;
  94. property Font;
  95. property Anchors;
  96. property BiDiMode;
  97. property Constraints;
  98. property DragKind;
  99. property ParentBiDiMode;
  100. property ImeMode;
  101. property ImeName;
  102. property MaxLength;
  103. property ParentColor;
  104. property ParentCtl3D;
  105. property ParentFont;
  106. property ParentShowHint;
  107. property PopupMenu;
  108. property ReadOnly;
  109. property ShowHint;
  110. property TabOrder;
  111. property TabStop;
  112. property Visible;
  113. property OnBottomClick: TNotifyEvent read FOnBottomClick write FOnBottomClick;
  114. property OnTopClick: TNotifyEvent read FOnTopClick write FOnTopClick;
  115. property OnGetValue: TUpDownEditGetValue read FOnGetValue write FOnGetValue;
  116. property OnSetValue: TUpDownEditSetValue read FOnSetValue write FOnSetValue;
  117. property OnChange;
  118. property OnClick;
  119. property OnDblClick;
  120. property OnDragDrop;
  121. property OnDragOver;
  122. property OnEndDrag;
  123. property OnEnter;
  124. property OnExit;
  125. property OnKeyDown;
  126. property OnKeyPress;
  127. property OnKeyUp;
  128. property OnMouseDown;
  129. property OnMouseMove;
  130. property OnMouseUp;
  131. property OnStartDrag;
  132. property OnContextPopup;
  133. property OnMouseWheelDown;
  134. property OnMouseWheelUp;
  135. property OnEndDock;
  136. property OnStartDock;
  137. end;
  138. procedure Register;
  139. implementation
  140. uses
  141. CommCtrl;
  142. procedure Register;
  143. begin
  144. RegisterComponents('Martin', [TUpDownEdit]);
  145. end;
  146. function DefBtnWidth: Integer;
  147. begin
  148. Result := GetSystemMetrics(SM_CXVSCROLL);
  149. if Result > 15 then Result := 15;
  150. end;
  151. type
  152. TEmbededUpDown = class(TCustomUpDown)
  153. private
  154. FChanging: Boolean;
  155. procedure ScrollMessage(var Message: TWMVScroll);
  156. procedure WMHScroll(var Message: TWMHScroll); message CN_HSCROLL;
  157. procedure WMVScroll(var Message: TWMVScroll); message CN_VSCROLL;
  158. procedure WMSize(var Message: TWMSize); message WM_SIZE;
  159. public
  160. constructor Create(AOwner: TComponent); override;
  161. destructor Destroy; override;
  162. published
  163. property OnClick;
  164. end;
  165. constructor TEmbededUpDown.Create(AOwner: TComponent);
  166. begin
  167. inherited Create(AOwner);
  168. Orientation := udVertical;
  169. Min := -1;
  170. Max := 1;
  171. Position := 0;
  172. end;
  173. destructor TEmbededUpDown.Destroy;
  174. begin
  175. OnClick := nil;
  176. inherited Destroy;
  177. end;
  178. procedure TEmbededUpDown.ScrollMessage(var Message: TWMVScroll);
  179. begin
  180. if Message.ScrollCode = SB_THUMBPOSITION then begin
  181. if not FChanging then begin
  182. FChanging := True;
  183. try
  184. if Message.Pos > 0 then Click(btNext)
  185. else if Message.Pos < 0 then Click(btPrev);
  186. if HandleAllocated then
  187. SendMessage(Handle, UDM_SETPOS, 0, 0);
  188. finally
  189. FChanging := False;
  190. end;
  191. end;
  192. end;
  193. end;
  194. procedure TEmbededUpDown.WMHScroll(var Message: TWMHScroll);
  195. begin
  196. ScrollMessage(TWMVScroll(Message));
  197. end;
  198. procedure TEmbededUpDown.WMVScroll(var Message: TWMVScroll);
  199. begin
  200. ScrollMessage(Message);
  201. end;
  202. procedure TEmbededUpDown.WMSize(var Message: TWMSize);
  203. begin
  204. inherited;
  205. if Width <> DefBtnWidth then Width := DefBtnWidth;
  206. end;
  207. { TUpDownEdit }
  208. constructor TUpDownEdit.Create(AOwner: TComponent);
  209. begin
  210. inherited Create(AOwner);
  211. Text := '0';
  212. ControlStyle := ControlStyle - [csSetCaption];
  213. FIncrement := 1.0;
  214. FDecimal := 2;
  215. FEditorEnabled := True;
  216. FArrowKeys := True;
  217. FButtonsVisible := True;
  218. RecreateButton;
  219. end;
  220. destructor TUpDownEdit.Destroy;
  221. begin
  222. Destroying;
  223. FChanging := True;
  224. if FUpDown <> nil then
  225. begin
  226. FUpDown.Free;
  227. FUpDown := nil;
  228. end;
  229. inherited Destroy;
  230. end;
  231. procedure TUpDownEdit.RecreateButton;
  232. begin
  233. if (csDestroying in ComponentState) then Exit;
  234. FUpDown.Free;
  235. FUpDown := nil;
  236. FUpDown := TEmbededUpDown.Create(Self);
  237. with TEmbededUpDown(FUpDown) do begin
  238. Visible := True;
  239. SetBounds(0, 0, DefBtnWidth, Self.Height);
  240. if (BiDiMode = bdRightToLeft) then Align := alLeft
  241. else Align := alRight;
  242. Parent := Self;
  243. OnClick := UpDownClick;
  244. end;
  245. end;
  246. procedure TUpDownEdit.SetArrowKeys(Value: Boolean);
  247. begin
  248. FArrowKeys := Value;
  249. ResizeButton;
  250. end;
  251. procedure TUpDownEdit.UpDownClick(Sender: TObject; Button: TUDBtnType);
  252. begin
  253. if TabStop and CanFocus then SetFocus;
  254. case Button of
  255. btNext: UpClick(Sender);
  256. btPrev: DownClick(Sender);
  257. end;
  258. end;
  259. function TUpDownEdit.GetButtonWidth: Integer;
  260. begin
  261. if FUpDown.Visible then Result := FUpDown.Width
  262. else Result := 0;
  263. end;
  264. procedure TUpDownEdit.ResizeButton;
  265. begin
  266. if FUpDown <> nil then
  267. begin
  268. FUpDown.Width := DefBtnWidth;
  269. if (BiDiMode = bdRightToLeft) then FUpDown.Align := alLeft
  270. else FUpDown.Align := alRight;
  271. FUpDown.Visible := ButtonsVisible;
  272. end
  273. end;
  274. procedure TUpDownEdit.KeyDown(var Key: Word; Shift: TShiftState);
  275. begin
  276. inherited KeyDown(Key, Shift);
  277. if ArrowKeys and (Key in [VK_UP, VK_DOWN]) then
  278. begin
  279. if Key = VK_UP then UpClick(Self)
  280. else
  281. if Key = VK_DOWN then DownClick(Self);
  282. Key := 0;
  283. end;
  284. end;
  285. procedure TUpDownEdit.Change;
  286. begin
  287. if not FChanging then inherited Change;
  288. end;
  289. procedure TUpDownEdit.KeyPress(var Key: Char);
  290. begin
  291. if not IsValidChar(Key) then
  292. begin
  293. Key := #0;
  294. MessageBeep(0)
  295. end;
  296. if Key <> #0 then
  297. begin
  298. inherited KeyPress(Key);
  299. if (Key = Char(VK_RETURN)) or (Key = Char(VK_ESCAPE)) then
  300. begin
  301. { must catch and remove this, since is actually multi-line }
  302. GetParentForm(Self).Perform(CM_DIALOGKEY, Byte(Key), 0);
  303. if Key = Char(VK_RETURN) then Key := #0;
  304. end;
  305. end;
  306. end;
  307. function TUpDownEdit.IsValidChar(Key: Char): Boolean;
  308. var
  309. ValidChars: TSysCharSet;
  310. begin
  311. ValidChars := ['+', '-', '0'..'9'];
  312. if ValueType = vtFloat then
  313. begin
  314. if Pos(FormatSettings.DecimalSeparator, Text) = 0 then
  315. ValidChars := ValidChars + [FormatSettings.DecimalSeparator];
  316. if Pos('E', AnsiUpperCase(Text)) = 0 then
  317. ValidChars := ValidChars + ['e', 'E'];
  318. end
  319. else
  320. if ValueType = vtHex then
  321. begin
  322. ValidChars := ValidChars + ['A'..'F', 'a'..'f'];
  323. end;
  324. Result := CharInSet(Key, ValidChars) or (Key < #32);
  325. if not FEditorEnabled and Result and ((Key >= #32) or
  326. (Key = Char(VK_BACK)) or (Key = Char(VK_DELETE))) then Result := False;
  327. end;
  328. procedure TUpDownEdit.CreateParams(var Params: TCreateParams);
  329. const
  330. Alignments: array[Boolean, TAlignment] of DWORD =
  331. ((ES_LEFT, ES_RIGHT, ES_CENTER), (ES_RIGHT, ES_LEFT, ES_CENTER));
  332. begin
  333. inherited CreateParams(Params);
  334. Params.Style := Params.Style or ES_MULTILINE or WS_CLIPCHILDREN or
  335. Alignments[UseRightToLeftAlignment, FAlignment];
  336. end;
  337. procedure TUpDownEdit.CreateWnd;
  338. begin
  339. inherited CreateWnd;
  340. SetEditRect;
  341. SetValue(Value);
  342. end;
  343. procedure TUpDownEdit.SetEditRect;
  344. var
  345. Loc: TRect;
  346. begin
  347. if (BiDiMode = bdRightToLeft) then
  348. SetRect(Loc, GetButtonWidth + 1, 0, ClientWidth - 1, ClientHeight + 1)
  349. else
  350. SetRect(Loc, 0, 0, ClientWidth - GetButtonWidth - 2, ClientHeight + 1);
  351. SendMessage(Handle, EM_SETRECTNP, 0, Longint(@Loc));
  352. end;
  353. procedure TUpDownEdit.SetAlignment(Value: TAlignment);
  354. begin
  355. if FAlignment <> Value then
  356. begin
  357. FAlignment := Value;
  358. RecreateWnd;
  359. end;
  360. end;
  361. procedure TUpDownEdit.WMSize(var Message: TWMSize);
  362. var
  363. MinHeight: Integer;
  364. begin
  365. inherited;
  366. MinHeight := GetMinHeight;
  367. { text edit bug: if size to less than minheight, then edit ctrl does
  368. not display the text }
  369. if Height < MinHeight then
  370. Height := MinHeight
  371. else begin
  372. ResizeButton;
  373. SetEditRect;
  374. end;
  375. end;
  376. procedure TUpDownEdit.GetTextHeight(var SysHeight, Height: Integer);
  377. var
  378. DC: HDC;
  379. SaveFont: HFont;
  380. SysMetrics, Metrics: TTextMetric;
  381. begin
  382. DC := GetDC(0);
  383. GetTextMetrics(DC, SysMetrics);
  384. SaveFont := SelectObject(DC, Font.Handle);
  385. GetTextMetrics(DC, Metrics);
  386. SelectObject(DC, SaveFont);
  387. ReleaseDC(0, DC);
  388. SysHeight := SysMetrics.tmHeight;
  389. Height := Metrics.tmHeight;
  390. end;
  391. function TUpDownEdit.GetMinHeight: Integer;
  392. var
  393. I, H: Integer;
  394. begin
  395. GetTextHeight(I, H);
  396. if I > H then I := H;
  397. Result := H + (GetSystemMetrics(SM_CYBORDER) * 4) + 1;
  398. end;
  399. procedure TUpDownEdit.UpClick(Sender: TObject);
  400. var
  401. OldText: string;
  402. begin
  403. if ReadOnly then MessageBeep(0)
  404. else begin
  405. FChanging := True;
  406. try
  407. OldText := inherited Text;
  408. Value := Value + FIncrement;
  409. finally
  410. FChanging := False;
  411. end;
  412. if CompareText(inherited Text, OldText) <> 0 then
  413. begin
  414. Modified := True;
  415. Change;
  416. end;
  417. if Assigned(FOnTopClick) then FOnTopClick(Self);
  418. end;
  419. end;
  420. procedure TUpDownEdit.DownClick(Sender: TObject);
  421. var
  422. OldText: string;
  423. begin
  424. if ReadOnly then MessageBeep(0)
  425. else begin
  426. FChanging := True;
  427. try
  428. OldText := inherited Text;
  429. Value := Value - FIncrement;
  430. finally
  431. FChanging := False;
  432. end;
  433. if CompareText(inherited Text, OldText) <> 0 then
  434. begin
  435. Modified := True;
  436. Change;
  437. end;
  438. if Assigned(FOnBottomClick) then FOnBottomClick(Self);
  439. end;
  440. end;
  441. procedure TUpDownEdit.CMBiDiModeChanged(var Message: TMessage);
  442. begin
  443. inherited;
  444. ResizeButton;
  445. SetEditRect;
  446. end;
  447. procedure TUpDownEdit.CMFontChanged(var Message: TMessage);
  448. begin
  449. inherited;
  450. ResizeButton;
  451. SetEditRect;
  452. end;
  453. procedure TUpDownEdit.CMCtl3DChanged(var Message: TMessage);
  454. begin
  455. inherited;
  456. ResizeButton;
  457. SetEditRect;
  458. end;
  459. procedure TUpDownEdit.CMEnabledChanged(var Message: TMessage);
  460. begin
  461. inherited;
  462. if FUpDown <> nil then
  463. begin
  464. FUpDown.Enabled := Enabled;
  465. ResizeButton;
  466. end;
  467. end;
  468. procedure TUpDownEdit.WMPaste(var Message: TWMPaste);
  469. begin
  470. if not FEditorEnabled or ReadOnly then Exit;
  471. inherited;
  472. end;
  473. procedure TUpDownEdit.WMCut(var Message: TWMCut);
  474. begin
  475. if not FEditorEnabled or ReadOnly then Exit;
  476. inherited;
  477. end;
  478. procedure TUpDownEdit.CMExit(var Message: TCMExit);
  479. begin
  480. inherited;
  481. SetValue(Value);
  482. end;
  483. procedure TUpDownEdit.CMEnter(var Message: TMessage);
  484. begin
  485. if AutoSelect and not (csLButtonDown in ControlState) then SelectAll;
  486. inherited;
  487. end;
  488. function TUpDownEdit.GetValue: Extended;
  489. var
  490. Handled: Boolean;
  491. begin
  492. Handled := False;
  493. if Assigned(FOnGetValue) then FOnGetValue(Self, Text, Result, Handled);
  494. if not Handled then
  495. begin
  496. try
  497. if ValueType = vtFloat then Result := StrToFloat(Text)
  498. else if ValueType = vtHex then Result := StrToInt('$' + Text)
  499. else Result := StrToInt(Text);
  500. except
  501. if ValueType = vtFloat then Result := FMinValue
  502. else Result := Trunc(FMinValue);
  503. end;
  504. end;
  505. end;
  506. procedure TUpDownEdit.SetValue(NewValue: Extended);
  507. var
  508. Handled: Boolean;
  509. AText: string;
  510. begin
  511. NewValue := CheckValue(NewValue);
  512. Handled := False;
  513. if Assigned(FOnSetValue) then
  514. begin
  515. AText := Text;
  516. FOnSetValue(Self, NewValue, AText, Handled);
  517. if Handled then Text := AText;
  518. end;
  519. if not Handled then
  520. begin
  521. if ValueType = vtFloat then
  522. Text := FloatToStrF(NewValue, ffFixed, 15, FDecimal)
  523. else if ValueType = vtHex then
  524. Text := IntToHex(Round(NewValue), 1)
  525. else
  526. Text := IntToStr(Round(NewValue));
  527. end;
  528. end;
  529. function TUpDownEdit.GetAsInteger: Longint;
  530. begin
  531. Result := Trunc(GetValue);
  532. end;
  533. procedure TUpDownEdit.SetAsInteger(NewValue: Longint);
  534. begin
  535. SetValue(NewValue);
  536. end;
  537. procedure TUpDownEdit.SetValueType(NewType: TValueType);
  538. begin
  539. if FValueType <> NewType then
  540. begin
  541. FValueType := NewType;
  542. Value := GetValue;
  543. if FValueType in [vtInt, vtHex] then
  544. begin
  545. FIncrement := Round(FIncrement);
  546. if FIncrement = 0 then FIncrement := 1;
  547. end;
  548. end;
  549. end;
  550. function TUpDownEdit.IsIncrementStored: Boolean;
  551. begin
  552. Result := FIncrement <> 1.0;
  553. end;
  554. function TUpDownEdit.IsMaxStored: Boolean;
  555. begin
  556. Result := (MaxValue <> 0.0);
  557. end;
  558. function TUpDownEdit.IsMinStored: Boolean;
  559. begin
  560. Result := (MinValue <> 0.0);
  561. end;
  562. function TUpDownEdit.IsValueStored: Boolean;
  563. begin
  564. Result := (GetValue <> 0.0);
  565. end;
  566. procedure TUpDownEdit.SetDecimal(NewValue: Byte);
  567. begin
  568. if FDecimal <> NewValue then
  569. begin
  570. FDecimal := NewValue;
  571. Value := GetValue;
  572. end;
  573. end;
  574. function TUpDownEdit.CheckValue(NewValue: Extended): Extended;
  575. begin
  576. Result := NewValue;
  577. if (FMaxValue <> FMinValue) then
  578. begin
  579. if NewValue < FMinValue then
  580. Result := FMinValue
  581. else if NewValue > FMaxValue then
  582. Result := FMaxValue;
  583. end;
  584. end;
  585. procedure TUpDownEdit.SetButtonsVisible(Value: Boolean);
  586. begin
  587. if ButtonsVisible <> Value then
  588. begin
  589. FButtonsVisible := Value;
  590. ResizeButton;
  591. SetEditRect;
  592. end;
  593. end;
  594. initialization
  595. end.