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.

3 comments:

  1. Interesting, but I have a small question, this is in regards to hooking a event, but lets say that the object wrapper for checkbox1, includes a OnClick wrapper

    something like:

    Type
    TCHeckboxWrapper = class
    ...
    published
    ....
    property OnClick: TNotifyEvent read FOnclick write FOnClick;
    end;

    Can I have code in my script that assigns the event?

    something like:

    function MyScriptInit() {
    CheckBox1.OnClick = scriptclick
    }

    ReplyDelete
  2. I prefer my way to connect events... i have a wrapper over activescript prepared to handle javascript.

    1) You need the idispatch of the function(), you can just get using ActiveScriptSite.ScriptDispatch. (is a olevariant)
    2) ActiveScriptSite.CreateEventSynker('event_name', function_dispatch) return a tmethod... just link... all idispatch are release when the component is destroyed or the iactivescript interface is released.
    3) I can link event INSIDE javascript code, like:

    function main() {
    var form = CreateObject('TForm', Application);
    form.OnClick = CreateEvent('TNotifyEvent', formclick);
    form.ShowModal();
    }

    function formclick(Event) {
    Event.Sender.Close();
    }

    :)

    ReplyDelete
  3. Could you post the code Sérgio????

    ReplyDelete

Please keep your comments related to the post on which you are commenting. No spam, personal attacks, or general nastiness. I will be watching and will delete comments I find irrelevant, offensive and unnecessary.