Procedures on Free Pascal - pascal

Im relatively new to Pascal and, though i have a fair understanding of the language, there's still some stuff i cant figure out how to implement. I've ran into this problem and, after trying for like hours on my own and looking for similar cases on the internet, i have not found anything. I hope this question is a fair one because, honestly, i dont know how to figure this out.
Here's the thing.
I have an application which dynamically creates TextBoxes (TextEdits in this case) and adds them to a panel for displaying. Thing is, i need to execute some procedures on the newly created elements. I added a new procedure in my app (this is for explaining purposes only):
procedure Demo_Procedure(i: integer, a: String);
Then i proceeded to "develop" my procedure underneath the "implementation" part of the Form.
procedure Demo_Procedure(i: integer, a: String);
begin
ShowMessage(a, ' ' ,i);
end;
Now, for my dynamically created elements im trying to set the "OnKeyDow" event to run my new procedure (this is what i dont A- know if its possible to do or B- how to do it)
NewlyButton.OnClick:= Demo_Procedure(5, 'Hi');
Im getting different errors depending on how i call up my procedure. For example:
If i do it like this: Demo_Procedures(5, 'Hi'), it says:
Error: Incompatible types: got "untyped" expected "procedure variable type of procedure(TObject,var Word,TShiftState) of object;Register>"
Now, researching around i found out that some people that put an '#' before calling the method, the only difference is that this time instead of saying "untyped" it says that it got "procedure variable type of procedure(AnsiString,LongInt) of object" and that it was expecting the same as before (procedure(TObject,var> Word,Tshift...etc)
Can anyone help me out here? I really am lost so any help would be greatly appreciated. Thanks in advance :)

There are errors in your code:
procedure Demo_Procedure(i: integer, a: String); // Wrong
procedure Demo_Procedure(i: integer; a: String); // Right, use semicolon as parameters delimiter
ShowMessage(a, ' ' ,i); // Wrong, ShowMessage takes only one string parameter
ShowMessage(Format('%s %d', [a, i])); // Right, %s means string value and %d means decimal value, see help about Format function
Events is a procedural variables so they have its own types. For example, OnKeyDown event have a type
TKeyEvent = procedure(Sender: TObject; var Key: Word; Shift: TShiftState) of Object;
where of Object means that your event handler must be a class method.
So, you can not assign to the event any procedure but only class method with parameters provided in the type declaration.
Here is the simple code:
unit Unit1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, Dialogs, StdCtrls;
type
{ TForm1 }
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
var
e: TEdit;
begin
e := TEdit.Create(Self); // Create new TEdit control
e.Parent := Self; // Place control onto the form
e.Left := 10; // Set control coordinates
e.Top := 10;
e.OnKeyDown := #EditKeyDown; // Assign event handler
end;
procedure TForm1.EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
ShowMessage(Format('Key code is %d', [Key]));
end;
end.

Related

Is there a way to determine if a reference to an object procedure corresponds to the object instance in Pascal?

