我试图创建一个自定义控件,在该控件中我只需将图像绘制到按钮上即可。调整图像大小以填充按钮的区域,同时仍保持图像的原始比例。在设计和运行模式下,我总是出现一些非常奇怪的行为。
在设计人员中,大多数情况下,图像的透明部分为黑色。如果我使控件足够小,则透明区域将开始填充它们在我的屏幕上可以找到的任何随机内容。在运行模式下,透明度始终填充为黑色(请参见下图)。
我觉得我可能以错误的方式使用winforms控件,但是我对此没有太多经验。我尝试了所有在这里找到的建议:Using Graphics.DrawImage() to Draw Image with Transparency/Alpha Channel,以及其他一些我在网上找到的建议,都无济于事。
using System.Windows.Forms;
using System.Drawing;
namespace Tools
{
public class CustomButton : Button
{
public CustomButton()
{
Image = (Image)Properties.Resources.ResourceManager.GetObject("Custom-Logo-Horiz-RGB");
ForeColor = BackColor = Color.FromArgb(88, 88, 88);
DoubleBuffered = true;
}
protected override void OnPaint(PaintEventArgs pevent)
{
DrawCustomImage(pevent.Graphics);
}
private void DrawCustomImage(Graphics graphics)
{
float baseHeight = Image.Height;
float baseWidth = Image.Width;
float maxHeight = (Height - borderWidth * 2);
float maxWidth = (Width - borderWidth * 2);
float newWidth = maxWidth;
float heightToWidth = baseHeight / baseWidth;
float newHeight = heightToWidth * newWidth;
if (newHeight > maxHeight)
{
newHeight = maxHeight;
float widthToHeight = 1 / heightToWidth;
newWidth = widthToHeight * newHeight;
}
graphics.DrawImage(Image, new RectangleF(Width / 2 - newWidth / 2, Height / 2 - newHeight / 2, newWidth, newHeight));
}
#region Settings
private float borderWidth = 6.0F;
public float BorderWidth
{
get { return borderWidth; }
set { borderWidth = value; }
}
#endregion
}
}
图片:
答案 0 :(得分:0)
我建议在base.OnPaint(e)
覆盖中调用OnPaint
,因为除非Button FlatStyle(或者更好的是Button派生的ButtonBase类的样式)类型为FlatStyle.System,则Button控件被视为OwnerDrawn。结果,它是用ControlStyles.UserPaint创建的。这对从ButtonBaseAdapter派生的ButtonBase类分派器绘制控件的方式产生了许多影响,这些分派器决定了呈现样式和内部PaintWorker方法的操作。
此外,正如您在ButtonBase constructor中看到的那样,按钮是用 ControlStyles.Opaque
样式创建的(并且您还可以看到 ControlStyles.OptimizedDoubleBuffer
< / strong>样式)。这意味着Button类不会绘制背景,而是PaintWorker
调用PaintThemedButtonBackground(如果是 Application.RenderWithVisualStyles = true
,则是标准背景),使用为Button类生成的PaintEventArgs
相同(您还可以确定默认情况下启用 DoubleBuffering
)。
因此,如果希望控件正确呈现,则需要在覆盖中调用base.OnPaint(e)
。
对base.OnPaint(e)
的调用还会绘制分配给Image属性的位图(如果有)。
这就是为什么我建议在不设置Image属性的情况下将自己的Bitmap分配给一个字段(或另一个自定义属性)的原因。如果这样做,图像将被绘制两次:一个以您自己的方式绘制,另一个以PaintWorker
绘制。
关于处置非托管对象:
如果从.Net控件派生自定义控件,则实际上不必担心控件本身。全部由内部处理。您可以在我在此处发布的代码中看到使用 protected override void Dispose(bool disposing)
的地方:我将其放在此处,因此您可以看到仅在应用程序关闭时才调用此方法。同样,它是通过将 disposing
参数设置为 false
来调用的:它是Finalizer
的调用对象,对象已经连同其资源一起处置。
在创建对象时,您可能需要照顾自己创建的对象,尤其是Graphics对象:立即处理这些对象,在它们上调用Dispose()或用{{3}声明这些对象},将在其内部创建一个 try/finally
块,将对象放置在finally
部分中。
您可以在此处发布的代码中看到,设置了新图像后,旧图像将立即被处理掉。
using statement方法被重写,以摆脱分配给Field的当前对象,该对象保存您的Button正在显示的位图。这是因为此位图来自嵌入式资源,因此在不再需要它时,最好对其进行废弃处理。
如果您改为创建一个使用非托管资源的类,该类不是从已经处理垃圾回收的另一个类派生的,请实现OnHandledDistroyed接口。
有关此主题的一些文档:
埃里克·利珀特(Eric Lippert)关于垃圾收集和终结处理程序的系列:IDisposable
MSDN:When everything you know is wrong, part one(及后续页面)。
这是一个实现了一些建议的修改后的类:
BackgroundImage
属性被隐藏。
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
[DesignerCategory("code")]
public class PayceButton : Button
{
private Image myImage = null;
private float borderWidth = 6.0F;
public PayceButton() => InitializeComponent();
private void InitializeComponent()
{
this.myImage = Properties.[A Resource Image Name];
this.BackColor = Color.FromArgb(88, 88, 88);
}
public float BorderWidth {
get => borderWidth;
set { borderWidth = value; this.Invalidate(); }
}
public override string Text {
get => string.Empty;
set => base.Text = string.Empty;
}
public new Image Image {
get => this.myImage;
set { this.myImage?.Dispose();
this.myImage = value;
Invalidate();
}
}
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public override Image BackgroundImage {
get => base.BackgroundImage;
set => base.BackgroundImage = null;
}
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
public override ImageLayout BackgroundImageLayout {
get => base.BackgroundImageLayout;
set => base.BackgroundImageLayout = ImageLayout.None;
}
protected override void OnPaint(PaintEventArgs e) {
base.OnPaint(e);
DrawPayceImage(e.Graphics);
}
private void DrawPayceImage(Graphics g)
{
float scale = (Math.Min(this.Height, this.Width) - (borderWidth * 4)) /
Math.Min(myImage.Height, myImage.Width);
var scaledImageSize = new SizeF(this.myImage.Width * scale, myImage.Height * scale);
var imageLocation = new PointF((this.Width - scaledImageSize.Width) / 2,
(this.Height - scaledImageSize.Height) /2);
g.DrawImage(myImage,
new RectangleF(imageLocation, scaledImageSize),
new RectangleF(PointF.Empty, myImage.Size), GraphicsUnit.Pixel);
}
protected override void OnHandleDestroyed(EventArgs e) {
this.myImage?.Dispose();
base.OnHandleDestroyed(e);
}
protected override void Dispose(bool disposing) {
if (disposing) { this.myImage?.Dispose(); }
base.Dispose(disposing);
}
}