Help Center

PsProgressBar

An owner-drawn progress bar for FreeBASIC / Win32, built on the AfxNova framework.

A rounded track with a fill that grows along it. The fill can be a flat colour or a two-stop linear gradient, and it can be continuous or broken into blocks — those two settings are independent, so all four combinations are available. When there is nothing to measure, switch it to indeterminate mode and a chunk floats back and forth inside the track (or slides off one end and in at the other, if you prefer the classic Windows marquee).

It covers what a standard Win32 progress bar does — range, position, step, marquee, and the normal / paused / error states of PBM_SETSTATE — and adds gradients, blocks, an optional percentage or caption drawn across the bar in two colours split at the fill boundary, and a vertical orientation.

It is display-only. It takes no focus, no mouse capture and no keyboard, and it never reacts to a click. Every message it receives is offered to your message callback, so anything interactive — click-to-cancel being the obvious case — is yours to add.

Repository: <https://github.com/PaulSquires/PsProgressBar>


What it looks like

PsProgressBar


Requirements

Copy these files into your project:

FilePurpose
PsProgressBar.biPublic surface, state type, layout
PsProgressBar.incImplementation
PsBufferPaint.biFlicker-free drawing surface (header)
PsBufferPaint.incFlicker-free drawing surface (implementation)
PsTipHost.bi / PsTipHost.incThe tooltip backend switch — see Tooltips: two backends
PsTooltip.bi / PsTooltip.incThe owner-drawn tooltip. Required even if you never switch to it: PsTipHost.inc includes it.

PsProgressBar.bi includes PsBufferPaint.bi and PsTipHost.bi itself, and PsProgressBar.inc includes PsTipHost.inc (which in turn includes PsTooltip.inc), so the extra files cost you no include lines — they only have to be present.

Include order

Headers first, implementations after your globals — the same order main.bas uses:

#include once "windows.bi"
#include once "AfxNova\CWindow.inc"
#include once "AfxNova\AfxStr.inc"
#include once "AfxNova\AfxGdiplus.inc"

using AfxNova

' ... your fonts, theme and other globals here ...

#include once "PsBufferPaint.inc"
#include once "PsProgressBar.inc"

Your sources reference AfxNova as AfxNova\..., i.e. relative to the workspace root, so the compiler needs that root on its include path:

fbc64.exe -i "C:\dev" -w all main.bas main.rc

GDI+ must be running

All geometry is drawn through GDI+, and the gradient fills are GDI+ linear-gradient brushes. Initialise it before the first repaint and shut it down only after every window is destroyed:

CoInitialize(null)
dim as ULONG_PTR gdipToken = AfxGdipInit()

function = frmMain_Show( 0 )

AfxGdipShutdown( gdipToken )   ' before CoUninitialize -- GDI+ leans on COM
CoUninitialize

Without this the control draws nothing at all.

Do not name anything ok

GDI+ defines Ok = 0 as a Status enum value that lands at global scope, and FreeBASIC identifiers are case-insensitive. Any variable or parameter of yours named ok, Ok or OK becomes a duplicate definition the moment you include PsBufferPaint.bi. Dropping using AfxNova does not help. Use bOK.

There is no pump obligation

PsProgressBar has no PsProgressBar_FilterMessage, and needs no call in your message loop. If you are coming from a neighbouring control you will look for one — there isn't one. The marquee animation runs on a WM_TIMER, and neither tooltip backend needs your loop: the system tip subclasses the control itself (TTF_SUBCLASS) and PsTooltip has no FilterMessage at all. Switching backends therefore adds no pump obligation either.

Nor does it need IsDialogMessage: it takes no focus and is not a tab stop.

do while GetMessage(@uMsg, null, 0, 0)
    if uMsg.message = WM_QUIT then exit do
    TranslateMessage @uMsg
    DispatchMessage @uMsg
loop

Quick start

' Create it, place it, show it. Orientation is chosen here and cannot change later.
dim as HWND hBar = PsProgressBar_Create( hWndParent, IDC_MYBAR, PRG_HORIZONTAL )

