SftTree/DLL 7.5 - Tree Control
SftBox/OCX 5.0 - Combo Box Control
SftButton/OCX 3.0 - Button Control
SftMask/OCX 7.0 - Masked Edit Control
SftTabs/OCX 6.5 - Tab Control (VB6 only)
SftTree/OCX 7.5 - Tree Control
SftPrintPreview/DLL 2.0 - Print Preview Control (discontinued)
SftTree/DLL 7.5 - Tree Control
SftBox/OCX 5.0 - Combo Box Control
SftButton/OCX 3.0 - Button Control
SftDirectory 3.5 - File/Folder Control (discontinued)
SftMask/OCX 7.0 - Masked Edit Control
SftOptions 1.0 - Registry/INI Control (discontinued)
SftPrintPreview/OCX 1.0 - Print Preview Control (discontinued)
SftTabs/OCX 6.5 - Tab Control (VB6 only)
SftTree/OCX 7.5 - Tree Control
SftTabs/NET 6.0 - Tab Control (discontinued)
SftTree/NET 2.0 - Tree Control
SftButton/DLL 3.0 is fully Per-Monitor v2 DPI-aware. A button control hosted on a Per-Monitor v2 aware top-level window will re-render automatically when its window moves to a monitor of a different DPI or when the system DPI changes. The control owns the metrics it controls and, by default, also scales caller-supplied images and pixel dimensions for the current monitor; two flags let the caller take that scaling back over if needed.
The host application must declare Per-Monitor v2 DPI awareness. This is the single most common reason SftButton/DLL 3.0 applications do not re-render correctly when moved between monitors of different DPI. Without a PMv2 declaration, Windows silently keeps the process in System-aware mode: the DPI is fixed for the process lifetime, SftButton does not observe DPI changes, SFTBUTTONN_DPI_CHANGED is never raised, and high-DPI monitors render at System-DPI sizes stretched by Windows. Visual Studio's default app.manifest does not declare PMv2 awareness - the developer must opt in explicitly.
Two approaches - pick one:
Add a <dpiAwareness> / <dpiAware> element to the application manifest. Both elements are usually included for back-compatibility with older Windows 10 builds:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>In a Visual Studio C++ project, set Project Properties -> Manifest Tool -> Input and Output -> Additional Manifest Files to the .manifest file above, or edit the auto-generated manifest directly. In a C# / .NET project, check Application -> DPI awareness and select Per Monitor V2.
Alternatively, call SetProcessDpiAwarenessContext at process startup, before any window is created:
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
Caveat: This call must run before the first window (including hidden startup dialogs or splash screens) is created. If a window has already been created, Windows rejects the call and the process remains in whichever mode the manifest specified - which, with the Visual Studio default, is System-aware.
Quick check at runtime: GetDpiForWindow on the button control returns the current monitor's DPI - 96 at 100%, 120 at 125%, 144 at 150%, 192 at 200%. If the returned value never changes as you drag the window between monitors with different scale factors, the host is not in PMv2 mode.
When the host is Per-Monitor v2 aware, the button control scales these metrics itself on every DPI change without any caller involvement:
Two independent flags let the caller choose whether caller-supplied pixel metrics and caller-supplied images also scale with DPI. Both default to STRETCH - on a Per-Monitor v2 host, a fresh button automatically scales its images and pixel metrics to the current monitor without any opt-in. Applications that ship multiple image sizes, pre-scaled pixel layouts, or otherwise want full control over the rendered size can opt out by switching either flag to ASIS.
| Flag | Covers | STRETCH (default) | ASIS |
|---|---|---|---|
| SetImageScaling | Every image the control draws: per-state foreground pictures (Picture1 / Picture1Hover / Picture1Pressed / Picture1Disabled and the matching Picture2*), per-state background pictures (PictureBG*). | Images are scaled by currentDPI / 96. Bitmaps use HALFTONE stretch; GDI+ images use InterpolationModeHighQualityBicubic. | Images are drawn at their native pixel size. Bitmaps supplied at 96 DPI look physically smaller on a high-DPI monitor. |
| SetPixelScaling | Caller-supplied pixel dimensions on the SFTBUTTON_CONTROL structure (any pixel-valued offsets, insets and forced sizes the application sets). | Values are interpreted as 96-DPI reference pixels. A value of 20 is 20 pixels at 100%, 30 pixels at 150%, 40 pixels at 200%. Storage and getters always return caller-reference units so serialized configurations stay portable. | Values are used verbatim in physical screen pixels. |
| Goal | Setting |
|---|---|
| "I want crisp images on high-DPI monitors without code changes" | Default behavior - no call needed. SetImageScaling stays at STRETCH. |
| "My existing application already ships pre-scaled images and wants the control to draw them as-is" | Call SetImageScaling with SFTBUTTON_IMAGESCALING_ASIS once at control creation. |
| "Pixel offsets stored in a SFTBUTTON_CONTROL structure should stay physically the same size as the user moves between monitors" | Default behavior - no call needed. SetPixelScaling stays at STRETCH. |
| "My serialized / saved button configurations must stay portable across DPI" | Default behavior - no call needed. Storage stays in 96-DPI reference pixels regardless of monitor. |
| "My pixel offsets are already pre-scaled for the runtime monitor and should be used verbatim" | Call SetPixelScaling with SFTBUTTON_PIXELSCALING_ASIS once at control creation. |
When the control's monitor DPI changes, SftButton raises SFTBUTTONN_DPI_CHANGED to the parent window. The application should:
Platform note: Per-Monitor v2 DPI awareness requires Windows 10 version 1703 or later. On older platforms the host process runs in System-aware or Unaware mode and DPI is effectively fixed for the process lifetime - SftButton still renders correctly but does not fire SFTBUTTONN_DPI_CHANGED.
