.NET(WinForms,而不是 WPF)中的 CSS3 函数 repeating-linear-gradient() 是否有任何替代(模拟)? 我需要以 45 度角绘制重复的“斑马条纹”(例如红、蓝、绿、红、蓝、绿……)。
更新: 按照吉米的建议,我只部分解决了问题:
private void DrawRepeatingStripes(int degree, int stripeWidth, Color[] colors, Rectangle rect, Graphics graphics)
{
using (var img = new Bitmap(colors.Length * stripeWidth, rect.Height))
{
using (var g = Graphics.FromImage(img))
{
for (int i = 0; i < colors.Length; i++)
{
// TODO: cache SolidBrush
g.FillRectangle(new SolidBrush(colors[i]), stripeWidth * i, 0, stripeWidth, rect.Height);
}
}
using (var tb = new TextureBrush(img, WrapMode.Tile))
{
using (var myMatrix = new Matrix())
{
myMatrix.Rotate(degree);
graphics.Transform = myMatrix;
graphics.FillRectangle(tb, rect);
graphics.ResetTransform();
}
}
}
}
用法(以某种形式的代码):
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
DrawRepeatingStripes(45, 10, new Color[] { Color.Red, Color.Yellow, Color.Green }, e.ClipRectangle, e.Graphics);
}
问题是旋转是......好吧,一个旋转,所以矩形的一部分充满了条纹,而部分是空的。不知道如何解决:(
有关使用 TextureBrush 填充用作画布的控件表面的示例。
LinearRepeatingGradient
类公开了一个可绑定的 ColorBands
属性(类型为 BindingList<ColorBand>
),允许添加或删除 ColorBand
对象,该记录定义了要生成的每个带的颜色和大小。
RotationAngle
属性指定应用于渲染的旋转。
在用作画布的 Control 的
Paint
事件中,调用 Fill(Graphics g)
方法,并传递 e.Graphics
参数提供的 PaintEventArgs
对象。ColorBands
属性的内容生成一个新的位图。90
时,画布的尺寸会膨胀其对角线的三分之一(作为距非旋转矩形的最大距离)。由于此测试示例是使用 .NET 7 构建的,因此我使用
record
来存储色带的设置。您可以将其替换为类对象,而无需更改其余代码。
public record ColorBand(Color Color, int Size) {
public override string ToString() => $"Color: {Color.Name} Size: {Size}";
}
using
声明代替using
声明
using System.Drawing;
using System.Drawing.Drawing2D;
public class LinearRepeatingGradient
{
public LinearRepeatingGradient(float rotation = .0f)
{
ColorBands = new BindingList<ColorBand>();
RotationAngle = rotation;
}
public float RotationAngle { get; set; }
[Bindable(true), ListBindable(BindableSupport.Default)]
public BindingList<ColorBand> ColorBands { get; }
public void Fill(Graphics g) => Fill(g, g.ClipBounds);
public void Fill(Graphics g, Rectangle fillArea) => Fill(g, new RectangleF(fillArea.Location, fillArea.Size));
protected virtual void Fill(Graphics g, RectangleF display)
{
if (ColorBands is null || ColorBands.Count == 0 || g.Clip.IsInfinite(g)) return;
var canvas = InflateCanvas(display);
var centerPoint = new PointF(canvas.X + canvas.Width / 2, canvas.Y + canvas.Height / 2);
using var texture = GetTexture(canvas.Width);
if (texture is null) return;
using var brush = new TextureBrush(texture, WrapMode.Tile);
using var mx = new Matrix();
mx.RotateAt(RotationAngle, centerPoint);
g.Transform = mx;
g.FillRectangle(brush, canvas);
g.ResetTransform();
}
private RectangleF InflateCanvas(RectangleF rect)
{
if (RotationAngle % 90.0f == 0) return rect;
float maxInflate = (float)Math.Sqrt(Math.Pow(rect.X - rect.Right, 2) +
Math.Pow(rect.Y - rect.Bottom, 2)) / 3.0f;
var canvas = rect;
canvas.Inflate(maxInflate, maxInflate);
return canvas;
}
private Bitmap? GetTexture(float width)
{
int height = ColorBands!.Sum(c => c.Size);
if (height <= 0) return null;
var texture = new Bitmap((int)(width + .5f), height);
int startPosition = 0;
using var g = Graphics.FromImage(texture);
for (int i = 0; i < ColorBands!.Count; i++) {
var rect = new Rectangle(0, startPosition, texture.Width, ColorBands![i].Size);
using var brush = new SolidBrush(ColorBands![i].Color);
g.FillRectangle(brush, rect);
startPosition += ColorBands![i].Size;
}
return texture;
}
}
这就是它的工作原理:
由于
ColorBands
属性是可绑定的,因此当添加或删除 ColorBand
对象并将 ColorBands
集合绑定到 Controls 时,您可以使用数据绑定来执行操作,如动画所示:
public partial class SomeForm : Form {
LinearRepeatingGradient gradient = new();
public SomeForm()
{
InitializeComponent();
[Your DataGridView].DataSource = gradient.ColorBands;
gradient.ColorBands.ListChanged += (s, e) => someControl.Invalidate();
}
private void someControl_Paint(object sender, PaintEventArgs e) =>
gradient.Fill(e.Graphics);
因此,当您添加新的
ColorBand
(或删除它)时,内部集合会发生变化,并且用作画布的控件将失效,显示新的填充:
gradient.ColorBands.Add(new ColorBand(Color.Red, 45f));
RotationAngle
属性不使用数据绑定,因此在更改画布时必须手动使其无效。您当然可以更改它并使该属性可绑定:
gradient.RotationAngle = 215f;
someControl.Invalidate();