PsProgressBar_SetRange( hBar, 0, 100 )
PsProgressBar_SetPos( hBar, 45 )

' A vertical gradient across a horizontal bar gives a glossy top-to-bottom sheen.
PsProgressBar_SetFillStyle( hBar, PRG_FILL_GRADIENT )

' Optional: write the percentage across the bar. The font is yours -- the control
' borrows the handle and never destroys it.
PsProgressBar_SetFont( hBar, ghFont(GUIFONTBOLD_9) )
PsProgressBar_SetTextMode( hBar, PRG_TEXT_PERCENT )

PsProgressBar_SetMessageCallback( hBar, @MyBar_MessageCallback )

SetWindowPos( hBar, 0, 20, 20, 320, 18, SWP_NOZORDER )
ShowWindow( hBar, SW_SHOW )

The message callback — the only place this control can be made interactive:

function MyBar_MessageCallback( byval m as PSPROGRESSBAR_MESSAGEINFO ptr ) as boolean
    select case m->uMsg
    case WM_LBUTTONUP
        ' The control will never act on this itself.
        MyCancelTheJob()
    end select
    return false        ' let the control's default handling proceed
end function

To drive it while work happens, call PsProgressBar_SetPos (or PsProgressBar_StepIt) as the work progresses. The bar moves immediately — it does not ease toward the new value.


Concepts

The handle is a real HWND

PsProgressBar_Create returns an ordinary window handle, not an opaque type, so you place and size the control with SetWindowPos / MoveWindow like any other child window. Every PsProgressBar_* function takes that handle.

The control frees itself when its window is destroyed, and destroys its own tooltip window with it. It does not own the font you give it.

Orientation is fixed at creation

Pass PRG_HORIZONTAL or PRG_VERTICAL to PsProgressBar_Create. It cannot change afterwards; PsProgressBar_GetOrientation reads it back. A vertical bar fills bottom-up by default.

PsProgressBar_SetReverse mirrors the growth direction on whichever axis is in use: a horizontal bar grows right-to-left, a vertical bar grows top-down.

Three rects, all derived

You never set the control's geometry directly. It derives three rects from the client area and your settings, and recomputes them lazily — a setter marks the layout stale, and the next repaint (or the next rect query) rebuilds it.

rcTrack   spans the client along the growth axis;
          nThickness deep across it, centred on that axis.
          nThickness = 0 (the default) means "fill the client".

rcFill    rcTrack deflated by nBarInset on all four sides.
          This is where a 100% bar reaches.

rcBar     the drawn portion.
            determinate    anchored to rcFill's start edge,
                           length = (pos - min) / (max - min) * fill length
            indeterminate  a chunk of nChunkPercent of the fill, floating
                           at whatever offset the marquee gives for the
                           current tick. NOT anchored to either end.

Read them with PsProgressBar_GetTrackRect, GetFillRect and GetBarRect. All three are in client coordinates.

If the client is smaller than the settings ask for, the rects are computed honestly and the overflow is clipped — the track keeps its proper shape rather than being squeezed into a different one.

Everything is silent

There is no change callback of any kind. A progress bar's position is only ever set programmatically, so there is nothing a user did that you would need telling about. Set the position and the bar repaints; that is the whole contract.

The position jumps, it does not animate

PsProgressBar_SetPos moves the bar immediately. Windows' own common control eases toward the new value; this one does not, which means the control runs a timer only in indeterminate mode. A determinate bar costs nothing between repaints.

If you want smooth motion, drive the position from your own timer.

Text is drawn twice

When a text mode is set, the string is drawn centred on rcTrack in two clipped passes over the same rectangle: the part lying over the filled bar takes TextColorOnBar, the part over the empty track takes TextColor. Because both passes use the identical rect, the glyphs land in the same place and only the colour changes at the fill boundary — including part-way through a single glyph.

A disabled bar draws the text once, in TextColorDisabled.


