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.