Inno Setup disable component selection when a spec

2019-02-16 00:39发布

I would like to be able to disable the selection of a component based on a specific component being selected. I cannot do this through nesting of components, as the component needs to be selectable on it's own, but not if another specific component is selected. Currently I handle this using the NextButtonClick event displaying a message:

if IsComponentSelected('Client') and IsComponentSelected('Sync') then
  begin
    MsgBox('A Client installation cannot have the Synchronisation component selected.',
      mbError, MB_OK);
      Result := False;      
  end;

which prevents the user from continuing until they deselect the incompatible combination. However, it would be far more elegant if I could simply disable the selection of the component rather than displaying a message:

if CurPageID = wpSelectComponents then
  begin
    if IsComponentSelected('Client') then
      begin
        WizardForm.ComponentsList.Checked[15] := False;
        WizardForm.ComponentsList.ItemEnabled[15] := False;
      end
    else
      begin
        WizardForm.ComponentsList.ItemEnabled[15] := True;
      end;
  end;

The problem is there doesn't seem to be an event that allows this to change dynamically as the user selects or deselects components. Is there an event I can place this code in or another way to do this?

标签: inno-setup
2条回答
爷、活的狠高调
2楼-- · 2019-02-16 01:06

The code I used in the end is based entirely on @TLama's code that he provided previously as this does not require the use of an external DLL.

[Code]
const
//Define global constants
  CompIndexSync = 15;
  CompIndexSyncClient = 16;
  CompIndexSyncServer = 17;

var
//Define global variables
  CompPageVisited: Boolean;
  DefaultCompClickCheck: TNotifyEvent;
  DefaultCompTypeChange: TNotifyEvent;

//Uncheck and set the enabled state of the Sync components based on whether the Client component is selected
procedure UpdateComponents;
begin
  with WizardForm.ComponentsList do
    begin
      if IsComponentSelected('Client') then
        begin
          CheckItem(CompIndexSync, coUncheck);
        end; 
      ItemEnabled[CompIndexSync] := not IsComponentSelected('Client');
      ItemEnabled[CompIndexSyncClient] := not IsComponentSelected('Client');
      ItemEnabled[CompIndexSyncServer] := not IsComponentSelected('Client');
      Invalidate; //required for text state to update correctly
    end;
end;

//Update the component states if the component states change and restore the original event handler procedures
procedure ComponentsClickCheck(Sender: TObject);
begin
  DefaultCompClickCheck(Sender);
  UpdateComponents;
end;

procedure ComponentsTypesComboChange(Sender: TObject);
begin
  DefaultCompTypeChange(Sender);
  UpdateComponents;
end;

procedure InitializeWizard();
begin
//Store the original Components Page OnClickCheck and Types Combo Box OnChange event procedures and assign custom procedures
  DefaultCompClickCheck := WizardForm.ComponentsList.OnClickCheck;
  WizardForm.ComponentsList.OnClickCheck := @ComponentsClickCheck;
  DefaultCompTypeChange := WizardForm.TypesCombo.OnChange;
  WizardForm.TypesCombo.OnChange := @ComponentsTypesComboChange;
end;

procedure CurPageChanged(CurPageID: Integer);
begin
//Update the Components Page if entered for the first time
  if (CurPageID = wpSelectComponents) and not CompPageVisited then
    begin
      CompPageVisited := True;
      UpdateComponents;
    end;
end;

Although this and @RobeN's solutions both work, they both exhibit a graphical update issue i.e. when changing the status of the components from active to disabled and vice versa, the text doesn't automatically dim or undim until the scroll bar is dragged.

Note that the refresh issue described above is resolved by adding an `Invalidate;' line. Code updated above with a comment to reflect this. See Inno Setup Components graphical refresh issue for further information. Thanks @TLama.

查看更多
疯言疯语
3楼-- · 2019-02-16 01:21

You had TLama's solution which was probably better than the following, but still this code is also a way to achieve the goal (though you will need innocallback.dll):

[Setup]
AppName=My Program
AppVersion=1.5
DefaultDirName={pf}\My Program

[Files]
Source: ".\InnoCallback.dll"; DestDir: "{tmp}"; Flags: dontcopy nocompression

[Components]
Name: "Client"; Description: "Client"; Types: custom
Name: "Sync"; Description: "Sync"; Types: custom

[Code]
var
TimerID: Integer;

type
TTimerProc = procedure(Wnd: HWND; Msg: UINT; TimerID: UINT_PTR; 
SysTime: DWORD);
function WrapTimerProc(Callback: TTimerProc; ParamCount: Integer): LongWord;
external 'wrapcallback@files:InnoCallback.dll stdcall';    
function SetTimer(hWnd: HWND; nIDEvent, uElapse: UINT;
lpTimerFunc: UINT): UINT; external 'SetTimer@user32.dll stdcall';

function KillTimer(hWnd: HWND; uIDEvent: UINT): BOOL; 
external 'KillTimer@user32.dll stdcall'; 

procedure KillComponentsTimer;
begin
  if TimerID <> 0 then 
    begin
      if KillTimer(0, TimerID) then
        TimerID := 1;
    end;
end;

procedure OnComponentsCheck(Wnd: HWND; Msg: UINT; TimerID: UINT_PTR; 
SysTime: DWORD);
begin
    if IsComponentSelected('Client') then begin
        WizardForm.ComponentsList.Checked[1] := False;
        WizardForm.ComponentsList.ItemEnabled[1] := False;
    end
    else begin
        WizardForm.ComponentsList.ItemEnabled[1] := True;
    end;    
    if IsComponentSelected('Sync') then begin
        WizardForm.ComponentsList.Checked[0] := False;
        WizardForm.ComponentsList.ItemEnabled[0] := False;
    end
    else begin 
        WizardForm.ComponentsList.ItemEnabled[0] := True;
    end;
end;

procedure ComponentsCheck();
var
  TimerCallback: LongWord;
begin
  TimerCallback := WrapTimerProc(@OnComponentsCheck, 4);
  TimerID := SetTimer(0, 0, 100, TimerCallback);
end;

procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpSelectComponents then
  begin
    ComponentsCheck;
  end;
if CurPageID = not wpSelectComponents then 
  begin
   KillComponentsTimer;
  end;
end;
查看更多
登录 后发表回答