I suggested to call base.OnPaint(e)
in the OnPaint
override because, unless the Button FlatStyle (or, better, the style of the ButtonBase class from which Button derives) is of type FlatStyle.System, the Button control is considered OwnerDrawn. As a consequence, it's created with ControlStyles.UserPaint. This has a number of consequences in the way the control is drawn by the ButtonBase class dispatchers derived from ButtonBaseAdapter which decide the rendering style and the actions of the internal PaintWorker methods.
Plus, as you can see in the ButtonBase constructor, Buttons are created with ControlStyles.Opaque
style (and you can also see that ControlStyles.OptimizedDoubleBuffer
style is used). This means that the Button class doesn't draw it's background, it's the PaintWorker
that calls PaintThemedButtonBackground (if Application.RenderWithVisualStyles = true
, otherwise the standard background), using the same PaintEventArgs
generated for the Button class (you can also determine that DoubleBuffering
is enabled by default).
As a consequence, you need to call base.OnPaint(e)
in the override if you want the control to render properly.
The call to base.OnPaint(e)
also draws the bitmap assigned to the Image property, if any.
That's why I suggested to assign your own Bitmap to a field (or another custom property), without setting the Image property. If you do, the Image will be painted twice: one on your own terms and the other by the PaintWorker
.
About disposing of the unmanaged object:
If you derive a Custom Control from a .Net control, you don't really need to worry that much about the control itself. It's all handled internally. You can see in the code I posted here that protected override void Dispose(bool disposing)
is used: I put it there so you can see that this method is only called when the application closes; also, it's called with the disposing
parameter set to false
: it's the Finalizer
that's calling it, the object has already been disposed of, its resources along with it.
You may want to take care of the object you create, especially the Graphics object, when you create them: dispose of these objects right away, calling Dispose() on them or declaring these objects with a using statement, which under the hood will create a try/finally
block, disposing of the object in the finally
section.
You can see in the code posted here, that when a new Image is set, the old one is disposed of right away.
The OnHandledDistroyed method is overridden, to get rid of the current object assigned to Field that holds the Bitmap your Button is displaying. This because this Bitmap comes from an embedded resource, better ddispose of it as soon as it's not needed anymore.
If you instead create a class that uses unmanaged resources, which doesn't derive from another that already handles garbage collection, then implement the IDisposable interface.
Some documents on the subject:
Eric Lippert's series on Garbage collection and finalizers: When everything you know is wrong, part one
MSDN: Implementing a Dispose method (and following pages).
Here's a modified class that implements some of the suggestions:
- Note that a private Field is used to substitute the Image property: the Image property will be null (and not painted), while the property is still accessible in the Designer and you can assign another Image without compromising the result.
- The old Image, if any, is disposed each time is substituted with a new one.
- The
BackgroundImage
property is instead hidden.
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);
}
}