Please support our sponsor:
DirectX4VB.Com - All You Need For Multimedia Visual Basic Programming

Main Site Links Resources Tutorials
News VB Gaming Code Downloads DirectX 7
Contact Webmaster VB Programming Product Reviews DirectX 8
  General Multimedia Articles DirectX 9
      Miscellaneous

 

DirectXInput: Keyboard Control
Author: Jack Hoxley
Written: 17th June 2001
Contact: [EMail]
Download: DI8_Keyboard.Zip (15kb)


Contents of this lesson
1. Introduction
2. Starting a DirectX project
3. Polling Based Keyboard Control
4. Event Based Keyboard Control


1. Introduction

Welcome to the first lesson in learning DirectInput 8. Direct Input 8 does exactly what it's name suggests - input; it allows your application to gain control of almost every possible type of game controller and input device known on the PC - keyboards, Joysticks, steering wheels, gamepads, force feedback, mice... the list goes on.

DirectInput8 has many advantages over using VB's built in input controls - Form_KeyUp, Form_KeyDown, Form_MouseMove etc.. and has more control than the standard Win32 functions - GetCursorPos, GetKeyState ... In general it is faster, more efficient and much more powerful. It was designed for games (like all of DirectX8) and therefore has no excess baggage that will slow your application down. Just what I like...

We're going to start by using the keyboard as it's by far the simplest example - and if you've already done the DirectXGraphics8 series then you'll be in for a pleasant surprise - this one is actually quite short, to the point and very easy. After you've got the basics of this lesson under your belt, learning how to use the mouse and joystick is relatively easy...


2. Starting a DirectX Project

For those of you who've already been using DirectX8, or have already read other series on this site then this section isn't new; feel free to skip it.

For those of you who are new to the DirectX experience, before we do any programming we need to tell visual basic that we're going to be using DirectX. First off, you'll need visual basic 5 or 6 (or higher if available) as DirectX is a COM based system, and VB 4 and below cant address this sort of thing. I've read numerous message board posts about people trying hard to get it working in VB4 - dont waste your time; it wont work; YOU NEED version 5 or 6. To get a project started using DirectX, follow these simple steps:

1. Open up Visual Basic - I hope you know how to do this ;)

2. Start a new "Standard EXE" project

3. On the project menu click on "references". A window should appear with a long list of libraries and files that you can use (each with a check box next to it)

4. Scroll down until you find a "DirectX 8 for visual basic type library" (or similiar). Check it's box.

5. Click okay. You're project is now linked to DirectX; if you open a code window and type "As " the variable list will drop down, start typing "D" and you'll see a list of several 100 Direct3D this DirectInput that... etc...

6. Save your project somewhere, or if you're feeling clever create a template version of the project so you can start it easily next time...

That didn't work? Cant find the library? well, in the references window click on "Browse" and navigate to C:\Windows\System\ and find a file Dx8VB.DLL and select it - you should see an entry (checked) with the name "DirectX 8 for visual basic type library"... continue as normal.

There are 2 final notes regarding the distribution of DirectX based applications.

1. Do not assume the end user has DirectX installed, or that it's the correct version. Always check to see which version is installed. if it's not the correct version, make them run the setup program

2. You can freely distribute the DirectX runtimes/distributables - you'll often find them sitting on the root folder of a commercial game CD... or magazine coverdisks.


3. Polling Based Keyboard Control

now we've got the boring stuff out of the way we can hopefully get onto doing some more interesting stuff...

Although, dont get too excited - using the keyboard isn't really that amazing to watch :-)

There are two methods of using the keyboard in DirectX8 - polling or event based; both have their advantages and disadvantages. In general, as with almost all aspects of game design I much prefer event based methods - it makes debugging exceedingly easy (just log every message sent), and when there's no messages waiting to be processed your application doesn't do any work - very efficient. Polling tends to be slightly more responsive and easy to control - and you can quite easily gain the same results from polling as you can event based and vice versa. If you dont know much about polling and event based programming - or general program structure I strongly suggest you go find some good articles online about them, with respect to game development Gamasutra and GameDev are good places to look; if you intend to get into game development then the code design and structure is incredibly important - make it ugly and you'll kill performance.

Polling is the simplest method, which is why we start here - it also allows me to explain the initialisation procedure - something very important and very common across all of DirectX.

