Here's a fresh version of my code. It is now even closer, if I look at the updated version in Resource Hacker it tells me that the group has nine icons, which is true, that they're 16.8 million color, which is true, that they're all 16 x 16, which is not true, and that it can't actually show me what they look like, which is annoying. Also that they all have an ordinal name of 150 if that means anything to anyone.
procedure TForm1.Button1Click(Sender: TObject);
var vResHandle: THandle;
MyIcon: TMemoryStream;
begin
// Get the icon.
MyIcon := TMemoryStream.Create;
MyIcon.LoadFromFile('icon.ico');
// Set the position in the memory stream to the start.
MyIcon.Seek(0, soFromBeginning);
// Get the handle.
vResHandle := BeginUpdateResource('exec.exe', False);
if vResHandle=0 then
raise Exception.Create('System giving error message: '
+ SysErrorMessage(GetLastError));
try
// Change the icon.
if not UpdateResource(vResHandle
, RT_GROUP_ICON
, PChar('MAINICON')
, LANG_NEUTRAL
, MyIcon.Memory
, MyIcon.Size)
then
raise Exception.Create('System giving error message: '
+ SysErrorMessage(GetLastError));
finally
EndUpdateResource(vResHandle, False);
end;
MyIcon.Free;
end;
Short version of how it works: So. Before you try to put any bit of data into an .exe file using a resource update you must be sure it will fit. Icon files are difficult. In this particular case I needed to modify the structure of the .ico file and split it into different pieces and do a resource update separately on each. I didn't do that. I was like someone trying to fit a seven-fingered hand into one finger of a five-fingered glove.
How the thing works is explained in the code but what exact effect it has on Windows must be explained up here.
(1) Although the application icon (in the top left corner of your main form) can be set to be completely different from the main icon for the program, it seems like it's overwritten to be in line with the main icon once you do the update. 99% of the time this would be exactly what you want. If it isn't what you want you'll have to take it from here.
(2) File Explorer caches this stuff so hard that you won't see any change in how the icon's displayed there unless you restart Explorer. This is fine for my purposes, if it's a problem for you then again you'll have to solve it yourself, sorry.
(3) This is NOT an answer to that frequently-asked question, "How do I change the toolbar icon of my Pascal-based application while it's running?" Because you can't do a resource update on an executable that's being executed, whether your own or another.
(4) If you're going to use this in Pascal, then you're going to need to add Windows to your uses statement. If you're going to use this in any language in other than Pascal but you're still using Windows, then it will translate kind of easily because it's basically telling the Windows OS to do stuff, but you'll have to find out which library or whatever lets you do that and what syntax it wants you to use.
(5) If you're wondering about how to do the thing the other way round and extract an .ico file from an executable file, then this is of course theoretically possible and has been done by cleverer people then me and indeed done in Pascal. (Download Resource Hacker for an example.) But you can't just do this by reversing my code as it were, there are obstacles in your way. Doing it this way Windows has built in facilities for me to do this. Doing it the other way it seems like it doesn't.
procedure TForm1.Button1Click(Sender: TObject);
var vResHandle: THandle;
MyIcon: TMemoryStream;
i,j: integer;
s: string;
ImageCount: Word;
ImageSize: DWord;
ab, m: TMemoryStream;
const HeaderSize = 6;
IcoEntrySize = 16;
ResEntrySize = 14;
begin
// Short explanation. An icon file consists of (a) a six-byte header which
// includes among other things information about how many icons are in
// the file; (b) sixteen bytes of metadata for each icon; (c) the icons.
// But that's how icons are stored as files. As executable resources,
// Windows considers that (a) and (b) are one resource but (c) is a different
// resource, indeed one resource for each image, so we have to split the icon
// file up and do several resource updates.
// It also requires only fourteen bytes of metadata per entry: instead of the
// last parameter being a double word referring to the position of the image
// in memory, it's a single word conferring an ID.
// Initialize stuff
MyIcon := TMemoryStream.Create;
ab := TMemoryStream.Create;
m := TMemoryStream.Create;
// Get the icon
MyIcon.LoadFromFile('icon.ico');
// Get the handle for the resource update..
vResHandle := BeginUpdateResource('test.exe', False);
// We skip forward in the memory stream to where Windows keeps the image count and read it.
MyIcon.Seek(4,soFromBeginning);
ImageCount:=MyIcon.ReadWord;
// Go back to the beginning ...
MyIcon.Seek(0,soFromBeginning);
// We read the directory information into ab, modifying its format as we do so.
for j:=1 to HeaderSize do ab.WriteByte(MyIcon.ReadByte);
for i:=1 to ImageCount do
begin
for j:=1 to IcoEntrySize - 4 do ab.WriteByte(MyIcon.ReadByte);
MyIcon.ReadDWord; // To skip over it.
ab.WriteWord(i);
end;
// Update the icon directory with ab, which is now in the correct format.
UpdateResource(vResHandle
, RT_GROUP_ICON
, PChar('MAINICON')
, LANG_NEUTRAL
, ab.Memory
, ab.Size);
// Now the size of each icon is contained as a double word in the directory
// entries for each item, so we use that to cut the remainder of the memory
// stream into chunks and update them one at a time.
for i := 1 to ImageCount do
begin
m := TMemoryStream.Create;
ab.Seek(HeaderSize+(i-1)*ResEntrySize+8,soFromBeginning);
ImageSize:=ab.ReadDWord;
for j:=1 to ImageSize do m.WriteByte(MyIcon.ReadByte);
UpdateResource(vResHandle
, RT_ICON
, MAKEINTRESOURCE(i)
, LANG_NEUTRAL
, m.Memory
, m.Size);
m.Free;
end;
EndUpDateResource(vResHandle,False);
MyIcon.Free;
ab.Free;
end;
Related
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).
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
i need to get the current select text independently the windows (browser, text editor, games wtc.)
i found this code in stackoverflow, but it aint work.
...
var
Buff: array[0..65535] of char;
...
function CurrentSelectedText: string;
var
hFocus: hWnd;
aStart, aEnd: integer;
begin
//added by andrei, attach input to current thread
AttachThreadInput(GetCurrentThreadId, GetWindowThreadProcessId(GetForegroundWindow), true);
hFocus:= GetFocus;
SendMessage(hFocus, WM_GETTEXT, 65535, integer(#buff));
SendMessage(hFocus, EM_GETSEL, Integer(#aStart), Integer(#aEnd));
result:= Copy(StrPas(Buff), 1+aStart, aEnd-aStart);
end;
There are some possibly significant problems (failing to detach the thread input, not allowing for the foreground window in the current thread, a complete absence of error checking) and a minor improvement (caching thread IDs) that can be made to your code, but in essence it should work.
I implemented this slightly modified version of your code in a TTimer, set to simply retrieve the current focussed window text into a TEdit control every 1/4 second (250 ms), and it worked exactly as expected.
var
buff: array[0..65535] of char;
thisThread: Integer;
focusThread: Integer;
hForeground: HWND;
hFocus: HWND;
a, b: Integer;
attached: Boolean;
begin
hForeground := GetForegroundWindow;
// ** check hForegound is valid
thisThread := GetCurrentThreadID;
focusThread := GetWindowThreadProcessID(hForeground);
// ** check focusThread is valid
attached := thisThread <> focusThread;
if attached then
begin
AttachThreadInput(thisThread, focusThread, TRUE);
// ** check attach was successful
end;
try
hFocus := GetFocus;
// ** check hFocus is valid/not null
SendMessage(hFocus, WM_GETTEXT, 65535, Integer(#buff));
// ** check SendMessage was successful
Edit1.Text := String(#buff);
if SendMessage(hFocus, EM_GETSEL, Integer(#a), Integer(#b)) <> 0 then
Edit1.Text := Copy(Edit1.Text, 1 + a, b - a);
finally
if attached then
AttachThreadInput(thisThread, focusThread, FALSE);
end;
end;
If this code does not work for you then you can (and in any case should) add error checking code at the points that I have indicated with ** comments, to determine why it is not working in your particular case.
Note that you cannot attach a thread to itself, which is taken into account in the above modified code by only attaching (and detaching) if the two threads are different.
The EM_GETSEL Problem:
It is most likely possible that the problem you have is specifically with the attempt to retrieve the Selection range. As indicated by the message number (EM_...) this is a message that only EDIT controls respond to, so if the current foreground window is not an edit control then this will almost certainly fail.
You should DEFINITELY check the success/failure of the SendMessage( .. EM_GETSEL .. ) call, and only extract a range from the window text if you receive a valid response to that message.
I also found that trying to use Copy() directly on a cast version of the buf char array did not work. This surprised me and might bear further investigation, but to avoid whatever problems might lie in this area, simply convert to a string first and then perform your Copy() to extract the range from the string.
In my working example above this is achieved by rather inefficiently assigning the converted buf to the Edit1.Text property and then using Copy() on that Edit1.Text property. In practice you would use an intermediate string variable.
WM_GETTEXT Limitations
In your question you ask for a technique that will work for any window type, including browsers and games. I do not think there is a universal approach that will achieve this, short of a combination of screen capture and OCR. This is because applications such as browsers and games and even some "ordinary applications", implement window classes which render their content independent of any concept of "Window Text", as reported by WM_GETTEXT. e.g. the "Window text" of a browser window is typically the browser/tab caption text, not the HTML content of the page it is currently presenting, which is maintained internally by the browser application.
If an application implements a custom window class to render some arbitrary content, then you have no way to retrieve that content without intimate knowledge of the specific application in question and/or a published mechanism supported by that application to achieve what you want.
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 have built an installer to install application using Inno Setup. But I want to display an error message showing that there is not enough space in the drive or path, where I am going to install application, if there is no space available.
By default I am getting Inno built in ability to show message when there is no space available in the hard disk or selected path. But it shows YES and NO button to continue or cancel. Here I want to show error message with a OK button and when the user clicks ok button it should stop installation. Please help me on this issue. I could not find any ways to do so.
To determine a free space on a drive of a specific folder (in your case the selected directory), you can call the GetSpaceOnDisk or GetSpaceOnDisk64 function. The difference between them is that the first one is able to return space info in bytes as well as in megabytes. The latter returns this info just in bytes. For the following example I chose the first mentioned function, so you can decide in which units you want to operate by modifying a single boolean parameter:
[Code]
procedure ExitProcess(uExitCode: UINT);
external 'ExitProcess#kernel32.dll stdcall';
function IsEnoughFreeSpace(const Path: string; MinSpace: Cardinal): Boolean;
var
FreeSpace, TotalSpace: Cardinal;
begin
// the second parameter set to True means that the function operates with
// megabyte units; if you set it to False, it will operate with bytes; by
// the chosen units you must reflect the value of the MinSpace paremeter
if GetSpaceOnDisk(Path, True, FreeSpace, TotalSpace) then
Result := FreeSpace >= MinSpace
else
RaiseException('Failed to check free space.');
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
if CurPageID = wpSelectDir then
begin
// the second parameter in this function call is the expected min. space in
// units specified by the commented parameter above; in this example we are
// checking if there's at least 1 MB of free space on drive of the selected
// directory; we need to extract a drive portion of the selected directory,
// because it's probable that the directory won't exist yet when we check
if not IsEnoughFreeSpace(ExtractFileDrive(WizardDirValue), 1) then
begin
MsgBox('There is not enough space on drive of the selected directory. ' +
'Setup will now exit.', mbCriticalError, MB_OK);
// in this input parameter you can pass your own exit code which can have
// some meaningful value indicating that the setup process exited because
// of the not enough space reason
ExitProcess(666);
end;
end;
end;
Maybe my answer looks like off-topic.
I had more or less the same problem.
If you have in the files section a check function made by yourself, setup can only count the number of (Mega)bytes of those files which have "normal" check flags.
A way to avoid this is count-up the bytes by yourself and put the result in the ExtraDiskSpaceRequired directive in the [setup] section
I have built an installer to install application using Inno Setup. But I want to display an error message showing that there is not enough space in the drive or path, where I am going to install application, if there is no space available.
By default I am getting Inno built in ability to show message when there is no space available in the hard disk or selected path. But it shows YES and NO button to continue or cancel. Here I want to show error message with a OK button and when the user clicks ok button it should stop installation. Please help me on this issue. I could not find any ways to do so.
To determine a free space on a drive of a specific folder (in your case the selected directory), you can call the GetSpaceOnDisk or GetSpaceOnDisk64 function. The difference between them is that the first one is able to return space info in bytes as well as in megabytes. The latter returns this info just in bytes. For the following example I chose the first mentioned function, so you can decide in which units you want to operate by modifying a single boolean parameter:
[Code]
procedure ExitProcess(uExitCode: UINT);
external 'ExitProcess#kernel32.dll stdcall';
function IsEnoughFreeSpace(const Path: string; MinSpace: Cardinal): Boolean;
var
FreeSpace, TotalSpace: Cardinal;
begin
// the second parameter set to True means that the function operates with
// megabyte units; if you set it to False, it will operate with bytes; by
// the chosen units you must reflect the value of the MinSpace paremeter
if GetSpaceOnDisk(Path, True, FreeSpace, TotalSpace) then
Result := FreeSpace >= MinSpace
else
RaiseException('Failed to check free space.');
end;
function NextButtonClick(CurPageID: Integer): Boolean;
begin
Result := True;
if CurPageID = wpSelectDir then
begin
// the second parameter in this function call is the expected min. space in
// units specified by the commented parameter above; in this example we are
// checking if there's at least 1 MB of free space on drive of the selected
// directory; we need to extract a drive portion of the selected directory,
// because it's probable that the directory won't exist yet when we check
if not IsEnoughFreeSpace(ExtractFileDrive(WizardDirValue), 1) then
begin
MsgBox('There is not enough space on drive of the selected directory. ' +
'Setup will now exit.', mbCriticalError, MB_OK);
// in this input parameter you can pass your own exit code which can have
// some meaningful value indicating that the setup process exited because
// of the not enough space reason
ExitProcess(666);
end;
end;
end;
Maybe my answer looks like off-topic.
I had more or less the same problem.
If you have in the files section a check function made by yourself, setup can only count the number of (Mega)bytes of those files which have "normal" check flags.
A way to avoid this is count-up the bytes by yourself and put the result in the ExtraDiskSpaceRequired directive in the [setup] section