When in the need of storing some content related to your Delphi application on the user's hard disk, you should take care of the support for state separation of user data, user settings, and computer settings. For example, The "Application Data" folder in Windows should be used to store application specific documents such as INI files, application state, temp files or similar. You should never use hard-coded paths to specific locations, such as "c:\Program Files", as this may not work on other versions of Windows because the location of folders and directories can change with different versions of Windows. The SHGetFolderPath Windows API function The SHGetFolderPath is available in the SHFolder unit. SHGetFolderPath retrieves the full path of a known folder identified. Here's a custom wrapper function around the SHGetFolderPath API to help you get any of the standard folders for all or the currently logged Windows user. uses SHFolder;
function GetSpecialFolderPath(folder : integer) : string; const ___SHGFP_TYPE_CURRENT = 0; var ___ path: array [0..MAX_PATH] of char; begin ___if SUCCEEDED(SHGetFolderPath(0,folder,0,SHGFP_TYPE_CURRENT,@path[0])) then ______ Result := path ___else ______Result := ''; ___end; Here's an example of using the SHGetFolderPath function: - Drop a TRadioButtonGroup (name: "RadioGroup1") on a form
- Drop a TLabel (name: "Label1") on a form
- Add 5 items to the radio group:
- "[Currenty User]\My Documents"
- "All Users\Application Data"
- "[User Specific]\Application Data"
- "Program Files"
- "All Users\Documents"
- Handle the RadioGroup's OnClick event as:
Note: "[Current User]" is the name of the currently logged in Windows user. //RadioGroup1 OnClick procedure TForm1.RadioGroup1Click(Sender: TObject) ; var ___index : integer; ___specialFolder : integer; begin ___if RadioGroup1.ItemIndex = -1 then Exit;
___index := RadioGroup1.ItemIndex;
___case index of ______//[Current User]\My Documents ______0: specialFolder := CSIDL_PERSONAL; ______//All Users\Application Data ______1: specialFolder := CSIDL_COMMON_APPDATA; ______//[User Specific]\Application Data ______2: specialFolder := CSIDL_LOCAL_APPDATA; ______//Program Files ______3: specialFolder := CSIDL_PROGRAM_FILES; ______//All Users\Documents ______4: specialFolder := CSIDL_COMMON_DOCUMENTS; ___end;
___Label1.Caption := GetSpecialFolderPath(specialFolder) ; end; Note: The SHGetFolderPath is a superset of SHGetSpecialFolderPath. You should not store application-specific data (such as temporary files, user preferences, application configuration files, and so on) in the My Documents folder. Instead, use an application-specific file that is located in a valid Application Data folder. Always append a subfolder to the path that SHGetFolderPath returns. Use the following convention: "\Application Data\Company Name\Product Name\Product Version".
Executing and Running Applications and Files from Delphi Code How many times have you wanted to execute some program from your Delphi application? Let's say we have a database application that uses some external backup utility. The back up utility takes parameters from your application, archives data, while your program waits until backup finishes. On the other hand, have you ever needed to open documents presented in a file listbox, just by double clicking on them, without opening the associated program first? Finally, imagine a link-label in your program that will take the user to your home page (by executing the Internet Explorer). Moreover, what do you say about sending an e-mail directly from your Delphi application through default Windows e-mail client program (like MS Outlook Express). ShellExecute To launch an application or execute a file in Win32 environment we will use the ShellExecute Windows API function. Check out the help on ShellExecute for full description of parameters and error codes returned. As you will see we can open any type of document from our program without knowing which program is associated with it (this link is defined in the Windows Registry). Let's see some shell action! Be sure to add ShellApi to your Unit's uses clause. Run Notepad
uses ShellApi; ... ShellExecute(Handle, 'open', 'c:\Windows\notepad.exe', nil, nil, SW_SHOWNORMAL) ; Open SomeText.txt with Notepad ShellExecute(Handle,'open', 'c:\windows\notepad.exe','c:\SomeText.txt', nil, SW_SHOWNORMAL) ; Display the contents of the "DelphiDownload" folder ShellExecute(Handle,'open', 'c:\DelphiDownload', nil, nil, SW_SHOWNORMAL) ; Execute a file according to its extension. ShellExecute(Handle, 'open', 'c:\MyDocuments\Letter.doc',nil,nil,SW_SHOWNORMAL) ; Open web site or a *.htm file with the default web explorer
ShellExecute(Handle, 'open', 'http://delphi.about.com',nil,nil, SW_SHOWNORMAL) ; Send an e-mail with the subject and the message body
var em_subject, em_body, em_mail : string; begin em_subject := 'This is the subject line'; em_body := 'Message body text goes here';
em_mail := 'mailto:delphi.guide@about.com?subject=' + em_subject + '&body=' + em_body ;
ShellExecute(Handle,'open', PChar(em_mail), nil, nil, SW_SHOWNORMAL) ; end; Execute a program and wait until it has finished. The following example uses the ShellExecuteEx API function.
// Execute the Windows Calculator and pop up // a message when the Calc is terminated. uses ShellApi; ... var SEInfo: TShellExecuteInfo; ExitCode: DWORD; ExecuteFile, ParamString, StartInString: string; begin ExecuteFile:='c:\Windows\Calc.exe';
FillChar(SEInfo, SizeOf(SEInfo), 0) ; SEInfo.cbSize := SizeOf(TShellExecuteInfo) ; with SEInfo do begin fMask := SEE_MASK_NOCLOSEPROCESS; Wnd := Application.Handle; lpFile := PChar(ExecuteFile) ; { ParamString can contain the application parameters. } // lpParameters := PChar(ParamString) ; { StartInString specifies the name of the working directory. If ommited, the current directory is used. } // lpDirectory := PChar(StartInString) ; nShow := SW_SHOWNORMAL; end; if ShellExecuteEx(@SEInfo) then begin repeat Application.ProcessMessages; GetExitCodeProcess(SEInfo.hProcess, ExitCode) ; until (ExitCode <> STILL_ACTIVE) or Application.Terminated; ShowMessage('Calculator terminated') ; end else ShowMessage('Error starting Calc!') ; end;
Running Flash animations with Delphi | | How to display Macromedia Flash animations (swf) inside a Delphi application. Grasp the fundamentals of integrating Macromedia Flash animations and Delphi. | |
|
| | Scenario Suppose you are developing some kind of a Delphi application designed to display various graphics file formats. By default, Delphi lets you handle and show BMP, JPG, WMF and other "standard" picture file formats, and even animations. If you want to take you application to the next level, sooner or later, you'll want to include an option to play Flash animations. Macromedia Flash animations (*.swf) are those fancy movies you see everyday while surfing the Internet. How to prepare for Flash animations To display a Flash animation in a Delphi application, you'll need to import Macromedia Flash ActiveX control. The Flash ActiveX is titled 'SWFLASH.OCX' and it will probably be located somewhere inside the C:\Windows\System\Macromed\Flash folder. First, let's see how to import the control into Delphi - of course, you only need to do this once: - Start Delphi and select Component | Import ActiveX Control... from the main menu
- Look for the 'Shokwave Flash (Version x.x)' and simply click on Install.
- Select the Component palette location in which you want to place selected library. Maybe the best is to leave the ActiveX option selected.
- Click on Install.
- Select a package where the new component must be installed or create a new package for the control. By default, Delphi will propose the dclusr.dpk package.
- Click on OK.
- Delphi will prompt you whether you want to rebuild the modified/new package or not. Click on Yes.
- After the package is compiled, you'll get a message saying that the new TShokwaveFlash component was registered and already available as part of the VCL.
- Close the package detail window, allowing Delphi to save the changes to it.
- The component is now available in the ActiveX tab (if you didn't change this setting in step 4)
How to play Flash animations with Delhi You are now ready to create your fist Flash enabled Delphi application. Here's how: - Select the ActiveX tab on the Component palette.
- Pick the TShokwaveFlash component.
- Drop the component on a form.
- Select the component you just dropped on a blank form.
- Using the object inspector, set the Movie property to the name of an existing swf file on your system. Note that you must set it with the full path name.
- Make sure that the Playing property is set to True.
- Run the project, and here it is! A sample swf file is running below, though you do not see it running :)
Flash to the max Now, if you need to distribute you own very special Flash movie with your application, one way to go is to include a swf file inside your Delphi application exe file, as a resource.
If there is a need to register an ActiveX control on your user machine (while deploying your application) than you need to make sure the user has the ocx you need. See how to "Registering DLL and ActiveX controls from code" And here's how to work with other properties and methods of the Flash component: ActiveX and Macromedia Flash. |
One of the common problems in programming is to sort an array of values in some order (ascending or descending). While there are many "standard" sorting algorithms, QuickSort is one of the fastest. Quicksort sorts by employing a divide and conquer strategy to divide a list into two sub-lists. QuickSort Algorith The basic concept is to pick one of the elements in the array, called a pivot. Around the pivot, other elements will be rearranged. Everything less than the pivot is moved left of the pivot - into the left partition. Everything greater than the pivot goes into the right partition. At this point each partition is recursively "quick sorted". Here's QuickSort algorithm implemented in Delphi: procedure QuickSort(var A: array of Integer; iLo, iHi: Integer) ; var ___Lo, Hi, Pivot, T: Integer; begin ___Lo := iLo; ___ Hi := iHi; ___Pivot := A[(Lo + Hi) div 2]; ___repeat ______while A[Lo] <>do Inc(Lo) ; ______while A[Hi] > Pivot do Dec(Hi) ; ______if Lo <= Hi then ______begin _________T := A[Lo]; _________A[Lo] := A[Hi]; _________A[Hi] := T;______ _________Inc(Lo) ; _________Dec(Hi) ; ______end; ___until Lo > Hi; ___if Hi > iLo then QuickSort(A, iLo, Hi) ; ___if Lo <>then QuickSort(A, Lo, iHi) ; end; Usage: var ___intArray : array of integer; begin ___SetLength(intArray,10) ;
//Add values to intArray ___intArray[0] := 2007; ___... ___intArray[9] := 1973;
//sort ___QuickSort(intArray, Low(intArray), High(intArray)) ; end;
Note: in practice, the QuickSort becomes very slow when the array passed to it is already close to being sorted.
| | How to enhance the functionality of a TDBgrid component using colors | | | | | Adding color to your database grids will enhance the appearance and differentiate the importance of certain rows or columns within the database. Since the DBGrid is a great user interface tool for displaying data, this article will focus on questions like "How do I change the color of particular row / column / cell in a DBGrid?" Preparations This article assumes that you know how to connect a database to DBGrid component. The easiest way to accomplish this is to use the Database Form Wizard. Select the employee.db from DBDemos alias. Select all fields except EmpNo. Note: if you do not wont to work with the BDE, consider the chapters of the Free Database Course For Beginner Delphi Developers - Focus on ADO. Coloring Columns The first (and the easiest) thing you can do, to visually enhance the user interface, is to color individual column in the data-aware grid. We will accomplish this through TColumns property of the grid. Select the grid component in the form and invoke the Columns editor by double clicking on the grid's Columns property in the Object Inspector. For more information on Columns editor look for "Columns editor: creating persistent columns" in your Delphi help files. Now, everything you have to do is to specify the background color of the cells of the particular column. For text foreground color, see the font property. This is what I have done, after few clicks... You have to agree that this is much better that the standard black'n'white grid (of course, use colors if you really need them). Coloring Rows If you want to color the selected row in a DBGrid but you don't want to use the dgRowSelect option because you want to be able to edit the data you should use the DBGrid.OnDrawColumnCell event. This technique demonstrates how to dynamically change the color of text in a DBGrid: procedure TForm1.DBGrid1DrawColumnCell (Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin if Table1.FieldByName('Salary').AsCurrency>36000 then DBGrid1.Canvas.Font.Color:=clMaroon; DBGrid1.DefaultDrawColumnCell (Rect, DataCol, Column, State); end;
| If an employee's salary is greater than 36 thousand, its row is displayed in Maroon colored text. Next technique demonstrates how to dynamically change the color of row in a DBGrid: procedure TForm1.DBGrid1DrawColumnCell (Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin if Table1.FieldByName('Salary').AsCurrency>36000 then DBGrid1.Canvas.Brush.Color:=clWhite; DBGrid1.DefaultDrawColumnCell (Rect, DataCol, Column, State); end;
| If an employee's salary is greater than 36 thousand, its row is displayed in White. Coloring Cells And finally: if you want to change the background color of the cells of the particular column (+ text foreground color), this is what you have to do: procedure TForm1.DBGrid1DrawColumnCell (Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin if Table1.FieldByName('Salary').AsCurrency>40000 then begin DBGrid1.Canvas.Font.Color:=clWhite; DBGrid1.Canvas.Brush.Color:=clBlack; end; if DataCol = 4 then //4 th column is 'Salary' DBGrid1.DefaultDrawColumnCell (Rect, DataCol, Column, State); end;
| If an employee's salary is greater than 40 thousand, its Salary cell is displayed in Black and text is displayed in White. Conclusion Techniques described in this article can really distinguish you from others, but be careful: too much coloring is too much... Now, when I look, there are far too much colors in these examples. How about graphics? component has many nice features and is more powerful than you would have thought. The "standard" DBGrid does its job of displaying and manipulating records from a dataset in a tabular grid. However, there are many ways (and reasons) why you should consider customizing the output of a DBGrid...
|
The Delphi Project Our task is to have Delphi do all the work. We want to create a new database from code, add all three tables from code, add indexes from code and even set up a referential integrity between those tables - again from code. As usual, have an empty Delphi form. Add two button component. Add a TADOConnection, TADOCommand. We'll use TADOCommand with a DDL language to create and link tables. Add a TADOXCatalog component (ActiveX page). The TADOXCatalog will do the trick of creating a new database. Let the name of the first button be btnNewDatabase (caption: 'Create database'), the second one should be called btnAddTables (caption: 'Create and link tables'). All the other components should have the default name. In this chapter we'll link those components from code. Therefore, you do not need to set up a ConnectionString for the ADOConnection component and the Connection property for the ADOCOmmand component now. New...Database Before we move on to creating tables and linking them we have to create a new (empty) database. This is the code: procedure TForm1.btnNewDatabaseClick(Sender: TObject); var DataSource : string; dbName : string; begin dbName:='c:\aboutdelphi.mdb';
DataSource := 'Provider=Microsoft.Jet.OLEDB.4.0' + ';Data Source=' + dbName + ';Jet OLEDB:Engine Type=4';
ADOXCatalog1.Create1(DataSource); end;
| Would you belive - simple as that. Obviously the ADOXCatalog has a method called Create1 that creates a new database. Pretty unusual since we've accustomed that Create methods are used to create an object from a class. The ADOXCatalog really has a Create method which has nothing in common to Create1. The variable DataSource looks pretty much like a standard connection string for the TADOConnection component. There is only one addition, the Jet OLEDB:Engine Type=X part. Engine type 4 is used to create an MS Access 97 database, type 5 is for MS Access 2000. Note that the above code does not check for the existance of the c:\aboutdelphi.mdb database. If you run this code twice it will complain that the databae already exists. Add table, create index, set referential integrity The next step is to create all tables (three of them), add indexes, and create referential integrity. Even though we could use ADOX, that is, TADOXTable, TADOXKey, etc. I'm somehow more familiar with the (standard) DDL language and the TADOCommand component. Back in the chapter 11 of this course we discussed database tables porting issues. This time we'll create tables from nothing. The following peaces of code are to be placed inside the button's btnAddTables OnClick even handler, I'll slice the code and add some explanations in between. First, we need to connect to the newly created database with the TADOConnection component. Since we've left the ADOCommand unattached to ADOConnection - we'll link them from code (this should be obvious by now): procedure TForm1.btnAddTablesClick(Sender: TObject); var DataSource : string; cs : string; begin DataSource := 'Provider=Microsoft.Jet.OLEDB.4.0'+ ';Data Source=c:\aboutdelphi.mdb'+ ';Persist Security Info=False';
ADOConnection1.ConnectionString := DataSource; ADOConnection1.LoginPrompt := False; ADOCommand1.Connection := ADOConnection1; ...
| Second, we create both Types and Authors tables, the structures are given in the first chapter. To build a new table with DDL by using the Jet SQL, we use the CREATE TABLE statement by providing it the name the table, name the fields, and fiedl type definitions. Then, the Execute method of the ADOCommand component is used. ... cs:='CREATE TABLE Types (typename TEXT(50))'; ADOCommand1.CommandText := cs; ADOCommand1.Execute;
cs:='CREATE TABLE Authors (' + 'authorname TEXT(50),' + 'email TEXT(50),' + 'web TEXT(50))'; ADOCommand1.CommandText := cs; ADOCommand1.Execute; ...
| Next, we add indexes to those two tables. When you apply an index to a table, you are specifying a certain arrangement of the data so that it can be accessed more quickly. To build an index on a table, you must name the index, name the table to build the index on, name the field or fields within the table to use, and name the options you want to use. You use the CREATE INDEX statement to build the index. There are four main options that you can use with an index: PRIMARY, DISALLOW NULL, IGNORE NULL, and UNIQUE. The PRIMARY option designates the index as the primary key for the table. ... cs:='CREATE INDEX idxPrimary '+ 'ON Types (typename) WITH PRIMARY'; ADOCommand1.CommandText := cs; ADOCommand1.Execute;
cs:='CREATE INDEX idxPrimary '+ 'ON Authors (authorname) WITH PRIMARY'; ADOCommand1.CommandText := cs; ADOCommand1.Execute; ...
| Finally, we add the last table. Applications table is linked with both Types and Authors in a master detail relationship. Back in the last chapter we were discussing one-to-many relationships that define the following: for every record in the master table, there are one or more related records in the child table. In our case, one Author (Authors table) can post more Applications; and the Application can be of some type. When defining the relationships between tables, we use the CONSTRAINT declarations at the field level. This means that the constraints are defined within a CREATE TABLE statement. ... cs:='CREATE TABLE Applications ('+ ' Name TEXT(50),'+ ' Description TEXT(50),'+ ' Author TEXT(50) CONSTRAINT idxauthor '+ 'REFERENCES Authors (authorname),'+ ' Type TEXT(50) CONSTRAINT idxtype '+ 'REFERENCES Types (typename),'+ ' [Size] FLOAT,'+ ' Cost CURRENCY,'+ ' DateUpl DATETIME,'+ ' Picture LONGBINARY)';
ADOCommand1.CommandText := cs; ADOCommand1.Execute;
end;//btnAddTablesClick | That's it. Now run the project, click the btnNewDatabase button, click the btnAddTables button and you have a new (empty) aboutdelphi.mdb database in the root of the C disk. If you have MS Access installed on your system you can open this database with it and check that all thje tables are here and in the Relationships window all the tables are linked.
In complex form designs where you have dozens of controls contained in several containers it is sometimes necessary to find out whether the focused control (the one with the input focus) is a child of some overall parent of a group of controls. Picture the next design: you are using frame objects to group controls together (or any other type of container control). You are dynamically instatiating the frame object(s) to be arranged on the form at run-time. At some point in the life of the application you need to figure out if a specific frame has the input focus - or better to say - if the frame contains the control with the input focus. TWinControl.Contains Focus? The ContainsFocus focus function returns true if the control, or one of its child controls, currently has the input focus. function ContainsFocus(control : TWinControl) : boolean; var . focusedHandle : HWND; . focusedControl : TWinControl; begin . focusedHandle := Windows.GetFocus() ;
. focusedControl := FindControl(focusedHandle) ;
. if focusedControl = nil then . result := false . else . begin . result := control.ContainsControl(focusedControl) ; . end; end; Let's say you have a few Edit boxes inside a Panel inside a GroupBox (GroupBox1). If you need to figure out whether the GroupBox contains the control with the input focus (active control) you can call the ContainsFocus, as in:
if ContainsFocus(GroupBox1) then begin // read from all edit boxes and // store the values somewhere end;
Windows defines special constants for each key the user can press. The virtual-key codes identify various virtual keys. These constants can then be used to refer to the keystroke when using Delphi and Windows API calls or in an OnKeyUp or OnKeyDown event handler. Virtual keys mainly consist of actual keyboard keys, but also include "virtual" elements such as the three mouse buttons. Delphi defines all constants for Windows virtual key codes in the Windows unit. Here are some of the Delphi articles that deal with the keyboard and VK codes: · Keyboard Symphony Delphi For Beginners: Get familiar with the OnKeyDown, OnKeyUp, and onKeyPress event procedures to respond to various key actions or handle and process ASCII characters along with other special purpose keys. · How to Translate a Virtual Key Code to a Character Windows defines special constants for each key the user can press. The virtual-key codes identify various virtual keys. In Delphi, the OnKeyDown and OnKeyUp events provide the lowest level of keyboard response. To use OnKeyDown or OnKeyUp to test for keys the user presses, you must use Virtual key codes to get the key pressed. Here's how to translate the virtual key code to the corresponding Windows character. · Touch Me - I'm Untouchable Intercepting keyboard input for controls that cannot receive the input focus. Working with keyboard hooks from Delphi. · ENTERing TAB Using the Enter key like a Tab key with Delphi controls. · Abort a Loop by Pressing a Key Use the VK_ESCAPE to abort a (for) loop. · Use Arrow Keys to Move Between Controls The UP and DOWN arrow keys are virtually useless in edit controls. So why not use them for navigating between fields. · Shift, Ctrl, Alt Key Checking Using virtual key codes to check the key state. · Simulating keystrokes from code A handy function to simulate the pressing of keyboard keys. The following table shows the symbolic constant names, hexadecimal values, and keyboard equivalents for the virtual-key codes used by Windows. Some Windows 2000 and OEM specific constants are missing, the entire list is available from Microsoft. The codes are listed in numerical order. Symbolic constant name | Value (hexadecimal) | Keyboard (or mouse) equivalent | VK_LBUTTON | 01 | Left mouse button | VK_RBUTTON | 02 | Right mouse button | VK_CANCEL | 03 | Control-break processing | VK_MBUTTON | 04 | Middle mouse button (three-button mouse) | VK_BACK | 08 | BACKSPACE key | VK_TAB | 09 | TAB key | VK_CLEAR | 0C | CLEAR key | VK_RETURN | 0D | ENTER key | VK_SHIFT | 10 | SHIFT key | VK_CONTROL | 11 | CTRL key | VK_MENU | 12 | ALT key | VK_PAUSE | 13 | PAUSE key | VK_CAPITAL | 14 | CAPS LOCK key | VK_ESCAPE | 1B | ESC key | VK_SPACE | 20 | SPACEBAR | VK_PRIOR | 21 | PAGE UP key | VK_NEXT | 22 | PAGE DOWN key | VK_END | 23 | END key | VK_HOME | 24 | HOME key | VK_LEFT | 25 | LEFT ARROW key | VK_UP | 26 | UP ARROW key | VK_RIGHT | 27 | RIGHT ARROW key | VK_DOWN | 28 | DOWN ARROW key | VK_SELECT | 29 | SELECT key | VK_PRINT | 2A | PRINT key | VK_EXECUTE | 2B | EXECUTE key | VK_SNAPSHOT | 2C | PRINT SCREEN key | VK_INSERT | 2D | INS key | VK_DELETE | 2E | DEL key | VK_HELP | 2F | HELP key |
| 30 | 0 key |
| 31 | 1 key |
| 32 | 2 key |
| 33 | 3 key |
| 34 | 4 key |
| 35 | 5 key |
| 36 | 6 key |
| 37 | 7 key |
| 38 | 8 key |
| 39 | 9 key |
| 41 | A key |
| 42 | B key |
| 43 | C key |
| 44 | D key |
| 45 | E key |
| 46 | F key |
| 47 | G key |
| 48 | H key |
| 49 | I key |
| 4A | J key |
| 4B | K key |
| 4C | L key |
| 4D | M key |
| 4E | N key |
| 4F | O key |
| 50 | P key |
| 51 | Q key |
| 52 | R key |
| 53 | S key |
| 54 | T key |
| 55 | U key |
| 56 | V key |
| 57 | W key |
| 58 | X key |
| 59 | Y key |
| 5A | Z key | VK_NUMPAD0 | 60 | Numeric keypad 0 key | VK_NUMPAD1 | 61 | Numeric keypad 1 key | VK_NUMPAD2 | 62 | Numeric keypad 2 key | VK_NUMPAD3 | 63 | Numeric keypad 3 key | VK_NUMPAD4 | 64 | Numeric keypad 4 key | VK_NUMPAD5 | 65 | Numeric keypad 5 key | VK_NUMPAD6 | 66 | Numeric keypad 6 key | VK_NUMPAD7 | 67 | Numeric keypad 7 key | VK_NUMPAD8 | 68 | Numeric keypad 8 key | VK_NUMPAD9 | 69 | Numeric keypad 9 key | VK_SEPARATOR | 6C | Separator key | VK_SUBTRACT | 6D | Subtract key | VK_DECIMAL | 6E | Decimal key | VK_DIVIDE | 6F | Divide key | VK_F1 | 70 | F1 key | VK_F2 | 71 | F2 key | VK_F3 | 72 | F3 key | VK_F4 | 73 | F4 key | VK_F5 | 74 | F5 key | VK_F6 | 75 | F6 key | VK_F7 | 76 | F7 key | VK_F8 | 77 | F8 key | VK_F9 | 78 | F9 key | VK_F10 | 79 | F10 key | VK_F11 | 7A | F11 key | VK_F12 | 7B | F12 key | VK_F13 | 7C | F13 key | VK_F14 | 7D | F14 key | VK_F15 | 7E | F15 key | VK_F16 | 7F | F16 key | VK_F17 | 80H | F17 key | VK_F18 | 81H | F18 key | VK_F19 | 82H | F19 key | VK_F20 | 83H | F20 key | VK_F21 | 84H | F21 key | VK_F22 | 85H | F22 key | VK_F23 | 86H | F23 key | VK_F24 | 87H | F24 key | VK_NUMLOCK | 90 | NUM LOCK key | VK_SCROLL | 91 | SCROLL LOCK key | VK_LSHIFT | A0 | Left SHIFT key | VK_RSHIFT | A1 | Right SHIFT key | VK_LCONTROL | A2 | Left CONTROL key | VK_RCONTROL | A3 | Right CONTROL key | VK_LMENU | A4 | Left MENU key | VK_RMENU | A5 | Right MENU key | VK_PLAY | FA | Play key | VK_ZOOM | FB | Zoom key |
|