NewProgressBar.pas 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. unit NewProgressBar;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2025 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. TNewProgressBar component - a smooth 32 bit TProgressBar
  8. Note: themed animated progress bars don't immediately show changes.
  9. This applies both to Position and State. For example if you set State while the
  10. progress bar is still moving towards a new Position, the new State doesnt show until
  11. the moving animation has finished.
  12. Define VCLSTYLES for full VCL Styles support.
  13. }
  14. interface
  15. uses
  16. Windows, Messages, Classes, Controls, ComCtrls,
  17. {$IFDEF VCLSTYLES} Vcl.Themes, {$ELSE} Themes, {$ENDIF}
  18. ExtCtrls, Types, Graphics;
  19. type
  20. TNewProgressBarState = (npbsNormal, npbsError, npbsPaused);
  21. TNewProgressBarStyle = (npbstNormal, npbstMarquee);
  22. TNewProgressBar = class(TWinControl)
  23. private
  24. FMin: LongInt;
  25. FMax: LongInt;
  26. FPosition: LongInt;
  27. FState: TNewProgressBarState;
  28. FStyle: TNewProgressBarStyle;
  29. class constructor Create;
  30. class destructor Destroy;
  31. procedure SetMin(Value: LongInt);
  32. procedure SetMax(Value: LongInt);
  33. procedure SetPosition(Value: LongInt);
  34. procedure SetState(Value: TNewProgressBarState);
  35. procedure SetStyle(Value: TNewProgressBarStyle);
  36. procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
  37. protected
  38. procedure CreateParams(var Params: TCreateParams); override;
  39. procedure CreateWnd; override;
  40. public
  41. constructor Create(AOwner: TComponent); override;
  42. published
  43. property Anchors;
  44. property Min: LongInt read FMin write SetMin;
  45. property Max: LongInt read FMax write SetMax;
  46. property Position: LongInt read FPosition write SetPosition default 0;
  47. property State: TNewProgressBarState read FState write SetState default npbsNormal;
  48. property Style: TNewProgressBarStyle read FStyle write SetStyle default npbstMarquee;
  49. property Visible default True;
  50. end;
  51. TNewProgressBarStyleHook = class(TStyleHook)
  52. {$IFDEF VCLSTYLES}
  53. strict private
  54. FMarqueeTimer: TTimer;
  55. FMarqueeStep: Integer;
  56. procedure MarqueeAction(Sender: TObject);
  57. function GetBarRect: TRect;
  58. function GetBorderWidth: Integer;
  59. function GetMax: Integer;
  60. function GetMin: Integer;
  61. function GetOrientation: TProgressBarOrientation;
  62. function GetPercent: Single;
  63. function GetPosition: Integer;
  64. procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
  65. strict protected
  66. procedure PaintBar(Canvas: TCanvas); virtual;
  67. procedure PaintFrame(Canvas: TCanvas); virtual;
  68. procedure Paint(Canvas: TCanvas); override;
  69. property BarRect: TRect read GetBarRect;
  70. property BorderWidth: Integer read GetBorderWidth;
  71. property Max: Integer read GetMax;
  72. property Min: Integer read GetMin;
  73. property Orientation: TProgressBarOrientation read GetOrientation;
  74. property Position: Integer read GetPosition;
  75. public
  76. constructor Create(AControl: TWinControl); override;
  77. destructor Destroy; override;
  78. {$ENDIF}
  79. end;
  80. procedure Register;
  81. implementation
  82. uses
  83. CommCtrl, SysUtils, GraphUtil;
  84. procedure Register;
  85. begin
  86. RegisterComponents('JR', [TNewProgressBar]);
  87. end;
  88. { TNewProgressBar }
  89. class constructor TNewProgressBar.Create;
  90. begin
  91. TCustomStyleEngine.RegisterStyleHook(TNewProgressBar, TNewProgressBarStyleHook);
  92. end;
  93. constructor TNewProgressBar.Create(AOwner: TComponent);
  94. begin
  95. inherited;
  96. Width := 150;
  97. Height := GetSystemMetrics(SM_CYVSCROLL);
  98. FMin := 0;
  99. FMax := 100;
  100. end;
  101. procedure TNewProgressBar.CreateParams(var Params: TCreateParams);
  102. const
  103. PBS_SMOOTH = 1;
  104. PBS_MARQUEE = 8;
  105. begin
  106. InitCommonControls;
  107. inherited;
  108. CreateSubClass(Params, PROGRESS_CLASS);
  109. Params.Style := Params.Style or PBS_SMOOTH;
  110. if Style = npbstMarquee then
  111. Params.Style := Params.Style or PBS_MARQUEE;
  112. end;
  113. procedure TNewProgressBar.CreateWnd;
  114. const
  115. PBM_SETMARQUEE = WM_USER+10;
  116. begin
  117. inherited CreateWnd;
  118. SendMessage(Handle, PBM_SETRANGE, 0, MAKELPARAM(0, 65535));
  119. SetPosition(FPosition);
  120. SetState(FState);
  121. SendMessage(Handle, PBM_SETMARQUEE, WPARAM(FStyle = npbstMarquee), 0);
  122. end;
  123. class destructor TNewProgressBar.Destroy;
  124. begin
  125. TCustomStyleEngine.UnRegisterStyleHook(TNewProgressBar, TNewProgressBarStyleHook);
  126. end;
  127. procedure TNewProgressBar.SetMin(Value: LongInt);
  128. begin
  129. FMin := Value;
  130. SetPosition(FPosition);
  131. end;
  132. procedure TNewProgressBar.SetMax(Value: LongInt);
  133. begin
  134. FMax := Value;
  135. SetPosition(FPosition);
  136. end;
  137. procedure TNewProgressBar.SetPosition(Value: LongInt);
  138. begin
  139. if Value < FMin then
  140. Value := FMin
  141. else if Value > FMax then
  142. Value := FMax;
  143. FPosition := Value;
  144. if HandleAllocated and (FStyle <> npbstMarquee) then
  145. SendMessage(Handle, PBM_SETPOS, MulDiv(Value - FMin, 65535, FMax - FMin), 0);
  146. end;
  147. procedure TNewProgressBar.SetState(Value: TNewProgressBarState);
  148. const
  149. PBST_NORMAL = $0001;
  150. PBST_ERROR = $0002;
  151. PBST_PAUSED = $0003;
  152. PBM_SETSTATE = WM_USER+16;
  153. States: array[TNewProgressBarState] of UINT = (PBST_NORMAL, PBST_ERROR, PBST_PAUSED);
  154. begin
  155. FState := Value;
  156. if HandleAllocated then
  157. SendMessage(Handle, PBM_SETSTATE, States[Value], 0);
  158. end;
  159. procedure TNewProgressBar.SetStyle(Value: TNewProgressBarStyle);
  160. begin
  161. if FStyle <> Value then begin
  162. FStyle := Value;
  163. RecreateWnd;
  164. end;
  165. end;
  166. procedure TNewProgressBar.WMEraseBkgnd(var Message: TWMEraseBkgnd);
  167. begin
  168. { Bypass TWinControl's default WM_ERASEBKGND handling.
  169. On Windows Vista with COMCTL32 v6, a WM_ERASEBKGND message is sent every
  170. time a progress bar's position changes. TWinControl.WMEraseBkgnd does a
  171. FillRect on the whole client area, which results in ugly flickering.
  172. Previous versions of Windows only sent a WM_ERASEBKGND message when a
  173. progress bar moved backwards, so flickering was rarely apparent. }
  174. DefaultHandler(Message);
  175. end;
  176. {$IFDEF VCLSTYLES}
  177. { TNewProgressBarStyleHook - same as Vcl.ComCtrls' TProgressBarStyleHook
  178. except that it accesses the Control property as a TNewProgressBar instead
  179. of a TProgressBar }
  180. constructor TNewProgressBarStyleHook.Create(AControl: TWinControl);
  181. const
  182. cDefaultMarqueeInterval = 10;
  183. begin
  184. inherited;
  185. OverridePaint := True;
  186. DoubleBuffered := True;
  187. FMarqueeTimer := TTimer.Create(nil);
  188. FMarqueeTimer.Interval := cDefaultMarqueeInterval;
  189. FMarqueeTimer.OnTimer := MarqueeAction;
  190. FMarqueeTimer.Enabled := (TNewProgressBar(Control).Style = npbstMarquee) and
  191. not (csDesigning in Control.ComponentState);
  192. end;
  193. function TNewProgressBarStyleHook.GetPercent: Single;
  194. var
  195. LMin, LMax, LPos: Integer;
  196. begin
  197. LMin := Min;
  198. LMax := Max;
  199. LPos := Position;
  200. if (LPos >= LMin) and (LMax >= LPos) and (LMax - LMin <> 0) then
  201. Result := (LPos - LMin) / (LMax - LMin)
  202. else
  203. Result := 0;
  204. end;
  205. destructor TNewProgressBarStyleHook.Destroy;
  206. begin
  207. FreeAndNil(FMarqueeTimer);
  208. inherited;
  209. end;
  210. function TNewProgressBarStyleHook.GetBarRect: TRect;
  211. begin
  212. Result := TRect.Create(0, 0, Control.Width, Control.Height);
  213. InflateRect(Result, -BorderWidth, -BorderWidth);
  214. end;
  215. procedure TNewProgressBarStyleHook.WMNCCalcSize(var Message: TWMNCCalcSize);
  216. begin
  217. Message.Result := 0;
  218. Handled := True;
  219. end;
  220. procedure TNewProgressBarStyleHook.PaintFrame(Canvas: TCanvas);
  221. var
  222. R: TRect;
  223. Details: TThemedElementDetails;
  224. LStyle: TCustomStyleServices;
  225. begin
  226. LStyle := StyleServices;
  227. if not LStyle.Available then
  228. Exit;
  229. R := BarRect;
  230. if Orientation = pbHorizontal then
  231. Details := LStyle.GetElementDetails(tpBar)
  232. else
  233. Details := LStyle.GetElementDetails(tpBarVert);
  234. LStyle.DrawElement(Canvas.Handle, Details, R);
  235. end;
  236. procedure TNewProgressBarStyleHook.MarqueeAction(Sender: TObject);
  237. begin
  238. if StyleServices.Available and Control.Visible and (Control is TNewProgressBar) and
  239. (TNewProgressBar(Control).Style = npbstMarquee) and not (csDesigning in Control.ComponentState) then
  240. Invalidate
  241. else
  242. FMarqueeTimer.Enabled := False;
  243. end;
  244. {$IF CompilerVersion < 36.0}
  245. { From Delphi 12.3's GraphUtil - including the function name typo }
  246. procedure SetPreMutipliedColor(ABitMap: TBitmap; Color: TColor);
  247. var
  248. X, Y: Integer;
  249. R, G, B: Byte;
  250. LRGBQuad: PRGBQuad;
  251. begin
  252. if ABitMap.PixelFormat <> pf32bit then
  253. Exit;
  254. Color := ColorToRGB(Color);
  255. R := GetRValue(Color);
  256. G := GetGValue(Color);
  257. B := GetBValue(Color);
  258. ABitmap.AlphaFormat := afIgnored;
  259. for Y := 0 to ABitMap.Height - 1 do
  260. begin
  261. LRGBQuad := ABitMap.ScanLine[Y];
  262. for X := 0 to ABitMap.Width - 1 do
  263. begin
  264. LRGBQuad.rgbRed := R;
  265. LRGBQuad.rgbGreen := G;
  266. LRGBQuad.rgbBlue := B;
  267. Inc(LRGBQuad);
  268. end;
  269. end;
  270. ABitmap.AlphaFormat := afPremultiplied;
  271. end;
  272. {$ENDIF}
  273. {$IF CompilerVersion < 35.0}
  274. { From Delphi 12.3's GraphUtil }
  275. type
  276. PRGBAArray = ^TRGBAArray;
  277. TRGBAArray = array[0..0] of TRGBQuad;
  278. procedure InitAlpha(ABitmap: TBitmap; AAlpha: Byte);
  279. var
  280. I: Integer;
  281. LLastLine: PRGBAArray;
  282. begin
  283. LLastLine := ABitmap.ScanLine[ABitmap.Height - 1];
  284. {$IFOPT R+} {$DEFINE RANGECHECKS_ON} {$R-} {$ENDIF}
  285. for I := 0 to ABitmap.Width * ABitmap.Height - 1 do
  286. LLastLine[I].rgbReserved := AAlpha;
  287. {$IFDEF RANGECHECKS_ON} {$R+} {$UNDEF RANGECHECKS_ON} {$ENDIF}
  288. end;
  289. {$ENDIF}
  290. procedure TNewProgressBarStyleHook.PaintBar(Canvas: TCanvas);
  291. const
  292. cStateColorAlpha = 130;
  293. cStateErrorColor = clRed;
  294. cStatePausedColor = clYellow;
  295. cMarqueeSize = 125;
  296. cMarqueeSteps = 5;//cMarqueeSize div 3;
  297. var
  298. FillR, R: TRect;
  299. W, Pos: Integer;
  300. Details: TThemedElementDetails;
  301. LStyle: TCustomStyleServices;
  302. LIsMarquee: Boolean;
  303. LBuffer: TBitmap;
  304. begin
  305. LStyle := StyleServices;
  306. if not LStyle.Available then
  307. Exit;
  308. R := BarRect;
  309. InflateRect(R, -1, -1);
  310. if Orientation = pbHorizontal then
  311. W := R.Width
  312. else
  313. W := R.Height;
  314. LIsMarquee := (Control is TNewProgressBar) and
  315. (TNewProgressBar(Control).Style = npbstMarquee) and not (csDesigning in Control.ComponentState);
  316. if LIsMarquee then
  317. Pos := Control.ScaleValue(cMarqueeSize)
  318. else
  319. Pos := Integer(Round(W * GetPercent));
  320. FillR := R;
  321. if Orientation = pbHorizontal then
  322. begin
  323. FillR.Right := FillR.Left + Pos;
  324. Details := LStyle.GetElementDetails(tpChunk);
  325. end
  326. else
  327. begin
  328. FillR.Top := FillR.Bottom - Pos;
  329. Details := LStyle.GetElementDetails(tpChunkVert);
  330. end;
  331. if LIsMarquee then
  332. begin
  333. FillR.SetLocation(FMarqueeStep, FillR.Top);
  334. Inc(FMarqueeStep, cMarqueeSteps);
  335. if FMarqueeStep >= Control.Width then
  336. FMarqueeStep := -cMarqueeSize;
  337. end;
  338. LStyle.DrawElement(Canvas.Handle, Details, FillR);
  339. if not FillR.IsEmpty and not LIsMarquee and (Control is TNewProgressBar) and (TNewProgressBar(Control).State <> npbsNormal) then
  340. begin
  341. LBuffer := TBitmap.Create;
  342. try
  343. LBuffer.PixelFormat := pf32bit;
  344. LBuffer.SetSize(FillR.Width, FillR.Height);
  345. InitAlpha(LBuffer, 0);
  346. LStyle.DrawElement(LBuffer.Canvas.Handle, Details, TRect.Create(0, 0, LBuffer.Width, LBuffer.Height));
  347. case TNewProgressBar(Control).State of
  348. npbsError:
  349. SetPreMutipliedColor(LBuffer, cStateErrorColor);
  350. npbsPaused:
  351. SetPreMutipliedColor(LBuffer, cStatePausedColor);
  352. end;
  353. Canvas.Draw(FillR.Left, FillR.Top, LBuffer, cStateColorAlpha);
  354. finally
  355. LBuffer.Free;
  356. end;
  357. end
  358. end;
  359. procedure TNewProgressBarStyleHook.Paint(Canvas: TCanvas);
  360. var
  361. Details: TThemedElementDetails;
  362. LStyle: TCustomStyleServices;
  363. begin
  364. LStyle := StyleServices;
  365. if LStyle.Available then
  366. begin
  367. Details.Element := teProgress;
  368. if LStyle.HasTransparentParts(Details) then
  369. LStyle.DrawParentBackground(Handle, Canvas.Handle, Details, False);
  370. end;
  371. PaintFrame(Canvas);
  372. PaintBar(Canvas);
  373. end;
  374. function TNewProgressBarStyleHook.GetMax: Integer;
  375. begin
  376. Result := Integer(SendMessage(Handle, PBM_GetRange, 0, 0));
  377. end;
  378. function TNewProgressBarStyleHook.GetMin: Integer;
  379. begin
  380. Result := Integer(SendMessage(Handle, PBM_GetRange, 1, 0));
  381. end;
  382. function TNewProgressBarStyleHook.GetOrientation: TProgressBarOrientation;
  383. begin
  384. Result := pbHorizontal;
  385. if (Handle <> 0) and (GetWindowLong(Handle, GWL_STYLE) and PBS_VERTICAL = PBS_VERTICAL) then
  386. Result := pbVertical;
  387. end;
  388. function TNewProgressBarStyleHook.GetPosition: Integer;
  389. begin
  390. Result := Integer(SendMessage(Handle, PBM_GETPOS, 0, 0));
  391. end;
  392. function TNewProgressBarStyleHook.GetBorderWidth: Integer;
  393. begin
  394. Result := 0;
  395. end;
  396. {$ENDIF}
  397. end.