简体   繁体   中英

Inno Setup Components graphical refresh issue

Following on from my question Inno Setup disable component selection when a specific component is selected , both solutions exhibit a problem whereby the component list does not refresh properly until some further user interaction with it takes place. ie 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. Can anyone provide a way to get this to refresh correctly without the need to drag the scroll bar?

Here is a compilable example script with just nine component entries that exhibits the same behaviour. The state of the check boxes update correctly, but the state of the text does not. Scrolling or clicking the items appears to be the only way to get the state (dimmed or undimmed) to refresh.

#define MyAppName "My Program"
#define MyAppVersion "1.5"
#define MyAppPublisher "My Company, Inc."
#define MyAppURL "http://www.example.com/"
#define MyAppExeName "MyProg.exe"

[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{1A3F520A-40DD-4E79-A711-201077215049}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={pf}\{#MyAppName}
DefaultGroupName={#MyAppName}
OutputDir=Output
OutputBaseFilename=ComponentTest
Compression=lzma
SolidCompression=yes

[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

[Components]
;Define which installation components are preselected for each installation type
Name: "Client"; Description: "Client"; Flags: disablenouninstallwarning
Name: "Dummy1"; Description: "Dummy Entry"; Flags: disablenouninstallwarning
Name: "Dummy2"; Description: "Dummy Entry"; Flags: disablenouninstallwarning
Name: "Dummy3"; Description: "Dummy Entry"; Flags: disablenouninstallwarning
Name: "Dummy4"; Description: "Dummy Entry"; Flags: disablenouninstallwarning
Name: "Dummy5"; Description: "Dummy Entry"; Flags: disablenouninstallwarning
Name: "Sync"; Description: "Synchronisation"; Flags: disablenouninstallwarning
Name: "Sync\Client"; Description: "Sync Client"; Flags: exclusive disablenouninstallwarning
Name: "Sync\Server"; Description: "Sync Server"; Flags: exclusive disablenouninstallwarning

[Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked

[Files]
Source: "C:\Program Files (x86)\Inno Setup 5\Examples\MyProg.exe"; DestDir: "{app}"; Flags: ignoreversion
; NOTE: Don't use "Flags: ignoreversion" on any shared system files

[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon

[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent

[Code]
const
//Define global constants
  CompIndexSync = 6;
  CompIndexSyncClient = 7;
  CompIndexSyncServer = 8;

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');
    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;

The problem is in the way the ItemEnabled setter invalidates the item. Instead of invalidating the whole item rectangle it calls the InvalidateCheck method which invalidates only the check box portion of the item. See this code (commented by me):

procedure TNewCheckListBox.InvalidateCheck(Index: Integer);
var
  IRect: TRect;
begin
  // store the full item rectangle into the IRect variable
  IRect := ItemRect(Index);
  // offset this stored rectangle's left position by the item level
  Inc(IRect.Left, (FCheckWidth + 2 * Offset) * (ItemLevel[Index]));
  // set its right position to the left edge + width of the check image + margin offsets,
  // but with no item text consideration, because this method was intended to invalidate
  // just the check box portion of the item which is not enough for enabled state change
  IRect.Right := IRect.Left + (FCheckWidth + 2 * Offset);
  // flip the rectangle for RTL reading and ask the control to invalidate that rectangle
  FlipRect(IRect, ClientRect, FUseRightToLeft);
  InvalidateRect(Handle, @IRect, FThemeData <> 0);
end;

The enabled state setter would need to have something like this (pseudo-code):

procedure TNewCheckListBox.SetItemEnabled(Index: Integer; const AEnabled: Boolean);
begin
  if ItemStates[Index].Enabled <> AEnabled then
  begin
    ItemStates[Index].Enabled := AEnabled;
    // invalidate the check portion of the item
    InvalidateCheck(Index);
    // and invalidate also the text portion of the item
    InvalidateText(Index);
  end;
end;

So this is a bug in the TNewCheckListBox control. Because there is no access to the item rectangles, you can ask the whole control to invalidate by calling Invalidate when you finish updating the enabled state of the item(s):

procedure UpdateComponents;
begin
  with WizardForm.ComponentsList do
  begin
    ...
    ItemEnabled[CompIndexSync] := not IsComponentSelected('Client');
    ItemEnabled[CompIndexSyncClient] := not IsComponentSelected('Client');
    ItemEnabled[CompIndexSyncServer] := not IsComponentSelected('Client');
    ...
    Invalidate;
  end;
end;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM