I have a component list of more than 80 options,where user can select and then install.
The setup remembers the previous install components and automatically checks the Components, Now if user deselects all, the "Component Exist" Warning Message Box is shown.
Because user deselected all 80 options the list becomes to long and the Message box goes out of screen space and user is now stuck.
I know there is NoUninstallWarning in Messages which has the text for the warning message and takes one argument as %1
Is there a way I can change the argument value , rather than having all options listed in indiviual line , I would like to have them as comma separated?
Or if I can have a Scrollbar in the Message box?
Please help
No, this message is internal and you can't customise it like that without modifying Inno's own source code.
In that situation the user shouldn't be completely stuck -- they should be able to press ESC to return to the component selection window and then select everything again.
A simple way to avoid this problem is to not allow the user to deselect components, once installed. You can do this with a bit of code like this:
var
InstalledComponentsDisabled: Boolean;
procedure CurPageChanged(CurPageId: Integer);
var
i: Integer;
begin
if (CurPageId = wpSelectComponents) and
(WizardForm.PrevAppDir <> '') and
not InstalledComponentsDisabled then begin
InstalledComponentsDisabled := True;
for i := 0 to WizardForm.ComponentsList.Items.Count - 1 do begin
if WizardForm.ComponentsList.Checked[i] then begin
WizardForm.ComponentsList.ItemEnabled[i] := False;
end;
end;
end;
end;
This has a similar effect to making anything already installed on upgrades fixed.
An alternate option is to put disablenouninstallwarning on all of your components and then either implement the messagebox completely yourself, add a bit of static text warning about removing components permanently on the page, or even do something to actually support removing components (eg. [InstallDelete] entries or UninsHs).
Related
Referring to the question Basic or Advanced installation mode choice to skip or use advanced options pages, I need to skip the Preparing to Install wizard page now.
In my case this page is displayed because one or more programs are using files that need to be replaced by the installer; so the installer asks to the user if they want the setup to automatically close the applications and restart at the end of the setup.
I need that this page is hide from the setup process in Basic mode, and if some files are used, that the setup automatically closes the applications using them without asking anything to the user.
I've tried editing ShouldSkipPage as:
function ShouldSkipPage(PageID: Integer): Boolean;
begin
{ If "Basic" mode is selected, skip Directory and Components pages }
Result :=
ModePage.Values[0] and
((PageID = wpSelectDir) or (PageID = wpSelectComponents) or (PageID = wpReady) or (PageID = wpPreparing));
end;
adding (PageID = wpPreparing) but the page still displayed in Basic mode.
Is there a way to implement this using Inno Setup?
ShouldSkipPage event is not even called for wpPreparing. That page is not to be skipped.
If you still want to skip it, you have to use hacks like these:
How to skip all the wizard pages and go directly to the installation process?
Inno Setup - How to close finished installer after a certain time?
With the first approach, your code would look like:
[Code]
const
BN_CLICKED = 0;
WM_COMMAND = $0111;
CN_BASE = $BC00;
CN_COMMAND = CN_BASE + WM_COMMAND;
procedure CurPageChanged(CurPageID: Integer);
var
Param: Longint;
begin
{ If Basic mode is selected, skip Preparing page }
if (CurPageID = wpPreparing) and ModePage.Values[0] then
begin
Param := 0 or BN_CLICKED shl 16;
PostMessage(WizardForm.NextButton.Handle, CN_COMMAND, Param, 0);
end;
end;
Just don't do that. Ever. It is absolutely unacceptable for you to close an arbitrary list of applications without prompting the user. It's equally impolite to barrel ahead and then require a reboot at the end of the install. (It's unforgivable to then trigger the reboot without asking.)
What you can do is to put some code in the PrepareToInstall [Code] function which will automatically close your application. This executes before the user is prompted to close apps, so if it was only your apps involved then they will not be prompted.
I have an Excel AddIn written in Delphi that has a VCL form with a TMemo on it.
When I try to enter text into the Memo the input goes to Excel instead.
When I start the form modal (ShowModal), all works fine but obviously it's not possible to work with the main excel window and the addin's window concurrently.
The issue seems to be the exact similar to this question: Modeless form cannot receive keyboard input in Excel Add-in developed by Delphi
This answer suggests to handle WM_PARENTNOTIFY so I tried the following:
TMyForm = class(TForm)
...
procedure OnParentNotify(var Msg: TMessage); message WM_PARENTNOTIFY;
And in that procedure tried things like SetFocus, WinApi.Windows.SetFocus(self.Handle), SetForeGroundWindows, SetActiveWindow but that doesn't appear to work.
Other suggestions I've read is to run the UI in a different thread (which is of course not possible with VCL) and to install a keyboard hook with SetWindowsHookEx. Obviously that will give us keypress events but not sure what to do with those.
I am not using 3rd party tooling such as Add-In Express but just implementing IDTExtensibility2.
EDIT: more research suggests that Office uses an interface called IMsoComponent and and IMsoComponentManager as a way of tracking the active component in the application. Visual Studio uses these as IOleComponent and IOleComponentManager.
This link and this one suggest to register a new empty IOleComponent/IMsoComponent.
EDIT: MCVE can be fetched here, it's the smallest possible Excel AddIn code that will launch a VCL Form with a TEdit on it. The edit looses keyboard focus as soon as a worksheet is active.
I was having the same kind of problem. I am also implementing IDTExtensibility2 but as I am doing it on C++ I already managed to run the UI on a different thread. But anyway I was not fully happy with this solution. I would still have this problem if I wanted to use a VBA Userform as a TaskPane Window. I did try but as (I guess, didn´t check) the VBA userform will run on the native Excel Thread, just calling it on a different thread (to use as a TaskPane window) just marshalled it, didn´t mean that it was created on a different thread, so as I did try, there was this kind of problem.
I too did read and try to to handle WM_PARENTNOTIFY messages with SetFocus.. on my window but didn´t work.
This both interfaces IOleComponent and IOleComponentManager were new to me. Didn´t find the header files, but could write and implement from the descriptions at the link you shared.
How it worked for me was to register my IOleComponent implementation on every WM_SETCURSOR e WM_IME_SETCONTEXT at my Form Window. (I am not sure if this is exactly the best messages, but did work for me) and Revoke the component on every click back at EXCEL7 window.
The MSOCRINFO options I used to register was msocrfPreTranslateKey and msocadvfModal.
Hope that with this answer I will not receive tons of criticism. I know that it is a very specific issue, the question was with a -1 status when I read it, but was exactly what I needed to finish with this point. So I am just trying to be honest and share back something.
I finally found the solution to this after I decided to have another look at this...
Seems I was on the right track about needing IMsoComponentManager and IMsoComponent.
So first we need to retrieve the ComponentManager:
function GetMsoComponentManager(out ComponentManager: IMsoComponentManager): HRESULT;
var
MessageFilter: IMessageFilter;
ServiceProvider: IServiceProvider;
begin
MessageFilter := nil;
// Get the previous message filter by temporarily registering a new NULL message filter.
Result := CoRegisterMessageFilter(nil, MessageFilter);
if Succeeded(Result) then
begin
CoRegisterMessageFilter(MessageFilter, nil);
if (MessageFilter <> nil) then
begin
try
ServiceProvider := MessageFilter as IServiceProvider;
Result := ServiceProvider.QueryService(IID_IMsoComponentManager,
SID_SMsoComponentManager, ComponentManager);
if Assigned(ComponentManager) then
begin
end;
except
on E: Exception do
begin
Result := E_POINTER;
end;
end;
end;
end;
end;
Then we need to register a dummy component using msocrfPreTranslateAll (or msocrfPreTranslateKey)
procedure TVCLForm.RegisterComponent;
var
RegInfo: MSOCRINFO;
//MsoComponentManager: IMsoComponentManager;
hr: HRESULT;
bRes: Boolean;
begin
if FComponentId = 0 then
begin
FDummyMsoComponent := TDummyMsoComponent.Create;
ZeroMemory(#RegInfo, SizeOf(RegInfo));
RegInfo.cbSize := SizeOf(RegInfo);
RegInfo.grfcrf := msocrfPreTranslateAll or msocrfNeedIdleTime;
RegInfo.grfcadvf := DWORD(msocadvfModal);
bRes := ComponentManager.FRegisterComponent(FDummyMsoComponent, RegInfo,
FComponentId);
Memo1.Lines.Add(Format('FMsoComponentManager.FRegisterComponent: %s (Component ID: %d)', [BoolToStr(bRes, True), FComponentId]));
end
else begin
Memo1.Lines.Add(Format('Component with ID %d was already registered', [FComponentId]));
end;
if FComponentId > 0 then
begin
bRes := ComponentManager.FOnComponentActivate(FComponentId);
Memo1.Lines.Add(Format('FMsoComponentManager.FOnComponentActivate: %s (Component ID: %d)', [BoolToStr(bRes, True), FComponentId]));
end;
end;
Now in the Dummy Component implementation class we must handle FPreTranslateMessage:
function TDummyMsoComponent.FPreTranslateMessage(MSG: pMsg): BOOL;
var
hWndRoot: THandle;
begin
// this is the magic required to make sure non office owned windows (forms)
// receive Window messages. If we return True they will not, however if we
// return False, they will -> so we check if the message was meant for the
// window owner
hWndRoot := GetAncestor(MSG^.hwnd, GA_ROOT);
Result := (hWndRoot <> 0) and (IsDialogMessage(hWndRoot, MSG^));
end;
Finally a good place to to (un)register the Dummy component is when receiving WM_ACTIVATE. For example:
procedure TVCLForm.OnActivate(var Msg: TMessage);
var
bRes: Boolean;
begin
case Msg.WParam of
WA_ACTIVE:
begin
Memo1.Lines.Add('WA_ACTIVE');
RegisterComponent;
end;
WA_CLICKACTIVE:
begin
Memo1.Lines.Add('WA_CLICKACTIVE');
RegisterComponent;
end;
WA_INACTIVE:
begin
Memo1.Lines.Add('WA_INACTIVE');
UnRegisterComponent;
end
else
Memo1.Lines.Add('OTHER/UNKNOWN');
end;
end;
This all seems to work well and does not require intercepting WM_SETCURSOR or WM_IME_SETCONTEXT nor does it need subclassing of the Excel Window.
Once cleaned up will probably write a blog and place all the complete code on Github.
I am creating installer using Inno Setup. I have calculated available disk space using function GetSpaceOnDisk. I am displaying error message if available disk space is not enough, and then installer will not continue.
But before my error message displays, Inno Setup Disk space warning is shown with Yes/No option. How can I disable this warning?
You cannot disable the check, nor change the buttons.
What you can do, is to revert a meaning of a question in the message by overriding the default text using [Messages] section, to say something like:
Do you want to cancel installation?
If the user presses No, the installer stays on the Select Destination Location page. If the user presses Yes, the NextButtonClick(wpSelectDir) gets called. There you repeat the check for the disk space (to distinguish the call from a basic scenario, with no warning), and if there's not enough space, you abort the installer forcefully.
[Messages]
DiskSpaceWarning=Setup requires at least %1 KB of free space to install, but the selected drive only has %2 KB available.%n%nDo you want to cancel installation?
[Code]
function NotEnoughSpace: Boolean;
begin
Result := { Check disk space };
end;
procedure ExitProcess(exitCode:integer);
external 'ExitProcess#kernel32.dll stdcall';
function NextButtonClick(CurPageID: Integer): Boolean;
begin
if CurPageID = wpSelectDir then
begin
if NotEnoughSpace then
begin
ExitProcess(0);
end;
end;
Result := True;
end;
The ultimate solution is to re-implement the Select Destination Location page. It's not that difficult. It's just one edit box and one button.
I am using the Modular Dependencies scripts found here : http://www.codeproject.com/Articles/20868/NET-Framework-Installer-for-InnoSetup to install .Net and VC++.
Now, it's all well and good, and has allowed me to reduce my installer size by about 6MB.
But I want to provide the user with the fastest setup possible, and when the user already has all the necessary dependencies, he is presented with an empty "memo" screen. That's really less than optimal, and I'd wish to at least skip this step if there is nothing.
I can test if there is any component to install using if (GetArrayLength(products) = 0) then, but I can't find out where to place this test.
In the InitializeSetup function I can't find how to tell "skip this screen", and in the ShouldSkipPage function, I can't find how to tell "skip the Memo page".
Thanks !
It might be this way:
[Code]
function ShouldSkipPage(PageID: Integer): Boolean;
begin
// skip page when we are on the ready page and the memo is empty
Result := (PageID = wpReady) and (WizardForm.ReadyMemo.Text = '');
end;
I want to install the same application multiple times on the same system, for example for two user using two different web services (each one has their own).
In in my setup script I want to change the AppID and AppName based on input from the user (for example my default AppName="Service App" should be changed to AppName="Service App One" if the user has entered "One").
Above changes should be reflected in the "Select Start Menu Folder" page.
How can the Next click events for the "Select Start Menu Folder" and the "Select Destination Location" wizard pages be caught? This is required to validate the user input.
AppID can include {code...} constants (see the Inno Setup documentation), so you are free to add a custom wizard page to enter the additional string that is part of the AppID. I don't think it makes sense to do this for AppName, as according to the documentation it is used for display purposes in the wizard only.
You should insert your custom input page before the "Select Destination Location" page and try to use a {code...} constant for DefaultDirName too, using the value the user entered before.
See the CodeDlg.iss sample script for adding wizard pages and the NextButtonClick handler.
It's not a conplete answer to your question, but I thought I'd show it anyway. If you are into scripts you can maybe take it as a starting point.
In my scripts I want the copyright year (AppCopyright) to reflect the current year (when the script is build).
I use the following code:
AppCopyright=2003-{code:TodayAsYYYY} © E.De.L.Com bvba
Notice the {code:FunctionName}
In the [code] section at the end of the script I have the following routine:
function TodayAsYYYY(param:string) : string;
begin
Result := GetDateTimeString('yyyy', #0, #0);
end;
function TodayAsYYYYMMDD(param:string) : string;
begin
Result := GetDateTimeString('yyyymmdd', #0, #0);
end;
As I said, it's not a complete answer. But I use InnoSetup maybe 1, 2 weeks a year, so I can't help you any more. Hope it helps, anyway.
Hi thanks for replying after some struggle I found answers to my own question and then realised maybe I was not asking the question correctly.
The output required was having two separate installed application with their own uninstaller under the start menu and thought Changing Appid and Appname will do the trick.
Here's what I did
#define MyAppName "My Application"
[Setup]
DefaultDirName={pf}\Application\MyApp
DefaultGroupName={#MyAppName}
above two required editing which was possible in the custom pages using following
WizardForm.DirEdit.Text := 'for DefaultDirName' (Select Destination Location")
WizardForm.GroupEdit.Text:= 'DefaultGroupName'
WizardGroupValue is used for reading values of "Select Start Menu"
for accessing the next event on in built wizard I used the following
//Validate Select Start Menu and Destination values
function NextButtonClick(CurPageID: Integer): Boolean;
var
ResultCode: Integer;
begin
case CurPageID of
wpSelectDir:
begin
//Do validation
end;
wpSelectProgramGroup:
begin
//Do validation
end;
end;
Result := True;
end;