Inno Setup Uncheck a task when another task is che

2020-03-07 06:49发布

问题:

I am trying to intercept the WizardForm.TasksList.OnClickCheck event so that I can uncheck a task when another task is selected. I know that normally radio buttons would be used in this situation, but automatically unchecking one task when another is selected works better here due to the use of multiple hierarchical tasks and the fact that if radio buttons are used, you always have to have one of the two selected when at the top of the task subtree. Redesigning the task hierarchy is not feasible in order to maintain consistency, as this is to include two temporary tasks that will be removed in a future version of the installer. I have written the following to do this:

var
  DefaultTasksClickCheck: TNotifyEvent;

{ Uncheck tasks based on what other tasks are selected }
procedure UpdateTasks();
var
  intIndex: Integer;
begin
  with WizardForm.TasksList do
    begin
      if IsTaskSelected('Task1') then
        begin
          intIndex := WizardForm.TasksList.Items.IndexOf('Task36 Description');
          CheckItem(intIndex, coUncheck);
        end;
      if IsTaskSelected('Task36') then
        begin
          intIndex := WizardForm.TasksList.Items.IndexOf('Task1 Description');
          CheckItem(intIndex, coUncheck);
        end;
    end;
end;

{ Update the task states if the task states change and restore the original event handler procedure }
procedure TasksClickCheck(Sender: TObject);
begin
  DefaultTasksClickCheck(Sender);
  UpdateTasks;
end;

procedure InitializeWizard();
begin
  { Store the original Tasks Page OnClickCheck event procedure and assign custom procedure }
  DefaultTasksClickCheck := WizardForm.TasksList.OnClickCheck;
  WizardForm.TasksList.OnClickCheck := @TasksClickCheck;
end;

However, when I run the code, I get an:

Out of Proc Range

error, when clicking any checkbox, with DefaultTasksClickCheck(Sender); highlighted as the offending line. If I comment out this line, I no longer get the error, but am obviously no longer restoring the original event handler and it still doesn't check and uncheck the tasks correctly, with Task36 uncheckable when Task1 is checked. What have I done wrong?

回答1:

  1. The WizardForm.TasksList.OnClickCheck is not assigned by the Inno Setup itself (contrary to WizardForm.ComponentsList.OnClickCheck), so you cannot call it.

    To fix the problem, either:

    • completely remove the DefaultTasksClickCheck;
    • or if you want to be covered in case the event starts being used in future versions of Inno Setup, check if it is nil before calling it.
  2. You cannot know what task was checked most recently in the OnClickCheck handler. So you have to remember the previously checked task to correctly decide what task to unselect.

[Tasks]
Name: Task1; Description: "Task1 Description"
Name: Task36; Description: "Task36 Description"; Flags: unchecked

[Code]

var
  DefaultTasksClickCheck: TNotifyEvent;
  Task1Selected: Boolean;

procedure UpdateTasks;
var
  Index: Integer;
begin
  { Task1 was just checked, uncheck Task36 }
  if (not Task1Selected) and IsTaskSelected('Task1') then
  begin
    Index := WizardForm.TasksList.Items.IndexOf('Task36 Description');
    WizardForm.TasksList.CheckItem(Index, coUncheck);
    Task1Selected := True;
  end
    else 
  { Task36 was just checked, uncheck Task1 }
  if Task1Selected and IsTaskSelected('Task36') then
  begin
    Index := WizardForm.TasksList.Items.IndexOf('Task1 Description');
    WizardForm.TasksList.CheckItem(Index, coUncheck);
    Task1Selected := False;
  end;
end;

procedure TasksClickCheck(Sender: TObject);
begin
  if DefaultTasksClickCheck <> nil then
    DefaultTasksClickCheck(Sender);
  UpdateTasks;
end;

procedure CurPageChanged(CurPageID: Integer);
begin
  if CurPageID = wpSelectTasks then
  begin
    { Only now is the task list initialized, check what is the current state }
    { This is particularly important during upgrades, }
    { when the task does not have its default state }
    Task1Selected := IsTaskSelected('Task1');
  end;
end;

procedure InitializeWizard();
begin
  DefaultTasksClickCheck := WizardForm.TasksList.OnClickCheck;
  WizardForm.TasksList.OnClickCheck := @TasksClickCheck;
end;

In Inno Setup 6, instead of using indexes, you can also use task names with use of WizardIsTaskSelected and WizardSelectTasks. For an example, see Inno Setup: how to auto select a component if another component is selected?.


For a more generic solution to detecting what item has been checked, see Inno Setup Detect changed task/item in TasksList.OnClickCheck event.