I'm attempting to finalise high DPI support for an Office COM Add-In running .NET Framework 4.6.2. I'm following the principles outlined by Microsoft for handling DPI Scaling in Office. The only approach that can be used for Office add-ins is to set the DPI awareness per-thread, as the typical manifest and startup solutions aren't available.
The form I'm scaling is a Forms window hosting a WPF UserControl in an Element Host. This was proposed as a working solution and I've observed it being used successfully in demo code provided by Add-In Express.
As recommended by Microsoft, I'm opening my form after setting the thread DPI context to Per Monitor. Using this, the demo code provided above by Add-In Express is being used to apply a LayoutTransform
to the UserControl, and scale all elements to the correct size.
private void ApplyScale(int dpi)
{
var source = (HwndSource) PresentationSource.FromVisual(this);
if (source is null) return;
var wpfDpi = 96 * source.CompositionTarget.TransformToDevice.M11;
var scaleFactor = dpi / wpfDpi;
var scaleTransform = Math.Abs(scaleFactor - 1.0) < 0.001 ? null : new ScaleTransform(scaleFactor, scaleFactor);
this.SetValue(LayoutTransformProperty, scaleTransform);
}
This is working as expected, and the window is successfully scaling on each monitor with differing DPI, increasing the size of both window and contents to the correct size.
The issue arises when I attempt to interact with the scaled content. On monitors above 100% scaling, a percentage of the right and bottom edges of the window cannot be interacted with correctly. I have Save and Cancel buttons at the bottom right corner of the form, and when mousing over them the hover event will flicker, but they cannot be clicked normally. This is also true of a ListView featured in the form.
I can confirm the thread is definitely set to Per Monitor awareness, and the visual scaling is working perfectly. No elements are overlapping, and the size of the area that can't be clicked increases with scale.
I'm two days into debugging this and have attempted everything I can think of. What could be causing this?
TLDR: Construct a WPF
UserControl
before aDispatcher
is created for the UI thread.We were experiencing the same issue when a WPF view was placed inside an
ElementHost
in anADXOlForm
, and theLayoutTransform
was set in the event handler of the form'sADXDPIChanged
event.The solution proposed in the linked forum thread ("use
OnSendMessage
instead ofDispatcher
") gave me the idea that the issue might have something to do with theDispatcher
being initialized in a "wrong" way. Since WPF usesDispatcher
internally, its presence should not be the problem itself.If no reference to the UI thread's
Dispatcher
was stored, then the issue was not present (just like the post said), but it provides essential functionality, so like you mentioned, it would mean extensive rewrite in our case as well. But our project doesn't need theDispatcher
during startup, so I checked if we initialized it after a WPF view was displayed fixed the issue, and it did. Fortunately, it even works if the WPF control is not displayed.Only a small modification in the startup event handler was required to fix the flickering:
I'm not sure why this modification fixes the flickering. My guess is that the WPF control initializes the
Dispatcher
"correctly". Maybe it adds some tasks with the right priority, but I was not able to verify this theory.