Behaviour and limits

  • Orientation cannot change after creation. Create a second control if you need both.
  • Vertical bars draw their text horizontally. The string is not rotated, so a text mode on a narrow vertical bar will clip. Leave it at PRG_TEXT_NONE.
  • PsProgressBar_StepIt clamps at the maximum; it does not wrap. Windows' PBM_STEPIT wraps around to the minimum, which makes a finished job look restarted.
  • An empty or inverted range draws an empty bar rather than dividing by zero. PsProgressBar_SetRange normalises an inverted pair and pulls the position into the new range.
  • In indeterminate mode the position means nothing. It is still stored and still readable, but nothing draws from it until you switch back.
  • The marquee animates only while the control is enabled and not hidden. Disabling it or hiding it stops the timer; re-enabling or re-showing restarts it.
  • Blocks inherit the track's curvature. At the default stadium setting each block is drawn as a small capsule rather than a rectangle, because the curvature is clamped to half the block's own shorter side. Call PsProgressBar_SetCurvature( hBar, 0 ) for square blocks, or a small value such as 3 for softened ones.
  • PRG_MARQUEE_WRAP clips its chunk at the track edges. That is what makes it slide in and out rather than snap, so the visible chunk is narrower during the transition. PRG_MARQUEE_BOUNCE never clips — the chunk keeps one width throughout.
  • At curvature 0 with no track border, the fill leaves a one-pixel seam down its right edge and along its bottom. This is the rounded-rectangle fill convention the drawing layer uses throughout, matched to what GDI's RoundRect covers. Any curvature above 0 hides it behind the end caps, and a track border of 1 or more covers it.
  • PsProgressBar_SetFont borrows the handle. The control never creates or destroys a font; yours must outlive the control.
  • There is no PsProgressBar_HitTest. The whole client is the same inert surface.
  • No accessibility support. The control does not expose itself to screen readers.

API reference

Creation and lifetime

FunctionBehaviour
PsProgressBar_Create( hWndParent, CtrlID, nOrientation = PRG_HORIZONTAL ) as HWNDCreates the control as a child of hWndParent at 0,0,0,0 — place it yourself. Orientation is fixed here; an unrecognised value falls back to PRG_HORIZONTAL. Returns the control's HWND.
PsProgressBar_GetOrientation( hProgressBar ) as longPRG_HORIZONTAL or PRG_VERTICAL.
PsProgressBar_Refresh( hProgressBar )Marks the layout stale and repaints with a background erase. You rarely need this — every setter does it.

Range and position

Every function in this group is silent: none of them fires a callback.

FunctionBehaviour
PsProgressBar_GetRange( hProgressBar, byref nMin, byref nMax )Reads the current range.
PsProgressBar_SetRange( hProgressBar, nMin, nMax )Sets the range. An inverted pair is normalised (swapped), and the current position is clamped into the result.
PsProgressBar_GetPos( hProgressBar ) as longThe current position.
PsProgressBar_SetPos( hProgressBar, nPos )Clamps to the range. Repaints only if the value actually changed.
PsProgressBar_GetStep( hProgressBar ) as longThe StepIt increment. Default 10.
PsProgressBar_SetStep( hProgressBar, nStep )Sets the increment. Does not repaint — nothing visible changed.
PsProgressBar_StepIt( hProgressBar ) as longAdvances by the step and returns the new position. Clamps at the maximum; does not wrap.
PsProgressBar_DeltaPos( hProgressBar, nDelta ) as longAdvances by nDelta (negative is fine) and returns the new position. Clamps.
PsProgressBar_GetPercent( hProgressBar ) as longThe position as a whole-number percentage, 0..100. Answers 0 for an empty range.

Mode and state

FunctionBehaviour
PsProgressBar_GetMode( hProgressBar ) as longPRG_MODE_DETERMINATE or PRG_MODE_INDETERMINATE.
PsProgressBar_SetMode( hProgressBar, nMode )Switches mode. Entering or leaving indeterminate resets the animation to its start, and starts or stops the timer. An unrecognised value is treated as determinate.
PsProgressBar_GetState( hProgressBar ) as longPRG_STATE_NORMAL, PRG_STATE_PAUSED or PRG_STATE_ERROR.
PsProgressBar_SetState( hProgressBar, nState )Swaps the bar's colour pair. An unrecognised value falls back to PRG_STATE_NORMAL. Repaints without re-laying out — no geometry changed.
PsProgressBar_GetEnabled( hProgressBar ) as booleanWhether the control is enabled.
PsProgressBar_SetEnabled( hProgressBar, isEnabled )Goes through EnableWindow, so the disable is enforced by the system. Switches to the disabled colours and stops the marquee.

