一、首先创建一个上传文件的类,继承Control类,如下:
public class UploadControl : Control
{
private Image _image;
public UploadControl()
{
this.SetStyle(
ControlStyles.UserPaint | //控件自行绘制,而不使用操作系统的绘制
ControlStyles.AllPaintingInWmPaint | //忽略背景擦除的Windows消息,减少闪烁,只有UserPaint设为true时才能使用。
ControlStyles.OptimizedDoubleBuffer |//在缓冲区上绘制,不直接绘制到屏幕上,减少闪烁。
ControlStyles.ResizeRedraw | //控件大小发生变化时,重绘。
ControlStyles.SupportsTransparentBackColor, //支持透明背景颜色
true);
_image =Properties.Resources.upload;
this.Cursor = Cursors.Hand;
this.AllowDrop = true;
}
}
准备好上传的图片
二、我们需要绘制圆角矩形,所以先准备一个圆角路径,如下:
private GraphicsPath GetRoundedRectPath(Rectangle rect, uint radius)
{
int r = (int)radius << 1;
Rectangle arcRect = new Rectangle(rect.Location, new Size(r, r));
GraphicsPath path = new GraphicsPath();
path.AddArc(arcRect, 180, 90);
// 右上圆弧
arcRect.X = rect.Right - r;
path.AddArc(arcRect, 270, 90);
// 右下圆弧
arcRect.Y = rect.Bottom - r;
path.AddArc(arcRect, 0, 90);
// 左下圆弧
arcRect.X = rect.Left;
path.AddArc(arcRect, 90, 90);
path.CloseFigure();
return path;
}
三、重写OnPaint事件,绘制填充圆角背景
Rectangle outRect = new Rectangle(0, 0, this.Width, this.Height);
using var outPath = GetRoundedRectPath(outRect, Radius);
this.Region = new Region(path);
using SolidBrush b = new SolidBrush(this.BackColor);
e.Graphics.FillPath(b, outPath);
效果如下:
发现使用Region属性有锯齿,尽管开启高质量绘图也无用,我们去掉Region属性,但是新的问题出现,此控件的背景必须和父级窗体的背景一致,不然圆角效果就没有了。后续我们都是不设置Region属性,保持背景与父级控件背景一致就行。
四、在OnPaint事件,绘制圆角边框
var innerRect = Rectangle.Inflate(outRect, -(int)border, -(int)border);
using GraphicsPath innerPath = GetRoundedRectPath(innerRect, Radius - border);
using Pen pen = new Pen(ColorState switch
{
ControlState.Hover => this.BorderHoverColor,
ControlState.Pressed => this.BorderPressedColor,
ControlState.DragEnter => this.BorderHoverColor,
_ => this.BorderNormalColor,
}, border);
pen.DashStyle = BorderStyle;
e.Graphics.DrawPath(pen, innerPath);
效果如下:
四、在OnPaint事件,绘制上传图像以及文字
const int h = 5;
SizeF sizeF = SizeF.Empty;
if (!string.IsNullOrWhiteSpace(Text))
sizeF = e.Graphics.MeasureString(Text, this.Font);
if (_image != null)
{
int imageH = 0;
if (sizeF != SizeF.Empty)
{
imageH = (int)((this.Height - _image.Height - h - sizeF.Height) / 2);
TextRenderer.DrawText(e.Graphics, Text, this.Font, new Point((int)((this.Width - sizeF.Width) / 2), imageH + _image.Height + h), this.ForeColor);
}
else
imageH = this.Height - _image.Height >> 1;
e.Graphics.DrawImage(_image, this.Width - _image.Width >> 1, imageH);
}
else
{
if (sizeF != SizeF.Empty)
{
TextRenderer.DrawText(e.Graphics, Text, this.Font, new Point((int)((this.Width - sizeF.Width) / 2), (int)((this.Height - sizeF.Height) / 2)), this.ForeColor);
}
}
效果如下:
五、我们定义一个枚举,用来实现鼠标移、出移入、点击、文件拖动让边框变色
public enum ControlState { Hover, Normal, Pressed, DragEnter }
然后重写OnMouseEnter、OnMouseLeave、OnMouseDown、OnMouseUp事件,如下:
private ControlState ColorState { get; set; }= ControlState.Normal;
protected override void OnMouseEnter(EventArgs e)//鼠标进入时
{
ColorState = ControlState.Hover;//Hover
this.Invalidate();
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(EventArgs e)//鼠标离开
{
ColorState = ControlState.Normal;//正常
this.Invalidate();
base.OnMouseLeave(e);
}
protected override void OnMouseDown(MouseEventArgs e)//鼠标按下
{
if (e.Button == MouseButtons.Left && e.Clicks == 1)//鼠标左键且点击次数为1
{
ColorState = ControlState.Pressed;//按下的状态
this.Invalidate();
using OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "所有文件 (*.*)|*.*";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
FilesCallback?.Invoke(new string[] { openFileDialog.FileName });
}
}
base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseEventArgs e)//鼠标弹起
{
if (e.Button == MouseButtons.Left && e.Clicks == 1)
{
if (ClientRectangle.Contains(e.Location))//控件区域包含鼠标的位置
{
ColorState = ControlState.Hover;
}
else
{
ColorState = ControlState.Normal;
}
this.Invalidate();
}
base.OnMouseUp(e);
}
就是在OnPaint事件中笔的颜色代码:
ColorState switch
{
ControlState.Hover => this.BorderHoverColor,
ControlState.Pressed => this.BorderPressedColor,
ControlState.DragEnter => this.BorderHoverColor,
_ => this.BorderNormalColor,
}
当然我们在OnMouseDown事件中还实现了点击选择文件:
using OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "所有文件 (*.*)|*.*";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
FilesCallback?.Invoke(new string[] { openFileDialog.FileName });
}
其中FilesCallback是一个事件:
public event Action<string[]> FilesCallback;
六、实现文件拖动上传
protected override void OnDragEnter(DragEventArgs drgevent)
{
base.OnDragEnter(drgevent);
if (drgevent.Data.GetDataPresent(DataFormats.FileDrop))
drgevent.Effect = DragDropEffects.Copy;
else
drgevent.Effect = DragDropEffects.None;
ColorState = ControlState.DragEnter;
this.Invalidate();
}
protected override void OnDragLeave(EventArgs e)
{
base.OnDragLeave(e);
ColorState = ControlState.Normal;
this.Invalidate();
}
protected override void OnDragDrop(DragEventArgs drgevent)
{
base.OnDragDrop(drgevent);
ColorState = ControlState.Normal;
this.Invalidate();
object? obj = drgevent.Data?.GetData(DataFormats.FileDrop);
if (obj == null) return;
FilesCallback?.Invoke((string[])obj);
}
同时需要在构造函数中开启AllowDrop:
this.AllowDrop = true;
其它代码未在这里展示详情请见:
https://gitee.com/feng-cai/Seal-Bubbles