Home > Code Library > SmartEdge Library

Last Update 29 Oct 2001









SmartEdge Library

About this page

This article describes how to modify your controls and make them have up to 7 different border styles.

You can also download the library and use it in your VB projects.

Download SmartEdge

How to change border styles

How many times have you wanted to change a control border style? Controls in Visual Basic normally come with only 2 border styles, sometimes three, maybe four, but they never have the ability of displaying ANY border style. This may seem a small problem when trying to connect our application to Oracle's latest database, but when it's time to design the GUI we normally check for the small details. Microsoft Windows has changed its look-and-feel many times since 1995 and window borders are an important key when developing nice user interfaces.

Microsoft in their article "Design Specifications and Guidelines - Visual Design" describes how the window borders are created using combinations of 4 different borders styles:

        - Raised Outer Border
        - Raised Inner Border
        - Sunken Outer Border
        - Sunken Inner Border

You can get more details about these border styles by reading the above article. We're now going to discuss how how 7 different borders can be implemented in VB using combinations of the 4 border styles. Our 7 window borders are:

        - None
        - Sunken
        - SunkenOuter
        - Raised
        - RaisedInner
        - Bump
        - Etched

Where Sunken is a combination of SunkenInner and SunkenOuter, Raised is a combination of RaisedInner and RaisedOuter, Bump is a combination of SunkenInner and RaisedOuter and Etched is a combination of SunkenOuter and RaisedInner.

The first way in which we could try to change a window border is by changing its window style. This can be done by using the API SetWindowLong. Every window has two areas of information where the "window style" and the "window extended style" are stored. These areas are filled-in the first time the window is created but they can be accessed and modified later. The styles are combinations of flags used by functions CreateWindow and CreateWindowEx.

Lets see an example: create an new EXE project in VB, add a PictureBox, name it Picture1 and paste the following code.

Option Explicit

Private Const

Private Const WS_EX_CLIENTEDGE = &H200&
Private Const WS_EX_STATICEDGE = &H20000

Private Const SWP_FRAMECHANGED = &H20
Private Const SWP_NOMOVE = &H2
Private Const SWP_NOOWNERZORDER = &H200
Private Const SWP_NOSIZE = &H1
Private Const SWP_NOZORDER = &H4

Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" ( _
    ByVal hWnd As Long, _
    ByVal nIndex As Long) As Long

Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" ( _
    ByVal hWnd As Long, _
    ByVal nIndex As Long, _
    ByVal dwNewLong As Long) As Long

Declare Function SetWindowPos Lib "user32" ( _
    ByVal hWnd As Long, _
    ByVal hWndInsertAfter As Long, _
    ByVal X As Long, ByVal Y As Long, _
    ByVal cx As Long, ByVal cy As Long, _
    ByVal wFlags As Long) As Long

Private Sub

    Dim lRet As Long
        'Get the window extended style...
lRet = GetWindowLong(.hWnd, GWL_EXSTYLE)
        'Set the new extended style...
        'Refresh the window...
SetWindowPos Picture1.hWnd, 0, 0, 0, 0, 0, _
            SWP_NOMOVE Or _
            SWP_NOSIZE Or _
            SWP_NOOWNERZORDER Or _
            SWP_NOZORDER Or _
    End With
End Sub

Well, it seems that we've managed to change the border style of a PictureBox to SunkenOuter so... what's the problem? The problem is that it isn't a complete solution. By combining "window styles" and "window extended styles" we can only implement 4 of 7 border styles (None, Sunken, SunkenOuter, Raised).

The API DrawEdge provides another possible solution. We can certainly draw any of our 7 edges with this function but we can only do it on the client area of our controls. This could be enough if we only wanted an static object but as soon as we start resizing or scrolling we'll see other problems emerging. Why? Because we need to clear the object and redraw the edge every time the object resizes and this creates "flickering" effects.

Lets see an example: create an new EXE project in VB, add a PictureBox, name it Picture1 and paste the following code.

Option Explicit

        Left As Long
Top As Long
        Right As Long
Bottom As Long

Private Const BDR_SUNKENOUTER = &H2
Private Const BDR_RAISEDINNER = &H4

Private Const BF_LEFT = &H1
Private Const BF_RIGHT = &H4
Private Const BF_TOP = &H2
Private Const BF_BOTTOM = &H8

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

Private Sub
    Picture1.BorderStyle = 0
End Sub

Public Sub

    Dim recClient As RECT

    With Picture1
        'Get the client area in pixels...
recClient.Left = 0
        recClient.Top = 0
        recClient.Right = .ScaleX(.ScaleWidth, 1, 3)
        recClient.Bottom = .ScaleY(.ScaleHeight, 1, 3)
        'Clear the picture...
        'Draw an Etched edge...
DrawEdge _
            Picture1.hDC, _
            recClient, _
    End With
End Sub

Private Sub
End Sub

Private Sub

    'Resize the picture...
Picture1.Move _
        Picture1.Left, _
        Picture1.Top, _
        IIf(Me.ScaleWidth > Picture1.Left * 2, Me.ScaleWidth - Picture1.Left * 2, 0), _
        IIf(Me.ScaleHeight > Picture1.Top * 2, Me.ScaleHeight - Picture1.Top * 2, 0)
    'Call the function to draw the edge again...
End Sub

As you can see this solutions seems to work, and the border style of the PictureBox is now Etched, but if we place a label into the picture we can see how it flickers every time we resize the form (make the FontSize bigger than 10. Try with 24). Another disadvantage of this solution is that we are drawing in the client area of the picture and, although it seems to be the border, it isn't. You can check this by placing a CommandButton into the picture with properties Left and Top equal to 0. The button will overlap with the border. In other words, this a good solution for static or empty controls, allows us to implement all 7 borders, but it isn't a good solution for resizing.

So how can we merge both examples and come up with a solution to implement the 7 different border styles, resize without flickering and draw within the border area rather than the client area? the answer is Subclassing.

As you all probably know, subclassing allows VB to receive all messages sent to a window and modify its behaviour as much as we want. Every time the non-client area (caption and border) of a window needs to be painted Windows sends the message WM_NCPAINT. We could intercept this message and use our own functions to draw the window edge. We are obviously going to use the API DrawEdge for this purpose and we are also going to use the first example to make sure that we have a window border with the required size for drawing (ex: Raised needs 2 pixels width but RaisedInner only needs one).

The solution has been placed into a VB module. A VB project that includes this library is available to download. 

    Download SmartEdge

If you browse the demo project  you will notice that the border of a picture can now be changed just by calling one function. This function can be used with any control that has a window handle (hWnd).

I would like to thank Steve McMahon (http://www.vbaccelerator.com) for all the very good ideas he shares on his web page. He has this fantastic article "Subclassing without the crashes" where he describes with full details how can we subclass a window "safely" and which tips and tricks we should follow to avoid crashing our application.

Copyright © 2001, Andrés Pons (andres@vbsmart.com). All rights reserved.