要获得与background-size: cover
CSS 中的模式类似的行为,您可以编写自己的派生PictureBox
类,并重写该OnPaint
方法以实现您自己的自定义大小调整行为。
下面展示了一个我为此编写的自定义 PictureBox 实现,它具有“封面”和“适合”模式。该类具有设计器支持,因此可以在设计器中轻松更改属性,其结果将在视图中可见。请阅读以下说明以获取更多信息。
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
// Source: https://stackoverflow.com/a/67452837/192077
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();
}
}
// When changing the Padding property in the designer nothing seems to happen by default. Since our custom
// control depends on the Padding property, we want the designer to repaint the control whenever its
// value is changed, so we override the property and call Invalidate() in the setter to account for this.
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 窗体应用程序时,我还需要像 CSS 这样的“覆盖”行为,因此我决定编写自己的 PictureBox 实现。此类ResponsivePictureBox
有一个名为的新属性,ExtendedSizeMode
它可以是或。封面模式模仿了 CSS 封面模式,并且 fit 类似于默认的 PictureBox “缩放”模式,但会尽可能以原始大小显示图像。Cover
Fit
Off
此外,当ExtendedSizeMode
使用时,新ExtendedImageAlign
属性会将图像对齐在适当的角落。
此类还有一个InterpolationMode
andPixelOffsetMode
属性,可让您进一步优化/自定义渲染。这是基于此处提供的帖子。
当ExtendedSizeMode
设置为Off
时,PictureBox 将正常运行,除了InterpolationMode
和PixelOffsetMode
也将在默认模式下工作。
默认Padding
属性对适合和覆盖模式也有影响,允许您在 PictureBox 内偏移图像。
附带说明:代码远非完美,因此请随时报告任何错误或进一步改进!