Version 2.0!
Features
Tutorials
Files
Glossary
Projects
Contact
Links
Message Board
Extras
LuckyCam
Old News
Sign Guestbook
View Guestbook
VB Horoscope
VB Photo Album
.
ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!

Full Screen GUI Development

Pt 2 – More Controls & Text

By Jim (Machaira) Perry

 

Welcome back! If you’re saying "Huh?!" then you probably haven’t read the first part of this tutorial. Please read it before continuing because we’ll be building on it.

First Up

Let’s build on our base window by adding the standard Minimize, Maximize/Restore, and Close buttons (see WindowSample3 project).

We’ve added enums for the new buttons:

CloseBtn

MinBtn

MaxBtn

RestoreBtn

created bitmaps for them (close.bmp, minimize.bmp, maximize.bmp, and restore.bmp), and added instances of the clsWindow class in the modDirectDraw module to create them:

Public WithEvents CloseButton As New clsWindow

Public WithEvents MinButton As New clsWindow

Public WithEvents MaxButton As New clsWindow

Public WithEvents RestoreButton As New clsWindow

Notice that we’ve added WithEvents to the declarations. This will allow us to do things outside of the class when the control is clicked. We’ll go over this a little later.

New controls mean that we have to add them as child controls to the base window object. We’ve done this in the CreateWindowObjects function, setting the necessary properties for them:

With frmMain.CloseButton

.ObjectType = CloseBtn

.ParentHeight = frmMain.Window.Height

.ParentWidth = frmMain.Window.Width

.ParentX = frmMain.Window.X

.ParentY = frmMain.Window.Y

.ObjectSurface = objDD.CreateSurfaceFromFile(App.Path & "\close.bmp", ddsdSurf4)

.WindowName = "CloseBtn"

End With

frmMain.Window.AddChild frmMain.CloseButton

With frmMain.MinimizeButton

.ObjectType = MinBtn

.ParentHeight = frmMain.Window.Height

.ParentWidth = frmMain.Window.Width

.ParentX = frmMain.Window.X

.ParentY = frmMain.Window.Y

.ObjectSurface = objDD.CreateSurfaceFromFile(App.Path & "\minimize.bmp", ddsdSurf4)

.WindowName = "MinBtn"

End With

frmMain.Window.AddChild frmMain.MinimizeButton

With frmMain.MaximizeButton

.ObjectType = MaxBtn

.ParentHeight = frmMain.Window.Height

.ParentWidth = frmMain.Window.Width

.ParentX = frmMain.Window.X

.ParentY = frmMain.Window.Y

.ObjectSurface = objDD.CreateSurfaceFromFile(App.Path & "\maximize.bmp", ddsdSurf4)

.WindowName = "MaxBtn"

End With

frmMain.Window.AddChild frmMain.MaximizeButton

With frmMain.RestoreButton

.ObjectType = RestoreBtn

.ParentHeight = frmMain.Window.Height

.ParentWidth = frmMain.Window.Width

.ParentX = frmMain.Window.X

.ParentY = frmMain.Window.Y

.ObjectSurface = objDD.CreateSurfaceFromFile(App.Path & "\restore.bmp", ddsdSurf4)

.WindowName = "RestoreBtn"

End With

frmMain.Window.AddChild frmMain.RestoreButton