Marquee (indeterminate mode)

Setting any of these while in determinate mode is legal and simply has no visible effect until you switch.

FunctionBehaviour
PsProgressBar_GetMarqueeStyle( hProgressBar ) as longPRG_MARQUEE_BOUNCE or PRG_MARQUEE_WRAP.
PsProgressBar_SetMarqueeStyle( hProgressBar, nStyle )Sets the motion. Resets the animation to its start. An unrecognised value is treated as PRG_MARQUEE_BOUNCE.
PsProgressBar_GetMarqueeSpeed( hProgressBar, byref nIntervalMs, byref nStepPx )The timer interval and the pixels moved per tick.
PsProgressBar_SetMarqueeSpeed( hProgressBar, nIntervalMs, nStepPx )Values of 0 or less are ignored, leaving that setting unchanged. Restarts a running timer so a new interval takes effect immediately.
PsProgressBar_GetMarqueeChunkPercent( hProgressBar ) as longThe chunk's length as a percentage of the track.
PsProgressBar_SetMarqueeChunkPercent( hProgressBar, nPercent )Clamped to 1..100.

Fill style and segmentation

These two settings are independent — every combination of them is legal.

FunctionBehaviour
PsProgressBar_GetFillStyle( hProgressBar ) as longPRG_FILL_SOLID or PRG_FILL_GRADIENT.
PsProgressBar_SetFillStyle( hProgressBar, nFillStyle )Anything other than PRG_FILL_GRADIENT is treated as solid. A solid fill uses only the start colour of the state's pair.
PsProgressBar_GetGradientMode( hProgressBar ) as longThe LinearGradientMode the ramp runs along.
PsProgressBar_SetGradientMode( hProgressBar, nMode )LinearGradientModeHorizontal / Vertical / ForwardDiagonal / BackwardDiagonal. Out-of-range values fall back to Vertical.
PsProgressBar_GetSegmented( hProgressBar ) as booleanWhether the bar is drawn as blocks.
PsProgressBar_SetSegmented( hProgressBar, isSegmented )Turns block mode on or off. With a gradient fill, each block takes its slice of one ramp spanning the whole bar, so the blocks read as a single perforated run.
PsProgressBar_GetBlockMetrics( hProgressBar, byref nBlockLen, byref nBlockGap )Block length and the gap between blocks, in pixels.
PsProgressBar_SetBlockMetrics( hProgressBar, nBlockLen, nBlockGap )A length of 0 or less is ignored; a negative gap is ignored. Values are raw pixels — scale them yourself.
PsProgressBar_GetBlockCount( hProgressBar ) as longHow many blocks are currently drawn. Answers 0 when not segmented. Note this counts the blocks in the bar, not the slots in the track — in indeterminate mode the blocks tile the floating chunk.
PsProgressBar_GetBlockRect( hProgressBar, idx, byref rc ) as booleanThe idx-th drawn block, in client coordinates. Returns FALSE for a negative index, an index past the last drawn block, or a control that is not segmented.

Geometry and chrome

Setters here take raw pixels. Only the creation-time defaults are DPI-scaled for you; scale your own values with CWindow::ScaleX / ScaleY.

