Thursday, June 14, 2007

Connecting an event to a Active Script function

In this article I described how to connect Active Script to a VCL Win32 application. This post will describe how to extend that demo to show how to directly connect an event (like OnClick) directly up to an ActiveScript function. You can get the new version of the demo from CodeCentral right here. Let's get started.

First off we'll add a button to the form and change the caption to “Hook Event.” We'll use the TEdit that's already there to get the name of the Active Script function to hook up when the Hook Even button is pressed. Here'e what the form looks like now:



Double-click the Hook Event button and then add this code:
procedure TForm3.Button2Click(Sender: TObject);
var
Info: EXCEPINFO;
State: TOleEnum;
Code: WideString;
Disp: IDispatch;
Id: Cardinal;
Name: WideString;
PropInfo: PPropInfo;
begin
Code := Memo1.Text;
OleCheck(FScript.GetScriptState(State));
if State = SCRIPTSTATE_CONNECTED then
OleCheck(FScript.SetScriptState(SCRIPTSTATE_DISCONNECTED));
OleCheck(FParse.ParseScriptText(PWideChar(Code), nil, nil, nil, 0, 0,
SCRIPTITEM_ISVISIBLE or SCRIPTITEM_ISPERSISTENT, nil, Info));
OleCheck(FScript.SetScriptState(SCRIPTSTATE_CONNECTED));
OleCheck(FScript.GetScriptDispatch(nil, Disp));
Name := Edit1.Text;
OleCheck(Disp.GetIDsOfNames(GUID_NULL, @Name, 1, GetThreadLocale, @Id));
if FClickMethod.Code <> nil then
begin
ReleaseMethodPointer(FClickMethod);
FillChar(FClickMethod, SizeOf(FClickMethod), 0);
end;
PropInfo := GetPropInfo(CheckBox1, 'OnClick', [tkMethod]);
FClickMethod := CreateMethodPointer(Disp, Id, PropInfo.PropType^);
SetMethodProp(CheckBox1, 'OnClick', FClickMethod);
end;

So the difference we have from the last method is that we don't want the script state to be disconnected at the end, so we first get the script state. If its connected, then disconnect it. Just like the last installment, we'll parse the script text, and set the state to connected. If there are any statements outside a function block, they'll execute immediately but we want to leave the script in the connected state. So the next thing is to get an IDispatch for the whole script. That is the GetScriptDispatch call. This is where we'll use the TEdit control on the form. The text contains the name of the Active Script function to look up in the GetIDsOfNames call. If the function is found, this function will return its “ID.” Once we have that, the rest of the code is all about creating a special TMethod* “stub” that is then assigned to the OnClick of the CheckBox1 component. Remember I mentioned the CreateMethodPointer() function in ObjComAuto? This is where that little tidbit of magic is used. Once we have a TMethod we can assign that to the OnClick of CheckBox1 using the SetMethodProp function. We have to do it this way to get around the type incompatibility of the TNotifyEvent and TMethod. That's all there is to wiring up the event. Let's see this puppy in action!

Run the application and paste the following code into Memo1:
function CheckBox1Click(sender) {
if (sender.Checked == 1) {
Application.MainForm.Caption = "CheckBox1 is checked"
} else {
Application.MainForm.Caption = "CheckBox1 is NOT checked"
}
}

Then enter “CheckBox1Click” into Edit1 and press the “Hook Event” button. As long as you got no error messages, you should be able to click CheckBox1 on and off and the caption of the form should change to reflect the checked state of the checkbox!

Again, the source code for this demo is in CodeCentral here: http://cc.codegear.com/item/24667
*TMethod - This is a data structure that reflects the actual in-memory representation of a method pointer. It simply consists of the address of the actual method to call and a reference to the specific instance of the class on which the method is defined.