Can't use local variable in method - iolanguage

I wrote following method to get all slot names of object (without slots of Lobby and highter):
Object allSlotNames := method(
result := list()
object := self
while(object != Lobby,
result appendSeq(object slotNames)
object := object proto
)
result
)
But when I run it i get an error:
Io> 123 allSlotNames
Exception: Number does not respond to 'object'
---------
Number object Command Line 1
Number allSlotNames Command Line 1
Why?

Your code works as posted I ran it as a file. I was able to get it to work in the REPL by adding some explicit semicolons:
Object allSlotNames := method(
result := list();
object := self;
while(object != Lobby,
result appendSeq(object slotNames);
object := object proto;
);
result;
)

Related

Delphi REST not updating VCL db components when created runtime

I have a REST client which sends a request and updates some VCL db components with the response using an adapter, that works fine when the REST VCL components are put on a TDatamodule, however if I try to create the same REST components runtime, the VCL db components are not showing the data. I can see that the data are in fact returned (I can read them in the FDMemtable and store the data as a file) so the data is there, but the GUI components doesnt display data. The DBGrid does however show the vertical scroll bare after the update.
I'm using Delphi XE10.2.
I got stuck with this, I got to be missing something.
function TRESTClientModule.UpdateHistory: Boolean;
var
ARESTClient : TRESTClient;
ARESTRequest: TRESTRequest;
ARESTResponse: TRESTResponse;
ARESTResponseDataSetAdapter: TRESTResponseDataSetAdapter;
i : Integer;
s : string;
begin
Result := False;
try
ARESTClient := TRESTClient.Create('http://localhost:8080');
ARESTRequest := TRESTRequest.Create(nil);
ARESTResponse := TRESTResponse.Create(nil);
ARESTResponseDataSetAdapter := TRESTResponseDataSetAdapter.Create(nil);
try
// bind to RESTClient
ARESTRequest.Client := ARESTClient;
//- Prepare to get data from 'alarms' end-point.
ARESTRequest.Resource := 'alarms';
ARESTRequest.Method := TRESTRequestMethod.rmGET;
ARESTRequest.Response := ARESTResponse;
// Link Response to dataset (FDMemtable + TDataSource)
ARESTResponseDataSetAdapter.Dataset := AlarmHistory;
ARESTResponseDataSetAdapter.ResponseJSON := ARESTResponse;
ARESTResponseDataSetAdapter.RootElement := 'alarms';
//- Add the parameter to the request.
ARESTREquest.Params.Clear;
with ARESTRequest.Params.AddItem do
begin
name := 'IncludeOOS';
value := '0';
end;
ARESTRequest.Execute;
s := ARESTResponse.Content; // returned content is present
i := AlarmHistory.RecordCount; // the expected Recordcount is present
Result := True;
finally
ARESTResponse.Free;
ARESTResponseDataSetAdapter.Free;
ARESTRequest.Free;
end;
except on E: Exception do
end;
end;

Delphi Unique Dynamic Form Creation

I am using multiple forms on my project. (Client Server Application) I used chat but i have some problems.
1- I have a user list on my listview. And i am open new chat form here.
procedure CreateNewChat(User: String);
var
ChatForm: TChatForm;
begin
ChatForm:= TChatForm.Create(nil);
if assigned (ChatForm) then
ChatForm.User:= User;
Chat.Socket:= MySocket; // TClientSocket new Instance
ChatForm.Show;
end;
///Chatform
procedure ParseData(Cmd:string);
begin
if Cmd <> '' then
begin
/// parsing...
end;
New connections are adding to listview like that
var
Item: TListItem;
NewTempForm: TTempForm;
begin
NewTempForm := tempForm.Create;
Item := Listview.Items.Add;
if User = '' then
Exit;
Item.Caption := User;
Item.SubItems.Add('OK');
Item.GroupId := GroupId;
Item.SubItems.Objects[0] := NewTempForm;
end;
My problems are started on here.
1- How can i detect form according to user? So, if two or higher form is open then how can i show received message on them? Because i was create them dynamically. I need a object for detect.
I tryed like that
var
tempForm: TTempForm; // this is an empty object class
sTempStr: String;
begin
if ListView.Selected <> nil then
begin
tempForm := TTempForm(ListView.Selected.SubItems.Objects[0]); // this is for unique form creation
if tempForm.frmTasks = nil then // if there is not, create!
begin
tempForm.frmTasks := TfrmTasks.Create(nil);
end;
But my received message is appear on all forms. Actually should appear just one form. (Which user sent it)
Thanks.