I have a not very good solution for this task, but can someone tell me how to use the standard library or a more elegant approach. The current solution is based on the fact that the reference to the procedure of the object stores two pointers in memory, a pointer to the procedure code and a pointer to the object.
function IsObject( const AEvent: TNotifyEvent; const AObject: TObject ): Boolean;
begin
Result := PointerArray( Pointer( #AEvent )^ )[1] = Pointer( AObject );
end;
I'd use the TMethod record declared in the System unit. The record contains two member fields of type Pointer - Code and Data, that allow you to pick out the two pointers of the method. In your case, you need the Data member, which is the method's subject:
function ObjectIsMethodSubject(AObject: TObject; AEvent: TNotifyEvent): Boolean;
begin
Result := TMethod(AEvent).Data = AObject;
end;

Loading an array of records back into a program from a file?

I am creating a program that moves through an array of records and save these student records to a file.
However I now wish to reload the data (StudentName,Class,Grade) back into the array and subsequently display them in a list box on another form.
I have tried a few methods but with no success.
This is the code that wrote the file:
unit NewStudent;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
ExtCtrls, studentdata;
{ TFormNewStudent }
Type
TFormNewStudent = class(TForm)
Button1: TButton;
ButtonAddStudent: TButton;
Button3: TButton;
ComboBoxPredictedGrade: TComboBox;
EditClass: TEdit;
EditName: TEdit;
procedure Button1Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure ButtonAddStudentClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
Type
TPupil = Record
Name:String[30];
ClassGroup:String;
ComboBoxPredictedGrade:Integer;
end;
var
FormNewStudent: TFormNewStudent;
StudentRecArray : Array[1..30] of TPupil;
StudentNo:integer;
studentFile:TextFile;
implementation
{$R *.lfm}
{ TFormNewStudent }
procedure TFormNewStudent.Button1Click(Sender: TObject);
begin
FormStudentData.visible:=true;
FormNewStudent.visible:=false;
end;
procedure TFormNewStudent.Button3Click(Sender: TObject);
begin
FormStudentData.visible:=False;
FormNewStudent.visible:=True;
end;
procedure TFormNewStudent.ButtonAddStudentClick(Sender: TObject);
var
newStudent:string;
Begin
assignfile(studentFile,'G:\ExamGen\studentfile.txt');
StudentRecArray[StudentNo].Name:=EditName.text;
StudentRecArray[StudentNo].ClassGroup:=EditClass.text;
StudentRecArray[StudentNo].ComboBoxPredictedGrade:=ComboBoxPredictedGrade.ItemIndex;
append(studentFile);
newStudent:=(StudentRecArray[StudentNo].Name)+','+(StudentRecArray[StudentNo].ClassGroup)+','+(IntToStr(StudentRecArray[StudentNo].ComboBoxPredictedGrade));
writeln(studentFile,newStudent);
closefile(StudentFile);
StudentNo := StudentNo + 1;
end;
procedure TFormNewStudent.FormCreate(Sender: TObject);
begin
ComboBoxPredictedGrade.Items.Add('A');
ComboBoxPredictedGrade.Items.Add('B');
ComboBoxPredictedGrade.Items.Add('C');
ComboBoxPredictedGrade.Items.Add('D');
ComboBoxPredictedGrade.Items.Add('E');
ComboBoxPredictedGrade.Items.Add('U');
end;
end.
ScreenShot 1: StudentFile
ScreenShot 2: AddStudent Form
Answer given by Zamrony P. Juhara is correct, but your approach here may be not the most convenient. You define record which contains information about each student, then you write procedures to write this record to file and another one to read it. If you'll eventually change format of your record, you'll have to rewrite this code also. There are better ways, in my opinion.
You can define record containing only simplest members, like Ken White suggested:
TPupil = Record
Name:String[30];
ClassGroup:String[20]; //some convenient value
ComboBoxPredictedGrade:Integer;
end;
Such a record have fixed size and contains all needed information in itself (version with ClassGroup:String actually stores pointer to another area in memory where your string is), and then you can save and load it extremely easy:
var
myFile : File of TPupil;
procedure TFormNewStudent.ButtonAddStudentClick(Sender: TObject);
Begin
assignfile(studentFile,'G:\ExamGen\studentfile.txt');
StudentRecArray[StudentNo].Name:=EditName.text;
StudentRecArray[StudentNo].ClassGroup:=EditClass.text;
StudentRecArray[StudentNo].ComboBoxPredictedGrade:=ComboBoxPredictedGrade.ItemIndex;
append(studentFile);
Write(studentFile,StudentRecArray[StudentNo]); //THAT'S IT!
closefile(StudentFile);
inc(StudentNo);
end;
procedure TFormNewStudent.ReadFromFile;
begin
AssignFile(myFile,'G:\ExamGen\studentfile.txt');
Reset(studentFile);
StudentNo:=1;
while not Eof(studentFile) do begin
Read(studentFile,StudentRecArray[i]);
inc(StudentNo);
end;
end;
There is little drawback: file is not so readable as it was before, because Integer is saved exactly as 4-byte value, not its decimal representation.
There is much more interesting possibilities if you move from record to class, in that case you can use streaming system in a way as IDE saves forms to disc, in .dfm or .lfm files, so you'll be able to automatically save complex ierarchies of objects and load them back.
var
myFile : TextFile;
text : string;
lines : TStringList;
i : integer;
...
lines := TStringList.Create();
AssignFile(studentFile,'G:\ExamGen\studentfile.txt');
Reset(studentFile);
i:=1;
while not Eof(studentFile) do
begin
ReadLn(studentFile, text);
lines.CommaText := text;
studentRecArray[i].Name := lines[0];
studentRecArray[i].ClassGroup := lines[1];
studentRecArray[i].ComboBoxPredictedGrade := StrToInt(lines[2]);
inc(i);
end;
CloseFile(studentFile);
lines.Free();

how do I intercept was pressed the F1 key on my main window?

How to do that when I'm in the main window of my system by pressing F1 will display the help muetre in pdf format. how do I intercept was pressed the F1 key on my main window?
I use Delphi XE2
Thanks for the help!
Use the Application.OnHelpCommand event, which you can either assign in code:
interface
type
TForm1 = class(TForm)
// IDE generated code
private
procedure AppOnHelp(Command: Word; Data: Integer;
var CallHelp: Boolean);
end;
implementation
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnHelp := AppOnHelp;
end;
Or assign by using a TApplicationEvents component and creating a handler for the OnHelp event in the Object Inspector's Events tab.
You can set CallHelp to false to prevent the normal help processing, and launch your own help file via ShellExecute.
procedure TForm1.AppOnHelp(Command: Word; Data: Integer;
var CallHelp: Boolean);
begin
CallHelp := False;
// Launch your own help here
end;

Delphi DLL Issues - wrong integer value recevied and access violation issue

I'm having trouble getting to grips with DLLs in Delphi 7. I have two problems:
1) The procedure takes an integer parameter - but the dll receives a different value to the one I pass.
2) The application that called the dll crashes with an access violation after the function completes.
Here's my dll code:
library apmDLL;
uses
Classes, Messages, Windows, Dialogs, sysutils ;
const
WM_MY_MESSAGE = WM_USER + 1;
procedure sendtoACRPM (functionKey : integer); stdcall;
begin
showmessage('You sent - '+inttostr(functionKey));
showmessage('Finished Now');
end;
exports sendtoACRPM;
end.
So when I call this with the code below I get:
'Sending - 1'
'You Sent - 1636532'
'Finished Now'
Then the calling application crashes with an access violation.
The calling application looks like this:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, shlobj, shellapi;
const
WM_MY_MESSAGE = WM_USER + 1;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button2: TButton;
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
procedure sendtoACRPM (functionKey : integer) ; external 'apmDLL.dll';
implementation
{$R *.dfm}
procedure TForm1.Button2Click(Sender: TObject);
var
myInt: integer;
begin
myInt := strtoint(edit1.text);
showmessage('Sending - ' + inttostr(myInt));
sendtoACRPM(myInt);
end;
end.
Any ideas what I'm doing wrong here?
You need stdcall both in the DLL and in the calling code declaration. You only have it in the DLL.
Calling conventions need to match on both sides. :-)
procedure sendtoACRPM (functionKey : integer); stdcall; external 'apmDLL.dll';
You should use the standard Windows MessageBox instead of ShowMessage, so that the DLL can be used from non-Delphi applications as well.

