Send simple strings in SOAP Header in Delphi - web-services

I need to send something like this:
<soapenv:Header>
<ser:userName>admin</ser:userName>
<ser:userPassword>secret</ser:userPassword>
</soapenv:Header>
Delphi WSDL importer, generated this:
userName2 = class(TSOAPHeader)
private
FValue: string;
published
property Value: string read FValue write FValue;
end;
userName = type string;
WsService = interface(IInvokable)
function call(const userName: userName; const userPassword: userPassword);
and registered the type as:
InvRegistry.RegisterHeaderClass(TypeInfo(WsService), userName2, 'userName', 'http://localhost/path/to/services');
The problem is that when I call it using the delphi generated code it puts the userName and password in the Body section of the SOAP message, not in the Header.
So I tried sending the Headers myself, like this:
Changed the type definition to inherit from the userName2 class because I can't send a string using the ISOAPHeaders.Send() method.
userName = class(userName2);
Then sent the headers:
user := userName.Create;
user.Value := 'admin';
WS := GetWsService;
(WS as ISOAPHeaders).Send(user);
Now the headers are in the correct place, but they are being sent like this:
<SOAP-ENV:Header>
<NS1:userName xmlns:NS1="http://localhost/path/to/services">
<Value xmlns="http://localhost/path/to/services">admin</Value>
</NS1:userName>
</SOAP-ENV:Header>
Almost there, but I don't want the "Value" property, I just want a plain simple tag in the header.
How can I do it?
Thanks.
== EDIT ==
As requested, the WSDL is here: http://desenvolvimento.lemontech.com.br:8081/wsselfbooking/WsSelfBookingService?wsdl
SOAP UI imported it and generated this sample request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://lemontech.com.br/selfbooking/wsselfbooking/services">
<soapenv:Header>
<ser:userPassword></ser:userPassword>
<ser:userName></ser:userName>
<ser:keyClient></ser:keyClient>
</soapenv:Header>
<soapenv:Body>
<ser:pesquisarSolicitacao>
<!--You have a CHOICE of the next 2 items at this level-->
<idSolicitacaoRef></idSolicitacaoRef>
<dataInicial></dataInicial>
<dataFinal></dataFinal>
<registroInicial>1</registroInicial>
<!--Optional:-->
<quantidadeRegistros>50</quantidadeRegistros>
</ser:pesquisarSolicitacao>
</soapenv:Body>
</soapenv:Envelope>
This sample request works just fine, but I can't figure out how to make this call in Delphi.

You can override the serialization for any TSOAPHeader class.
Just override its ObjectToSOAP function.
I came up with this:
unit Unit16;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, WsSelfBookingService, StdCtrls,
InvokeRegistry, SOAPHTTPClient, opCOnvertOptions, XMLIntf, XSBuiltIns;
type
TForm1 = class(TForm)
Memo2: TMemo;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TSOAPCredentials = class(TSoapHeader)
private
FPassword: string;
FUsername: string;
FKeyClient: string;
public
function ObjectToSOAP(RootNode, ParentNode: IXMLNode;
const ObjConverter: IObjConverter;
const NodeName, NodeNamespace, ChildNamespace: InvString; ObjConvOpts: TObjectConvertOptions;
out RefID: InvString): IXMLNode; override;
published
property userName : string read FUsername write Fusername;
property userPassword : string read FPassword write FPassword;
property keyClient : string read FKeyClient write FKeyClient;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TSOAPCredentials }
function TSOAPCredentials.ObjectToSOAP(RootNode, ParentNode: IXMLNode; const ObjConverter: IObjConverter; const NodeName,
NodeNamespace, ChildNamespace: InvString; ObjConvOpts: TObjectConvertOptions; out RefID: InvString): IXMLNode;
begin
Result := ParentNode.AddChild('userName');
Result.Text := FUsername;
Result := ParentNode.AddChild('userPassword');
Result.Text := FPassword;
Result := ParentNode.AddChild('keyClient');
Result.Text := FKeyClient;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ws : WsSelfBooking;
Req : pesquisarSolicitacao;
Resp : pesquisarSolicitacaoResponse;
Rio : THTTPRIO;
Cred : TSOAPCredentials;
begin
Rio := THttpRIO.Create(nil);
ws := GetWsSelfBooking(false, '', Rio);
Cred := TSOAPCredentials.Create;
Cred.userName := 'admin';
Cred.userPassword := 'secret';
Cred.keyClient := 'key';
Rio.SOAPHeaders.Send(cred);
Req := pesquisarSolicitacao.Create;
Req.registroInicial := 1;
Req.quantidadeRegistros := 50;
Resp := ws.pesquisarSolicitacao(Req);
end;
end.
results in this request header:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Header>
<SOAP-ENV:userName>admin</SOAP-ENV:userName>
<SOAP-ENV:userPassword>secret</SOAP-ENV:userPassword>
<SOAP-ENV:keyClient>key</SOAP-ENV:keyClient>
</SOAP-ENV:Header>

