Andrei Smolin

Add-in Express version 9 adds support for multiple monitors in Office

Add-in Express 9 supports a recent change in Office 2016: support of multiple monitors having different DPI settings.

This story started with Windows 10 Fall Creators Update that allowed using different DPIs on different monitors. As said on this page at support.office.com, starting from version 1803, Word, PowerPoint and Visio support multiple monitors having different DPI settings. According to that page, other Office applications will support multiple DPIs in Spring 2018. That’s all. No more official Office-related information is available.

How does the above affect Office extensions? Not affected are non-UI extensions such as Excel UDFs and RTD servers. Affected are COM add-ins that show 1) panes/forms and 2) icons in the Ribbon UI.

What happens if you don’t react to this change? The previous Add-in Express versions are DPI unaware and the panes/forms they create are bitmap-stretched by Windows. Such a stretch makes your form/pane/Ribbon icons blurry. To see how this looks, drag an Office window showing your add-in’s UI to another monitor.

To avoid bitmap-stretching, you need to get notified about the DPI change and update the UI of your form/pane: resize the form/pane and controls on it, use a different font size, different images (optional) and the form icon.

To do this, you handle the ADXDPIChanged event available on Add-in Express task panes and forms starting from Add-in Express version 9.

This event is raised when the host application has reacted to a DPI change and now it’s your turn to update the UI of your pane. A trivial variant of updating your controls when reacting to a DPI change is the method shown below; you call this method from the ADXDPIChanged event as demonstrated in the code of the sample add-in:

C#

private void UpdateDPI(Control root, int dpi, float dpiFactor)
{
    foreach (Control cl in root.Controls)
    {
        cl.Bounds = Rectangle.Round(new RectangleF(cl.Left * dpiFactor, cl.Top * dpiFactor, cl.Width * dpiFactor, cl.Height * dpiFactor));
        if (cl.Controls.Count > 0)
            UpdateDPI(cl, dpi, dpiFactor);
        cl.Font = new Font(cl.Font.FontFamily, cl.Font.Size * dpiFactor, cl.Font.Unit);
    }
}

VB.NET

Private Sub UpdateDPI(ByVal root As Control, ByVal dpi As Integer, ByVal dpiFactor As Single)
    For Each cl As Control In root.Controls
        cl.Bounds = Rectangle.Round(New RectangleF(cl.Left * dpiFactor, cl.Top * dpiFactor, cl.Width * dpiFactor, cl.Height * dpiFactor))
        If cl.Controls.Count > 0 Then UpdateDPI(cl, dpi, dpiFactor)
        cl.Font = New Font(cl.Font.FontFamily, cl.Font.Size * dpiFactor, cl.Font.Unit)
    Next
End Sub

Your variant of this method will consider the actual controls used on your forms. Say, you may apply different images/controls or update your controls in a complex way to conform with the current DPI.

You may also want to get some controls hidden when you update them to minimize visual artefacts. You use the ADXBeforeDPIChanged event to hide these controls. In this case, in the code of the ADXDPIChanged event, you make the controls visible again after you update them.

Finally, about icons that you use on your Ribbon controls. If you use a multiple-image icon on your Ribbon control specifying the icon in the Glyph property of the control, Add-in Express 9 retrieves the best suited image when responding to a DPI change.

Below are two screenshots showing the same Add-in Express-based add-in in Excel 2016. Please notice the difference between the images: the controls are updated using the UpdateDPI() method above and the icon in the Ribbon is retrieved from the multi-image .ICO specified in the Glyph property of the Ribbon button. The information shown on the pane is updated using a separate method not shown above; see the sample project.

Good luck!

Add-in Express based add-in on 100% DPI

Add-in Express based add-in on 200% DPI

2 Comments

  • https://secure.gravatar.com/avatar/a804e7750f1c63a86e69a5bcf7cff71d?s=32&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G Taylour says:

    Hi Andrei,

    Thanks for the article and new release. It should be noted that the per-monitor-DPI-aware version of Microsoft Excel is currently only available in the 32-bit insider track version of Office365. There is also a compatibility setting that can be toggled which reverts Excel back to system-DPI-aware mode.

    Here are a few notes and comments:

    1) You said that in the previous versions of AddInExpress were DPI-unaware. I believe that they were system-DPI-aware since DPI awareness is per-process and add-ins run within the parent Office process and so inherit it’s DPI awareness (and previous Office releases were system-dpi-aware).

    2) Have you found a way to reliably scale CheckBoxes and RadioButtons in the CTP? Scaling the font is easy enough but the checkbox/radio-button icons do not seem to scale.

    3) Have you found a way to reliably get scrollbars in a CTP to scale? It looks like EnableNonClientDpiScaling() should help with this, but I was unable to get it to work.

    4) If you want your addin to be compatible with the per-monitor DPI aware version of Excel, you are also responsible for scaling any forms, that the addin displays, when the DPI changes (by handling WM_DPICHANGED in WndProc). As mentioned previously, certain controls like checkboxes, radio buttons, menu bars and scrollbars may be difficult (or not currently possible) to correctly scale. There is, however, a way to show forms and system dialogs (ex. OpenFileDialog and SaveFileDialog) as system-DPI-aware (where bitmap scaling will occur on secondary monitors and the forms will at least appear at the correct size with correct layout) even if the parent Excel process is per-monitor-DPI-aware. For this, see SetThreadDpiAwarenessContext: https://msdn.microsoft.com/en-us/library/windows/desktop/mt748629(v=vs.85).aspx
    Since this is all relatively new, there aren’t many C# examples regarding the correct values to use for DPI_AWARENESS_CONTEXT. This was helpful getting the native methods set up correctly for a C# project: https://github.com/emoacht/WpfBuiltinDpiTest/blob/master/WpfApiTest/DpiHelper.cs
    Note that you should also add this to the DPI_AWARENESS_CONTEXT enum: DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 18

    Note that you should call SetThreadDpiAwarenessContext with DPI_AWARENESS_CONTEXT_SYSTEM_AWARE before showing a form and then reset it back afterwards by calling SetThreadDpiAwarenessContext with its previous value(which should be DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2).

    Cheers,
    Taylour

  • https://secure.gravatar.com/avatar/29957f26ad2d8ba527fd9cc8cfa7b2e0?s=32&d=https%3A%2F%2Fsecure.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D32&r=G Andrei Smolin (Add-in Express Team) says:

    Hello Taylour,

    We’ve tested all these and we didn’t find a way to influence the controls that the COMCTL32 library draws: check box, option button, scroll bars, tree view, etc.

    Also, we’ve found that Office applications work in DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE, not DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2. This also influences the controls.

    This said, we hope that Office will provide some support for developers who run in DPI-related issues with Windows Forms, WPF, and COMCTL32 controls. As to now, the only way is to develop controls of your own. Some COMCTL32 controls support customizations and this may help you develop custom controls.

    The .NET Framework 4.7.2 started supporting multiple monitors in standalone applications only. Doing this in DLLs is still an open question. It would be helpful if a custom control has a way for developer to specify the current (required) DPI so that the control redraws itself with this DPI. It would be easy to use such controls. Our Ablebits add-ins use custom controls written in this way.

Post a comment

Have any questions? Ask us right now!