Using Word OLE in Lazarus FreePascal

Im trying to get FreePascal to open up a word document, append some text and data to it and then close it. I've managed to get connected and can write a single line to the document but anything over that is defeating me. Currently I am attempting methods details in this Visual Basic reference, which is pretty similar to how I would expect FreePascal to handle things.
Basically I think I have misunderstood how the relationship between Lazarus and the Word OLE actually works, can anyone offer me any examples on how to construct a simple document that I can build on?
The following code, opens the document but then completely replaces its contents
program officAuto;
{$IFDEF FPC}
{$MODE Delphi}
{$ELSE}
{$APPTYPE CONSOLE}
{$ENDIF}
uses
SysUtils, Variants, ComObj;
const
ServerName = 'Word.Application';
var
Server, Doc : Variant;
oPara : Variant;
w:widestring;
begin
if Assigned(InitProc) then
TProcedure(InitProc);
try
Server := CreateOleObject(ServerName);
except
WriteLn('Unable to start Word.');
Exit;
end;
w:= UTF8Decode('c:\mydoc.docx');
Server.Visible := True; {Make Word visible}
Doc := Server.Documents.Open(w);
Doc.Range.Text := 'This is a Heading';
Doc.Range.Font.Bold := True;
Doc.Format.SpaceAfter := 24;
end.
Whereas this, based on the code above, in attempting to print a string at a bookmark, opens the document, retains the contents, moves to the bookmark and then does nothing.
w:= UTF8Decode('c:\mydoc.docx');
Server.Visible := True;
Doc := Server.Documents.Open(w);
oPara := Doc.Content.Paragraphs.Add(Doc.Bookmarks.Item('\Bookmark1').Range);
oPara := Doc.Range.Text('Where will this appear if at all!');
Ah I worked it out. The following code works as expected:
program officAuto;
{$IFDEF FPC}
{$MODE Delphi}
{$ELSE}
{$APPTYPE CONSOLE}
{$ENDIF}
uses
SysUtils, Variants, ComObj;
var
Server, Connect : Variant;
oWord, oPara1, oPara2 : Variant;
w:widestring;
begin
if Assigned(InitProc) then
TProcedure(InitProc);
try
Server := CreateOleObject('Word.Application');
except
WriteLn('Unable to start Word.');
Exit;
end;
// oWord := Server.Documents.Add;
w:= UTF8Decode('c:\mydoc.docx');
Server.Visible := True;
Server.Documents.Open(w);
oPara1 := Server.ActiveDocument.Content.Paragraphs.Add;
oPara1.Range.Text := 'This is a Heading';
oPara1.Range.Font.Bold := True;
oPara1.Format.SpaceAfter := 24;
oPara1.Range.InsertParagraphAfter();
oPara2 := Server.ActiveDocument.Content.Paragraphs.Add;
oPara2.Range.Text := 'Where will this appear if at all!';
oPara2.Range.Font.Bold := False;
oPara2.Format.SpaceAfter := 24;
oPara2.Range.InsertParagraphAfter();
end.

InnoSetup Dynamic ComboBox, check which item is selected and execute program