Solution:
It wasn't much obvious, but I just had to add the IS_TEXT value to the Index and declare a new TSOAPHeader descendant, the solution was like this:
const
IS_TEXT = $0020;
type
TSimpleHeader = class(TSOAPHeader)
private
FValue: string;
published
property Value: string Index (IS_TEXT) read FValue write FValue;
end;
userName = class(TSimpleHeader);
Then register this header:
InvRegistry.RegisterHeaderClass(TypeInfo(WsService), userName, 'userName', 'http://localhost/path/to/services');
And send the Header manually:
User := userName.Create;
User.Value := 'username';
(WS as ISOAPHeaders).Send(User);
Basically, the IS_TEXT value in the Index prevents Delphi from creating a userName tag and a Value tag inside it. It just places the string of the Value property inside the userName tag.
It's sad that the Index keywork is used for something so not obvious, also the documentation about it is difficult to find and hard to understand:
The AS_ATTRIBUTE feature has been deprecated. It still works for legacy code, but the preferred approach is to use the index value of a property. The index property allows you to specify whether a property is an attribute, an unbounded element, an optional element, a text value, or can have a value of NULL.
Source: http://docwiki.embarcadero.com/RADStudio/XE3/en/Using_Remotable_Objects

You can inject the tags by using a stringreplace on the XML string right before it goes out the door "onto the wire". You need a RIO_BeforeExecute handler, and you can then deal with the SOAPRequest directly.

Related

AmazonCloudWatch PutMetricData request format parsing

How to parse PutMetricData Sample Request as show below.
I want to parse all the MetricData and stores the values in a struct in golang.
https://monitoring.&api-domain;/doc/2010-08-01/
?Action=PutMetricData
&Version=2010-08-01
&Namespace=TestNamespace
&MetricData.member.1.MetricName=buffers
&MetricData.member.1.Unit=Bytes
&MetricData.member.1.Value=231434333
&MetricData.member.1.Dimensions.member.1.Name=InstanceID
&MetricData.member.1.Dimensions.member.1.Value=i-aaba32d4
&MetricData.member.1.Dimensions.member.2.Name=InstanceType
&MetricData.member.1.Dimensions.member.2.Value=m1.small
&MetricData.member.2.MetricName=latency
&MetricData.member.2.Unit=Milliseconds
&MetricData.member.2.Value=23
&MetricData.member.2.Dimensions.member.1.Name=InstanceID
&MetricData.member.2.Dimensions.member.1.Value=i-aaba32d4
&MetricData.member.2.Dimensions.member.2.Name=InstanceType
&MetricData.member.2.Dimensions.member.2.Value=m1.small**
&AUTHPARAMS
Not able to understand this is in which format and how to parse it. Any library available to generate and parse this kind of formatted message?
If you remove the newlines that is a URL. Start with url.Parse, then use the Query() function to get access to the url parameters:
func main() {
var input = `https://monitoring.&api-domain;/doc/2010-08-01/
?Action=PutMetricData
&Version=2010-08-01
&Namespace=TestNamespace
&MetricData.member.1.MetricName=buffers
&MetricData.member.1.Unit=Bytes
&MetricData.member.1.Value=231434333
&MetricData.member.1.Dimensions.member.1.Name=InstanceID
&MetricData.member.1.Dimensions.member.1.Value=i-aaba32d4
&MetricData.member.1.Dimensions.member.2.Name=InstanceType
&MetricData.member.1.Dimensions.member.2.Value=m1.small
&MetricData.member.2.MetricName=latency
&MetricData.member.2.Unit=Milliseconds
&MetricData.member.2.Value=23
&MetricData.member.2.Dimensions.member.1.Name=InstanceID
&MetricData.member.2.Dimensions.member.1.Value=i-aaba32d4
&MetricData.member.2.Dimensions.member.2.Name=InstanceType
&MetricData.member.2.Dimensions.member.2.Value=m1.small**
&AUTHPARAMS`
// possibly also needs to replace \r
input = strings.ReplaceAll(input, "\n", "")
uri, err := url.Parse(input)
if err != nil {
log.Fatal(err)
}
for key, val := range uri.Query() {
fmt.Println(key, val)
}
}
Playground
From here on out it's up to you how you want the target struct to look like.

