custom button control

Anti-Rich

Well-known member
Joined
Jul 1, 2006
Messages
325
Location
Perth, Australia
Programming Experience
1-3
hi all,


i have a problem with a custom control i am creating... now i have a custom gradient background that paints when the button is used, the only problem is, it clears the text when it paints the rectangle!! i know why this happens, but i dont know the overrides method to use to set the text.

could someone please help me? i have posted my class, and what i have done so far.

cheers all :)

adam

VB.NET:
Namespace Custom
Public Class Button : Inherits System.Windows.Forms.Button
#Region " Overriding Subs "
Protected Overrides Sub OnCreateControl()
    MyBase.OnCreateControl()
End Sub
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
   Dim b As Brush = New Drawing2D.LinearGradientBrush(e.ClipRectangle, Color.LightBlue, Color.DarkBlue, Drawing2D.LinearGradientMode.Vertical)
   e.Graphics.FillRectangle(b, e.ClipRectangle)
End Sub
 
Protected Overrides Sub OnPrint(ByVal e As System.Windows.Forms.PaintEventArgs)
   MyBase.OnPrint(e)
End Sub
#End Region
Public Sub New()
   InitializeComponent()
End Sub
Private Sub InitializeComponent()
 
End Sub
End Class
End Namespace
 
You need to draw the text everytime the button repaints. So in the Overridden Paint event you will also need..

VB.NET:
//Sformat not completely necessary but i always use it.
 
Dim Sformat as new StringFormat
SFormat.Trimming = System.Drawing.StringTrimming.EllipsisCharacter
SFormat.LineAlignment = System.Drawing.StringAlignment.Center
Sformat.Alignment = System.Drawing.StringAlignment.Center
SFormat.FormatFlags = System.Drawing.StringFormatFlags.NoWrap
 
Using TextBrush as New SolidBrush(me.Forecolor)
e.graphics.drawstring(Me.Text,Me.Font, TextBrush, Me.ClientRectangle,Sformat)
End Using


You are going to want to put the following in the contructor, to avoid flickering.

VB.NET:
Mybase.SetStyle(ControlStyles.AllPaintingInWMPaint Or 
ControlStyles.OptimizedDoubleBuffer Or ControlStyles.UserPaint,True)
 
cheers for that vis, that was a great help!!!

i have another question, my button renders without a border. now i thought to do this i would need to use the DrawPath method, using the same RoundedRectangle method (with the graphics path returned) described above, the only problem is, it doesnt render all of the border, only one or two lines, or it renders the border as a normal rectangle...

how do i get it to render the shape of the rounded rectangle? i have tried everything i could think of and im just stumped!

cheers
adam
 
For some reason, and don't ask me why When MS did this whole framework thing they made the client rectangle property a little to big. So when you are creating graphicspaths that are the size of your control you need to create a slightly smaller rectangle first.

VB.NET:
Dim Rct as rectangle = me.clientrectangle
 
Rct.Width-=1
Rct.Height-=1


The use this rectangle to create your graphics path and use the drawpath method to draw round it and it will work just fine.
 
i have now run intoa bit of a weird problem, which i cant explain. when i drag the window around the mdi container it looks like the controls dont repaint properly... i have attached a sample to show what i mean.

is there anyway to fix this? i never see the windows buttons do this...

cheers all.
adam
 

Attachments

  • sample4.JPG
    sample4.JPG
    5.1 KB · Views: 75
VB.NET:
MyBase.SetStyle(ControlStyles.AllPaintingInWMPaint , True)
MyBase.SetStyle(ControlStyles.OptimizedDoubleBuffer , True)
MyBase.SetStyle(ControlStyles.UserPaint,True)
MyBAse.SetStyle(ControlStyles.ResizeRedraw, True)

Have you set the above in the constructor? If you have it might be worth showing the code you are using.
 
cheers for the response vis,

as it turns out, i was missing resizeredraw, but it still does that weird thing i described above...


so here is the code for my onpaint method...

VB.NET:
e.Graphics.Clear(Me.BackColor)
Dim g As Graphics = e.Graphics
g.SmoothingMode = SmoothingMode.AntiAlias
Dim f As Font = New Font("Tahoma", 12, FontStyle.Bold, GraphicsUnit.Pixel)
'base rectangle graphics path
Dim cPath As GraphicsPath = RoundRect(e.ClipRectangle, 20)
'reflection ellipse
'Dim Ellipse3 As System.Drawing.Rectangle = New System.Drawing.Rectangle(2, 1, Me.Width, 8)
Dim Ellipse3 As System.Drawing.Rectangle = New System.Drawing.Rectangle(CInt(Me.Width / 2), 1, CInt((Me.Width / 4)), 8)
'to customize the position of the above ellipse i would need to change the x and y values 
'to near the corner of the control
'base and reflection gradients
Dim bGrad As New LinearGradientBrush(e.ClipRectangle, Color.DarkBlue, Color.LightBlue, 90.0!)
Dim rGrad As New LinearGradientBrush(e.ClipRectangle, Color.White, Color.LightBlue, 270.0!)
 
 
Dim sbGrad As New LinearGradientBrush(e.ClipRectangle, Color.LightBlue, Color.Blue, 90.0!)
'Dim srGrad As New LinearGradientBrush(e.ClipRectangle, Color.White, Color.Orchid, 270.0!)
'draw basic control and gradients
If Selected Then
g.FillPath(sbGrad, cPath)
Else
g.FillPath(bGrad, cPath)
End If
 
