UpDownEdit.pas 17 KB

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