FunctionBehaviour
PsProgressBar_GetThickness( hProgressBar ) as longThe track's depth across the growth axis.
PsProgressBar_SetThickness( hProgressBar, nThickness )0 (the default) means fill the client. A value larger than the client is treated as 0. Negatives clamp to 0.
PsProgressBar_GetBarInset( hProgressBar ) as longThe gap between the track edge and the bar.
PsProgressBar_SetBarInset( hProgressBar, nBarInset )Deflates the fill area on all four sides. Negatives clamp to 0. An inset larger than half the track collapses the fill to empty rather than inverting it.
PsProgressBar_GetBorderThickness( hProgressBar ) as longThe track outline's thickness. 0 (the default) means no outline.
PsProgressBar_SetBorderThickness( hProgressBar, nThickness )Negatives clamp to 0. Not DPI-scaled — a hairline stays a hairline.
PsProgressBar_GetCurvature( hProgressBar ) as longThe corner ellipse diameter, or PSPROGRESSBAR_CURVATURE_STADIUM (-1).
PsProgressBar_SetCurvature( hProgressBar, nCurvature )0 gives square corners. Any negative value normalises to the stadium sentinel, which makes both ends exact semicircles whatever the track's thickness. Not DPI-scaled.
PsProgressBar_GetReverse( hProgressBar ) as booleanWhether the growth direction is mirrored.
PsProgressBar_SetReverse( hProgressBar, isReverse )Horizontal bars grow right-to-left; vertical bars grow top-down.
PsProgressBar_GetTrackRect( hProgressBar, byref rc ) as booleanThe groove, in client coordinates. Runs a pending layout first.
PsProgressBar_GetFillRect( hProgressBar, byref rc ) as booleanWhere a 100% bar reaches.
PsProgressBar_GetBarRect( hProgressBar, byref rc ) as booleanThe drawn portion. In indeterminate mode this is the floating chunk.

Text

FunctionBehaviour
PsProgressBar_GetTextMode( hProgressBar ) as longOne of the PRG_TEXT_* values.
PsProgressBar_SetTextMode( hProgressBar, nTextMode )Out-of-range values fall back to PRG_TEXT_NONE.
PsProgressBar_GetFont( hProgressBar ) as HFONTThe borrowed font handle, or 0.
PsProgressBar_SetFont( hProgressBar, hFont )Borrows the handle — you keep ownership and must outlive the control. Passing 0 falls back to the device context's own font.
PsProgressBar_GetDisplayText( hProgressBar ) as DWSTRINGExactly what would be drawn right now, with the mode and any callback already applied. "" when nothing would be drawn.

Colors

FunctionBehaviour
PsProgressBar_GetColors( hProgressBar, pColors )Copies the current colours into your PSPROGRESSBAR_COLORS.
PsProgressBar_SetColors( hProgressBar, pColors )Copies yours in and repaints. Read-modify-write: get, change the fields you care about, set.
PsProgressBar_ResolveBarColors( hProgressBar, byref clr1, byref clr2 )The two bar stops the control would use right now, with the state-and-enabled precedence already applied. Useful from a paint callback that wants to match.

Tooltips

FunctionBehaviour
PsProgressBar_GetTooltipText( hProgressBar ) as DWSTRINGThe authored tip text, or "".
PsProgressBar_SetTooltipText( hProgressBar, Text )Sets static tip text. When this is non-empty the tooltip callback is not consulted.
PsProgressBar_GetTooltipHandle( hProgressBar ) as HWNDThe comctl32 tooltip window, for any TTM_* message you want to send it yourself. The control owns it and destroys it. Returns 0 while this control is on the PsTooltip backend — the honest answer, since a TTM_* sent to a PsTooltip window is silently ignored.
PsProgressBar_GetPsTooltipHandle( hProgressBar ) as HWNDThe PsTooltip window, or 0 while on the system backend. The door to PsTooltip_SetColors / SetFonts / SetStyle / SetMaxWidth / SetTitle / SetGlyph — none of which is mirrored here.
PsProgressBar_SetTooltipMode( hProgressBar, nMode ) as booleanPSTIP_MODE_SYSTEM (default) or PSTIP_MODE_PS. Returns TRUE if the requested backend is live on return. See below.
PsProgressBar_GetTooltipMode( hProgressBar ) as longWhich backend is live.
PsProgressBar_SetHoverTime( hProgressBar, milliseconds )Initial delay (TTDT_INITIAL) — how long the cursor must rest. Honoured by both backends.
PsProgressBar_SetAutoPopTime( hProgressBar, milliseconds )How long the tip stays up (TTDT_AUTOPOP).
PsProgressBar_SetReshowTime( hProgressBar, milliseconds )The shorter delay after a tip was recently dismissed (TTDT_RESHOW).
Tooltips: two backends