Delphi XE and Trapping Arrow Key with OnKeyDown

I want my form to handle the arrow keys, and I can do it -- as long as there is no button on the form. Why is this?
Key messages are processed by the controls themselves who receives these messages, that's why when you're on a button the form is not receiving the message. So normally you would have to subclass these controls, but the VCL is kind enough to ask the parenting form what to do if the form is interested:
type
TForm1 = class(TForm)
..
private
procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;
..
procedure TForm1.DialogKey(var Msg: TWMKey);
begin
if not (Msg.CharCode in [VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT]) then
inherited;
end;
François editing: to answer the OP original question, you need to call onKeyDown somehow so that his event code would work (feel free to edit; was too long for a comment).
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
private
{ Private declarations }
procedure DialogKey(var Msg: TWMKey); message CM_DIALOGKEY;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.DialogKey(var Msg: TWMKey);
begin
case Msg.CharCode of
VK_DOWN, VK_UP, VK_RIGHT, VK_LEFT:
if Assigned(onKeyDown) then
onKeyDown(Self, Msg.CharCode, KeyDataToShiftState(Msg.KeyData));
else
inherited
end;
end;
procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
case Key of
VK_DOWN: Top := Top + 5;
VK_UP: Top := Top - 5;
VK_LEFT: Left := Left - 5;
VK_RIGHT: Left := Left + 5;
end;
end;
Arrow keys are used to navigate between buttons on a form. This is standard Windows behaviour. Although you can disable this standard behaviour you should think twice before going against the platform standard. Arrow keys are meant for navigation.
If you want to get the full low down on how a key press finds its way through the message loop I recommend reading A Key's Odyssey. If you want to intercept the key press before it becomes a navigation key, you need to do so in IsKeyMsg or earlier. For example, Sertac's answer gives one such possibility.
Only the object that has the focus can receive a keyboard event.
To let the form have access to the arrow keys event,
declare a MsgHandler in the public part of the form.
In the form create constructor, assign the Application.OnMessage to this MsgHandler.
The code below intercepts the arrow keys only if they are coming from a TButton descendant. More controls can be added as needed.
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnMessage := Self.MsgHandler;
end;
procedure TForm1.MsgHandler(var Msg: TMsg; var Handled: Boolean);
var
ActiveControl: TWinControl;
key : word;
begin
if (Msg.message = WM_KEYDOWN) then
begin
ActiveControl := Screen.ActiveControl;
// if the active control inherits from TButton, intercept the key.
// add other controls as fit your needs
if not ActiveControl.InheritsFrom(TButton)
then Exit;
key := Msg.wParam;
Handled := true;
case Key of // intercept the wanted keys
VK_DOWN : ; // doStuff
VK_UP : ; // doStuff
VK_LEFT : ; // doStuff
VK_RIGHT : ; // doStuff
else Handled := false;
end;
end;
end;
Because they are preempted to deal with setting the focus on the next available WinControl.
(I'm pretty sure that if you put an Edit instead of a Button you see the same thing).
If you want to handle them yourself, you can provide the Application with an OnMessage event that will filter those before they are processed and handle them yourself there.
var
KBHook: HHook; {this intercepts keyboard input}
implementation
{$R *.dfm}
function KeyboardHookProc(Code: Integer; WordParam: Word; LongParam: LongInt): LongInt; stdcall;
begin
case WordParam of
vk_Space: ShowMessage ('space') ;
vk_Right:ShowMessage ('rgt') ;
vk_Left:ShowMessage ('lft') ;
vk_Up: ShowMessage ('up') ;
vk_Down: ShowMessage ('down') ;
end; {case}
end;
procedure TForm4.FormCreate(Sender: TObject);
begin
KBHook:=SetWindowsHookEx(WH_KEYBOARD,#KeyboardHookProc,HInstance,GetCurrentThreadId());
end;
This code will work even when a control is focused (buttons , listboxes), so be careful some controls may loose their keyboard events (Read David haffernans answer) .
keyboard events with Focused controls
eg: If you are having textbox in your app and want to recive text(if focused) also , then
add an applicationevent1
procedure TForm4.ApplicationEvents1Message(var Msg: tagMSG;var Handled: Boolean);
begin
if Msg.message = WM_KEYFIRST then
KBHook:=SetWindowsHookEx(WH_KEYBOARD,#KeyboardHookProc,HInstance,GetCurrentThreadId());
end;
add the following code at the bottom of the function KeyboardHookProc
UnhookWindowsHookEx(KBHook);
and remove
KBHook:=SetWindowsHookEx(WH_KEYBOARD,#KeyboardHookProc, HInstance,
GetCurrentThreadId());
from oncreate event.

Resources