SOAP implicit headers

I want to add a custom implicit soap header to my response.
MsqFlow:
ESQL:
CREATE FUNCTION Main() RETURNS BOOLEAN
BEGIN
SET OutputRoot.MQMD = InputRoot.MQMD;
CREATE LASTCHILD OF OutputRoot DOMAIN 'SOAP' NAME 'SOAP';
SET OutputRoot.SOAP.Header.eaie:apiHeader.messageId = UUIDASCHAR;
SET OutputRoot.SOAP.Header.eaie:apiHeader.timestamp = CURRENT_TIMESTAMP;
SET OutputRoot.SOAP.Header.eaie:apiHeader.transactionId = Environment.Variables.Generic.Session.TransactionId;
SET OutputRoot.SOAP.Header.eaie:apiHeader.correlationId = Environment.Variables.Generic.Session.CorrelationId;
SET OutputRoot.SOAP.Header.eaie:apiHeader.scrSystem = 'EAI';
SET OutputRoot.XMLNSC = InputRoot.XMLNSC;
RETURN TRUE;
END;
END MODULE;
in breakpoint between [Compute Node] & [SOAP Reply] in message is filled SOAP with correct parameters, but in response is still see only soap body with no header.
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
What I'm missing?
I finally found a solution, I didn't created XMLNSC and didn't add SOAP Body which is mandatory.
Final code:
CREATE FUNCTION Main() RETURNS BOOLEAN
BEGIN
SET OutputRoot.MQMD = InputRoot.MQMD;
CREATE LASTCHILD OF OutputRoot DOMAIN 'XMLNSC' NAME 'XMLNSC';
SET OutputRoot.SOAP.Header.eaie:apiHeader.messageId = UUIDASCHAR;
SET OutputRoot.SOAP.Header.eaie:apiHeader.timestamp = CURRENT_TIMESTAMP;
SET OutputRoot.SOAP.Header.eaie:apiHeader.transactionId = Environment.Variables.Generic.Session.TransactionId;
SET OutputRoot.SOAP.Header.eaie:apiHeader.correlationId = Environment.Variables.Generic.Session.CorrelationId;
SET OutputRoot.SOAP.Header.eaie:apiHeader.scrSystem = 'EAI';
SET OutputRoot.SOAP.Body.nsOut:getXXXResponse = InputRoot.XMLNSC.nsOut:getXXXResponse;
SET OutputRoot.XMLNSC = InputRoot.XMLNSC;
RETURN TRUE;
END;
END MODULE;

Delphi 2007 SOAP Fault Processing