This is code that used to be in the InitDD function, but has been moved out for a good reason, as you’ll see soon. It allows us to recreate the base window object and all children whenever we want. Notice also that we’ve changed the code to use the With…EndWIth statements. It’s saves our poor little fingers from having to type as much and speeds up the code a bit (not that it’ll be noticeable unless you timed it.

If you compare this chunk of code with the code to create the OK button, you’ll notice that we’re not setting any X and Y or ObjectState properties for the buttons. That’s because you can’t. The buttons only go in one place, the standard position on the window they’re attached to. The properties are set in the ObjectSurface Property Set method:

Case MinBtn

iHeight = ddsd.lHeight / 2

iX = iParentWidth + iParentX - iWidth - 40

iY = iParentY + 7

iObjectState = iEnabled

Case MaxBtn

iHeight = ddsd.lHeight / 2

iX = iParentWidth + iParentX - iWidth - 24

iY = iParentY + 7

iObjectState = iEnabled

Case CloseBtn

iHeight = ddsd.lHeight / 2

iX = iParentWidth + iParentX - iWidth - 6

iY = iParentY + 7

iObjectState = iEnabled

Case RestoreBtn

iHeight = ddsd.lHeight / 2

iX = iParentWidth + iParentX - iWidth - 24

iY = iParentY + 7

iObjectState = iEnabled

The DrawObject function is also modified to handle drawing the new controls:

Case MinBtn, MaxBtn, CloseBtn, RestoreBtn

rectObject.Left = 0

rectObject.Right = iWidth

Select Case iObjectState

Case iEnabled

rectObject.Top = 0

rectObject.Bottom = iHeight

Case iPressed

rectObject.Top = iHeight

rectObject.Bottom = iHeight * 2

End Select

If you run the app, you’ll see the new buttons in the upper right hand corner of the window. You’ll also notice that we’ve added a caption for the window. This is held by the sCaption member of the clsWindow class. If you look at the declarations section for the class you’ll also see two other new members:

Private iCaptionX As Integer

Private iCaptionY As Integer

These will be used to draw the caption where it is needed for the specific control type. If you look at the DrawObject function in the class you’ll see we’ve added the code necessary to handle the caption:

Case BaseWindow

'Nothing needed here since we use the base rectangle

If Len(sCaption) > 0 Then

iCaptionX = iX + 10

iCaptionY = iY + 5

bDrawCaption = True

Else

bDrawCaption = False

End If

If bDrawCaption Then

lOldColor = objSurface.GetForeColor

objSurface.SetForeColor RGB(255, 255, 255)

objSurface.DrawText iCaptionX, iCaptionY, sCaption, False

objSurface.SetForeColor lOldColor

End If

The setup of the base window object has been changed to add the caption:

With frmMain.Window

.ObjectSurface = objDD.CreateSurfaceFromFile(App.Path & "\window.bmp", ddsdSurf2)

.ParentX = 0

.ParentY = 0

.ParentHeight = 600

.ParentWidth = 800

.CenterX = True

.CenterY = True

.WindowName = "Base"

.Caption = "Test Window"

End With

The next thing that has been added is the event raising. Take a look at the MouseUp function in the clsWindow class:

Public Function MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) As Boolean

Dim iLp As Integer

Dim bRetVal As Boolean

MouseUp = False

If X >= iX And X <= iX + iWidth And Y >= iY And Y <= iY + iHeight Then

For iLp = 1 To colChildren.Count

'If a child handles the event no need to do anything else

bRetVal = colChildren(iLp).MouseUp(Button, Shift, X, Y)

If bRetVal Then Exit Function

Next iLp

If iObjectType >= CloseBtn Then

iObjectState = iEnabled

If iObjectType = CloseBtn Then

RaiseEvent Clicked

End If

Else

If Not (iObjectState = iDisabled) Then

If iObjectState = iPressed And iObjectType = Btn Then

iObjectState = iEnabled

RaiseEvent Clicked

End If

End If

End If

MouseUp = True

End If

End Function

If the control has been clicked we use RaiseEvent to notify the app that this is so. The app can then decide what to do based on which control has been clicked. In our case we do two things – destroy the window and close the app:

Private Sub CloseButton_Clicked()

Window.RemoveChildren

Set Window = Nothing

End Sub

Private Sub OKButton_Clicked()

gbRun = False

End Sub

You can recreate the window by pressing the F1 key. Try it and see. I’ll wait while you do. J

Got all that? Good, then lets more on to more cool stuff.

Next Up

Open up the WindowSample4 project and take a look at the modMain module. You’ll see we’ve added RadioBtn and FrameWnd enums. Also not the constant cFrameGrey. We’ll be using this a little later on to help draw the FrameWnd control.

Next take a look at the frmMain code. We’ve added declarations for the new frame and radio button objects. Notice the two sets of radio buttons – WindowRadio1,2, and 3 and Radio1, 2, and 3. The WindowRadio controls will go on the base window and the Radio controls will be contained by the Frame control. You’ll see that you can change a control in one group without affecting the other, just as you can in VB. This is due to the Parent/Child relationship of the clsWindow objects. We’ve also added the code to support the RaiseEvent call in the class for each of the objects. We merely set two controls that weren’t clicked to be unchecked.

Not much has changed in the modDirectDraw module. The only thing of significance is in setting up the radio buttons that are contained by the Frame control:

With frmMain.Radio1

.ObjectType = RadioBtn

.ObjectState = iUnchecked

.ParentHeight = frmMain.Frame1.Height

.ParentWidth = frmMain.Frame1.Width

.ParentX = frmMain.Frame1.X