Step 1: The declarations.
We'll be using a standard form with a text box on it (multiline, locked, vertical scroll bars), so set one of those up (Standard EXE should give you a form to start with).

In the declarations section of the form, place the following code:

Option Explicit

'//Important, only set one of the following 2 to be true.
Private Const UsePollingMethod As Boolean = True
Private Const UseEventMethod As Boolean = False

'//Status variables and other stuff :)
Private bRunning As Boolean 'for the polling version, states when we've finished.

'//The following object definitions are the master controlling
' units for DirectInput.
Private DX As DirectX8
Private DI As DirectInput8

'//These next two objects are used to access our device (keyboard)
Private DIDevice As DirectInputDevice8
Private DIState As DIKEYBOARDSTATE
Private KeyState(0 To 255) As Boolean 'so we can detect if the key has gone up or down!
Private Const BufferSize As Long = 10 'how many events the buffer holds.
'This can be 1 if using event based, but 10-20 if polling based...

'Sleep() - stops our polling loop going too fast ;)
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)


The first line, Option Explicit, is important - DirectX likes all it's variables to be explicitly created - none of this crazy no-declaration variable coding please! good clean code is the order of the day. The next part are two control constants, not actually part of DirectInput programming, but it allows you to select which version of the program to run - polling or event based. The next variable bRunning is a variable that allows us to control the master loop - something D3D programmers will be familiar with.

The next two lines, declaring DX and DI are very important, the DirectX8 object is the master object - it controls everything else basically; we'll use it in a minute to create the DirectInput8 object. the DI object is equally important, we must have one of these to use DirectInput. the DirectInput object, if imagined on a tree, comes as a branch from the DX object.

The next 4 lines, starting with DIDevice. DIDevice is important as well, and if imagined on the tree analogy again is a sub-branch of the DI object. the DIDevice represents our device (funny that), be it a keyboard, mouse or joystick - you can create multiple devices for different hardware, ie, mouse and keyboard, but not two device for 1 keyboard. DIState represents the current state of the keyboard, everytime we poll the device for information it will fill this structure with the state of all the keys - up or down. The next array, KeyState() is one that I've set up to simplify checking KeyUp events (by default we only *see* keydown events). The final line, BufferSize, is so that we can create a buffer for retrieving keyboard events - it makes things much easier when detecting keydown events... Note the comment by this line - if you use event based methods, we only need a 1-event buffer, otherwise we'll need 10-20 events to be buffered - depending on how fast/slow your game loop runs and how fast/slow the end user is at typing.

The last line is the Sleep() API, it's required to slow down the main loop. As you'll see in a second we're using a Do...Loop structure, this can easily travel at 1000 loops per second; when polling this can have the adverse effect of making it impossible to type properly - if you tap the key as fast as you can you'll probably register 10-20 keypresses (at 1000fps), typing at normal speed and suddenly the simplest things end up like hhhhhhhhhhhhhhhhheeeeeeeeeeelllllllllllllllllllllllllllllllllllllllllllllooooooooo - not good.

Step 2: Initialisation
This part is crucial to the existance of a DirectInput application, as is it's counter operation - Termination. DirectInput is not as simple as an API call, we are integrating ourselves with a potentially very fragile system and must treat it as it was designed - or who knows what will happen! A standard application will have an Initialisation stage, follows by the actual gameplay time, followed by Termination just before it finally closes down. Thankfully this sort of structure fits excellently with a modular program structure and the general ideas behind game design - as I said before, go read up on this stuff if you're not following me.

Initialisation follows 3 stages:
1. Create the objects and the devices.
2. Setup the properties for the device and configure it
3. Tell the device that we want to start using it.

In code this will look like:

Private Sub Form_Load()
On Local Error GoTo BailOut:
Me.Show
'//0. Any variables
     Dim I As Long
     Dim DevProp As DIPROPLONG
     Dim DevInfo As DirectInputDeviceInstance8
     Dim pBuffer(0 To BufferSize) As DIDEVICEOBJECTDATA
