HeaderPane
Main
Hide

SftButton/DLL 3.0 - Button Control

Share Link
Print

Per-Monitor DPI and Scaling

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.

Host setup

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:

Application manifest (recommended)

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.

Runtime API

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.

Verifying the declaration worked

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.

What scales automatically

When the host is Per-Monitor v2 aware, the button control scales these metrics itself on every DPI change without any caller involvement:

  • button border widths,
  • focus-ring inset,
  • dropdown-arrow width and glyph size,
  • internal padding between the border, images and text,
  • the bounce-animation pixel offset,
  • the control-owned dropdown arrow glyph.

What the caller controls

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.

FlagCoversSTRETCH (default)ASIS
SetImageScalingEvery 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.
SetPixelScalingCaller-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.

Decision guide

GoalSetting
"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.

Caller responsibilities on DPI change

When the control's monitor DPI changes, SftButton raises SFTBUTTONN_DPI_CHANGED to the parent window. The application should:

  • re-send WM_SETFONT with a font sized for the new DPI (SftButton does not own the application's font),
  • if SetImageScaling is STRETCH (default), no action needed - the control scales existing images automatically,
  • if SetImageScaling is ASIS and the caller wants crisp images, re-register per-state SFT_PICTURE images at the new physical size,
  • if SetPixelScaling is STRETCH (default), no action needed - the control scales stored values automatically,
  • if SetPixelScaling is ASIS, re-apply caller-supplied pixel dimensions scaled for the new DPI.

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.


Last Updated 04/26/2026 - (email)
© 2026 Softel vdm, Inc.