The new vbAccelerator Site - more VB and .NET Code and Controls
Source Code
3 Code Libraries Source Code &nbsp

NOTE: this code has been superceded by the version at the new site.


Drawing Borders and Edges using the API DrawEdge Function

[Draw Edge Tester Project]

The DrawEdge function is a useful function provided by the Win32 API. It can draw a variety of the edge styles used to draw buttons, frames and borders around controls and forms. The source code for this article shows you how to:
  • Use the DrawEdge to achieve a variety of edge styles
  • Evaluate the client area within an edge and prevent it being overdrawn

Download the DrawEdge project files (20kb)

DrawEdge is declared as follows:

Private Declare Function DrawEdge Lib "user32" ( _
&nbsp &nbsp ByVal hDC As Long, _
&nbsp &nbsp qrc As RECT, _
&nbsp &nbsp ByVal edge As Long, _
&nbsp &nbsp ByVal grfFlags As Long) As Long

The parameters to it are the hDC of the control, the rectangle in which to draw the edge, then two parameters to determine how the edge is drawn.

  • edge determines what type of edge is drawn. This can be set to show outer and inner border parts, either sunken or raised.
  • grfFlags sets which parts (left, top, right, bottom) of the edge are going to be drawn, and has some additional options which modify how the edge is drawn (although these don't seem to do anything for me!).
See the code below for a list of constants you can use in these parameters (EDEDBorderStyle enumerates settings for edge, EDEDBorderParts the settings for grfFlags).

Once you have drawn an edge, you then normally want to draw any other parts of the control within the edge rather than overdrawing it. The Inner and Outer parts of the border occupy 1 pixel (or TwipsPerPixel twips) each, so it isn't too difficult to work out what the client area within the border is. The EvaluateSize() function below shows how this is done. This function also demonstrates how to make drawing easier by preventing anything from being drawn over the border.

This is done by selecting a 'clipping region' which excludes the border into the device you are drawing into. When the clipping region is in place, Windows will only draw within the clipping region, all other drawing simply disappears. To select a clipping region, first create it with a region creating API function. The sample below uses CreateRectRgn, however you can create regions of any shape you like (see my article A window that's star-shaped, circular or tank-shaped for details on creating more arbitrary shaped regions). Once the region is created, you can make it the clipping region by using SelectClipRgn. To remove the region again, simply select a region handle of zero into the control.

The code below provides a simple UserControl which allows all the DrawEdge options to be exercised, and also demonstrates clipping to prevent drawing over the edge which has been drawn. The code in the download expands on the details below by drawing some text into the control to prove that the edge isn't overwritten. It also demonstrates a couple of other techniques:

  • Providing a neat interface for choosing different edge options using a hierarchical tree (a simple implementation of the control provided in my article A Hierarchy Selector control)
  • Drawing text at any angle - in this case text is drawn at 30 degrees across the control (see also Text at Any Angle for more details)

' ==================================================================
' EDGE Drawing:
' ==================================================================
Public Enum EDEDBorderStyle
&nbsp &nbsp BDR_RAISEDOUTER = 1
&nbsp &nbsp BDR_SUNKENOUTER = 2
&nbsp &nbsp BDR_RAISEDINNER = 4
&nbsp &nbsp BDR_SUNKENINNER = 8
&nbsp &nbsp
&nbsp &nbsp
&nbsp &nbsp
End Enum
Public Enum EDEDBorderParts
&nbsp &nbsp Bf_left = 1
&nbsp &nbsp Bf_Top = 2
&nbsp &nbsp Bf_right = 4
&nbsp &nbsp Bf_bottom = 8
&nbsp &nbsp BF_TOPLEFT = Bf_left Or Bf_Top
&nbsp &nbsp Bf_BOTTOMRIGHT = Bf_right Or Bf_bottom
&nbsp &nbsp BF_RECT = Bf_left Or Bf_Top Or Bf_right Or Bf_bottom
&nbsp &nbsp BF_MIDDLE = &H800
&nbsp &nbsp BF_SOFT = &H1000
&nbsp &nbsp BF_ADJUST = &H2000
&nbsp &nbsp BF_FLAT = &H4000
&nbsp &nbsp BF_MONO = &H8000
End Enum
Private Type RECT
&nbsp &nbsp Left As Long
&nbsp &nbsp Top As Long
&nbsp &nbsp Right As Long
&nbsp &nbsp Bottom As Long
End Type
Private Declare Function DrawEdge Lib "user32" (ByVal hDC As Long, qrc As RECT, ByVal edge As Long, ByVal grfFlags As Long) As Long

' ==================================================================
' Clipping functions:
' ==================================================================
Private Declare Function SelectClipRgn Lib "gdi32" (ByVal hDC As Long, ByVal hRgn As Long) As Long
Private Declare Function CreateRectRgn Lib "gdi32" (ByVal X1 As Long, ByVal y1 As Long, ByVal x2 As Long, ByVal y2 As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long

' ==================================================================
' Client Region:
' ==================================================================
Private m_lLeft As Long
Private m_lTop As Long
Private m_lWidth As Long
Private m_lHeight As Long
Private m_hRgn As Long

' ==================================================================
' Border styles:
' ==================================================================
Private m_lBorderStyle As Long
Private m_lFlags As Long

Public Property Get BackColor() As OLE_COLOR
&nbsp &nbsp BackColor = UserControl.BackColor
End Property

Public Property Let BackColor(ByVal eColor As OLE_COLOR)
&nbsp &nbsp UserControl.BackColor = eColor
&nbsp &nbsp PropertyChanged "BackColor"
&nbsp &nbsp Draw
End Property

Public Property Let BorderPart(ByVal ePart As EDEDBorderParts, ByVal bState As Boolean)
&nbsp &nbsp If (bState) Then
&nbsp &nbsp &nbsp &nbsp m_lFlags = m_lFlags Or ePart
&nbsp &nbsp Else
&nbsp &nbsp &nbsp &nbsp m_lFlags = m_lFlags And Not ePart
&nbsp &nbsp End If
&nbsp &nbsp Draw
&nbsp &nbsp PropertyChanged "BorderPart"
End Property

Public Property Get BorderPart(ByVal ePart As EDEDBorderParts) As Boolean
&nbsp &nbsp BorderPart = BitSet(m_lFlags, ePart)
End Property

Public Property Let BorderStyle(ByVal eStyle As EDEDBorderStyle, ByVal bState As Boolean)
&nbsp &nbsp If (bState) Then
&nbsp &nbsp &nbsp &nbsp m_lBorderStyle = m_lBorderStyle Or eStyle
&nbsp &nbsp Else
&nbsp &nbsp &nbsp &nbsp m_lBorderStyle = m_lBorderStyle And Not eStyle
&nbsp &nbsp End If
&nbsp &nbsp Draw
&nbsp &nbsp PropertyChanged "BorderStyle"
End Property

Public Property Get BorderStyle(ByVal eStyle As EDEDBorderStyle) As Boolean
&nbsp &nbsp BorderStyle = BitSet(m_lBorderStyle, eStyle)
End Property

Private Function BitSet(ByVal lIn As Long, ByVal lBits As Long) As Boolean
&nbsp &nbsp BitSet = ((lIn And lBits) = lBits)
End Function

Private Sub EvaluateSize()
Dim lBSize As Long
&nbsp &nbsp m_lWidth = UserControl.ScaleWidth \ Screen.TwipsPerPixelX
&nbsp &nbsp m_lHeight = UserControl.ScaleHeight \ Screen.TwipsPerPixelY
&nbsp &nbsp m_lLeft = 0
&nbsp &nbsp m_lTop = 0
&nbsp &nbsp If (m_lBorderStyle >= 4) Then
&nbsp &nbsp &nbsp &nbsp lBSize = 2
&nbsp &nbsp ElseIf (m_lBorderStyle >= 1) Then
&nbsp &nbsp &nbsp &nbsp lBSize = 1
&nbsp &nbsp End If
&nbsp &nbsp If (BitSet(m_lFlags, Bf_left)) Then
&nbsp &nbsp &nbsp &nbsp m_lLeft = lBSize
&nbsp &nbsp &nbsp &nbsp m_lWidth = m_lWidth - lBSize
&nbsp &nbsp End If
&nbsp &nbsp If (BitSet(m_lFlags, Bf_Top)) Then
&nbsp &nbsp &nbsp &nbsp m_lTop = lBSize
&nbsp &nbsp &nbsp &nbsp m_lHeight = m_lHeight - lBSize
&nbsp &nbsp End If
&nbsp &nbsp If (BitSet(m_lFlags, Bf_right)) Then
&nbsp &nbsp &nbsp &nbsp m_lWidth = m_lWidth - lBSize
&nbsp &nbsp End If
&nbsp &nbsp If (BitSet(m_lFlags, Bf_bottom)) Then
&nbsp &nbsp &nbsp &nbsp m_lHeight = m_lHeight - lBSize
&nbsp &nbsp End If
&nbsp &nbsp DeleteObject m_hRgn
&nbsp &nbsp m_hRgn = CreateRectRgn(m_lLeft, m_lTop, m_lWidth + m_lLeft, m_lHeight + m_lTop)
&nbsp &nbsp SelectClipRgn UserControl.hDC, m_hRgn
End Sub

Public Sub Draw()
Dim tR As RECT

&nbsp &nbsp ' Clear control:
&nbsp &nbsp If (m_hRgn 0) Then
&nbsp &nbsp &nbsp &nbsp SelectClipRgn UserControl.hDC, 0
&nbsp &nbsp End If
&nbsp &nbsp UserControl.Cls
&nbsp &nbsp
&nbsp &nbsp ' Draw the edge:
&nbsp &nbsp tR.Right = UserControl.ScaleWidth \ Screen.TwipsPerPixelX
&nbsp &nbsp tR.Bottom = UserControl.ScaleHeight \ Screen.TwipsPerPixelY
&nbsp &nbsp DrawEdge UserControl.hDC, tR, m_lBorderStyle, m_lFlags
&nbsp &nbsp
&nbsp &nbsp ' Draw in the 'client area'
&nbsp &nbsp EvaluateSize
&nbsp &nbsp &nbsp &nbsp
&nbsp &nbsp UserControl.Refresh

End Sub

Private Sub UserControl_Initialize()
&nbsp &nbsp m_lBorderStyle = BDR_CONTROL
&nbsp &nbsp m_lFlags = BF_RECT
End Sub

Private Sub UserControl_Paint()
&nbsp &nbsp Draw
End Sub

Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
&nbsp &nbsp m_lBorderStyle = PropBag.ReadProperty("BorderStyle", BDR_CONTROL)
&nbsp &nbsp m_lFlags = PropBag.ReadProperty("BorderPart", BF_RECT)
&nbsp &nbsp BackColor = PropBag.ReadProperty("BackColor", vbButtonFace)
End Sub

Private Sub UserControl_Resize()
&nbsp &nbsp Draw
End Sub

Private Sub UserControl_Terminate()
&nbsp &nbsp If (m_hRgn 0) Then
&nbsp &nbsp &nbsp &nbsp SelectClipRgn UserControl.hDC, 0
&nbsp &nbsp &nbsp &nbsp DeleteObject m_hRgn
&nbsp &nbsp End If
End Sub

Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
&nbsp &nbsp PropBag.WriteProperty "BorderStyle", m_lBorderStyle, BDR_CONTROL
&nbsp &nbsp PropBag.WriteProperty "BorderPart", m_lFlags, BF_RECT
&nbsp &nbsp PropBag.WriteProperty "BackColor", BackColor, vbButtonFace
End Sub

Back to top

Back to Source Code Overview


AboutContributeSend FeedbackPrivacy

Copyright 1998-1999, Steve McMahon ( All Rights Reserved.
Last updated: 21 June 1998