'//1. Check options.
If UsePollingMethod And UseEventMethod Then MsgBox "You must select only one of the constants before running" Unload Me End End If
If UsePollingMethod Then txtOutput.Text = "Using Polling Method" & vbCrLf If UseEventMethod Then txtOutput.Text = "Using Event Based Method" & vbCrLf '//2. Initialise the selected method Set DX = New DirectX8 'must create the object.
     Set DI = DX.DirectInputCreate
     Set DIDevice = DI.CreateDevice("GUID_SysKeyboard") 'the string is important, not just a random string...
     DIDevice.SetCommonDataFormat DIFORMAT_KEYBOARD
     DIDevice.SetCooperativeLevel frmMain.hWnd, DISCL_BACKGROUND Or DISCL_NONEXCLUSIVE
     'set up the buffer...
     DevProp.lHow = DIPH_DEVICE
     DevProp.lData = BufferSize
     DIDevice.SetProperty DIPROP_BUFFERSIZE, DevProp
     DIDevice.Acquire 'let DirectX know that we want to use the device now.
     'retrieve some information about the device; not really that useful - it'll only tell you
     'that your using a keyboard (as if we didn't already know that!)...
     Set DevInfo = DIDevice.GetDeviceInfo()
     txtOutput.Text = txtOutput.Text & "Product Name: " & DevInfo.GetProductName & vbCrLf
     txtOutput.Text = txtOutput.Text & "Device Type: " & DevInfo.GetDevType & vbCrLf
     txtOutput.Text = txtOutput.Text & "GUID: " & DevInfo.GetGuidInstance & vbCrLf
     'if we've gotten to this point, the user has decided to quit
     DIDevice.Unacquire
     Set DIDevice = Nothing
     Set DI = Nothing
     Set DX = Nothing
     Unload Me
     End
Exit Sub
BailOut:
     MsgBox "Error occured in Form_Load() - ", Err.Number, Err.Description
End Sub


hopefully the above doesn't look to bad - it's not that complicated really; and once you've learnt it there isn't really a great deal else to be done here. I've put it all in the Form_Load( ) procedure, which isn't a great idea - but it's good enough for this sample program. There is quite a large section removed from the above code which I'll show you in a second, but above is all of the initialisation and termination code for a standard DirectInput8 program.

Section 1 is nothing special, and is straight forward. Section 2 is where it gets more interesting. First we create the DirectX object, DX. I know that you can declare it initially as "Dim DX as New DirectX8", but it's actually slower that way, and for the extra speed it's much easier to late bind it - as I have done. We then use the newly created DirectX8 object to create the DirectInput8 object, DI. We then use the newly created DirectInput8 object to create a Device, DIDevice. Notice a pattern here? a heirachy! almost everything about DirectX is designed as a heirachy - so get to like it ;)

The "GUID_SysKeyboard" part in the CreateDevice( ) function is important (as the comment says!). it tells the DirectInput8 object to create a keyboard device - there are a lot of GUID's - Globally Unique IDentifier's... but we only need this GUID - the other standard one is for a mouse device; the rest have to be enumerated - a topic for later on... Either way, you'll be safe to use the "GUID_SysKeyboard" token.

Once we've created the device we set it's properties - what data format it uses, this is so that when we ask the device for information it formats it in the correct way for our program to read it. The data format is either joystick, keyboard or mouse. The cooperativelevel, this is important - and quite fun. There are two components to this line - Background or foreground and exclusive or non-exclusive - you can mix these as you choose. If you select background then your program has access to keyboard data at all times; if you select foreground then it'll only recieve keyboard information when your window is the currently active/selected window. If you select exclusive then no other device can access your device in exclusive mode - other apps can still create a non-exclusive device and see what the keyboard state is, but you get priority. Non-Exclusive mode indicates that you'll get notified of events/be able to access the keyboard, but your application wont intefere with other applications.

The next part is about setting up the buffer - we just tell DirectInput how many events to store; if more events than fit in the buffer are raised you will get DIERR_BUFFEROVERFLOW generated and you wont be able to access any of the data. everytime you read the buffer it will be cleared, so if you check it on every loop (as we will) it will need to be large enough to hold all possible key events between the last loop - so if the application is running slowly you'll need to increase the size of the buffer...

Finally, we tell the device that we're going to use it - we acquire the device. We must make sure we unaquire it when we want to finish up, as shown in the first line of the termination code.

The important part about termination is the order we terminate the objects (Set ?? = Nothing parts). Whilst in VB it's not usually the end of the world if you do it wrong, it's good practise to destroy things in the reverse order to that which you created them. DX>DI>DIDevice becomes DIDevice>DI>DX in termination... In some cases it's a good idea to reset the configuration of the device, but that's not required in this situation.

