Migrating to Dapplo.Windows 3.0
Dapplo.Windows 3.0 fixes many interop bugs. Where an API encoded a wrong concept, it was changed instead of kept for compatibility. This page lists every breaking change with the code you need to update. The full list of changes is in the changelog.
Upgrading an application with an AI assistant? Give it the upgrade prompt: it covers the renames and, more importantly, the changes that still compile but behave differently.
Target frameworks and packages
3.0 targets net480 and net10.0-windows. The core packages no longer pull in WinForms or WPF; add
Dapplo.Windows.Forms or Dapplo.Windows.Wpf when you use those helpers.
| 2.x | 3.0 |
|---|---|
Dapplo.Windows.Dpi.Forms.DpiAwareForm, DpiUnawareForm, FormsDpiExtensions.AttachDpiHandler(Form / ContextMenuStrip) |
Dapplo.Windows.Forms, Dapplo.Windows.Forms.Dpi |
BitmapScaleHandler.AddTarget(Button / ToolStripItem, …) |
Dapplo.Windows.Forms.Dpi.BitmapScaleHandlerExtensions; core: AddTargetAction(target, key, apply, execute) |
Dapplo.Windows.Messages.WinProcListener (AddHook(HwndSourceHook)) |
Dapplo.Windows.Forms.Messages.WinProcListener (AddHook(WinProcHook), same signature) |
Dapplo.Windows.Messages.WinProcFormsExtensions.WinProcFormsMessages() |
WinProcMessages() in Dapplo.Windows.Forms.Messages |
Dapplo.Windows.Extensions.FormsExtensions |
Dapplo.Windows.Forms.FormsExtensions |
Dapplo.Windows.Messages.WinProcHandler, WinProcHandlerHook, WinProcWindowsExtensions.WinProcMessages(Window) |
Dapplo.Windows.Wpf, Dapplo.Windows.Wpf.Messages |
Dapplo.Windows.Dpi.Wpf.WindowDpiExtensions |
Dapplo.Windows.Wpf.Dpi.WindowDpiExtensions |
Dapplo.Windows.Extensions.WindowsExtensions (AsInteropWindow, GetHandle, ApplyPlacement, RetrievePlacement) |
Dapplo.Windows.Wpf.WindowExtensions |
Dapplo.Windows.Extensions.BitmapExtensions.ToBitmapSource(Bitmap / Image) |
Dapplo.Windows.Wpf.BitmapSourceExtensions.ToBitmapSource(Bitmap / Image / Icon) |
interopWindow.PrintWindow<TBitmap>() |
PrintWindow() returns Bitmap; PrintWindowAsBitmapSource() in Dapplo.Windows.Wpf |
GetIcon<BitmapSource>(), IconHelper.*<BitmapSource> |
GetIcon<Bitmap>().ToBitmapSource() |
DwmApi.ColorizationColor (WPF Color) |
DwmApi.ColorizationColor.ToMediaColor() (now a System.Drawing.Color) |
Casts between Native* structs and System.Windows.Point / Size / Rect / Int32Rect |
Dapplo.Windows.Wpf.NativeStructWpfExtensions: ToPoint, ToNativePointFloat, ToSize, ToNativeSize, ToNativeSizeFloat, ToRect, ToNativeRectFloat, ToInt32Rect, ToNativeRect |
NativeRect(Float).Transform(Matrix) |
Dapplo.Windows.Wpf.NativeStructWpfExtensions.Transform |
// 2.x
System.Windows.Rect wpfRect = nativeRect;
// 3.0
using Dapplo.Windows.Wpf;
var wpfRect = nativeRect.ToRect();
Renames and moved types (sweep)
| 2.x | 3.0 |
|---|---|
window.GetParent() for an owned dialog |
window.GetOwner() (GetParent() is only the real parent now) |
window.GetChildren() meaning all descendants |
window.GetDescendants() |
new SafeCurrentInputDesktopHandle() |
ThreadDesktopScope.SwitchToInputDesktop() / SafeDesktopHandle.OpenInputDesktop() |
TrySendMessage(h, msg, wp, out r, lp, t) |
TrySendMessage(h, msg, wp, lp, out r, t) |
AttachThreadInput(a, b, 1) |
AttachThreadInput(a, b, true) |
MapWindowPoints(a, b, ref pt, 1) |
MapWindowPoints(a, b, ref pt) |
InteropWindow w = hWnd; / IntPtr h = w; |
InteropWindowFactory.CreateFor(hWnd) / w.Handle |
InteropWindowQuery.IgnoreClasses.Add(x) |
InteropWindowQuery.AddIgnoreClass(x) |
DesktopAccessRight.GENERIC_ALL (0x1FF) |
DesktopAccessRight.DESKTOP_ALL_SPECIFIC |
IconHelper.GetAppLogo<Bitmap>(window) |
window.GetAppLogo<Bitmap>() (package Dapplo.Windows) |
long hr = Win32.GetHResult(e) |
HResult hr = Win32.GetHResult(e) |
ColorSpace.LCS_GM_IMAGES |
ColorSpaceIntent.LCS_GM_IMAGES |
BitfieldColorMask.Create(255, 255, 255) |
BitfieldColorMask.Rgb888 |
int s = Kernel32Api.GlobalSize(h) |
ulong s = Kernel32Api.GlobalSize(h).ToUInt64() |
PackageInfo.IsRunningOnUwp |
PackageInfo.HasPackageIdentity |
Ole32Api.ClassIdFromProgId(p) == Guid.Empty |
!Ole32Api.ClassIdFromProgId(p).HasValue |
Dapplo.Windows.Kernel32.RestartManager, Kernel32.Enums.Rm*, Kernel32.Structs.Rm* |
Dapplo.Windows.InstallerManager.InstallerRestartManager, InstallerManager.Enums, InstallerManager.Structs |
RmProcessInfo.strAppName / Process.dwProcessId / bRestartable |
AppName / Process.ProcessId / IsRestartable |
session.Shutdown() (forced) |
session.Shutdown(RmShutdownType.Force); the default is now graceful |
ExtractIconEx(f, i, out l, out s, 1) |
var l = new IntPtr[1]; var s = new IntPtr[1]; ExtractIconEx(f, i, l, s, 1); count: CountIcons(f) |
Shell32Api.TaskbarPosition |
Shell32Api.TryGetTaskbarPosition(out var data) |
Advapi32Api.CurrentSessionId |
Advapi32Api.CurrentLogonSid (the session id is Process.GetCurrentProcess().SessionId) |
IntPtr timer = SystemStateApi.CreateWaitableTimer(...) |
SafeWaitHandle timer = ... |
WinFrame.IsAvailabe, ClientLatency.Avarage / Derivation, UserInfo.Domainname / Username |
IsAvailable, Average / Deviation, DomainName / UserName |
AppBarStates.AllwaysOnTop, AppBarMessages.SetAutohideAppBar |
AlwaysOnTop, SetAutoHideAppBar |
DwmWindowAttributes.NcrenderingPolicy, TransitionsForcedisabled |
NcRenderingPolicy, TransitionsForceDisabled |
VirtualKeyCode.Snapshot, Hangul, Kanji |
PrintScreen, Kana, Hanja |
MouseButtonStates.Button4Up / Button6Up / Button1Down |
ButtonX1Up / ButtonX2Up / LeftButtonDown |
LowerIntegretyInjected, HidUsagesGeneric.Consumer |
LowerIntegrityInjected, HidUsagePages.Consumer |
WM_KEYFIRST, WM_MOUSEFIRST |
WM_KEYDOWN, WM_MOUSEMOVE |
ApplicationRestartManager.MaxCommandLineLength |
RestartMaxCmdLine |
new KeyCombinationHandler(...) { TriggerOnKeyUp = true } |
{ TriggerMode = TriggerMode.FirstKeyUp }, or TriggerMode.AllKeysUp when the hotkey sends input |
window.GetZOrderedChildren(force) |
window.GetChildren(force) |
InteropWindowRetrieveSettings.ZOrderedChildren / CacheAllChildZorder |
Children / CacheAllWithChildren |
new IntPtr(-1) / new IntPtr(-2) for SetWindowPos |
WindowHandles.HWND_TOPMOST / WindowHandles.HWND_NOTOPMOST |
token.SetAsUnicodeString(x) without ClearContents() |
ClipboardNative.ReplaceContents(new ClipboardContents().AddUnicodeString(x)) |
Dapplo.Windows.Messages.Enumerations |
Dapplo.Windows.Messages.Enums |
Dapplo.Windows.Messages.Structs.WindowMessage |
Dapplo.Windows.Messages.WindowMessage |
WindowMessageInfo (Handle, Message, WordParam, LongParam) |
WindowMessage (Hwnd, Msg, WParam, LParam) |
WindowsMessage.RegisterWindowsMessage(name) / GetWindowsMessage(id) |
RegisteredWindowMessages.Register(name) / GetName(id) |
Msg.wParam / Msg.lParam |
Msg.WParam / Msg.LParam |
control.WinProcFormsMessages() |
control.WinProcMessages() |
InteropWindowQuery.GetTopLevelWindows() |
InteropWindowQuery.GetVisibleApplicationWindows() |
window.IsTopLevel() / window.IsPopup() |
window.IsVisibleApplicationWindow() / window.IsVisiblePopup() (for "no parent" use GetParent() == IntPtr.Zero) |
WinMm.Play(string) / Play(byte[]) / Play(IntPtr, SoundSettings) |
WinMm.PlayResource / PlayWave(byte[]) / PlayWave(IntPtr, SoundSettings) |
RmShutdownType.RmForceShutdown / RmShutdownOnlyRegistered |
RmShutdownType.Force / OnlyRegistered |
DwmApi.ColorizationDrawingColor / ColorizationSystemDrawingColor |
DwmApi.ColorizationColor |
Dapplo.Windows.Gdi32.Enums.DrawIconExFlags |
Dapplo.Windows.Icons.Enums.DrawIconExFlags |
GdiExtensions.AreRectangleCornersVisisble, CieXyzTripple |
AreRectangleCornersVisible, CieXyzTriple |
SystemStateApi.CloseHandle(h) |
Kernel32Api.CloseHandle(h) (the timers use SafeWaitHandle) |
Behaviour changes to check:
Dialogs must be shown from an STA thread (the UI thread or a
[STAThread]Main), not fromTask.Run.GetTopWindows()returns a list taken at call time; call it again for the current state.Owner vs parent:
IsVisibleApplicationWindow/IsVisiblePopup(formerlyIsTopLevel/IsPopup) accept owned windows, andGetInfo(autoCorrect)no longer crops owned dialogs.DPI:
DpiAwareFormlets WinForms scale on WM_DPICHANGED. Remove manual font or control scaling you did inFormDpiHandler.OnDpiChanged, or cancelForm.DpiChanged. DPI scaling now rounds (ScaleWithDpi(3, 144)is 5).Cursor drawing:
DrawCursorOnGraphics/DrawCursorOnBitmaptake the top-left of the cursor image; subtractcursor.HotSpotto draw at the mouse position.Empty rectangles: rectangles and sizes with a negative width or height count as empty.
Keyboard handlers: use one handler instance per subscription, or the
Where(() => new KeyCombinationHandler(...))factory overload.Strong names: the net10 assemblies are now strong-named too.
Saved settings
Check everything your application stored with 2.x:
NativeSizestrings: 2.x wroteHeight,Widthand read the values back the other way round. 3.0 reads and writesWidth,Height, so a size saved by 2.x comes back swapped. Swap stored values once when you upgrade (for example with a settings version number), or reset them.- Culture: the type converters now always write with the invariant culture. Values written by 2.x under a culture with a different minus sign (sv-SE, nb-NO) could not be read back before and may need a reset.
- Key names:
KeyHelper.VirtualKeyCodeFromStringstill accepts the removed alias namesSnapshot,Hangul,HangeulandKanji.VirtualKeyCode.ToString()now always gives the remaining name (PrintScreen,Kana,Hanja), so re-saved settings use the new names."win"now means either Windows key. - Enum numbers: if you stored
MonitorFrom,SysColorIndexes.Color3Dface,ProcessAccessRights,DialogDpiChangeBehaviors,ObjectStates.STATE_SYSTEM_VALIDorDesktopAccessRight.GENERIC_ALLas numbers, the values changed.
HRESULT
HResult is now enum HResult : int. Failed() and Succeeded() work, and ThrowOnFailure() throws. Failures
that were silently treated as success in 2.x now take the failure branch, so review code that calls COM, DWM or DPI
functions. Casts to uint need unchecked((uint)hr).
Window information and scrolling
MonitorFrom
The member names are unchanged but the values now match Win32, and the enum is no longer [Flags]. Recompiling is
enough, unless you stored the numbers or combined members with |.
SendMessage
WPARAM, LPARAM and LRESULT are pointer-sized everywhere.
// 2.x
User32Api.SendMessage(hWnd, msg, 5, 0);
int r = User32Api.SendMessage(hWnd, WindowsMessages.WM_VSCROLL, ScrollBarCommands.SB_LINEDOWN, 0);
// 3.0
User32Api.SendMessage(hWnd, msg, new IntPtr(5), IntPtr.Zero);
IntPtr r = User32Api.SendMessage(hWnd, WindowsMessages.WM_VSCROLL, ScrollBarCommands.SB_LINEDOWN, IntPtr.Zero);
ScrollBarInfo
ThumbTop and ThumbBottom were swapped. ThumbSize is renamed to LineButtonSize, because it is the size of the
arrow buttons. The thumb size is ThumbBottom - ThumbTop.
SysColorIndexes.Color3Dface
Now 15 (COLOR_3DFACE). No code change is needed.
Fill() caches again
InteropWindowExtensions.Fill() used to ignore its settings and always refresh, auto-correct and query the
maximized state. It now does what the flags say.
- Pass
InteropWindowRetrieveSettings.ForceUpdatewhen you need fresh values. CacheAll,CacheAllWithChildrenandCacheAllChildZorderno longer auto-correct bounds. UseCacheAllAutoCorrector addAutoCorrectValues.
Wheel scroll lines
// 2.x
int lines = WindowScroller.ScrollWheelLinesFromRegistry;
// 3.0: 0 means the wheel doesn't scroll, uint.MaxValue means one notch scrolls a page
uint lines = WindowScroller.ScrollWheelLines;
int delta = WindowScroller.CalculateWheelDelta(pageSize, lines);
Geometry
NativeRectExtensions.Intersect2 is removed; it returned the union on the Y axis. Use Intersect, which returns
NativeRect.Empty when the rectangles don't overlap.
// 2.x
var overlap = rect1.Intersect2(rect2);
// 3.0
var overlap = rect1.Intersect(rect2);
Docking and overlap
Right and Bottom are exclusive, as in a Win32 RECT. Flush rectangles (a.Right == b.Left) are now docked; if you
built docked rectangles with a 1-pixel gap, make them flush. HasOverlap is now the same as IntersectsWith.
Conversions
Lossy conversions are explicit:
// 2.x
NativeRect r = rectFloat;
NativePoint p = pointFloat;
// 3.0: the containing integer rectangle, or round explicitly
var r = (NativeRect)rectFloat;
var p = pointFloat.Round();
Points floor, sizes round up and rectangles become the smallest containing integer rectangle, so values can differ slightly from 2.x (10.5/30.5 now becomes 10/31 where it was 10/30).
Sorting and type converters
NativeSize.CompareTo now sorts ascending by area. NativeSizeTypeConverter writes Width,Height; strings saved by
2.x were Height,Width, so swap them once or re-save them.
Windows version
WindowsVersion reads the real version with RtlGetVersion. IsWindowsVista and IsWindows10 now mean exactly
that version:
// 2.x meaning "Windows 10 or later"
if (WindowsVersion.IsWindows10) { }
// 3.0
if (WindowsVersion.IsWindows10OrLater) { }
Icons and cursors
IconInfo and IconInfoEx no longer create a SafeHandle on every property read.
// 2.x
using var color = iconInfo.ColorBitmapHandle;
iconInfoEx.Dispose();
// 3.0: raw, non-owning handles
IntPtr color = iconInfo.ColorBitmap;
// take ownership once ...
iconInfoEx.TakeBitmaps(out var mask, out var colorBitmap);
using (mask) using (colorBitmap) { /* ... */ }
// ... or just free them
iconInfoEx.DeleteBitmaps();
DPI
DpiAwarenessContext is a pointer-sized struct. Compare contexts with the Win32 function, because the handles Windows
returns are not the pseudo values:
// 2.x
if (NativeDpiMethods.GetThreadDpiAwarenessContext() == DpiAwarenessContext.PerMonitorAwareV2) { }
// 3.0
if (NativeDpiMethods.AreDpiAwarenessContextsEqual(NativeDpiMethods.GetThreadDpiAwarenessContext(), DpiAwarenessContext.PerMonitorAwareV2)) { }
The members are static properties, so they can't be used in case labels or as default parameter values.
GetWindowDpiHostingBehavior()→GetWindowDpiHostingBehavior(hWnd).EnableNonClientDpiScaling(hWnd).Succeeded()→EnableNonClientDpiScaling(hWnd)(returnsbool).DialogDpiChangeBehaviors.DisableControlRelayoutis now 4.
COM
IUnknown is removed; use Marshal.QueryInterface, Marshal.AddRef and Marshal.Release. IDispatch.GetTypeInfo
now returns System.Runtime.InteropServices.ComTypes.ITypeInfo on every target, and Invoke reports the argument
error as out uint.
Messages and SharedMessageWindow
WindowMessage is a sealed class. Set Handled and Result synchronously inside OnNext; after ObserveOn the
reply has already been sent.
// 2.x (had no effect, the struct was a copy)
SharedMessageWindow.Messages.Subscribe(m => { m.Result = (nuint)1; m.Handled = true; });
// 3.0
SharedMessageWindow.Messages.Subscribe(m => { m.Result = 1; m.Handled = true; });
The window now exists for the whole process after first use. Handle never returns 0, so drop any "wait until the
window exists" code. Registrations that need the window go into Listen(onSetup, onTeardown), which runs both on the
window thread. Use SharedMessageWindow.Invoke(hwnd => ...) to run other code there.
End of session
// 2.x
ApplicationRestartManager.ListenForEndSession(onQuerySession: reason => canClose, onEndSession: reason => Save()).Subscribe();
// 3.0: answer synchronously inside OnNext
ApplicationRestartManager.ListenForEndSession().Subscribe(m =>
{
if (m.IsQuery)
{
if (!canClose) m.Veto("Unsaved captures");
}
else if (m.IsSessionEnding)
{
Save();
}
});
EndSessionMessage moved from Dapplo.Windows.Messages.Structs to Dapplo.Windows.AppRestartManager.
WasRestartRequested() becomes WasRestartRequested("/restore") with the argument you registered.
Keyboard and mouse hooks
KeyboardHook and MouseHook run on their own thread, so subscribers are no longer called on the UI thread. Decide
Handled quickly and synchronously, and move UI work to the UI thread:
KeyboardHook.KeyboardEvents
.Where(handler) // sets Handled synchronously
.ObserveOn(SynchronizationContext.Current) // then do the slow work on the UI thread
.Subscribe(args => OnHotkey());
For listeners that never set Handled, use KeyboardEventsNonBlocking / MouseEventsNonBlocking.
"win" parses to the new VirtualKeyCode.Win, which matches either Windows key. Use LeftWin to require the left key.
Raw input
args.RawInput.Device.HID.GetData() becomes args.HidData. RawInputApi.GetRawInputData(…) becomes
RawInputApi.TryGetRawInputData(lParam, out var rawInput, out var hidData).
Clipboard
Delayed rendering: register the renderer before
SetDelayedRenderedContent.// 2.x ClipboardNative.OnRenderFormat.Subscribe(r => r.AccessToken.SetAsUnicodeString(text, r.RequestedFormatId)); // 3.0 using var registration = ClipboardNative.RegisterDelayedRenderer(StandardClipboardFormats.UnicodeText, r => r.AccessToken.SetAsUnicodeString(text, r.RequestedFormatId));ClipboardUpdateInformation.Create(hWnd)becomesCreate().OnUpdateno longer reads content; callAccess()afterObserveOnto read it.Keep an access token on one thread and don't
awaitwhile holding it. CancellingAccessAsyncthrowsOperationCanceledExceptioninstead of returning a token withIsOpenTimeout.SetCloudClipboardOptions()without arguments now places nothing.SetExcludeClipboardContentFromMonitorProcessing(true)becomesExcludeFromMonitorProcessing().
System state
// 2.x
SystemStateApi.PreventSleep(); /* ... */ SystemStateApi.AllowSleep();
// 3.0
using (SystemStateApi.PreventSleep("Recording")) { /* ... */ }
Kernel32
ProcessAccessRights.QueryLimitedInformation had the value of QueryInformation (0x400). It is now 0x1000, which also
works for elevated processes. If you relied on the full query right, use QueryInformation. All is now the
Vista-and-later value 0x1FFFFF.
Multimedia
WinMm.Play(byte[]) is now WinMm.PlayWave(byte[]), returns bool and copies the data, so you no longer need to keep the array pinned. Callers must
recompile.