The control ships on the system (comctl32) tooltip. PSTIP_MODE_PS switches this instance to PsTooltip: owner-drawn, themeable, word-wrapping without a hand-sent TTM_SETMAXTIPWIDTH, and — the one that matters structurally — not a subclass of the control it serves. Either way the control adds no pump obligation: PsTooltip has no FilterMessage.

The default is deliberate, not caution. PsTooltip's colour defaults are dark, so a control that switched itself would put a dark tip on a light form. Theme every tip in the process with PsTooltip_SetDefaultColors and friends, then opt in per instance.

The mode changes how a tip is drawn, never what it says. Both backends resolve text through the same rule — this control's own text first, then PRG_TooltipCallbackFunc, then nothing.

The three delay setters are the control's only ones; before them a tip ran at whatever the backend derived and there was no way to say otherwise. A delay you set is stored as well as pushed, so it survives a switch in either direction. A delay you never set keeps the backend's own derivation from the system double-click time, which is what makes a tip appear on the same beat as every other tip on the machine.

PsTooltip_SetDefaultColors( @myTipColors )     ' once, at startup, for every tip in the process
PsTooltip_SetDefaultFonts( ghFontUI )

PsProgressBar_SetTooltipMode( hBar, PSTIP_MODE_PS )
PsProgressBar_SetHoverTime( hBar, 400 )
dim as HWND hTip = PsProgressBar_GetPsTooltipHandle( hBar )
if hTip then PsTooltip_SetTitle( hTip, "Copying files" )   ' not reachable on the system backend

Callback registration

FunctionBehaviour
PsProgressBar_SetPaintCallback( hProgressBar, usersub )Replaces the built-in painter entirely. Pass 0 to restore it.
PsProgressBar_SetMessageCallback( hProgressBar, userfunc )Observe or suppress messages.
PsProgressBar_SetTooltipCallback( hProgressBar, userfunc )Supply tip text on demand.
PsProgressBar_SetTextCallback( hProgressBar, userfunc )Supply the bar's caption. Only consulted in PRG_TEXT_CALLBACK mode.

Render probes

These render the control offscreen and measure the result. They exist so that a host supplying its own paint callback can assert it has not accidentally erased the control — the standing hazard described under Callbacks.

FunctionBehaviour
PsProgressBar_CountRenderedTones( hProgressBar, nPart ) as longDistinct colours inside nPart's rect. A part wiped by a filling call scores 1, so the signal to look for is the count leaving 1 — not the count being large. Returns 0 if the control has no geometry yet.
PsProgressBar_HashRenderedPart( hProgressBar, nPart ) as ulongAn FNV-1a hash of the same pixels. Compare two hashes of the same part in two different states: equal means the state change never reached the surface. It can only prove difference, never correctness — and it proves difference for any reason, so isolate the change you are testing.

Pure functions

These take no window and touch no state. They are the arithmetic the control itself uses, exposed so you can predict or reproduce it.

FunctionBehaviour
PsProgressBar_ComputeBarLength( nPos, nMin, nMax, nTrackLen ) as longBar length for a determinate position. Clamps nPos into the range; answers 0 for an empty or inverted range, or a track of zero length. Safe against overflow on very large ranges.
PsProgressBar_ComputeMarqueeOffset( nTrackLen, nChunkLen, nStep, nTick, nStyle ) as longThe chunk's leading edge at tick nTick, as an offset from the start of the track. The two styles have different ranges — see below. Negative ticks are handled. A step of 0 or less is treated as 1.
PsProgressBar_ComputeBlockCount( nLength, nBlockLen, nBlockGap ) as longHow many whole blocks fit in nLength. The last block needs no trailing gap. Answers 0 for a zero block length or a length shorter than one block.

PsProgressBar_ComputeMarqueeOffset's two ranges:

