dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.06k stars 1.17k forks source link

Despite setting DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 MessageBox are displayed blurred. #6775

Open vsfeedback opened 2 years ago

vsfeedback commented 2 years ago

_This issue has been moved from a ticket on Developer Community._


[severity:It bothers me. A fix would be nice] 1st monitor: 100 %, primary 2nd monitor: 150 %

If you have the App window on the 2nd monitor where a MessageBox is displayed, it will be displayed blurred in the correct size. On the other hand, if the 1st monitor also has a scaling of 150%, the MessaageBox on the 2nd monitor will be displayed in focus.

This affects WPF apps with .NET Framework 4.8 under Windows 11.

So the MessageBox is always created with the scaling of the primary monitor and then stretched to the scaling of the 2nd monitor. However, it should work the same way as other dialogs, e.g. OpenFileDialog and FolderBrowserDialog.


Original Comments

(no comments)


Original Solutions

(no solutions)

singhashish-wpf commented 2 years ago

Can you please share a minimal repro to check this further?

bronxzv commented 2 years ago

A good solution with minimal code change is to replace the calls to MessageBox with calls to TaskDialog

MessageBox has been superseded by TaskDialog so I suppose future Windows version will not fix the MessageBox High DPI bug(s)

mayphi commented 1 year ago

Repro with .Net7 (tested on Win10): https://github.com/mayphi/WpfBlurryMessageBox

Start the programm an move it to another monitor with a different resolution (for example from 1920x1080 to 3840x2160).

mayphi commented 1 year ago

@singhashish-wpf is this repro enough? Do you need more informations?

pchaurasia14 commented 1 year ago

@mayphi - We haven't got a chance to look at the repro yet. Will check it as soon as possible and will report back. Thanks.

FedePorti commented 1 year ago

Is there any news for this?

miloush commented 1 year ago

MessageBox is a native Windows function, there isn't much WPF can do. You can try filing an issue using Feedback Hub, but as @bronxzv said, the newer TaskDialog API works as expected, so I am not sure there would be enough business case to fix MessageBox.

If it helps, since Windows 1809 you can use SetThreadDpiAwarenessContext to switch to GDI scaling before showing the MessageBox and reverting after it returns - as far as I can tell that avoids blurry MessageBoxes.

FedePorti commented 1 year ago

Thank you. Can you show me an example of how to use correctly SetThreadDpiAwarenessContext with c#? Because I try to use it but I don't know how.

miloush commented 1 year ago

Sure, try this:

[DllImport("user32.dll")]
private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr context);

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    var previous = SetThreadDpiAwarenessContext(-5); // GDI scaling
    MessageBox.Show("MyMessageBoxText", "MyCaption", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
    SetThreadDpiAwarenessContext(previous);
}

I would probably turn it into a fancy IDisposable class.

FedePorti commented 1 year ago

Thank you, that works. And how is the correct way to implement IDisposble? What should I put in the method Dispose?

class MainWindow : IDisposable { private bool disposed;

    [DllImport("user32.dll")]
    private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr context);

    private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
        var previous = SetThreadDpiAwarenessContext(-5); // GDI scaling
        MessageBox.Show("MyMessageBoxText", "MyCaption", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning);
        SetThreadDpiAwarenessContext(previous);
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
               // What do I put here?
            }
        }
        disposed = true;
    }
}
miloush commented 1 year ago

If you do something like

public class ThreadDpiAwareness : IDisposable
{
    [DllImport("user32.dll")]
    private static extern IntPtr SetThreadDpiAwarenessContext(IntPtr context);
    private const int UNAWARE_GDISCALED = -5;

    private IntPtr _previousContext;

    private ThreadDpiAwareness(int context)
    {
        _previousContext = SetThreadDpiAwarenessContext(context);
    }

    public void Dispose()
    {
        SetThreadDpiAwarenessContext(_previousContext);
    }

    public static IDisposable GdiScaled => new ThreadDpiAwareness(UNAWARE_GDISCALED);
}

then you can use

using (ThreadDpiAwareness.GdiScaled)
    MessageBox.Show("Nazdar");