Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WinForms window changes dimensions when it encounters an async call

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.

like image 817
awj Avatar asked Jun 14 '17 14:06

awj


1 Answers

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...

like image 140
jtmnt Avatar answered Nov 10 '22 19:11

jtmnt