StyleRangeShape
PRG_MARQUEE_BOUNCE0 .. nTrackLen - nChunkLenTriangle wave. The chunk never leaves the track, so nothing is clipped. The period is twice the span — out and back.
PRG_MARQUEE_WRAP-nChunkLen .. nTrackLenSawtooth. It goes negative on purpose, which is what makes the chunk slide in from one edge and out of the other. The caller must clip the resulting rect to the track; the control does.

Colors

PSPROGRESSBAR_COLORS is a flat struct of COLORREF fields, all with defaults. Read-modify-write:

dim as PSPROGRESSBAR_COLORS clrs
PsProgressBar_GetColors( hBar, @clrs )
clrs.BarColor    = BGR( 62,140, 90)
clrs.BarColorEnd = BGR( 40,100, 62)
PsProgressBar_SetColors( hBar, @clrs )
FieldPaintsWhen
BackColorThe control's own background, behind the trackAlways
TrackColorThe grooveAlways
TrackBorderColorThe groove's outlineOnly when SetBorderThickness is 1 or more
BarColorThe bar's first gradient stopEnabled, PRG_STATE_NORMAL
BarColorEndThe bar's second stopAs above, and only with PRG_FILL_GRADIENT
BarColorPausedFirst stopEnabled, PRG_STATE_PAUSED
BarColorPausedEndSecond stopAs above, gradient only
BarColorErrorFirst stopEnabled, PRG_STATE_ERROR
BarColorErrorEndSecond stopAs above, gradient only
BarColorDisabledFirst stopDisabled, whatever the state
BarColorDisabledEndSecond stopAs above, gradient only
TextColorThe caption where it lies over the empty trackEnabled, and a text mode is set
TextColorOnBarThe caption where it lies over the filled barEnabled, and a text mode is set
TextColorDisabledThe whole caption, in one colourDisabled

Bar colour precedence: disabled > PRG_STATE_ERROR > PRG_STATE_PAUSED > PRG_STATE_NORMAL. Disabled wins outright — a paused-and-disabled bar draws grey, not yellow.

With PRG_FILL_SOLID only the first stop of the chosen pair is used; the *End field is ignored. That means one set of colours serves both fill styles and switching between them needs no changes.


Callbacks

Paint

type PRG_PaintCallbackSub as sub( byval p as PSPROGRESSBAR_PAINTINFO ptr )

Replaces the built-in painter entirely. Draw through p->b — the control's double buffer for this repaint — and never touch the screen DC. The client has already been filled with BackColor before you are called, so a callback that only adds something on top does not have to repaint the background.

A callback that fills a rectangle covering the whole control erases everything already drawn. PsBufferPaint's PaintBorderRect and PaintRoundBorderRect fill before they stroke, so using either as a frame over your own drawing wipes it out. Use PaintRoundOutline, which strokes only. PsProgressBar_CountRenderedTones exists so you can assert you have not done this.

PSPROGRESSBAR_PAINTINFO

FieldMeaning
hProgressBarThe control, so the callback can query it
bThe PsBufferPaint for this repaint — a pointer, not a copy
rcClientThe whole client area
rcTrackThe groove
rcFillWhere a 100% bar reaches
rcBarThe drawn portion. In indeterminate mode, the floating chunk — not anchored to either end
nPosCurrent position
nMinRange minimum
nMaxRange maximum
nPercent0..100, already clamped; 0 when the range is empty
isIndeterminateTRUE in indeterminate mode, where nPos means nothing
isEnabledDraw the disabled look when FALSE
isReverseThe bar grows from the right, or from the top
isSegmentedBlock mode is on
nOrientationPRG_HORIZONTAL or PRG_VERTICAL
nStatePRG_STATE_*
nFillStylePRG_FILL_*
nCurvatureAlready resolved — the stadium sentinel never reaches a callback
nBlockLengthBlock length as drawn
nBlockGapBlock gap as drawn
wszTextThe caption, mode and callback already applied. "" means draw nothing

Message

type PRG_MessageCallbackFunc as function( byval m as PSPROGRESSBAR_MESSAGEINFO ptr ) as boolean

Return TRUE if you handled the message and want the control's default handling suppressed, FALSE to let it proceed.