.ParentY = frmMain.Frame1.Y

.X = 100 + .ParentX

.Y = 100 + .ParentY

.ObjectSurface = objDD.CreateSurfaceFromFile(App.Path & "\radio.bmp", ddsdSurf6)

.WindowName = "Radio1"

.Caption = "Radio 1"

End With

'Add to frame not base window

frmMain.Frame1.AddChild frmMain.Radio1

With frmMain.Radio2

.ObjectType = RadioBtn

.ObjectState = iUnchecked

.ParentHeight = frmMain.Frame1.Height

.ParentWidth = frmMain.Frame1.Width

.ParentX = frmMain.Frame1.X

.ParentY = frmMain.Frame1.Y

.X = 100 + .ParentX

.Y = 115 + .ParentY

.ObjectSurface = objDD.CreateSurfaceFromFile(App.Path & "\Radio.bmp", ddsdSurf6)

.WindowName = "Radio2"

.Caption = "Radio 2"

End With

'Add to frame not base window

frmMain.Frame1.AddChild frmMain.Radio2

With frmMain.Radio3

.ObjectType = RadioBtn

.ObjectState = iUnchecked

.ParentHeight = frmMain.Frame1.Height

.ParentWidth = frmMain.Frame1.Width

.ParentX = frmMain.Frame1.X

.ParentY = frmMain.Frame1.Y

.X = 100 + .ParentX

.Y = 130 + .ParentY

.ObjectSurface = objDD.CreateSurfaceFromFile(App.Path & "\Radio.bmp", ddsdSurf6)

.WindowName = "Radio3"

.Caption = "Radio 3"

End With

'Add to frame not base window

frmMain.Frame1.AddChild frmMain.Radio3

Notice that we’re calling AddChild for the Frame1 control, not the Window control.

The clsWindow class has changed a bit as you probably expected. We’ve exposed the Width and Height members of the clsWindow class in order to set up the Frame control:

With frmMain.Frame1

.ObjectType = FrameWnd

.ObjectState = iEnabled

.ParentHeight = frmMain.Window.Height

.ParentWidth = frmMain.Window.Width

.ParentX = frmMain.Window.X

.ParentY = frmMain.Window.Y

.X = 75

.Y = 75

.Width = 200

.Height = 200

.WindowName = "Frame1"

.Caption = "Test Frame"

End With

frmMain.Window.AddChild frmMain.Frame1

The Property Let ObjectSurface and MouseDown function have had the RadioBtn added to the cases for the ChkBox:

Public Property Let ObjectSurface(ByVal objSurface As DirectDrawSurface7)

Case ChkBox, RadioBtn

iHeight = ddsd.lHeight / 4

End Property

Public Function DrawObject(objSurface As DirectDrawSurface7)

Dim clsWindow As clsWindow

Dim iLp As Integer

Dim ddsd As DDSURFACEDESC2

Dim rectBitmap As RECT

Dim rectObject As RECT

Dim lRet As Long

Dim bDrawCaption As Boolean

Dim lOldColor As Long

Dim lBltFlags As Long

On Error GoTo DrawObjectErr

rectBitmap.Left = iX

rectBitmap.Right = iX + iWidth

rectBitmap.Top = iY

rectBitmap.Bottom = iY + iHeight

Select Case iObjectType

Case Btn

rectObject.Left = 0

rectObject.Right = iWidth

Select Case iObjectState

Case iEnabled

rectObject.Top = 0

rectObject.Bottom = iHeight

Case iDisabled

rectObject.Top = iHeight * 2

rectObject.Bottom = iHeight * 3

Case iPressed

rectObject.Top = iHeight

rectObject.Bottom = iHeight * 2

End Select

Case ChkBox, RadioBtn

rectObject.Left = 0

rectObject.Right = iWidth

Select Case iObjectState

Case iUnchecked

rectObject.Top = 0

rectObject.Bottom = iHeight

Case iDisabled

rectObject.Top = iHeight * 2

rectObject.Bottom = iHeight * 3

Case iChecked

rectObject.Top = iHeight

rectObject.Bottom = iHeight * 2

Case iChecked_iDisabled

rectObject.Top = iHeight * 3

rectObject.Bottom = iHeight * 4

End Select

If Len(sCaption) > 0 Then

iCaptionX = iX + 15

iCaptionY = iY

bDrawCaption = True

Else

bDrawCaption = False

End If

Case MinBtn, MaxBtn, CloseBtn, RestoreBtn

