TInputDirWizardPage with Radio Buttons - radio-button

I need a setup page with two radio buttons. Clicking the second button should enable an input to define a path (Like it's done in TInputDirWizardPage).
Is it possible to customize TInputDirWizardPage for my needs?
Or do I need to build a CustomPage and define the controls by myself?
If the second question will be answered with yes, how am I able to use the "directory input" (from the TInputDirWizardPage), or is it also neccessary to build this on my own?

As you correctly guessed you have several options:
Start with TWizardPage and build everything from scratch
Start with TInputDirWizardPage and add the radio buttons
Start with TInputOptionWizardPage and add the edit box
The second option probably involves least customization. Though it needs a hack from Is it possible to allow a user to skip a TInputDirWizardPage in Inno Setup?
{ WORKAROUND }
{ Checkboxes and Radio buttons created on runtime do }
{ not scale their height automatically. }
{ See https://stackoverflow.com/q/30469660/850848 }
procedure ScaleFixedHeightControl(Control: TButtonControl);
begin
Control.Height := ScaleY(Control.Height);
end;
var
Page: TInputDirWizardPage;
DefaultLocationButton: TRadioButton;
CustomLocationButton: TRadioButton;
OldNextButtonOnClick: TNotifyEvent;
procedure LocationButtonClick(Sender: TObject);
begin
Page.Edits[0].Enabled := CustomLocationButton.Checked;
Page.Buttons[0].Enabled := CustomLocationButton.Checked;
end;
procedure NextButtonOnClick(Sender: TObject);
var
PrevDir: string;
begin
{ Do not validate, when "default location" is selected }
if (WizardForm.CurPageID = Page.ID) and DefaultLocationButton.Checked then
begin
PrevDir := Page.Values[0];
Page.Values[0] := GetWinDir; { Force value to pass validation }
OldNextButtonOnClick(Sender);
Page.Values[0] := PrevDir;
end
else
begin
OldNextButtonOnClick(Sender);
end;
end;
procedure InitializeWizard();
begin
Page := CreateInputDirPage(
wpWelcome,
'Select Personal Data Location', 'Where should personal data files be stored?',
'', False, 'New Folder');
Page.Add('');
DefaultLocationButton := TRadioButton.Create(WizardForm);
DefaultLocationButton.Parent := Page.Surface;
DefaultLocationButton.Top := Page.Edits[0].Top;
DefaultLocationButton.Caption := 'Use default location';
DefaultLocationButton.Checked := True;
DefaultLocationButton.OnClick := #LocationButtonClick;
ScaleFixedHeightControl(DefaultLocationButton);
CustomLocationButton := TRadioButton.Create(WizardForm);
CustomLocationButton.Parent := Page.Surface;
CustomLocationButton.Top :=
DefaultLocationButton.Top + DefaultLocationButton.Height + ScaleY(8);
CustomLocationButton.Caption := 'Use custom location';
CustomLocationButton.OnClick := #LocationButtonClick;
ScaleFixedHeightControl(DefaultLocationButton);
Page.Buttons[0].Top :=
Page.Buttons[0].Top +
((CustomLocationButton.Top + CustomLocationButton.Height + ScaleY(8)) -
Page.Edits[0].Top);
Page.Edits[0].Top :=
CustomLocationButton.Top + CustomLocationButton.Height + ScaleY(8);
Page.Edits[0].Left := Page.Edits[0].Left + ScaleX(16);
Page.Edits[0].Width := Page.Edits[0].Width - ScaleX(16);
Page.Edits[0].TabOrder := CustomLocationButton.TabOrder + 1;
Page.Buttons[0].TabOrder := Page.Edits[0].TabOrder + 1;
LocationButtonClick(nil); { Update edit for initial state of buttons }
OldNextButtonOnClick := WizardForm.NextButton.OnClick;
WizardForm.NextButton.OnClick := #NextButtonOnClick;
end;
More general question on the topic: Inno Setup Placing image/control on custom page.

Related

Opening a program using Remote Desktop on Windows server 2008 r2 from a program e written in Delphi 11.2

if PageControl1.ActivePage.Pageindex = 0 then
begin
rdp13 := TMsRdpClient7NotSafeForScripting.Create(Self);
with Rdp13 do
begin
Rdp13.align:= alclient;
Rdp13.BringToFront;
Rdp13.Server := s ;
Rdp13.UserName := 'xxx';
Rdp13.AdvancedSettings2.ClearTextPassword:= 'tttt';
Rdp13.RemoteProgram2.RemoteProgramMode:=True;
Rdp13.Connect;
RDP13.Onconnected := OnRDPConnect ;
procedure TForm2.Button7Click(Sender: TObject);
begin
rdp13.RemoteProgram.ServerStartProgram('C:\Program Files (x86)\xxxxxxx.exe', '', 'C:\Program Files (x86)\xxxxxx', false, '', false);
end ;
I can connect,but when use rdp13.RemoteProgram2.RemoteProgramMode:=True;
I always get a blank screen after connect.
Help Please

Send credentials in SOAP header

I'm trying to develop a small program in Delphi xe10.3 to get data from a web service using SOAP.
The WSDL is https://www1.gsis.gr/wsaade/RgWsPublic2/RgWsPublic2?WSDL.
I did import it into Delphi.
The problem I’m facing is that I couldn’t find a working way to pass the credentials (basic authentication) into request header even I did use some methods I found on the web. The service responds that there are not credentials into request.
I want to mention that this is my first try to use SOAP technology and I have been confused. It will be appreciated if someone could help me to resolve this issue.
The code is :
procedure TForm1.EchoClick(Sender: TObject);
var Service : RgWsPublic2Service_Interface;
httpRio : THTTPRIO;
inpr : rg_ws_public2_input_rtType2;
ddate : TXSDate;
ReturnInfo : Result;
AskedInfo : rg_ws_public2_basic_rtType2;
ErrorStr : string;
begin
httpRio := THTTPRIO.Create(nil);
ReturnInfo := Result.Create;
ddate := TXSdate.Create;
ddate.AsDate := date;
inpr := rg_ws_public2_input_rtType2.Create;
inpr.afm_called_by := ed_myAfm.Text;
inpr.afm_called_for := ed_ClientAfm.Text;
inpr.as_on_date := ddate;
httpRio.HTTPWebNode.UserName := Ed_usname.Text;
httpRio.HTTPWebNode.Password := Ed_pass.Text;
Service := GetRgWsPublic2Service_Interface(true,'',httpRio);
ReturnInfo := Service.rgWsPublic2AfmMethod(inpr);
ErrorStr := ReturnInfo.rg_ws_public2_result_rtType.error_rec.error_code+' / '+
ReturnInfo.rg_ws_public2_result_rtType.error_rec.error_descr;
if ErrorStr <> '' then
begin
memo1.Lines.Add('Error!!!!');
memo1.Lines.Add(ErrorStr);
end
else
begin
memo1.Lines.Text := ReturnInfo.rg_ws_public2_result_rtType.ToString;
AskedInfo := ReturnInfo.rg_ws_public2_result_rtType.basic_rec;
EdEnterpriceName.Text := AskedInfo.onomasia;
mm_DoyInfo.Text := AskedInfo.doy+' / '+AskedInfo.doy_descr;
end;
ReturnInfo.Destroy;
ddate.Destroy;
end;

Manipulating MS Word Open Dialog from Delphi

Every time I run this code, I manually confirm that I want to convert "PDF Files".
Is there any way to automate it?
procedure TFWordAnalyzer.Button1Click(Sender: TObject);
var
Clipboard:TClipboard;
WordApp,WordDocument: OleVariant;
begin
try
WordApp := GetActiveOleObject('Word.Application');
except
WordApp := CreateOleObject('Word.Application') ;
end;
WordApp.visible := true;
WordApp.DisplayAlerts := False;
WordDocument := WordApp.Documents.Open('D:\Corpus\Adyg\Hudmir.pdf', true, false);
WordDocument.range.copy;
sleep(1000);//Otherwise it fails
RichEdit1.Clear;
RichEdit1.PasteFromClipboard;
WordDocument.close;
WordApp.Quit;
WordApp := Unassigned;
end;
Both of the problems you have mentioned namely how to avoid the Convert File dialog and the pop-up prompt about saving data copied to the Clipboard can by avoided by specifiying the correct values in calls to Word's automation.
1. Avoiding the Convert File dialog
Update the line of your code which opens the document to
WordDocument := WordApp.Documents.Open('D:\Corpus\Adyg\Hudmir.pdf', ConfirmConversions := False);
This should open the document without the Confirm Conversion dialog being invoked.
Assuming that works, you should be able to reinstate your second and third arguments, so that you have
WordDocument := WordApp.Documents.Open('D:\Corpus\Adyg\Hudmir.pdf', true, false, ConfirmConversions := False);
Note that the syntax
WordApp.Documents.Open([...], ConfirmConversions := False)
was added at the same time (in D2) at the same time as OLE automation via OleVariants, to support optional arguments being passed to automation calls.
PS: With this change, you may find that your call to Sleep in unnecessary. I say "may" because my test code did not need it in the first place.
2. Avoiding the prompt to save the contents of the Clipboard.
This prompt pops if you attempt to close Word using automation when you have copied a large amount of data to the Clipboard.
To avoid it, simply replace
WordApp.Quit;
by
WordApp.Quit(SaveChanges := False);
That should be all you need. The fact that this works is slightly counter-intuitive: As you saved the data from your WordDocument, you might expect that using WordDocument is the way to clear the Clipboard data (see the now-superfluous update below). However, by setting up a test app with a number of checkboxes which control the parameter values for WordDocument.Close and WordApp.Close, and testing the various permutations of their settings, I established that only the value of SaveChanges passed to WordApp.Quit influences this behaviour and avoids the dialog (if it would otherwise have been displayed).
The remainder of this answer can be disregarded but I have let it remain aas it may help future readers with similar problems. I have also added the VBA tag to the q, as I found numerous questions about how to avoid the Clipboard-save prompt in VBA questions, but none with a definitive solution which worked with the code here.
+++++++++++++++++++++++++++++++++++++++++
Update The OP reported that the above code can result in a prompt from
Word as it closes down saying that there is a large amount of data on the Clipboard,
and queried whether it was possible to automate responding to the prompt.
Google found several suggestions for avoiding the prompt, but I found that none
of them worked reliably working with a 17Mb .Pdf file. The method shown below,
which I arrived at by experiment, does seem to work reliably but is not as simple
as I would like it to be.
The reason the prompt occurs is that Word evidently sets up internal pointers to
the contents provided to the Clipboard, so I wondered if setting the text of the
Range to something much smaller and then discarding it before closing the document
would work. I ran into 2 problems:
calling vRange.End := 1 had no effect
trying to call vRange.Set_Text := ' ' provoked an exception claiming that vRange
does not support the Set_Text method despite the fact that the Range interface in
the Word2000.Pas import unit for MS Word clearly does. So, the code below picks up
the Range object contained in the vRange variant and calls Set_Text on that. This works
but means that you need to add Word2000.Pas (or one of the more recent Word import
units) to the Uses clause.
Note that I removed the call to GetActiveOle object to avoid an exception being
raised when it failed, which disrupted debugging.
procedure TForm1.Button1Click(Sender: TObject);
var
Clipboard:TClipboard;
WordApp,
WordDocument: OleVariant;
vRange : OleVariant;
iRange : Range; // or WordRange for D10.3.1+
begin
try
WordApp := CreateOleObject('Word.Application') ;
WordApp.visible := true;
WordApp.DisplayAlerts := False;
try
WordDocument := WordApp.Documents.Open('D:\aaad7\aaaofficeauto\test.pdf', ConfirmConversions := False); //, true, false);
vRange := WordDocument.range;
Label1.Caption := IntToStr(vRange.Start) + ':' + IntToStr(vRange.End);
vRange.copy;
sleep(1000);//Otherwise it fails
RichEdit1.Clear;
RichEdit1.PasteFromClipboard;
// vRange.Set_Text(' '); // This provokes a "Method Set_Text not supported by automation object" exception
// SO ...
// Pick up the Range interface contained in the vRange variant
iRange := IDispatch(WordDocument.range) as Range;
// and call Set_Text on it to set the Range's text to a single space
iRange.Set_Text(' ');
// Clear the iRange interface
iRange := Nil;
finally
// beware that the following discards and changes to the .Pdf
// so if you want to save any changes, you should do it
// before calling iRange.Set_Text
WordDocument.close(False, False, False);
WordDocument := UnAssigned;
end;
finally
WordApp.Quit(False, False, False);
WordApp := Unassigned;
end;
end;
Tested with MS Word 2010 on Windows 10 64-bit/
Here is the working code:
procedure TFWordAnalyzer.Button5Click(Sender: TObject);
var
Clipboard:TClipboard;
WordApp,
WordDocument: OleVariant;
vRange : OleVariant;
iRange : WordRange;
begin
try
WordApp := CreateOleObject('Word.Application') ;
WordApp.visible := true;
WordApp.DisplayAlerts := False;
try
WordDocument := WordApp.Documents.Open('D:\Corpus\Adyg\Hudmir.pdf', ConfirmConversions := False); //, true, false);
vRange := WordDocument.range;
Label1.Caption := IntToStr(vRange.Start) + ':' + IntToStr(vRange.End);
vRange.copy;
sleep(1000);//Otherwise it fails
RichEdit1.Clear;
RichEdit1.PasteFromClipboard;
iRange := IDispatch(WordDocument.range) as WordRange;
iRange.Set_Text(' ');
iRange := Nil;
finally
WordDocument.close(False, False, False);
WordDocument := UnAssigned;
end;
finally
WordApp.Quit(False, False, False);
WordApp := Unassigned;
end;
end;

How can I set submenus to pop-up on the right side in Delphi?

I'm building applications in Delphi and have problems with sub-menus. It seems that the default position of a submenu is to the left when activated in later Windows versions(i.e w8 and Windows Server 2012). I want them to appear on the right side (by default) The problem cam be solved in the Settings for TabletPC in the control panel in Windows 8. But in Windows Server 2012 there is no such alternative. How could I set it in Windows Server 2012 or better still, how could I do it programmatically in Delphi?
Thanks in anticipation
Peter
Do you need for MainMenu or PopupMenu?
{ for Popup menu }
procedure TForm1.FormContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean);
var
LMousePosition: TPoint;
begin
LMousePosition := ClientToScreen(MousePos);
PopupMenu1.Popup(LMousePosition.X - 125, LMousePosition.Y);
end;
{ for Main menu }
procedure TForm1.FormCreate(Sender: TObject);
var
LMenuItem: TMenuItemInfo;
LMainMenu: HMENU;
LBuffer: Array [0..79] of Char;
begin
LMainMenu := Self.Menu.Handle;
LMenuItem.cbSize := SizeOf(LMenuItem);
LMenuItem.fMask := MIIM_TYPE;
LMenuItem.dwTypeData := LBuffer;
LMenuItem.cch := SizeOf(LBuffer);
GetMenuItemInfo(LMainMenu, miMenuItem1.Command, False, LMenuItem);
LMenuItem.fType := LMenuItem.fType or MFT_RIGHTJUSTIFY;
SetMenuItemInfo(LMainMenu, miMenuItem1.Command, False, LMenuItem);
end;

How to use Inno Setup to update a database using .sql script

I'd like to compile a setup that will connect to a remote database using the credentials provided by the user, then install few db components using .sql script.
Is that possible using Inno Setup?
More details:
I'd like to have a custom form, asking the user to enter the database address and credentials, then run a command that will execute an sql script that will update the remote database server.
If the update is successful - complete the installation with success.
This is rather general question - I have a lot of customized setups that should connect to different servers/run different scripts - the idea is to build a generic form that will provide this functionality.
I don't think you can have a completely generic form, as for different servers you may need either a single connection string, or a server name and an (optional) port; for some servers you will use system authentication, for others a user name password tuple.
Having said that I will give you a small demo Inno script that asks for server name and port, user name and password, then makes a few tests, then executes an application that is extracted (by code) to the temp directory and will be deleted by the installer. You can use this as a starting point for your scripts. Having a few of such snippets, and including them in your scripts as necessary will probably be all you need:
[Setup]
AppID=DBUpdateTest
AppName=Test
AppVerName=Test 0.1
AppPublisher=My Company, Inc.
DefaultDirName={pf}\Test
DefaultGroupName=Test
DisableDirPage=yes
DisableProgramGroupPage=yes
OutputBaseFilename=setup
PrivilegesRequired=none
[Files]
Source: "isql.exe"; DestDir: "{tmp}"; Flags: dontcopy
Source: "update_V42.sql"; DestDir: "{tmp}"; Flags: dontcopy
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Code]
var
DBPage: TInputQueryWizardPage;
procedure InitializeWizard;
begin
DBPage := CreateInputQueryPage(wpReady,
'Database Connection Information', 'Which database is to be updated?',
'Please specify the server and the connection credentials, then click Next.');
DBPage.Add('Server:', False);
DBPage.Add('Port:', False);
DBPage.Add('User name:', False);
DBPage.Add('Password:', True);
DBPage.Values[0] := GetPreviousData('Server', '');
DBPage.Values[1] := GetPreviousData('Port', '');
DBPage.Values[2] := GetPreviousData('UserName', '');
DBPage.Values[3] := GetPreviousData('Password', '');
end;
procedure RegisterPreviousData(PreviousDataKey: Integer);
begin
SetPreviousData(PreviousDataKey, 'Server', DBPage.Values[0]);
SetPreviousData(PreviousDataKey, 'Port', DBPage.Values[1]);
SetPreviousData(PreviousDataKey, 'UserName', DBPage.Values[2]);
SetPreviousData(PreviousDataKey, 'Password', DBPage.Values[3]);
end;
function NextButtonClick(CurPageID: Integer): Boolean;
var
ResultCode: Integer;
begin
Result := True;
if CurPageID = DBPage.ID then begin
if DBPage.Values[0] = '' then begin
MsgBox('You must enter the server name or address.', mbError, MB_OK);
Result := False;
end else if DBPage.Values[2] = '' then begin
MsgBox('You must enter the user name.', mbError, MB_OK);
Result := False;
end else if DBPage.Values[3] = '' then begin
MsgBox('You must enter the user password.', mbError, MB_OK);
Result := False;
end else begin
ExtractTemporaryFile('isql.exe');
ExtractTemporaryFile('update_V42.sql');
if Exec(ExpandConstant('{tmp}') + '\isql.exe', '--user ' + DBPage.Values[2]
+ ' --password ' + DBPage.Values[3] + ' --database ' + DBPage.Values[0]
+ ':foo --script update_V42.sql', '',
SW_HIDE, ewWaitUntilTerminated, ResultCode)
then begin
// check ResultCode and set Result accordingly
Result := ResultCode = 0;
end else begin
MsgBox('Database update failed:'#10#10 + SysErrorMessage(ResultCode),
mbError, MB_OK);
Result := False;
end;
end;
end;
end;
Beware: I haven't fully tested this, so there may be more code necessary to properly clean everything up. Error handling is definitely missing!