7

I have a WinForms project which is several years old and has been retro-fitted with async event-handlers:

private async void dgvNewOrders_CellClick(object sender, DataGridViewCellEventArgs e)

Inside this method is an async call:

var projectTemplate = await GetProjectTemplateFile(companyId, sourceLang, targetLang);

When the program runs on a normal resolution screen, it runs as expected. However, when run on a high-DPI screen the window's dimensions - as well as those of all child controls - jump to half-size as soon as it encounters that inner async call. It's as if the program is suddenly run in a compatibility mode or the scaling has been disabled.

Currently, in an effort to debug the problem, the GetProjectTemplateFile method consists simply of

private async Task<ProjectTemplateFile> GetProjectTemplateFile(long companyId, string sourceLanguage, string targetLanguage)
{
    return null;
}

It makes no difference whether GetProjectTemplateFile performs an async operation or not.

If I comment-out that async call to GetProjectTemplateFile then the program runs as expected without any jump in dimensions, even though there are still other async calls made in the CellClick event.

I've tried appending .ConfigureAwait(true) to the async call, which makes no difference. Nor does running the call synchronously with .GetAwaiter().GetResult().

Can anyone explain why the window's dimensions are changing with this particular async call, and/or how to prevent this from happening?

Update
As per a request, here is a code sample which elicits the explained behaviour. There's nothing unusual happening here that I can see but I assure you, this very code is causing the explained behaviour.

private async void dgvNewOrders_CellClick(object sender, DataGridViewCellEventArgs e)
{
    var result = await _templateInteraction.GetProjectTemplateFile(1,
                                                                   "en-US",
                                                                   "de-CH");
    return;
}

public class TemplateInteraction : ITemplateInteraction
{
    public async Task<ProjectTemplateFile> GetProjectTemplateFile(long companyId, string sourceLanguage, string targetLanguage)
    {
        return null;

        // elided code
    }

    // other methods
}

Some other information which might be relevant:

  • The FormBorderStyle of the window is "FixedToolWindow"
  • The window is given an explicit width in a startup method
  • AutoSize = False
  • AutoSizeMode = GrowOnly
  • The computer which it's being developed on does not have the Windows 10 1703 (Creator's) update which has new scaling logic
  • If the GetprojectTemplateFile method is not async, i.e. has signature public ProjectTemplateFile GetProjecttemplateFile(...) then there is no problem. This problem appears to exist only when the method call is async - even if I make it a blocking call.

UPDATE 2:
I've found the specific line(s) of code which cause this problem:

MessageBox.Show(...);

The inner async call, GetProjectTemplateFile, calls an API and then checks the response:

var responseMessage = await client.GetAsync(uri);
if (!responseMessage.IsSuccessStatusCode)
{
    MessageBox.Show(...);
    return null;
}

If I comment-out the MessageBox.Show(...) call then everything is normal, no scaling problems, no jump in dimensions.

But the problem occurs when the MessageBox.Show(...) call is in-place.

Furthermore, the API responds with a 200 (OK) so the MessageBox code isn't even being used. My guess is that the JIT compiler sees it as a possibility so... it re-renders the form?

Also, importantly, this code is not in the form's code-behind, it's in a class which the form is given an instance of in its constructor.

awj
  • 7,482
  • 10
  • 66
  • 120
  • 2
    Please post a complete and minimal sample that will recreate the behavior you are seeing. Include anything that may be relevant, what framework you're using, os etc. If `async` is truly randomly changing the size of a form it would _interesting_ to investigate to say the least. – JSteward Jun 14 '17 at 14:51
  • Thank you for the update but I cannot reproduce what you're seeing. Here is the code I'm using: [dotnet Fiddle](https://dotnetfiddle.net/6Sn4cj) – JSteward Jun 14 '17 at 15:13
  • Even with .NET 4.6? – awj Jun 14 '17 at 15:15
  • Tried 4.5.2, 4.6, & 4.6.1 – JSteward Jun 14 '17 at 15:18
  • Well... I appreciate that you took the time @JSteward. Maybe I'll see if I can upload a very short video somewhere showing this happening with the code also in shot. – awj Jun 14 '17 at 15:21
  • Just to prove I'm not going mad, here's a video showing the behaviour and the relevant (insofar as I can see) code: https://www.screencast.com/t/kIaNsCILj8g – awj Jun 14 '17 at 15:57
  • @awj, could you upload a minimal but complete WinForms project showing this behavior? I have Win10 v1703 and a high-DPI screen and I'd try to repro. – noseratio Jun 15 '17 at 03:02
  • target your project to [.net 4.7 and run it on Win10 v1703 (Creators Update)to get DPI improvements for WinForms](https://stackoverflow.com/a/43808350/1466046) – magicandre1981 Jun 15 '17 at 14:19

1 Answers1

6

I guess you are using MessageBox from System.Windows namespace, referenced from PresentationFramework.dll, instead of System.Windows.Forms namespace?

// Causes DPI scaling problems:
System.Windows.MessageBox.Show() // loads WPF version from PresentationFramework.dll

// no DPI scaling issues:
System.Windows.Forms.MessageBox.Show() // uses standard winforms messagebox

So try using the standard MessageBox instead.

I've found that whenever any WPF-targeted dll gets loaded into memory, the DPI autoscaling gets reset. The specific function doesn't even need to be actually called - the dll's are loaded as soon as the parent function is called.

I had same problem by just having System.Windows.Input.Keyboard.IsKeyToggled(), which loaded PresentationCore.dll. Thought I was going mad as well...

jtmnt
  • 746
  • 7
  • 12
  • Yes, you're right. Sorry, I found this out several months ago but never updated the OP. – awj Oct 17 '17 at 09:08
  • If you need to use PresentationCore or PresentationFramework and this an issue, you can add this to your AssemblyInfo.cs file to avoid the reset when loading a type from these libraries: `[assembly: System.Windows.Media.DisableDpiAwareness]` – Scott P May 19 '21 at 14:33