Winforms 如何在高DPI上缩放Windows窗体按钮的图像?

Winforms 如何在高DPI上缩放Windows窗体按钮的图像?,winforms,button,scale,windows-forms-designer,dpi,Winforms,Button,Scale,Windows Forms Designer,Dpi,我找不到在Windows窗体按钮中缩放图像的方法。请参见下面DPI 200上显示的Windows窗体设计器的外观(我知道Windows窗体设计器应仅用于DPI 100%/96,此屏幕截图正好说明了我的观点) 当按钮大小正确缩放(34x33)时,按钮大小中的图像不会缩放/拉伸/缩放(仍为16x16)。我做了很多尝试来解决这个问题: 父控件AutoScaleMode被设置为Font,将其设置为Dpi则不起作用 将按钮AutoSize设置为true或false不起作用 将按钮或父控件AutoSize

我找不到在Windows窗体按钮中缩放图像的方法。请参见下面DPI 200上显示的Windows窗体设计器的外观(我知道Windows窗体设计器应仅用于DPI 100%/96,此屏幕截图正好说明了我的观点)

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

  • 父控件
    AutoScaleMode
    被设置为
    Font
    ,将其设置为
    Dpi
    则不起作用
  • 将按钮
    AutoSize
    设置为
    true
    false
    不起作用
  • 将按钮或父控件
    AutoSizeMode
    设置为任何值都不会使其工作
  • 没有
    按钮。ImageLayout
    可以设置为
    拉伸
    缩放
  • 使用新的
    App.Config
    设置
    无法正常工作
  • 更改按钮
    FlatStyle
    ImageAlign
    无法工作
您是如何在应用程序中解决此问题的


因此,尽管微软的理念是,按钮上的图像似乎需要手动拉伸。当然,更好的解决方案是,对于显示给用户的每个位图(在按钮和其他地方),定义几个适合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的公共静态IEnumerable AdjustControls(此Control.ControlCollection控件){
Assert(控件!=null);
if(二吡喃噻嗪){
返回新的IDisposable[0];//无需在DPI上进行调整
}
var list=新列表();
foreach(控件中的控件){
如果(control==null){continue;}
var button=控件作为ButtonBase;
如果(按钮!=null){
按钮。通过DPI(列表)调整控件;
继续;
}
//这里更多的控制按钮可以调整,如果需要。。。
//递归的
var nestedControls=control.Controls;
Assert(nestedControls!=null);
如果(nestedControls.Count==0){continue;}
var disposables=嵌套控件。通过DPI()调整控件;
列表。添加范围(一次性);
}
退货清单;
}
通过DPI的专用静态无效调整控件(此按钮基本按钮,IList列表){
Assert(按钮!=null);
Assert(list!=null);
var image=button.image;
如果(image==null){return;}
var ImageStretch=image.getImageStretchEdpi();
按钮。图像=图像拉伸;
列表。添加(图像拉伸);
}
私有静态映像GetImageStretchedDPI(此映像imageIn){
Assert(imageIn!=null);
var newWidth=imageIn.Width.multipledBydbiratio();
var newHeight=imageIn.Height.multipledBydbiratio();
var newBitmap=新位图(newWidth、newHeight);
使用(var g=Graphics.FromImage(newBitmap)){
//根据这篇博文http://blogs.msdn.com/b/visualstudio/archive/2014/03/19/improving-high-dpi-support-for-visual-studio-2013.aspx
//最近的邻居更适合于200%和200%+DPI
var interpolationMode=interpolationMode.HighQualityBicubic;
如果(s_DPIRatio>=2.0f){
插值模式=插值模式。最近的邻居;
}
g、 插值模式=插值模式;
g、 DrawImage(imageIn,新矩形(0,0,newWidth,newHeight));
}
Dispose();
返回newBitmap;
}
请注意,将返回可枚举的创建的一次性位图。若你们不关心处理按钮上的位图,你们就不必关心处理拉伸的位图

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

请注意我们自己的成员处理DPI:
multipledbydpiratio(此int)
DPIRatioIsOne:bool
s\u DPIRatio
。你可以自己写,关键是要获得实际的DPI比率。收集DPI比率的最佳方法是

请注意对博客文章的引用,其中VS团队解释说,对于他们的图标样式,他们确定在]200%,100%[之间拉伸的图像最好用双三次算法实现,大于或等于200%的图像最好用朴素的最近邻算法实现。给出的代码反映了这些选择


编辑:在200%DPI下各种插值模式的屏幕截图下方,IMHO
插值模式。高质量双三次
优于
插值模式。最近邻


这是一个基于公认答案的现成帮助类,包括检索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));
    }
}
公共静态类HighDpiHelper
{
公共静态空隙调整控制图像缩放(控制容器)
{
var dpiScale=GetDpiScale(container).Value;
if(关闭按钮(dpiScale))
返回;
调整ControlImagesDPiscale(container.Controls,dpiScale);
}
专用静态空隙调整按钮图像显示(按钮基础按钮、浮动图像显示)
{
var image=button.image;
if(image==null)
返回;
button.Image=ScaleImage(图像,dpiScale);
}
专用静态空隙调整控件图像缩放(Control.ControlCollection控件,float dpiScale)
{
foreach(控件中的控件)
{
var button=控件作为ButtonBase;
如果(按钮!=null)
调整ButtonImagedPiscale(按钮,dpiScale);
其他的
{
var pictureBox=控件作为pictureBox;
如果(pictureBox!=null)
调整PictureBoxDPiscale(pictureBox,dpiScale);
}
调整ControlImagesDPiscale(control.Controls,dpiScale);