HeaderPane
Main
Hide

SftTree/DLL 8.0 - Tree Control

Share Link
Print

Per-Monitor DPI and Scaling

SftTree/DLL 8.0 is fully Per-Monitor v2 DPI-aware. A tree 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; the caller owns the images and fonts it provides. Two opt-in flags let the caller hand those over to the control as well.

Host setup

The host application must declare Per-Monitor v2 DPI awareness. This is the single most common reason SftTree/DLL 8.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, SftTree does not observe DPI changes, SFTTREEN_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 tree 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 tree control scales these metrics itself on every DPI change without any caller involvement:

  • row height and item height,
  • grid-line thickness,
  • scroll bar and scroll arrow metrics,
  • drag threshold,
  • 3D frame widths,
  • column drop-down / filter button width,
  • the resize handle bitmap (between panes of a split tree),
  • the built-in expand / collapse glyphs (the control-owned plus/minus and triangle shapes used when the application does not supply custom tree button bitmaps),
  • the splitter bar width in a split tree control.

What the caller controls

Two independent opt-in flags let the caller choose whether caller-supplied pixel metrics and caller-supplied images also scale with DPI. Both default to back-compatible behavior (no automatic scaling) so existing applications keep working unchanged.

FlagCoversASIS (default)STRETCH
SetImageScalingEvery image the control draws: caller-supplied SFT_PICTURE cell / label / item / row-header / column-header / column-footer pictures; plus / minus bitmaps (SetPlusMinus); user-supplied tree button bitmaps (SetButtons); control-owned glyphs and the check-mark PNGs used in column-filter drop-downs.Images are drawn at their native pixel size. Bitmaps supplied at 96 DPI look physically smaller on a high-DPI monitor.Images are scaled by currentDPI / 96. Bitmaps use HALFTONE stretch; GDI+ images use InterpolationModeHighQualityBicubic.
SetPixelScalingCaller-supplied pixel dimensions: column widths (SFTTREE_COLUMN_EX.width / .minWidth), indentation (SetIndentation), row header width (SetRowHeaderWidth), horizontal extent and offset, item min / max heights, splitter offset (SetSplitterOffset, SetSplitterOffsetMin).Values are used verbatim in physical screen pixels. A column width of 100 is 100 pixels on any monitor.Values are interpreted as 96-DPI reference pixels. A column width of 100 is 100 pixels at 100%, 150 pixels at 150%, 200 pixels at 200%. Storage and getters always return caller-reference units so serialized configurations stay portable.

Decision guide

GoalSetting
"My existing application already ships multiple image sizes or only targets 96 DPI"Leave SetImageScaling ASIS (default). Ship SFT_PICTURE images sized for the target DPI.
"I want crisp images on high-DPI monitors without code changes"Call SetImageScaling with SFTTREE_IMAGESCALING_STRETCH once at control creation.
"Column widths should stay physically the same size as the user moves between monitors"Call SetPixelScaling with SFTTREE_PIXELSCALING_STRETCH once at control creation.
"My serialized / saved column widths must stay portable across DPI"SetPixelScaling STRETCH. Storage stays in 96-DPI reference pixels regardless of monitor.
"I have owner-draw code that caches pixel metrics"Stop caching. Read the dpi field on every SFTTREE_OWNERDRAW callback and re-compute pixel sizes each paint.

Caller responsibilities on DPI change

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

  • re-send WM_SETFONT with a font sized for the new DPI (SftTree does not own the application's font),
  • if SetImageScaling is ASIS and the caller wants crisp images, re-register SFT_PICTURE cell / label / item / row-header / column-header / column-footer images at the new physical size,
  • if SetImageScaling is STRETCH, no action needed - the control scales existing images automatically,
  • if SetPixelScaling is ASIS, re-apply column widths / indentation / etc. scaled for the new DPI,
  • if SetPixelScaling is STRETCH, no action needed - the control scales stored values automatically.

Owner-draw

The SFTTREE_OWNERDRAW structure carries the control's current effective DPI in its dpi field. Owner-draw code should read this value on every paint and must not cache pixel metrics across callbacks. GetDPI returns the same value outside a paint callback.

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 - SftTree still renders correctly but does not fire SFTTREEN_DPI_CHANGED.