1

This issue happens on Windows 10 Creators Update or later using 175% zoom or higher targeted .Net 4.7.2. Further, we are calling SetProcessDPIAware in the Program.cs file.

If we do not call this then fonts look horrible on High DPI, especially at 300%.

static class Program
{
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern bool SetProcessDPIAware();

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        //if (Environment.OSVersion.Version.Major >= 6)
        SetProcessDPIAware();

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

IMPORTANT STEP We also go into Advanced Scaling Settings and turn off the feature "Let windows try to fix apps so they are not blurry"...because we have users turning this off. Image of the Windows Setting

In the application below, we have 3 PictureBox controls. The PictureBox on the far left is the source and his image is a PNG file created at 96 dpi.

The user clicks the button above the middle PictureBox to copy the source image into a Metafile (used as the drawing canvas) and uses that to populate the Image property of the middle PictureBox. In High DPI you can see that the image does not size well or that only a portion of the image was copied into the metafile.

The button above the PictureBox on the far right copies the source Image using a Bitmap as the drawing canvas. He renders properly at 175%.

Picture of Application Results

This is the code that converts the source image to a metafile and sticks it into another PictureBox.

      private void DrawUsingMetafile()
    {
        try
        {
            Image img = this.pictureBox1.Image;

            Metafile mf = NewMetafile();

            using (Graphics gmf = Graphics.FromImage(mf))
            {
                gmf.DrawImage(img, 0, 0, img.Width, img.Height);
            }

            this.pictureBox2.Image = mf;
        }
        catch (Exception ex)
        {

            MessageBox.Show(ex.Message, "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

}

        public static Metafile NewMetafile()
    {
        using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))  // offscreen device context
        {
            IntPtr hdc = g.GetHdc(); // gets released by g.Dispose() called by using g
            return new Metafile(hdc, EmfType.EmfPlusOnly);
        }
    }

Any ideas why this is happening?

  • 1
    That constructor creates a Metafile with [MetafileFrameUnit.GdiCompatible](https://learn.microsoft.com/en-us/dotnet/api/system.drawing.imaging.metafileframeunit) (which also implies a unit conversion). Use [this other constructor](https://learn.microsoft.com/en-us/dotnet/api/system.drawing.imaging.metafile.-ctor?view=netframework-4.8#System_Drawing_Imaging_Metafile__ctor_System_IntPtr_System_Drawing_RectangleF_System_Drawing_Imaging_MetafileFrameUnit_System_Drawing_Imaging_EmfType_System_String_) (and make you app DPIAware, possibly). – Jimi Oct 18 '19 at 20:26
  • 1
    Otherwise you always risk to end up like this: [Image is not drawn at the correct spot](https://stackoverflow.com/a/51456467/7444103) – Jimi Oct 18 '19 at 20:33
  • Jimi, is there any way to get a Metafile with MetafileFrameUnit.Pixel without specifying a size Rectangle? In this simplified example, you know the size in advance... but in general, we don't know the size, which is why we are using a constructor that does NOT require one. Looking at the .NET source code, it looks like Gdip.GdipRecordMetafile allows you to pass in NullHandleRef for the FrameRect along with a MetafileFrameUnit. But the only Metafile constructors that use that require a file or stream... we want to do this in-memory. Is there a way? – Brian Kennedy Oct 18 '19 at 22:02
  • Jimi, alternatively is there a way to change the MetafileFrameUnit after the Metafile is constructed? – Brian Kennedy Oct 18 '19 at 22:03
  • The Stream can also be a `MemoryStream` (it doesn't need to be a file stream). This is used quite often, in constructor like this: `Metafile mf = new Metafile([MemoryStream], hDC, image.GetBounds(ref unit), MetafileFrameUnit.Pixel, EmfType.EmfPlusDual, "Metafile description");`. – Jimi Oct 19 '19 at 13:56
  • You have 2 assumptions that can create misbehaviors and memory leaks: 1) `EmfType.EmfPlusOnly` <= use `EmfType.EmfPlusDual` which is, btw, [the default](https://learn.microsoft.com/en-us/windows/win32/api/gdiplusheaders/nf-gdiplusheaders-metafile-metafile(inhdc_inconstrect__inmetafileframeunit_inemftype_inconstwchar)). – Jimi Oct 19 '19 at 13:57
  • 2) (this is the worst) *`IntPtr hdc = g.GetHdc();` // gets released by g.Dispose() called by using g* <= bad choice. Read the Remarks section about [Graphics.GetHdc](https://learn.microsoft.com/en-us/dotnet/api/system.drawing.graphics.gethdc): when you call `IntPtr hdc = g.GetHdc();` you **must** call `[Graphics].ReleaseHdc(hdc);` right after (these work in pairs and are meant to be used like that). GDI+ cannot work correctly while you have an active GDI object alive. – Jimi Oct 19 '19 at 13:57
  • If you application is not DPIAware, all non-dpi aware Win32 functions will assume a default DPI value. Most functions that retrieve a `RECT/GpRectF` will get the default values (non related to the current Desktop/Screen's DPI settings). See also this: [Using SetWindowPos with multiple monitors](https://stackoverflow.com/a/53026765/7444103), it might be interesting (maybe just the DPIAwareness part and the last section, related to the Screens DC ). – Jimi Oct 19 '19 at 14:12
  • On #2, Graphics.Dispose() calls ReleaseHdc(). The 'using' guarantees that g.Dispose() will get called. – Brian Kennedy Oct 20 '19 at 02:04
  • On #1, GDI+ has been standard in Windows since XP. All of our customers have at least Windows 7. If rendered in GDI, some of our graphics wouldn't look right. So, why would I want to make our docs twice as big just to carry around a GDI version that will never be needed?? What misbehavior would this cause? – Brian Kennedy Oct 20 '19 at 02:20
  • Our app is DPI Aware... system, not per monitor. Our WinForms generally autoscale to the DPI nicely. HOWEVER, if the user sets "Fix scaling for apps" to Off, then it seems Windows treats us as if we are DPI Unaware, and then our Graphics start misbehaving. – Brian Kennedy Oct 20 '19 at 02:24

1 Answers1

0

Just FYI - Microsoft confirmed this was a bug in .Net 4.8. The problem happens only if you turn off the feature "Let windows try to fix apps so they are not blurry" They confirmed the issue and patched it but have not released that fix as of yet.