I am writing a SOAP client in Delphi 2007 to do a simple Customs release check. I send the SOAP server some information and I am supposed to either receive details back about the Customs release or a SOAP fault if the server could not locate the information I sent it. The first part works fine but processing of the fault does not. The WSDL specifies a custom SOAP exception (this is included by the main WSDL - the whole WSDL is not shown):
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsd:schema targetNamespace="http://trips.crownagents.com/wsexception/message"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://trips.crownagents.com/wsexception/message">
<xsd:element name="WSException" type="WSException" nillable="true"/>
<xsd:complexType name="WSException">
<xsd:sequence>
<xsd:element name="ErrorCode" type="xsd:string" minOccurs="0" maxOccurs="1"/>
<xsd:element name="ErrorDescription" type="xsd:string" minOccurs="0" maxOccurs="1"/>
<xsd:element name="Stack" type="xsd:string" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
And the SOAP response I get back seems to reference the exception:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ns0="http://trips.crownagents.com/wsexception/message"
xmlns:ns1="http://trips.crownagents.com/external/customs/release/message"
xmlns:ns2="http://trips.crownagents.com/external/common/message">
<env:Body>
<env:Fault xsi:type="env:Fault">
<faultcode>env:Server</faultcode>
<faultstring xsi:nil="1"/>
<detail>
<ans1:WSExceptionResponse xmlns:ans1="http://msgsvr.trips.crownagents.com/">
<ErrorCode>0002</ErrorCode>
<ErrorDescription>Invalid Declaration</ErrorDescription>
<Stack>getSingleResult() did not retrieve any entities.</Stack>
</ans1:WSExceptionResponse>
</detail>
</env:Fault>
</env:Body>
</env:Envelope>
But, my code never sees the WSExceptionResponse. Instead, I get a generic ERemotableException:
Try
Res := Rel.releaseStatus(RelInfo);
Except
On E: WSExceptionResponse Do // This never fires
Status('Release check error (' + E.ErrorCode + ' - ' +
E.ErrorDescription + ').', True);
Else
Status('Release check error (' + Exception(ExceptObject).Message +
').', True);
End;
I have read that there are a couple of issues with SOAP processing in Delphi 2007 (https://groups.google.com/forum/#!msg/borland.public.delphi.webservices.soap/71t3P-vPMbk/qw9JVTEVS3YJ) and I have changed the OPToSOAPDomConv.pas file to revert it as per the suggestion but that doesn't help. Does anyone have any ideas as to what I might be doing wrong?
For anyone else still using Delphi 2007 that comes across this question, this is how I fixed this issue.
First, copy OPToSOAPDomConv.pas and InvokeRegistry.pas from the Delphi source directory (\Program Files< (x86)>\CodeGear\RAD Studio\5.0\source\Win32\soap) to your project directory. Add these two files to your project as you will be customizing the source and you will need these to recompile with your project instead of using the precompiled DCUs that come with Delphi.
In the OPToSOAPDomConv.pas file, find the ProcessFault procedure and replace it with the following:
procedure TOPToSoapDomConvert.ProcessFault(FaultNode: IXMLNode);
var
FA, FC, FD, FS, CustNode: IXMLNode;
I, J: Integer;
AMessage: WideString;
AClass: TClass;
URI, TypeName: WideString;
Count: Integer;
PropList: PPropList;
Ex: ERemotableException;
function GetNodeURIAndName(const Node: IXMLNode; var TypeURI,
ElemName: InvString): boolean;
var
Pre: InvString;
begin
ElemName := Node.NodeName;
if IsPrefixed(ElemName) then
begin
Pre := ExtractPrefix(ElemName);
ElemName := ExtractLocalName(ElemName);
TypeURI := Node.FindNamespaceURI(Pre);
end
else
TypeURI := Node.NamespaceURI;
Result := True;
end;
begin
FA := nil;
FC := nil;
FD := nil;
FS := nil;
Ex := nil;
for I := 0 to FaultNode.ChildNodes.Count - 1 do
begin
if SameText(ExtractLocalName(FaultNode.ChildNodes[I].NodeName), SSoapFaultCode) then
FC := FaultNode.ChildNodes[I]
else if SameText(ExtractLocalName(FaultNode.ChildNodes[I].NodeName), SSoapFaultString) then
FS := FaultNode.ChildNodes[I]
else if SameText(ExtractLocalName(FaultNode.ChildNodes[I].NodeName), SSoapFaultDetails) then
FD := FaultNode.ChildNodes[I]
else if SameText(ExtractLocalName(FaultNode.ChildNodes[I].NodeName), SSoapFaultActor) then
FA := FaultNode.ChildNodes[I];
end;
{ Retrieve message from FaultString node }
if FS <> nil then
AMessage := FS.Text;
{ If there's a <detail> node, try to map it to a registered type }
if FD <> nil then
begin
{ Some SOAP stacks, including Delphi6 and others (see
http://softwaredev.earthweb.com/script/article/0,,12063_641361_2,00.html)
use the approach of putting custom fault info right at the <detail> node:
Listing 4 - Application Fault Details
<SOAP-ENV:Fault>
<faultcode>300</faultcode>
<faultstring>Invalid Request</faultstring>
<runcode>1</runcode>
<detail xmlns:e="GetTemperatureErr-URI"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xsi:type="e:GetTemperatureFault">
<number>5575910</number>
<description>Sensor Failure</description>
<file>GetTemperatureClass.cpp</file>
<line>481</line>
</detail>
</SOAP-ENV:Fault>
However, much more common is the approach where the type and namespace
are on the childnode of the <detail> node. Apache, MS and the SOAP spec.
seem to lean towards that approach:
Example 10 from the SOAP 1.1 Spec:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<faultcode>SOAP-ENV:Server</faultcode>
<faultstring>Server Error</faultstring>
<detail>
<e:myfaultdetails xmlns:e="Some-URI">
<message>My application didn't work</message>
<errorcode>1001</errorcode>
</e:myfaultdetails>
</detail>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
For interop reasons we favor the later approach but we'll support both here!!
}
CustNode := nil;
if GetElementType(FD, URI, TypeName) then
CustNode := FD
else
begin
if ntElementChildCount(FD) > 0 then
begin
CustNode := ntElementChild(FD, 0);
if not GetElementType(CustNode, URI, TypeName) and
not GetNodeURIAndName(CustNode, URI, TypeName) then
CustNode := nil;
end;
end;
if (CustNode <> nil) then
begin
AClass := RemClassRegistry.URIToClass(URI, TypeName);
if AClass <> nil then
begin
if AClass.InheritsFrom(ERemotableException) then
begin
Ex := ERemotableExceptionClass(AClass).Create(AMessage);
LoadObject(Ex, FaultNode, CustNode);
end;
end;
end;
end;
{ Create default SOAP invocation exception if no suitable class was found }
if Ex = nil then
Ex := ERemotableException.Create(AMessage);
if FA <> nil then
Ex.FaultActor := FA.Text;
if FC <> nil then
Ex.FaultCode := FC.Text;
if FD <> nil then
Ex.FaultDetail := FD.XML;
raise Ex;
end;
Next, find the GetElementType function and replace it with the following:
function TSOAPDomConv.GetElementType(Node: IXMLNode; var TypeURI, TypeName: InvString): Boolean;
var
Idx: Integer;
S : InvString;
V: Variant;
Pre: InvString;
begin
TypeURI := '';
TypeName := '';
Result := False;
if (Node.NamespaceURI = SSoap11EncodingS5) and
(Node.LocalName = SSoapEncodingArray) then
begin
TypeURI := SSoap11EncodingS5;
TypeName := SSoapEncodingArray;
Result := True;
end
else
begin
V := GetTypeBySchemaNS(Node, XMLSchemaInstNameSpace);
if VarIsNull(V) then
V := Node.GetAttribute(SSoapType);
if not VarIsNull(V) then
begin
S := V;
Idx := Pos(':', S); { do not localize }
if Idx <> 0 then
begin
TypeName := Copy(S, Idx + 1, High(Integer));
Pre := Copy(S, 1, Idx - 1);
TypeURI := Node.FindNamespaceURI(Pre);
end
else
begin
TypeName := S;
TypeURI := '';
end;
Result := True;
end;
end
end;
Finally, open the InvokeRegistry.pas file and find the GetExternalPropName function. Change the line that says:
if Info.Kind = tkClass then
to this:
if (Info.Kind = tkClass) and Assigned(GetTypeData(info).ParentInfo) then
Compile and run your application and you should be good.
All credit for this goes to the users in this thread http://www.codenewsfast.com/cnf/article/859054074/permalink.art-ng1920q2368 and this one http://www.delphigroups.info/2/7/342954.html.

Releasing Variable used by WebService

How can I release a Variable that is being used by the WebService.
I'm using this form:
HttpPrincipal.WSDLLocation: = FrmPrincipal.edtWS.Text;
HttpPrincipal.Service: = 'CADServicesService';
HttpPrincipal.Port := 'CADServices';
Trinity: = HttpPrincipal as CADServices;
At the moment when I will close the Form appears an error, and discovered that when I declare this part:
Trinity: = HttpPrincipal as CADServices;
I think it is getting stuck in memory.
The error is the following:
"Invalid Pointer"
The error happens when you close the form, does not have any event in the OnClose or OnDestroy form.
Descriptions:
Trinity : CADServices,
HttpPrincipal is a THTTPRIO,
CADServices is my Unit containing all procedures / functions from WebService.
Instead of using the designtime component try to create HTTPRIO at runtime:
function GetCadServices(Addr : String): CadServices;
const
defSvc = 'CADServicesService';
defPrt = 'CADServices';
var
RIO: THTTPRIO;
begin
Result := nil;
RIO := THTTPRIO.Create(nil)
try
Result := (RIO as CadServices);
RIO.WSDLLocation := Addr;
RIO.Service := defSvc;
RIO.Port := defPrt;
finally
if (Result = nil) then
RIO.Free;
end;
end;
Usage:
Trinity := GetCadServices(FrmPrincipal.edtWS.Text);
If you imported the WSDL with the WSDL importer this code is automatically generated for you (look in the CadServices1 unit)

Inno Setup/Pascal Scripting - Casting Returning Nil

I'm trying to write some Pascal script for a installer I'm making with Inno Setup Compiler 5.5.1. I'm currently trying to add a custom wizard page that executes a command, taking user input from text fields (TEdit components). I defined the NextButtonClick function, and it checks that the Page ID is the custom page I defined and attempts to retrieve the user input from the field. When I get it from the components of the Page's Surface property, it gets returned as a TComponent. To get the next I need to cast it to a TEdit, so I tried casting it and it seems to be returning nil. Besides the scripting for Inno I've been doing for the past few days, I don't have much experience with Pascal, so I could possibly be doing something wrong. But I'd appreciate the help!
Here's the chunk of code giving me an issue for reference (with debugging lines left in):
function NextButtonClick(CurPageID: Integer): Boolean;
var
ResultCode: Integer;
CurrPage: TWizardPage;
Server : TComponent;
Server2: TEdit;
SurfacePage : TNewNotebookPage;
ServerStr : String;
begin
if CurPageID = 100 then
begin
CurrPage := PageFromID(100);
SurfacePage := CurrPage.Surface;
Server := SurfacePage.Controls[0];
Server2 := TEdit(Server); // RETURNS NIL HERE
if Server2 = nil then
MsgBox('', mbInformation, MB_OK);
ServerStr := Server2.Text;
MsgBox(ServerStr, mbInformation, MB_OK);
//ShellExec('', 'sqlcmd', '-S ' + ServerStr + ' -Q ":r setMemUsage.sql"', ExpandConstant('{app}') + '\sql', SW_SHOW, ewWaitUntilTerminated, ResultCode);
end;
Result := True;
end;
I can't simulate your problem. I've used this minimalistic code:
[Code]
var
CustomPageID: Integer;
procedure InitializeWizard;
var
EditBox: TEdit;
CustomPage: TWizardPage;
begin
CustomPage := CreateCustomPage(wpWelcome, '', '');
CustomPageID := CustomPage.ID;
EditBox := TEdit.Create(WizardForm);
EditBox.Parent := CustomPage.Surface;
end;
procedure CurPageChanged(CurPageID: Integer);
var
EditBox: TEdit;
Component: TComponent;
CustomPage: TWizardPage;
begin
if (CurPageID = CustomPageID) then
begin
CustomPage := PageFromID(CustomPageID);
Component := CustomPage.Surface.Controls[0];
if (Component is TEdit) then
begin
MsgBox('Controls[0] is assigned and is TEdit', mbInformation, MB_OK);
EditBox := TEdit(Component);
EditBox.Text := 'Hi, I''m just a modified edit text!';
end;
end;
end;