如何在高DPI上扩展Windows窗体按钮的图像?

时间:2014-11-12 10:17:17

标签: winforms button scale windows-forms-designer dpi

我找不到在Windows窗体按钮中缩放图像的方法。请参阅下面DPI 200%上显示的Windows窗体设计器的样子(我知道Windows窗体设计器应仅用于DPI 100%/ 96,这个截图只是说明了我的观点)。

当按钮尺寸正确缩放(34x33)时,按钮尺寸中的图像不会缩放/拉伸/缩放(它仍为16x16)。我做了很多尝试来解决这个问题:

  • 父控件AutoScaleMode设置为Font,将其设置为Dpi不会使其正常工作。
  • 将按钮AutoSize设置为truefalse无效。
  • 将按钮或父控件AutoSizeMode设置为任何值都不起作用。
  • 没有Button.ImageLayout可以设置为StretchZoom
  • 使用新的App.Config设置<add key="EnableWindowsFormsHighDpiAutoResizing" value="true" />无效。
  • 更改按钮FlatStyleImageAlign无效。

你是如何在你的应用中解决这个问题的?

Windows Form Button Image doesn't scale

2 个答案:

答案 0 :(得分:6)

因此,尽管MS哲学是to go toward out-of-the-box stretched images for Windows Form Controls when high DPI,但看起来Button上的图像需要手动拉伸。当然,更好的解决方案是,为用户显示的每个位图(在按钮上和其他任何位置)定义几个适应250%200%150%和125%DPI的位图。

以下是代码:

  public static IEnumerable<IDisposable> AdjustControlsThroughDPI(this Control.ControlCollection controls) {
     Debug.Assert(controls != null);
     if (DPIRatioIsOne) {
        return new IDisposable[0]; // No need to adjust on DPI One
     }

     var list = new List<IDisposable>();
     foreach (Control control in controls) {
        if (control == null) { continue; }

        var button = control as ButtonBase;
        if (button != null) {
           button.AdjustControlsThroughDPI(list);
           continue;
        }

        // Here more controls tahn button can be adjusted if needed...

        // Recursive
        var nestedControls = control.Controls;
        Debug.Assert(nestedControls != null);
        if (nestedControls.Count == 0) { continue; }
        var disposables = nestedControls.AdjustControlsThroughDPI();
        list.AddRange(disposables);
     }
     return list;
  }

  private static void AdjustControlsThroughDPI(this ButtonBase button, IList<IDisposable> list) {
     Debug.Assert(button != null);
     Debug.Assert(list != null);
     var image = button.Image;
     if (image == null) { return; }

     var imageStretched = image.GetImageStretchedDPI();
     button.Image = imageStretched;
     list.Add(imageStretched);
  }


  private static Image GetImageStretchedDPI(this Image imageIn) {
     Debug.Assert(imageIn != null);

     var newWidth = imageIn.Width.MultipliedByDPIRatio();
     var newHeight = imageIn.Height.MultipliedByDPIRatio();
     var newBitmap = new Bitmap(newWidth, newHeight);

     using (var g = Graphics.FromImage(newBitmap)) {
        // According to this blog post http://blogs.msdn.com/b/visualstudio/archive/2014/03/19/improving-high-dpi-support-for-visual-studio-2013.aspx
        // NearestNeighbor is more adapted for 200% and 200%+ DPI
        var interpolationMode = InterpolationMode.HighQualityBicubic;
        if (s_DPIRatio >= 2.0f) {
           interpolationMode = InterpolationMode.NearestNeighbor;
        }
        g.InterpolationMode = interpolationMode;
        g.DrawImage(imageIn, new Rectangle(0, 0, newWidth, newHeight));
     }

     imageIn.Dispose();
     return newBitmap;
  }

请注意,将返回创建的可枚举一次性位图。如果你不在乎在按钮上处理位图,你就不必在意处理拉伸的位图。

请注意我们处理原始按钮位图。

请注意我们自己的会员处理DPI:MultipliedByDPIRatio(this int)DPIRatioIsOne:bools_DPIRatio。你可以写自己的,棘手的一点是获得实际的DPI比率。为了收集DPI比率,我发现的最佳方式是this one

请注意对博客文章Improving High-DPI support for Visual Studio 2013的引用,其中VS团队解释说,对于他们的图标样式,他们确定图像在200%,100%之间拉伸[最好用Bicubic算法实现,并且高于或等于使用朴素最近邻算法可以达到200%。提供的代码反映了这些选择。


编辑:在200%DPI下各种插值模式的屏幕截图下,恕我直言InterpolationMode.HighQualityBicubic优于InterpolationMode.NearestNeighbor

Interpolation mode

答案 1 :(得分:3)

这是一个随时可用的助手类,它基于接受的答案,包括检索DPI比例,并增加了对PictureBox图像缩放的支持:

public static class HighDpiHelper
{
    public static void AdjustControlImagesDpiScale(Control container)
    {
        var dpiScale = GetDpiScale(container).Value;
        if (CloseToOne(dpiScale))
            return;

        AdjustControlImagesDpiScale(container.Controls, dpiScale);
    }

    private static void AdjustButtonImageDpiScale(ButtonBase button, float dpiScale)
    {
        var image = button.Image;
        if (image == null)
            return;

        button.Image = ScaleImage(image, dpiScale);
    }

    private static void AdjustControlImagesDpiScale(Control.ControlCollection controls, float dpiScale)
    {
        foreach (Control control in controls)
        {
            var button = control as ButtonBase;
            if (button != null)
                AdjustButtonImageDpiScale(button, dpiScale);
            else
            {
                var pictureBox = control as PictureBox;
                if (pictureBox != null)
                    AdjustPictureBoxDpiScale(pictureBox, dpiScale);
            }

            AdjustControlImagesDpiScale(control.Controls, dpiScale);
        }
    }

    private static void AdjustPictureBoxDpiScale(PictureBox pictureBox, float dpiScale)
    {
        var image = pictureBox.Image;
        if (image == null)
            return;

        if (pictureBox.SizeMode == PictureBoxSizeMode.CenterImage)
            pictureBox.Image = ScaleImage(pictureBox.Image, dpiScale);
    }

    private static bool CloseToOne(float dpiScale)
    {
        return Math.Abs(dpiScale - 1) < 0.001;
    }

    private static Lazy<float> GetDpiScale(Control control)
    {
        return new Lazy<float>(() =>
        {
            using (var graphics = control.CreateGraphics())
                return graphics.DpiX / 96.0f;
        });
    }

    private static Image ScaleImage(Image image, float dpiScale)
    {
        var newSize = ScaleSize(image.Size, dpiScale);
        var newBitmap = new Bitmap(newSize.Width, newSize.Height);

        using (var g = Graphics.FromImage(newBitmap))
        {
            // According to this blog post http://blogs.msdn.com/b/visualstudio/archive/2014/03/19/improving-high-dpi-support-for-visual-studio-2013.aspx
            // NearestNeighbor is more adapted for 200% and 200%+ DPI

            var interpolationMode = InterpolationMode.HighQualityBicubic;
            if (dpiScale >= 2.0f)
                interpolationMode = InterpolationMode.NearestNeighbor;

            g.InterpolationMode = interpolationMode;
            g.DrawImage(image, new Rectangle(new Point(), newSize));
        }

        return newBitmap;
    }

    private static Size ScaleSize(Size size, float scale)
    {
        return new Size((int)(size.Width * scale), (int)(size.Height * scale));
    }
}