In InnoSetup I want to disply a ComboBox on the Finished Page which shows the Components that were installed.
You can choose "None" or any of the installed Components and start the associated program when clicking on finish.
This is my code so far:
procedure CurPageChanged(CurPageID: Integer);
var
NewComboBox1: TNewComboBox;
begin
if (CurPageID = wpFinished) then begin
NewComboBox1 := TNewComboBox.Create(WizardForm);
with NewComboBox1 do begin
Parent := WizardForm.FinishedPage;
Left := ScaleX(256);
Top := ScaleY(208);
Width := ScaleX(145);
Height := ScaleY(21);
ItemIndex := 0;
Style := csDropDownList;
Items.Add('None');
if IsComponentSelected('1') then
Items.Add('Component 1');
if IsComponentSelected('2') then
Items.Add('Component 2');
if IsComponentSelected('3') then
Items.Add('Component 3');
end;
end;
end;
First I want to set "None" as automatically selected. when the page is shown. I have looked up many Pascal forums but none of the solutions worked, like NewComboBox1.ItemSelected=0 (or similar, don't remember correctly...). So how do I achieve this?
Then I don't know how to make a program start when clicking on Finish. I thought
function NextButtonClick
might help but then no Next button worked in the setup.
Maybe there is also a problem because the list is created depending on which Components were selected, so item 1 is not Component 1, if Component 1 was not selected but Component 2 for instance.
I thought one might solve this by making the items invisible instead of not creating them at all.
I looked in the Support Classes Reference in the IS help file but didn't find anything that would help me.
I am looking forward to your answers!
There's no simple way to do this due to a lack of missing access to the file name and destination directory which the component is binded to. Even TSetupComponentEntry internal record doesn't contain this information, but even if would, you won't be able to access it. So, the following script uses its own separate array which contains the component/file linkage needed for this task:
[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program
[Components]
Name: "program_32"; Description: "Program 32-bit"
Name: "program_x64"; Description: "Program 64-bit"
Name: "program_ia64"; Description: "Program IA 64-bit"
[Files]
Source: "MyProg.exe"; DestDir: "{app}"; Components: program_32
Source: "MyProg-x64.exe"; DestDir: "{app}"; Components: program_x64
Source: "MyProg-IA64.exe"; DestDir: "{app}"; Components: program_ia64
[Code]
type
TFileData = record
Component: string;
Description: string;
FileName: string;
Parameters: string;
end;
var
ComponentCombo: TNewComboBox;
ComponentArray: array of TFileData;
SelectionArray: array of TFileData;
procedure InitializeWizard;
begin
// this is a weakness of this solution - you need to fill the array
// of components that can be added to the final combo box when they
// are selected on component selection page. This is needed because
// you can't get neither file name nor destination directory of the
// file for the component from script. As first, set how many items
// you want to add to your component array storage
SetArrayLength(ComponentArray, 2);
// the Component member must match to the "Name" parameter from the
// [Components] section item since it's used in IsComponentSelected
// function call
ComponentArray[0].Component := 'program_32';
// the Description member is the text displayed in the combo item
ComponentArray[0].Description := 'Program 32-bit';
// the FileName member is the name of the file including path. This
// member may contain InnoSetup constants
ComponentArray[0].FileName := '{app}/MyProg.exe';
// the Parameters member contains execution parameters
ComponentArray[0].Parameters := '-a';
// this is the second item that can be added to the combo box, note
// that the program_ia64 component is not added to this array, what
// means, that it cannot be added to the "run" combo box. It's such
// kind of a filter for components like help files etc.
ComponentArray[1].Component := 'program_x64';
ComponentArray[1].Description := 'Program 64-bit';
ComponentArray[1].FileName := '{app}/MyProg-x64.exe';
ComponentArray[1].Parameters := '-b';
end;
procedure CurPageChanged(CurPageID: Integer);
var
I: Integer;
begin
if (CurPageID = wpFinished) then
begin
ComponentCombo := TNewComboBox.Create(WizardForm);
ComponentCombo.Parent := WizardForm.FinishedPage;
ComponentCombo.Left := ScaleX(256);
ComponentCombo.Top := ScaleY(208);
ComponentCombo.Width := ScaleX(145);
ComponentCombo.Height := ScaleY(21);
ComponentCombo.Style := csDropDownList;
ComponentCombo.Items.Add('None');
for I := 0 to GetArrayLength(ComponentArray) - 1 do
if IsComponentSelected(ComponentArray[I].Component) then
begin
ComponentCombo.Items.Add(ComponentArray[I].Description);
SetArrayLength(SelectionArray, GetArrayLength(SelectionArray) + 1);
SelectionArray[High(SelectionArray)] := ComponentArray[I];
end;
ComponentCombo.ItemIndex := 0;
end;
end;
function NextButtonClick(CurPageID: Integer): Boolean;
var
FileData: TFileData;
ResultCode: Integer;
begin
Result := True;
if (CurPageID = wpFinished) and (ComponentCombo.ItemIndex > 0) then
begin
FileData := SelectionArray[ComponentCombo.ItemIndex - 1];
Exec(ExpandConstant(FileData.FileName), FileData.Parameters, '', SW_SHOW,
ewNoWait, ResultCode);
end;
end;

Is it possible to accept custom command line parameters with Inno Setup

I am preparing an installer with Inno Setup. But I'd like to add an additional custom (none of the available parameters) command line parameters and would like to get the value of the parameter, like:
setup.exe /do something
Check if /do is given, then get the value of something. Is it possible? How can I do this?
With InnoSetup 5.5.5 (and perhaps other versions), just pass whatever you want as a parameter, prefixed by a /
c:\> myAppInstaller.exe /foo=wiggle
and in your myApp.iss:
[Setup]
AppName = {param:foo|waggle}
The |waggle provides a default value if no parameter matches. Inno setup is not case sensitive. This is a particularly nice way to handle command line options: They just spring into existence. I wish there was as slick a way to let users know what command line parameters the installer cares about.
BTW, this makes both #knguyen's and #steve-dunn's answers somewhat redundant. The utility functions do exactly what the built-in {param: } syntax does.
Further to #DanLocks' answer, the {param:*ParamName|DefaultValue*} constant is documented near the bottom of the Constants page:
http://www.jrsoftware.org/ishelp/index.php?topic=consts
I found it quite handy for optionally suppressing the license page. Here is all I needed to add (using Inno Setup 5.5.6(a)):
[code]
{ If there is a command-line parameter "skiplicense=true", don't display license page }
function ShouldSkipPage(PageID: Integer): Boolean;
begin
Result := False
if PageId = wpLicense then
if ExpandConstant('{param:skiplicense|false}') = 'true' then
Result := True;
end;
Inno Setup directly supports switches with syntax /Name=Value using {param} constant.
You can use the constant directly in sections, though this use is quite limited.
An example:
[Registry]
Root: HKCU; Subkey: "Software\My Company\My Program\Settings"; ValueType: string; \
ValueName: "Mode"; ValueData: "{param:Mode|DefaultMode}"
You will more likely want to use switches in Pascal Script.
If your switch has the syntax /Name=Value, the easiest way to read its value is using ExpandConstant function.
For example:
if ExpandConstant('{param:Mode|DefaultMode}') = 'DefaultMode' then
begin
Log('Installing for default mode');
end
else
begin
Log('Installing for different mode');
end;
If you want to use a switch value to toggle entries in sections, you can use Check parameter and a auxiliary function, like:
[Files]
Source: "Client.exe"; DestDir: "{app}"; Check: SwitchHasValue('Mode', 'Client')
Source: "Server.exe"; DestDir: "{app}"; Check: SwitchHasValue('Mode', 'Server')
[Code]
function SwitchHasValue(Name: string; Value: string): Boolean;
begin
Result := CompareText(ExpandConstant('{param:' + Name + '}'), Value) = 0;
end;
Ironically it is more difficult to check for a mere presence of switch (without a value).
Use can use a function CmdLineParamExists from #TLama's answer to Passing conditional parameter in Inno Setup.
function CmdLineParamExists(const Value: string): Boolean;
var
I: Integer;
begin
Result := False;
for I := 1 to ParamCount do
if CompareText(ParamStr(I), Value) = 0 then
begin
Result := True;
Exit;
end;
end;
You can obviously use the function in Pascal Script:
if CmdLineParamExists('/DefaultMode') then
begin
Log('Installing for default mode');
end
else
begin
Log('Installing for different mode');
end;
But you can even use it in sections, most typically using Check parameter:
[Files]
Source: "MyProg.hlp"; DestDir: "{app}"; Check: CmdLineParamExists('/InstallHelp')
A related problem:
Add user defined command line parameters to /? window
If you want to parse command line arguments from code in inno, then use a method similar to this. Just call the inno script from the command line as follows:
c:\MyInstallDirectory>MyInnoSetup.exe -myParam parameterValue
Then you can call the GetCommandLineParam like this wherever you need it:
myVariable := GetCommandLineParam('-myParam');
{ ================================================================== }
{ Allows for standard command line parsing assuming a key/value organization }
function GetCommandlineParam (inParam: String):String;
var
LoopVar : Integer;
BreakLoop : Boolean;
begin
{ Init the variable to known values }
LoopVar :=0;
Result := '';
BreakLoop := False;
{ Loop through the passed in arry to find the parameter }
while ( (LoopVar < ParamCount) and
(not BreakLoop) ) do
begin
{ Determine if the looked for parameter is the next value }
if ( (ParamStr(LoopVar) = inParam) and
( (LoopVar+1) <= ParamCount )) then
begin
{ Set the return result equal to the next command line parameter }
Result := ParamStr(LoopVar+1);
{ Break the loop }
BreakLoop := True;
end;
{ Increment the loop variable }
LoopVar := LoopVar + 1;
end;
end;
This is the function I wrote, which is an improvement of Steven Dunn's answer. You can use it as:
c:\MyInstallDirectory>MyInnoSetup.exe /myParam="parameterValue"
myVariable := GetCommandLineParam('/myParam');
{ util method, equivalent to C# string.StartsWith }
function StartsWith(SubStr, S: String): Boolean;
begin
Result:= Pos(SubStr, S) = 1;
end;
{ util method, equivalent to C# string.Replace }
function StringReplace(S, oldSubString, newSubString: String): String;
var
stringCopy: String;
begin
stringCopy := S; { Prevent modification to the original string }
StringChange(stringCopy, oldSubString, newSubString);
Result := stringCopy;
end;
{ ================================================================== }
function GetCommandlineParam(inParamName: String): String;
var
paramNameAndValue: String;
i: Integer;
begin
Result := '';
for i := 0 to ParamCount do
begin
paramNameAndValue := ParamStr(i);
if (StartsWith(inParamName, paramNameAndValue)) then
begin
Result := StringReplace(paramNameAndValue, inParamName + '=', '');
break;
end;
end;
end;
Yes it is possible, you can use the ParamStr function in PascalScript to access all the commandline parameters. The ParamCount function will give you the number of commandline parameters.
Another possibility is to use GetCmdTail
In response to:
"With InnoSetup 5.5.5 (and perhaps other versions), just pass whatever you want as a parameter, prefixed by a /"
"#NickG, yes, every constant you can expand by the ExpandConstant function"
This is not the case. Trying to use a command line parameter in ExpandConstant in InnoSetup 5.5.6 results in a runtime error.
PS: I would have added a comment directly but apparently I dont have enough "reputation"
I've modified a little bit knguyen's answer. Now it's case insensitive (you can write en console /myParam or /MYPARAM) and it can accept default value. Also I fixed the case when you receive larger parameter then expected (for ex: /myParamOther="parameterValue" in place of /myParam="parameterValue". Now myParamOther doesn't match).
function GetCommandlineParam(inParamName: String; defaultParam: String): String;
var
paramNameAndValue: String;
i: Integer;
begin
Result := defaultParam;
for i := 0 to ParamCount do
begin
paramNameAndValue := ParamStr(i);
if (Pos(Lowercase(inParamName)+'=', AnsiLowercase(paramNameAndValue)) = 1) then
begin
Result := Copy(paramNameAndValue, Length(inParamName)+2, Length(paramNameAndValue)-Length(inParamName));
break;
end;
end;
end;
I found the answer: GetCmdTail.
You can pass parameters to your installer scripts. Install the Inno Setup Preprocessor and read the documentation on passing custom command-line parameters.