rectObject.Left = 0

rectObject.Right = iWidth

Select Case iObjectState

Case iEnabled

rectObject.Top = 0

rectObject.Bottom = iHeight

Case iPressed

rectObject.Top = iHeight

rectObject.Bottom = iHeight * 2

End Select

Case FrameWnd

'see framesample.bmp for how this should look as a normal VB control

lOldColor = objSurface.GetFillColor

objSurface.SetForeColor cFrameGrey

objSurface.DrawLine iX + iParentX, iY + iParentY, iX + iParentX + iWidth, iY + iParentY

objSurface.DrawLine iX + iParentX, iY + iParentY, iX + iParentX, iY + iParentY + iHeight

objSurface.DrawLine iX + iParentX, iY + iParentY + iHeight, iX + iParentX + iWidth, iY + iParentY + iHeight

objSurface.DrawLine iX + iParentX + iWidth, iY + iParentY, iX + iParentX + iWidth, iY + iParentY + iHeight

objSurface.SetForeColor vbWhite

objSurface.DrawLine iX + iParentX + 1, iY + iParentY + 1, iX + iParentX + iWidth - 1, iY + iParentY + 1

objSurface.DrawLine iX + iParentX + 1, iY + iParentY + 1, iX + iParentX + 1, iY + iParentY + iHeight - 1

objSurface.DrawLine iX + iParentX, iY + iParentY + iHeight + 1, iX + iParentX + iWidth + 1, iY + iParentY + iHeight + 1

objSurface.DrawLine iX + iParentX + iWidth + 1, iY + iParentY, iX + iParentX + iWidth + 1, iY + iParentY + iHeight + 1

objSurface.SetForeColor lOldColor

If Len(sCaption) > 0 Then

iCaptionX = iX + iParentX + 10

iCaptionY = iY + ParentY - 8

bDrawCaption = True

Else

bDrawCaption = False

End If

Case BaseWindow

'Nothing needed here since we use the base rectangle

If Len(sCaption) > 0 Then

iCaptionX = iX + 10

iCaptionY = iY + 5

bDrawCaption = True

Else

bDrawCaption = False

End If

End Select

lBltFlags = DDBLT_WAIT

'Need to use transparency if it's a radio button

If iObjectType = RadioBtn Then

lBltFlags = DDBLT_WAIT Or DDBLT_KEYSRC

End If

'Don't blit if it a frame control

If iObjectType <> FrameWnd Then lRet = objSurface.Blt(rectBitmap, objBitmap, rectObject, lBltFlags)

If bDrawCaption Then

lOldColor = objSurface.GetForeColor

objSurface.SetForeColor RGB(255, 255, 255)

objSurface.DrawText iCaptionX, iCaptionY, sCaption, False

objSurface.SetForeColor lOldColor

End If

For iLp = 1 To colChildren.Count

colChildren(iLp).DrawObject objSurface

Next iLp

Exit Function

DrawObjectErr:

objErr.WriteLine "Error drawing " & sName & " - " & Err.Description

Exit Function

End Function

As you can see the changes are pretty extensive. The function now has to handle the two new controls and the new Caption member. The RadioBtn is fairly simple since it pretty much acts like the ChkBox inside the class. If you looked at the files included with this project you’ll notice that there’s no bitmap for the frame object. That’s because it uses DirectDraw’s DrawLine function to draw the outline of the frame. Open the framesample.bmp file and zoom in to see how it’s actually drawn.

We’ve added a function in the class to return a child object. This is necessary to change the RadioBtn’s ObjectState member when one radio button in a group is clicked:

Public Function GetChild(ByVal sKey As String) As clsWindow

Set GetChild = colChildren(sKey)

End Function

The AddChild method was changed to specify a key when adding a child in order to support this function:

Public Sub AddChild(clsChild As clsWindow)

colChildren.Add clsChild, clsChild.WindowName

End Sub

Take a minute to run the app and play with the new controls. There are a couple of things that could be changed to make the class better but I’ll leave them up to you. Some things could be:

  • Add members to allow the font type, size and color to be changed.
  • Add a Style member to allow the ChkBox and RadioBtn objects to be graphical similar to the way the VB controls act.
  • Change the frame drawing to not draw under the caption.
  • Adapt the class to add a LabelWnd object type. This would just use the Caption property

That’s it for this lesson. I hope it’s been fun and interesting. The next lesson will attempt to cover listboxes and comboboxes. Hope to see you there.