UpDownEdit.pas 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  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, PasTools, Math;
  142. procedure Register;
  143. begin
  144. RegisterComponents('Martin', [TUpDownEdit]);
  145. end;
  146. function DefBtnWidth: Integer;
  147. begin
  148. Result := Min(GetSystemMetrics(SM_CXVSCROLL), ScaleByPixelsPerInch(15));
  149. end;
  150. type
  151. TEmbededUpDown = class(TCustomUpDown)
  152. private
  153. FChanging: Boolean;
  154. procedure ScrollMessage(var Message: TWMVScroll);
  155. procedure WMHScroll(var Message: TWMHScroll); message CN_HSCROLL;
  156. procedure WMVScroll(var Message: TWMVScroll); message CN_VSCROLL;
  157. procedure WMSize(var Message: TWMSize); message WM_SIZE;
  158. public
  159. constructor Create(AOwner: TComponent); override;
  160. destructor Destroy; override;
  161. published
  162. property OnClick;
  163. end;
  164. constructor TEmbededUpDown.Create(AOwner: TComponent);
  165. begin
  166. inherited Create(AOwner);
  167. Orientation := udVertical;
  168. Min := -1;
  169. Max := 1;
  170. Position := 0;
  171. end;
  172. destructor TEmbededUpDown.Destroy;
  173. begin
  174. OnClick := nil;
  175. inherited Destroy;
  176. end;
  177. procedure TEmbededUpDown.ScrollMessage(var Message: TWMVScroll);
  178. begin
  179. if Message.ScrollCode = SB_THUMBPOSITION then begin
  180. if not FChanging then begin
  181. FChanging := True;
  182. try
  183. if Message.Pos > 0 then Click(btNext)
  184. else if Message.Pos < 0 then Click(btPrev);
  185. if HandleAllocated then
  186. SendMessage(Handle, UDM_SETPOS, 0, 0);
  187. finally
  188. FChanging := False;
  189. end;
  190. end;
  191. end;
  192. end;
  193. procedure TEmbededUpDown.WMHScroll(var Message: TWMHScroll);
  194. begin
  195. ScrollMessage(TWMVScroll(Message));
  196. end;
  197. procedure TEmbededUpDown.WMVScroll(var Message: TWMVScroll);
  198. begin
  199. ScrollMessage(Message);
  200. end;
  201. procedure TEmbededUpDown.WMSize(var Message: TWMSize);
  202. begin
  203. inherited;
  204. if Width <> DefBtnWidth then Width := DefBtnWidth;
  205. end;
  206. { TUpDownEdit }
  207. constructor TUpDownEdit.Create(AOwner: TComponent);
  208. begin
  209. inherited Create(AOwner);
  210. Text := '0';
  211. ControlStyle := ControlStyle - [csSetCaption];
  212. FIncrement := 1.0;
  213. FDecimal := 2;
  214. FEditorEnabled := True;
  215. FArrowKeys := True;
  216. FButtonsVisible := True;
  217. RecreateButton;
  218. end;
  219. destructor TUpDownEdit.Destroy;
  220. begin
  221. Destroying;
  222. FChanging := True;
  223. if FUpDown <> nil then
  224. begin
  225. FUpDown.Free;
  226. FUpDown := nil;
  227. end;
  228. inherited Destroy;
  229. end;
  230. procedure TUpDownEdit.RecreateButton;
  231. begin
  232. if (csDestroying in ComponentState) then Exit;
  233. FUpDown.Free;
  234. FUpDown := nil;
  235. FUpDown := TEmbededUpDown.Create(Self);
  236. with TEmbededUpDown(FUpDown) do begin
  237. Visible := True;
  238. SetBounds(0, 0, DefBtnWidth, Self.Height);
  239. if (BiDiMode = bdRightToLeft) then Align := alLeft
  240. else Align := alRight;
  241. Parent := Self;
  242. OnClick := UpDownClick;
  243. end;
  244. end;
  245. procedure TUpDownEdit.SetArrowKeys(Value: Boolean);
  246. begin
  247. FArrowKeys := Value;
  248. ResizeButton;
  249. end;
  250. procedure TUpDownEdit.UpDownClick(Sender: TObject; Button: TUDBtnType);
  251. begin
  252. if TabStop and CanFocus then SetFocus;
  253. case Button of
  254. btNext: UpClick(Sender);
  255. btPrev: DownClick(Sender);
  256. end;
  257. end;
  258. function TUpDownEdit.GetButtonWidth: Integer;
  259. begin
  260. if FUpDown.Visible then Result := FUpDown.Width
  261. else Result := 0;
  262. end;
  263. procedure TUpDownEdit.ResizeButton;
  264. begin
  265. if FUpDown <> nil then
  266. begin
  267. FUpDown.Width := DefBtnWidth;
  268. if (BiDiMode = bdRightToLeft) then FUpDown.Align := alLeft
  269. else FUpDown.Align := alRight;
  270. FUpDown.Visible := ButtonsVisible;
  271. end
  272. end;
  273. procedure TUpDownEdit.KeyDown(var Key: Word; Shift: TShiftState);
  274. begin
  275. inherited KeyDown(Key, Shift);
  276. if ArrowKeys and (Key in [VK_UP, VK_DOWN]) then
  277. begin
  278. if Key = VK_UP then UpClick(Self)
  279. else
  280. if Key = VK_DOWN then DownClick(Self);
  281. Key := 0;
  282. end;
  283. end;
  284. procedure TUpDownEdit.Change;
  285. begin
  286. if not FChanging then inherited Change;
  287. end;
  288. procedure TUpDownEdit.KeyPress(var Key: Char);
  289. begin
  290. if not IsValidChar(Key) then
  291. begin
  292. Key := #0;
  293. MessageBeep(0)
  294. end;
  295. if Key <> #0 then
  296. begin
  297. inherited KeyPress(Key);
  298. if (Key = Char(VK_RETURN)) or (Key = Char(VK_ESCAPE)) then
  299. begin
  300. { must catch and remove this, since is actually multi-line }
  301. GetParentForm(Self).Perform(CM_DIALOGKEY, Byte(Key), 0);
  302. if Key = Char(VK_RETURN) then Key := #0;
  303. end;
  304. end;
  305. end;
  306. function TUpDownEdit.IsValidChar(Key: Char): Boolean;
  307. var
  308. ValidChars: TSysCharSet;
  309. begin
  310. ValidChars := ['+', '-', '0'..'9'];
  311. if ValueType = vtFloat then
  312. begin
  313. if Pos(FormatSettings.DecimalSeparator, Text) = 0 then
  314. ValidChars := ValidChars + [FormatSettings.DecimalSeparator];
  315. if Pos('E', AnsiUpperCase(Text)) = 0 then
  316. ValidChars := ValidChars + ['e', 'E'];
  317. end
  318. else
  319. if ValueType = vtHex then
  320. begin
  321. ValidChars := ValidChars + ['A'..'F', 'a'..'f'];
  322. end;
  323. Result := CharInSet(Key, ValidChars) or (Key < #32);
  324. if not FEditorEnabled and Result and ((Key >= #32) or
  325. (Key = Char(VK_BACK)) or (Key = Char(VK_DELETE))) then Result := False;
  326. end;
  327. procedure TUpDownEdit.CreateParams(var Params: TCreateParams);
  328. const
  329. Alignments: array[Boolean, TAlignment] of DWORD =
  330. ((ES_LEFT, ES_RIGHT, ES_CENTER), (ES_RIGHT, ES_LEFT, ES_CENTER));
  331. begin
  332. inherited CreateParams(Params);
  333. Params.Style := Params.Style or ES_MULTILINE or WS_CLIPCHILDREN or
  334. Alignments[UseRightToLeftAlignment, FAlignment];
  335. end;
  336. procedure TUpDownEdit.CreateWnd;
  337. begin
  338. inherited CreateWnd;
  339. SetEditRect;
  340. SetValue(Value);
  341. end;
  342. procedure TUpDownEdit.SetEditRect;
  343. var
  344. Loc: TRect;
  345. begin
  346. if (BiDiMode = bdRightToLeft) then
  347. SetRect(Loc, GetButtonWidth + 1, 0, ClientWidth - 1, ClientHeight + 1)
  348. else
  349. SetRect(Loc, 0, 0, ClientWidth - GetButtonWidth - 2, ClientHeight + 1);
  350. SendMessage(Handle, EM_SETRECTNP, 0, Longint(@Loc));
  351. end;
  352. procedure TUpDownEdit.SetAlignment(Value: TAlignment);
  353. begin
  354. if FAlignment <> Value then
  355. begin
  356. FAlignment := Value;
  357. RecreateWnd;
  358. end;
  359. end;
  360. procedure TUpDownEdit.WMSize(var Message: TWMSize);
  361. var
  362. MinHeight: Integer;
  363. begin
  364. inherited;
  365. MinHeight := GetMinHeight;
  366. { text edit bug: if size to less than minheight, then edit ctrl does
  367. not display the text }
  368. if Height < MinHeight then
  369. Height := MinHeight
  370. else begin
  371. ResizeButton;
  372. SetEditRect;
  373. end;
  374. end;
  375. procedure TUpDownEdit.GetTextHeight(var SysHeight, Height: Integer);
  376. var
  377. DC: HDC;
  378. SaveFont: HFont;
  379. SysMetrics, Metrics: TTextMetric;
  380. begin
  381. DC := GetDC(0);
  382. GetTextMetrics(DC, SysMetrics);
  383. SaveFont := SelectObject(DC, Font.Handle);
  384. GetTextMetrics(DC, Metrics);
  385. SelectObject(DC, SaveFont);
  386. ReleaseDC(0, DC);
  387. SysHeight := SysMetrics.tmHeight;
  388. Height := Metrics.tmHeight;
  389. end;
  390. function TUpDownEdit.GetMinHeight: Integer;
  391. var
  392. I, H: Integer;
  393. begin
  394. GetTextHeight(I, H);
  395. if I > H then I := H;
  396. Result := H + (GetSystemMetrics(SM_CYBORDER) * 4) + 1;
  397. end;
  398. procedure TUpDownEdit.UpClick(Sender: TObject);
  399. var
  400. OldText: string;
  401. begin
  402. if ReadOnly then MessageBeep(0)
  403. else begin
  404. FChanging := True;
  405. try
  406. OldText := inherited Text;
  407. Value := Value + FIncrement;
  408. finally
  409. FChanging := False;
  410. end;
  411. if CompareText(inherited Text, OldText) <> 0 then
  412. begin
  413. Modified := True;
  414. Change;
  415. end;
  416. if Assigned(FOnTopClick) then FOnTopClick(Self);
  417. end;
  418. end;
  419. procedure TUpDownEdit.DownClick(Sender: TObject);
  420. var
  421. OldText: string;
  422. begin
  423. if ReadOnly then MessageBeep(0)
  424. else begin
  425. FChanging := True;
  426. try
  427. OldText := inherited Text;
  428. Value := Value - FIncrement;
  429. finally
  430. FChanging := False;
  431. end;
  432. if CompareText(inherited Text, OldText) <> 0 then
  433. begin
  434. Modified := True;
  435. Change;
  436. end;
  437. if Assigned(FOnBottomClick) then FOnBottomClick(Self);
  438. end;
  439. end;
  440. procedure TUpDownEdit.CMBiDiModeChanged(var Message: TMessage);
  441. begin
  442. inherited;
  443. ResizeButton;
  444. SetEditRect;
  445. end;
  446. procedure TUpDownEdit.CMFontChanged(var Message: TMessage);
  447. begin
  448. inherited;
  449. ResizeButton;
  450. SetEditRect;
  451. end;
  452. procedure TUpDownEdit.CMCtl3DChanged(var Message: TMessage);
  453. begin
  454. inherited;
  455. ResizeButton;
  456. SetEditRect;
  457. end;
  458. procedure TUpDownEdit.CMEnabledChanged(var Message: TMessage);
  459. begin
  460. inherited;
  461. if FUpDown <> nil then
  462. begin
  463. FUpDown.Enabled := Enabled;
  464. ResizeButton;
  465. end;
  466. end;
  467. procedure TUpDownEdit.WMPaste(var Message: TWMPaste);
  468. begin
  469. if not FEditorEnabled or ReadOnly then Exit;
  470. inherited;
  471. end;
  472. procedure TUpDownEdit.WMCut(var Message: TWMCut);
  473. begin
  474. if not FEditorEnabled or ReadOnly then Exit;
  475. inherited;
  476. end;
  477. procedure TUpDownEdit.CMExit(var Message: TCMExit);
  478. begin
  479. inherited;
  480. SetValue(Value);
  481. end;
  482. procedure TUpDownEdit.CMEnter(var Message: TMessage);
  483. begin
  484. if AutoSelect and not (csLButtonDown in ControlState) then SelectAll;
  485. inherited;
  486. end;
  487. function TUpDownEdit.GetValue: Extended;
  488. var
  489. Handled: Boolean;
  490. begin
  491. Handled := False;
  492. if Assigned(FOnGetValue) then FOnGetValue(Self, Text, Result, Handled);
  493. if not Handled then
  494. begin
  495. try
  496. if ValueType = vtFloat then Result := StrToFloat(Text)
  497. else if ValueType = vtHex then Result := StrToInt('$' + Text)
  498. else Result := StrToInt(Text);
  499. except
  500. if ValueType = vtFloat then Result := FMinValue
  501. else Result := Trunc(FMinValue);
  502. end;
  503. end;
  504. end;
  505. procedure TUpDownEdit.SetValue(NewValue: Extended);
  506. var
  507. Handled: Boolean;
  508. AText: string;
  509. begin
  510. NewValue := CheckValue(NewValue);
  511. Handled := False;
  512. if Assigned(FOnSetValue) then
  513. begin
  514. AText := Text;
  515. FOnSetValue(Self, NewValue, AText, Handled);
  516. if Handled then Text := AText;
  517. end;
  518. if not Handled then
  519. begin
  520. if ValueType = vtFloat then
  521. Text := FloatToStrF(NewValue, ffFixed, 15, FDecimal)
  522. else if ValueType = vtHex then
  523. Text := IntToHex(Round(NewValue), 1)
  524. else
  525. Text := IntToStr(Round(NewValue));
  526. end;
  527. end;
  528. function TUpDownEdit.GetAsInteger: Longint;
  529. begin
  530. Result := Trunc(GetValue);
  531. end;
  532. procedure TUpDownEdit.SetAsInteger(NewValue: Longint);
  533. begin
  534. SetValue(NewValue);
  535. end;
  536. procedure TUpDownEdit.SetValueType(NewType: TValueType);
  537. begin
  538. if FValueType <> NewType then
  539. begin
  540. FValueType := NewType;
  541. Value := GetValue;
  542. if FValueType in [vtInt, vtHex] then
  543. begin
  544. FIncrement := Round(FIncrement);
  545. if FIncrement = 0 then FIncrement := 1;
  546. end;
  547. end;
  548. end;
  549. function TUpDownEdit.IsIncrementStored: Boolean;
  550. begin
  551. Result := FIncrement <> 1.0;
  552. end;
  553. function TUpDownEdit.IsMaxStored: Boolean;
  554. begin
  555. Result := (MaxValue <> 0.0);
  556. end;
  557. function TUpDownEdit.IsMinStored: Boolean;
  558. begin
  559. Result := (MinValue <> 0.0);
  560. end;
  561. function TUpDownEdit.IsValueStored: Boolean;
  562. begin
  563. Result := (GetValue <> 0.0);
  564. end;
  565. procedure TUpDownEdit.SetDecimal(NewValue: Byte);
  566. begin
  567. if FDecimal <> NewValue then
  568. begin
  569. FDecimal := NewValue;
  570. Value := GetValue;
  571. end;
  572. end;
  573. function TUpDownEdit.CheckValue(NewValue: Extended): Extended;
  574. begin
  575. Result := NewValue;
  576. if (FMaxValue <> FMinValue) then
  577. begin
  578. if NewValue < FMinValue then
  579. Result := FMinValue
  580. else if NewValue > FMaxValue then
  581. Result := FMaxValue;
  582. end;
  583. end;
  584. procedure TUpDownEdit.SetButtonsVisible(Value: Boolean);
  585. begin
  586. if ButtonsVisible <> Value then
  587. begin
  588. FButtonsVisible := Value;
  589. ResizeButton;
  590. SetEditRect;
  591. end;
  592. end;
  593. initialization
  594. end.