|
|
|
|
|
|
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 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 GWL_EXSTYLE = (-20)
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
Private Declare Function SetWindowLong Lib "user32"
Alias "SetWindowLongA" ( _
ByVal hWnd As Long, _
ByVal nIndex As Long, _
ByVal dwNewLong As Long) As
Long
Private 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 Form_Load()
Dim lRet As
Long
With Picture1
'Get the window
extended style...
lRet = GetWindowLong(.hWnd,
GWL_EXSTYLE)
'Set the new
extended style...
SetWindowLong .hWnd,
GWL_EXSTYLE, (lRet Or WS_EX_STATICEDGE) And
Not WS_EX_CLIENTEDGE
'Refresh the
window...
SetWindowPos Picture1.hWnd,
0, 0, 0, 0, 0, _
SWP_NOMOVE
Or _
SWP_NOSIZE
Or _
SWP_NOOWNERZORDER
Or _
SWP_NOZORDER
Or _
SWP_FRAMECHANGED
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
Private Type RECT
Left As Long
Top As
Long
Right As Long
Bottom As
Long
End Type
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 Const BF_RECT = (BF_LEFT Or
BF_TOP Or BF_RIGHT Or BF_BOTTOM)
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 Form_Load()
Picture1.BorderStyle = 0
End Sub
Public Sub DrawEtched()
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...
Picture1.Cls
'Draw an Etched
edge...
DrawEdge _
Picture1.hDC,
_
recClient,
_
BDR_SUNKENOUTER
Or BDR_RAISEDINNER, _
BF_RECT
End With
End Sub
Private Sub Form_Paint()
DrawEtched
End Sub
Private Sub Form_Resize()
'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...
DrawEtched
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.
|
|