Step 3: Getting the keyboard input.
We're going to simulate a game loop using a standard Do..Loop structure, those familiar with my DirectXGraphics series will know this structure well. For the rest of you, most games are based around a tight loop of core code - the frame rate of a game is representative of this. Take a standard game - it'll update the graphics, AI, physics, sound - and receive input from the user (not in that order though!). This section is about recieving input from the user. Because of the structure (looping, check keys every frame) polling is a good choice; event based methods dont fit well into this situation (as shown later on). The following code is how we do it:

If Not Err.Number Then bRunning = True 'no errors, clear for takeoff...
Do While bRunning
     'a. retrieve the information
          DIDevice.GetDeviceStateKeyboard DIState 'get the keyboard state
          On Error Resume Next 'ignore the prev. err handler
          DIDevice.GetDeviceData pBuffer, DIGDD_DEFAULT 'retrieve buffer info.
          If Err.Number = DI_BUFFEROVERFLOW Then 'check for an error..
               Debug.Print vbCr & "BUFFER OVERFLOW (Compensating)..."
               GoTo ENDOFLOOP: 'too much data, just loop around to the next loop.. 
End If On Error GoTo BailOut: 'reinstate the old error handler.
'b. sort through this data... 'most apps would look at a specific key rather 'than loop through them all; but we're interested in all of them
For I = 0 To 255 'loop through all the keys If DIState.Key(I) = 128 And (Not KeyState(I) = True) Then 'it's been pressed... 'the value will almost always be 128, indicating a key was pressed... txtOutput.Text = txtOutput.Text & "{ DOWN } " & KeyNames(CInt(I))& vbCrLf txtOutput.SelStart = Len(txtOutput.Text) KeyState(I) = True End If Next I 'c. check for any key_up events For I = 0 To BufferSize If KeyState(pBuffer(I).lOfs) = True And pBuffer(I).lData = 0 Then KeyState(pBuffer(I).lOfs) = False txtOutput.Text = txtOutput.Text & "{ UP } " & KeyNames(CInt(pBuffer(I).lOfs)) & vbCrLf txtOutput.SelStart = Len(txtOutput.Text) End If Next I Sleep (50) DoEvents ENDOFLOOP: Loop


it's fairly simple really, divided into 3 parts - a,b and c. The first stage is where we get the current key-states. This sample uses two ways effectively. It would be easier to just fill the buffer with data and read what it says, rather than individually checking each keystate - but either is good. The reason for the latter method is so that you could rewrite it just to look at individual keys, using the "if DIState.Key(DIK_UP) = &H80& then"...

So, the first part retrieves all the data that we need. The second part checks for any keys that have been pressed, signified by the value &H80& - we can then write to the display that the key has been pressed down; in a normal application we'd just send this to a processing pipeline and do whatever the key corresponds with (move the character left/right for example). The final part checks for any key-up states; you could do this in the previous loop - any DIState.Key( ) that's now 0, but it's KeyState(i) = True would suggest that the key has been lifted, but to demonstrate both possible ways I've made it check the buffer. the lOfs is the keynumber, the lData is the key value (0 or 128). Note that both b and c use a function KeyNames( ) - this is a simple function that will output (as a string) the name of the key pressed. it's quite long so I haven't included it here - but you can get it in the downloadable source code.


4. Event Based Keyboard Control

Right, lets wrap this thing up now - I'm hungry and want some lunch :-) and I'm sure you've done enough reading now...

Luckily, event based control is simply an addition to the polling method, initialisation is almost identical and the basic principles are the same; we just need to restructure the data collection and processing loop. Onwards...

Step 1: Declarations and Initialisation
Whilst I said that they dont change much, there are a couple of lines here-and-there that need changing. First off, in the declarations...

Dim hEvent As Long 'a handle for an event...
Implements DirectXEvent8


add those lines in anywhere - it doesn't matter. BUT notice that if you now click (in the form window) on the list box that has the objects (forms, buttons etc..) in it there is now a new entry - one that has mysteriously appeared! wow! and it's all because of the "Implements DirectXEvent8" line, delete it and notice that the object disappears, type it in again and notice that it re-appears. This may not be anything special if your a seasoned VB-Pro; but it may seem a little strange otherwise. You dont really need to understand the mechanics of how or why, all you need to know is that it creates a CallBack function in this case. What's that? well, if you've never seen one before, you'll be familiar with the theory that all your program does is call functions - in your program or in DLL's. A CallBack function is where another program (DirectX in this case) calls your program - with no warning. What you need to understand is that when an event occurs (a key is pressed/unpressed) DirectX will tell your program by calling the DirectXEvent8_DXCallback( ) function; it wont directly tell you what has happened - but you, being the clever person that you are will say "hmm! somethings happened, lets check the buffers" - and you then look at the keystates and buffers (as in the polling example) - and what a surprise! something will have changed. You can then take the relevent course of action...

