要实现类似 CSS 中 background-size: cover
的行为,您可以编写自己的派生 PictureBox
类,并覆盖 OnPaint
方法以实现自定义大小调整行为。
下面提供了我编写的自定义 PictureBox 实现,它具有“覆盖”和“适应”模式。该类具有设计器支持,因此属性可以在设计器中轻松更改,其结果将在视图中可见。请阅读下面的注释获取额外信息。
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace System.Windows.Forms.Derived
{
public enum ExtendedPictureBoxSizeMode
{
Off = 0,
Cover = 1,
Fit = 2
}
public class ResponsivePictureBox : PictureBox
{
private ExtendedPictureBoxSizeMode extendedSizeMode = ExtendedPictureBoxSizeMode.Off;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(ExtendedPictureBoxSizeMode.Off)]
[Category("Behavior")]
public ExtendedPictureBoxSizeMode ExtendedSizeMode
{
get => extendedSizeMode;
set
{
extendedSizeMode = value;
Invalidate();
}
}
private ContentAlignment extendedImageAlign = ContentAlignment.MiddleCenter;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(ContentAlignment.MiddleCenter)]
[Category("Behavior")]
public ContentAlignment ExtendedImageAlign
{
get => extendedImageAlign;
set
{
extendedImageAlign = value;
Invalidate();
}
}
private InterpolationMode interpolationMode = InterpolationMode.Default;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(InterpolationMode.Default)]
[Category("Behavior")]
public InterpolationMode InterpolationMode
{
get => interpolationMode;
set
{
if (value == InterpolationMode.Invalid)
return;
interpolationMode = value;
Invalidate();
}
}
private PixelOffsetMode pixelOffsetMode = PixelOffsetMode.Default;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
[DefaultValue(PixelOffsetMode.Default)]
[Category("Behavior")]
public PixelOffsetMode PixelOffsetMode
{
get => pixelOffsetMode;
set
{
if (value == PixelOffsetMode.Invalid)
return;
pixelOffsetMode = value;
Invalidate();
}
}
public new Padding Padding
{
get => base.Padding;
set
{
base.Padding = value;
Invalidate();
}
}
protected override void OnPaint(PaintEventArgs pe)
{
pe.Graphics.InterpolationMode = InterpolationMode;
pe.Graphics.PixelOffsetMode = PixelOffsetMode;
if (ExtendedSizeMode == ExtendedPictureBoxSizeMode.Off || Image == null)
{
base.OnPaint(pe);
return;
}
switch (ExtendedSizeMode)
{
case ExtendedPictureBoxSizeMode.Cover:
PaintCovered(pe);
return;
case ExtendedPictureBoxSizeMode.Fit:
PaintFitted(pe);
return;
}
}
private void PaintFitted(PaintEventArgs pe)
{
Rectangle rect = DeflateRect(ClientRectangle, Padding);
if (rect.Height <= 0 || rect.Width <= 0) return;
Image img = Image;
int w, h;
if (img.Width > rect.Width || img.Height > rect.Height)
{
if ((double)img.Width / img.Height > (double)rect.Width / rect.Height)
{
w = rect.Width;
h = (int)((double)img.Height / img.Width * rect.Width);
}
else
{
w = (int)((double)img.Width / img.Height * rect.Height);
h = rect.Height;
}
}
else
{
w = img.Width;
h = img.Height;
}
rect = GetAlignedContentRect(rect, w, h, ExtendedImageAlign);
pe.Graphics.DrawImage(img, rect);
}
private void PaintCovered(PaintEventArgs pe)
{
Rectangle rect = DeflateRect(ClientRectangle, Padding);
if (rect.Height <= 0 || rect.Width <= 0) return;
Image img = Image;
int w, h;
if ((double)img.Width / img.Height > (double)rect.Width / rect.Height)
{
w = (int)((double)rect.Width / rect.Height * img.Height);
h = img.Height;
}
else
{
w = img.Width;
h = (int)((double)rect.Height / rect.Width * img.Width);
}
Rectangle imageRect = new Rectangle(0, 0, img.Width, img.Height);
Rectangle portion = GetAlignedContentRect(imageRect, w, h, ExtendedImageAlign);
pe.Graphics.DrawImage(img, rect, portion, GraphicsUnit.Pixel);
}
private static Rectangle GetAlignedContentRect(Rectangle containerRect, int contentW, int contentH, ContentAlignment imageAlign)
{
int containerW = containerRect.Width;
int containerH = containerRect.Height;
int x = (containerW - contentW) / 2;
int y = (containerH - contentH) / 2;
switch (imageAlign)
{
case ContentAlignment.TopLeft:
x = y = 0;
break;
case ContentAlignment.TopCenter:
y = 0;
break;
case ContentAlignment.TopRight:
x = containerW - contentW;
y = 0;
break;
case ContentAlignment.MiddleRight:
x = containerW - contentW;
break;
case ContentAlignment.BottomRight:
x = containerW - contentW;
y = containerH - contentH;
break;
case ContentAlignment.BottomCenter:
y = containerH - contentH;
break;
case ContentAlignment.BottomLeft:
x = 0;
y = containerH - contentH;
break;
case ContentAlignment.MiddleLeft:
x = 0;
break;
}
return new Rectangle(containerRect.X + x, containerRect.Y + y, contentW, contentH);
}
public static Rectangle DeflateRect(Rectangle rect, Padding padding)
{
rect.X += padding.Left;
rect.Y += padding.Top;
rect.Width -= padding.Horizontal;
rect.Height -= padding.Vertical;
return rect;
}
}
}
注意事项
在开发Windows Forms应用程序时,我需要类似CSS的“覆盖”行为,因此我决定编写自己的PictureBox实现。这个ResponsivePictureBox
类有一个新属性叫做ExtendedSizeMode
,它可以是Cover
、Fit
或Off
。覆盖模式模仿了CSS覆盖模式,适合于默认PictureBox“缩放”模式,但无论何时都会尽可能显示图像的原始大小。
此外,当使用ExtendedSizeMode
时,新的ExtendedImageAlign
属性将把图像对齐到适当的角落。
这个类还有一个InterpolationMode
和PixelOffsetMode
属性,允许您进一步优化/自定义渲染。这基于这里展示的帖子。
当ExtendedSizeMode
设置为Off
时,PictureBox会正常工作,除了InterpolationMode
和PixelOffsetMode
也会按照默认模式工作。
默认的Padding
属性对于适应和覆盖模式都有影响,允许您在PictureBox内部偏移图像。
![Designer view](https://istack.dev59.com/k5Eb7.webp)
作为一则附注:这段代码远非完美,欢迎报告任何错误或进一步改进!