Every message is offered and every answer is honoured, with one exception: WM_DESTROY and WM_NCDESTROY are never offered at all. They free the control's state and its tooltip window, and a callback suppressing either would leak both.

There are no other exemptions. Controls that take mouse capture have to ignore your answer for the button-up messages, or a callback could strand the capture; this control takes no capture, has no press gesture and no focus, so everything it offers you is genuinely suppressible. This is also the only place the control can be made interactive, since it never acts on a click itself.

PSPROGRESSBAR_MESSAGEINFO

FieldMeaning
hProgressBarThe control
uMsgThe message
wParamIts wParam
lParamIts lParam

Tooltip

type PRG_TooltipCallbackFunc as function( byval hProgressBar as HWND ) as DWSTRING

Called only when a tip is about to show, and only when the control has no tooltip text of its own. Return "" for no tooltip. There is no fallback to the displayed percentage — a bar with neither authored text nor a callback answer shows nothing.

Text

type PRG_TextCallbackFunc as function( byval hProgressBar as HWND, _
                                       byval nPos as long, _
                                       byval nMin as long, _
                                       byval nMax as long ) as DWSTRING

Consulted only in PRG_TEXT_CALLBACK mode, and called on every repaint — keep it cheap, and do not create windows in it. Returning "" draws nothing. If the mode is set but no callback is installed, nothing is drawn.


Constants

Orientation — fixed at creation

ValueMeaning
PRG_HORIZONTALGrows left-to-right (right-to-left when reversed)
PRG_VERTICALGrows bottom-up (top-down when reversed)

Mode

ValueMeaning
PRG_MODE_DETERMINATEThe position drives the bar
PRG_MODE_INDETERMINATEA chunk floats; the position is ignored

Marquee style

ValueMeaning
PRG_MARQUEE_BOUNCEPing-pongs off both ends. The default
PRG_MARQUEE_WRAPSlides off one end and in at the other

Fill style

ValueMeaning
PRG_FILL_SOLIDFlat colour, using the state's first stop only
PRG_FILL_GRADIENTTwo-stop linear gradient

State

ValueMeaning
PRG_STATE_NORMALThe normal colour pair
PRG_STATE_PAUSEDThe paused pair
PRG_STATE_ERRORThe error pair

Text mode

ValueDraws
PRG_TEXT_NONENothing. The default
PRG_TEXT_PERCENT"45%" — the position as a percentage of the range
PRG_TEXT_VALUE"45" — the raw position
PRG_TEXT_CALLBACKWhatever PRG_TextCallbackFunc returns

Probe parts

ValueRect measured
PRG_PART_CONTROLThe whole client
PRG_PART_TRACKThe groove
PRG_PART_BARThe drawn bar
PRG_PART_TEXTThe middle third of the track, where a centred caption lands

Defaults

ConstantValueDPI-scaled at creation
PSPROGRESSBAR_DEFAULT_THICKNESS0 (fill the client)yes
PSPROGRESSBAR_DEFAULT_BARINSET0yes
PSPROGRESSBAR_DEFAULT_BORDERTHICK0 (no outline)no
PSPROGRESSBAR_DEFAULT_CURVATURE-1 (stadium)no
PSPROGRESSBAR_DEFAULT_BLOCKLEN10yes
PSPROGRESSBAR_DEFAULT_BLOCKGAP3yes
PSPROGRESSBAR_DEFAULT_MARQUEE_MS30n/a
PSPROGRESSBAR_DEFAULT_MARQUEE_STEP2yes
PSPROGRESSBAR_DEFAULT_CHUNK_PCT25n/a
PSPROGRESSBAR_CURVATURE_STADIUM-1n/a

The range defaults to 0..100, the position to 0, and the step to 10.

Licence

Mozilla Public License 2.0.

MPL-2.0 is file-level copyleft, chosen deliberately for a drop-in control:

  • You may use this in closed-source software, commercial or otherwise. §3.2 permits static linking with no additional conditions.
  • If you modify these files, publish those files' changes. The obligation is per-file — your own sources are unaffected however tightly they are combined with these.
  • The Exhibit B "Incompatible With Secondary Licenses" notice is not applied, which keeps this GPL-compatible.