The beauty of event based programming is that you dont have to keep on querying the device, and dont have to do any wasted processing. Whilst polling when there's no changes wont have a big impact on performance, if you look at it on the simplest level - you're calling functions and checking things when absolutely nothing has changed. when you're trying to squeeze the extra few frames per second out of your game, this wasted function call could be important - it may only be 0.7ms of processing time, but it may be worth it ;)

The only change to the initialisation procedure is the next few lines - setting up an event.

If UseEventMethod Then
'event based requires some extra initialisation
hEvent = DX.CreateEvent(frmMain)
DIDevice.SetEventNotification hEvent
End If


Simple really. We use the master DirectX object to create an event, the hEvent variable is just a number - when the callback function is called it will have this number as a parameter, so you can compare it with hEvent and decide if it's a keyboard event (you can have audio events, and other input events for example) - then process it accordingly. Once the event is registered, you tell the DIDevice object to use it when it raises an event...

One final note on termination - you must destroy the event. VB tends to do it for you if you forget, it's a good practise to do it yourself... use the following code:

If hEvent <> 0 Then DX.DestroyEvent hEvent
Set DIDevice = Nothing
Set DI = Nothing
Set DX = Nothing

I've put this part in the Form_QueryUnload( ) procedure, but you can put it wherever you require the termination to occur...

Step 2: Using the event
Now we've set up the event we need to be able to do something with it! Notice that alot of the following code is very similiar to that of the polling example. This should be no surprise, as the only difference between polling and event based is that in polling we check on every pass of the loop, and in event based mode we check only when we know something has happened...

Private Sub DirectXEvent8_DXCallback(ByVal eventid As Long)
     '//0. any variables
          Dim I As Long
          Dim pBuffer(0 To BufferSize) As DIDEVICEOBJECTDATA
     If eventid = hEvent Then
          'this message is for the event we set up; whilst of little point
          'in this example it's useful if you set up multiple events - mouse and keyboard for example
          If DIDevice Is Nothing Then Exit Sub 'simple error checker...
          '//1. we know an event has occured, so we collect data from the device
               DIDevice.GetDeviceStateKeyboard DIState
               DIDevice.GetDeviceData pBuffer, DIGDD_DEFAULT 'retrieve some information...
          '//2. we now check through all the keys to see what happened...
               'most applications wouldn't do it this way, they would look for a specific set of keys...
               For I = 0 To 255
                    If DIState.Key(I) = 128 Then '128, &H80& indicates a key_down event.... 0 indicates a keyup.
                         'this key has triggered the event
                         If pBuffer(0).lData = 128 Then
                              txtOutput.Text = txtOutput.Text & "{ DOWN } " & KeyNames(CInt(I)) & vbCrLf
                         End If
                    End If
               'the above code will not catch a key_up event, so we add this next
               'part to catch a key_up...
               If (pBuffer(0).lData = 0 And pBuffer(0).lOfs = I) Then
                    txtOutput.Text = txtOutput.Text & "{ UP }" & KeyNames(CInt(I)) & vbCrLf
               End If
               txtOutput.SelStart = Len(txtOutput.Text)
          Next I
Else
     'this is almost never going to happen, but I leave it in here anyway...
     MsgBox "Incoming event, but not for me... ", vbInformation, "What the..."
End If
End Sub


above is all that you really need to know - it's not greatly complicated, and many of the things I mentioned in the polling example work fine here...


There, everything you should need to know about using the keyboard in DirectInput8 - not really that hard was it. I strongly suggest that you download the source code for this tutorial from the top of the page, or from the downloads page.

DirectX 4 VB © 2000 Jack Hoxley. All rights reserved.
Reproduction of this site and it's contents, in whole or in part, is prohibited,
except where explicitly stated otherwise.
Design by Mateo
Contact Webmaster
This site is hosted by Exhedra Solutions, Inc., the parent company of RentACoder.com and PlanetSourceCode.com