' Get the location to set the Text.
Dim ptSize As SizeF = e.Graphics.MeasureString(Text, f)
Dim ptF As New PointF
Select Case Me.TextAlign
Case ContentAlignment.BottomCenter
ptF.Y = Me.Height
ptF.Y -= ptSize.Height
ptF.X = Convert.ToInt32(Me.Width / 2)
ptF.X -= ptSize.Width / 2
Case ContentAlignment.BottomLeft
ptF.Y = Me.Height
ptF.Y -= ptSize.Height
ptF.X = 2
Case ContentAlignment.BottomRight
ptF.Y = Me.Height
ptF.Y -= ptSize.Height
ptF.X = Me.Width
ptF.X -= ptSize.Width
Case ContentAlignment.MiddleCenter
ptF.Y = Convert.ToInt32(Me.Height / 2)
ptF.Y -= ptSize.Height / 2
ptF.X = Convert.ToInt32(Me.Width / 2)
ptF.X -= ptSize.Width / 2
Case ContentAlignment.MiddleLeft
ptF.Y = Convert.ToInt32(Me.Height / 2)
ptF.Y -= ptSize.Height / 2
ptF.X = 0
Case ContentAlignment.MiddleRight
ptF.Y = Convert.ToInt32(Me.Height / 2)
ptF.Y -= ptSize.Height / 2
ptF.X = Me.Width
ptF.X -= ptSize.Width
Case ContentAlignment.TopCenter
ptF.Y = 0
ptF.X = Convert.ToInt32(Me.Width / 2)
ptF.X -= ptSize.Width / 2
Case ContentAlignment.TopLeft
ptF.Y = 0
ptF.X = 0
Case ContentAlignment.TopRight
ptF.Y = 0
ptF.X = Me.Width
ptF.X -= ptSize.Width
End Select
g.DrawString(Me.Text, f, Brushes.White, ptF)

and here is the roundrect function...

VB.NET:
[SIZE=2][/SIZE]Dim gp As New Drawing2D.GraphicsPath
Dim p1 As New Point(rct.Right, rct.Top + 10)
Dim p2 As New Point(rct.Right, rct.Bottom)
'top line
gp.AddLine((rct.Left + 10), (rct.Top), (rct.Right - 10), (rct.Top))
'gp.AddLine(p1, p2)
'top right arc
gp.AddArc(rct.X + rct.Width - radius, rct.Y, radius, radius, 270, 90)
'bottom right arc
gp.AddArc(rct.X + rct.Width - radius, rct.Y + rct.Height - radius, radius, radius, 0, 90)
'bottom left arc
gp.AddArc(rct.X, rct.Y + rct.Height - radius, radius, radius, 90, 90)
'top left arc
gp.AddArc(rct.X, rct.Y, radius, radius, 180, 90)
gp.CloseFigure()
Return gp

any help would be greatly appreciated.

cheers
adam
 
There doesn't seem to be a whole lot wrong there. Can you upload the project so i can take a look. You could also try putting

Me.Invalidate


In the overriden OnMove event. But you shouldn't have to, to get it to work.
 
hi vis, sorry about the lack of response recently, other parts of the project have taken priority over button. (even though the button is more fun! :p)

ok, i figured out a better way to describe the situation...

i run the program, and start a form. i have a datetimepicker, which when choosing a date, the minicalendar that displays blocks part of the button. when i choose a date on the picker, and the mini calendar goes away, it redraws the control incorrectly, or maybe a better way to describe it would be to say it overlays another rounded rectangle over it...

there are pictures attached to show what i mean at steps 1, 2 and 3. note the reset button in 3.

cheers mate
regards
adam
 

Attachments

  • 1.JPG
    1.JPG
    7.8 KB · Views: 36
  • 2.JPG
    2.JPG
    17.1 KB · Views: 37
  • 3.JPG
    3.JPG
    5.9 KB · Views: 36
hi vis,

i think i may have fixed it....


in the defwndproc override i put

VB.NET:
protected override void DefWndProc(ref Message m)
{
base.DefWndProc(ref m);
Refresh();
}
it now seems to work perfectly!

the only problem being i have noticed a drop in response time when the button changes color on the MouseEnter event (3 buttons look like they can be selected at once!)... do you know of a quicker method than what i have done above? ie. a better method to be the Refresh() call in?

cheers
adam
 
success!!!

well, it seems after a bit more digging around i came across this little gem on the internet!!

http://www.apress.com/ApressCorporate/supplement/1/107/159059035X-534.pdf

at page 188 it lists some slightly more advanced uses of the Invalidate procedure! including the way to invalidate a single region, rather than simple vanilla Invalidate() which re-renders everything!

this has fixed my control completely, and it now works as its supposed to. cheers for everyone's input on this thread, it has been much appreciated.


regards
adam
 

Latest posts

Back
Top