|
Drawing Borders and Edges using the API DrawEdge Function
![[Draw Edge Tester Project]](/images/drawedge.gif)
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" ( _
    ByVal hDC As Long, _
    qrc As RECT, _
    ByVal edge As Long, _
    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
    BDR_RAISEDOUTER = 1
    BDR_SUNKENOUTER = 2
    BDR_RAISEDINNER = 4
    BDR_SUNKENINNER = 8
   
    BDR_BUTTON = BDR_RAISEDINNER Or BDR_RAISEDOUTER
    BDR_CONTROL = BDR_SUNKENINNER Or BDR_SUNKENOUTER
    BDR_THINBUTTON = BDR_RAISEDOUTER
    BDR_THINCONTROL = BDR_SUNKENOUTER
   
    BDR_ETCHRAISE = BDR_RAISEDOUTER Or BDR_SUNKENINNER
    BDR_ETCHINSET = BDR_SUNKENOUTER Or BDR_RAISEDINNER
   
    BDR_ALL = BDR_BUTTON Or BDR_CONTROL
End Enum
Public Enum EDEDBorderParts
    Bf_left = 1
    Bf_Top = 2
    Bf_right = 4
    Bf_bottom = 8
    BF_TOPLEFT = Bf_left Or Bf_Top
    Bf_BOTTOMRIGHT = Bf_right Or Bf_bottom
    BF_RECT = Bf_left Or Bf_Top Or Bf_right Or Bf_bottom
    BF_MIDDLE = &H800
    BF_SOFT = &H1000
    BF_ADJUST = &H2000
    BF_FLAT = &H4000
    BF_MONO = &H8000
    BF_ALL = BF_RECT Or BF_MIDDLE Or BF_SOFT Or BF_ADJUST Or BF_FLAT Or BF_MONO
End Enum
Private Type RECT
    Left As Long
    Top As Long
    Right As Long
    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
    BackColor = UserControl.BackColor
End Property
Public Property Let BackColor(ByVal eColor As OLE_COLOR)
    UserControl.BackColor = eColor
    PropertyChanged "BackColor"
    Draw
End Property
Public Property Let BorderPart(ByVal ePart As EDEDBorderParts, ByVal bState As Boolean)
    If (bState) Then
        m_lFlags = m_lFlags Or ePart
    Else
        m_lFlags = m_lFlags And Not ePart
    End If
    Draw
    PropertyChanged "BorderPart"
End Property
Public Property Get BorderPart(ByVal ePart As EDEDBorderParts) As Boolean
    BorderPart = BitSet(m_lFlags, ePart)
End Property
Public Property Let BorderStyle(ByVal eStyle As EDEDBorderStyle, ByVal bState As Boolean)
    If (bState) Then
        m_lBorderStyle = m_lBorderStyle Or eStyle
    Else
        m_lBorderStyle = m_lBorderStyle And Not eStyle
    End If
    Draw
    PropertyChanged "BorderStyle"
End Property
Public Property Get BorderStyle(ByVal eStyle As EDEDBorderStyle) As Boolean
    BorderStyle = BitSet(m_lBorderStyle, eStyle)
End Property
Private Function BitSet(ByVal lIn As Long, ByVal lBits As Long) As Boolean
    BitSet = ((lIn And lBits) = lBits)
End Function
Private Sub EvaluateSize()
Dim lBSize As Long
    m_lWidth = UserControl.ScaleWidth \ Screen.TwipsPerPixelX
    m_lHeight = UserControl.ScaleHeight \ Screen.TwipsPerPixelY
    m_lLeft = 0
    m_lTop = 0
    If (m_lBorderStyle >= 4) Then
        lBSize = 2
    ElseIf (m_lBorderStyle >= 1) Then
        lBSize = 1
    End If
    If (BitSet(m_lFlags, Bf_left)) Then
        m_lLeft = lBSize
        m_lWidth = m_lWidth - lBSize
    End If
    If (BitSet(m_lFlags, Bf_Top)) Then
        m_lTop = lBSize
        m_lHeight = m_lHeight - lBSize
    End If
    If (BitSet(m_lFlags, Bf_right)) Then
        m_lWidth = m_lWidth - lBSize
    End If
    If (BitSet(m_lFlags, Bf_bottom)) Then
        m_lHeight = m_lHeight - lBSize
    End If
    DeleteObject m_hRgn
    m_hRgn = CreateRectRgn(m_lLeft, m_lTop, m_lWidth + m_lLeft, m_lHeight + m_lTop)
    SelectClipRgn UserControl.hDC, m_hRgn
End Sub
Public Sub Draw()
Dim tR As RECT
    ' Clear control:
    If (m_hRgn 0) Then
        SelectClipRgn UserControl.hDC, 0
    End If
    UserControl.Cls
   
    ' Draw the edge:
    tR.Right = UserControl.ScaleWidth \ Screen.TwipsPerPixelX
    tR.Bottom = UserControl.ScaleHeight \ Screen.TwipsPerPixelY
    DrawEdge UserControl.hDC, tR, m_lBorderStyle, m_lFlags
   
    ' Draw in the 'client area'
    EvaluateSize
       
    UserControl.Refresh
End Sub
Private Sub UserControl_Initialize()
    m_lBorderStyle = BDR_CONTROL
    m_lFlags = BF_RECT
End Sub
Private Sub UserControl_Paint()
    Draw
End Sub
Private Sub UserControl_ReadProperties(PropBag As PropertyBag)
    m_lBorderStyle = PropBag.ReadProperty("BorderStyle", BDR_CONTROL)
    m_lFlags = PropBag.ReadProperty("BorderPart", BF_RECT)
    BackColor = PropBag.ReadProperty("BackColor", vbButtonFace)
End Sub
Private Sub UserControl_Resize()
    Draw
End Sub
Private Sub UserControl_Terminate()
    If (m_hRgn 0) Then
        SelectClipRgn UserControl.hDC, 0
        DeleteObject m_hRgn
    End If
End Sub
Private Sub UserControl_WriteProperties(PropBag As PropertyBag)
    PropBag.WriteProperty "BorderStyle", m_lBorderStyle, BDR_CONTROL
    PropBag.WriteProperty "BorderPart", m_lFlags, BF_RECT
    PropBag.WriteProperty "BackColor", BackColor, vbButtonFace
End Sub
Back to top
Back to Source Code Overview
|
  |