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.
Related
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;
I have an Excel AddIn written in Delphi that has a VCL form with a TMemo on it.
When I try to enter text into the Memo the input goes to Excel instead.
When I start the form modal (ShowModal), all works fine but obviously it's not possible to work with the main excel window and the addin's window concurrently.
The issue seems to be the exact similar to this question: Modeless form cannot receive keyboard input in Excel Add-in developed by Delphi
This answer suggests to handle WM_PARENTNOTIFY so I tried the following:
TMyForm = class(TForm)
...
procedure OnParentNotify(var Msg: TMessage); message WM_PARENTNOTIFY;
And in that procedure tried things like SetFocus, WinApi.Windows.SetFocus(self.Handle), SetForeGroundWindows, SetActiveWindow but that doesn't appear to work.
Other suggestions I've read is to run the UI in a different thread (which is of course not possible with VCL) and to install a keyboard hook with SetWindowsHookEx. Obviously that will give us keypress events but not sure what to do with those.
I am not using 3rd party tooling such as Add-In Express but just implementing IDTExtensibility2.
EDIT: more research suggests that Office uses an interface called IMsoComponent and and IMsoComponentManager as a way of tracking the active component in the application. Visual Studio uses these as IOleComponent and IOleComponentManager.
This link and this one suggest to register a new empty IOleComponent/IMsoComponent.
EDIT: MCVE can be fetched here, it's the smallest possible Excel AddIn code that will launch a VCL Form with a TEdit on it. The edit looses keyboard focus as soon as a worksheet is active.
I was having the same kind of problem. I am also implementing IDTExtensibility2 but as I am doing it on C++ I already managed to run the UI on a different thread. But anyway I was not fully happy with this solution. I would still have this problem if I wanted to use a VBA Userform as a TaskPane Window. I did try but as (I guess, didn´t check) the VBA userform will run on the native Excel Thread, just calling it on a different thread (to use as a TaskPane window) just marshalled it, didn´t mean that it was created on a different thread, so as I did try, there was this kind of problem.
I too did read and try to to handle WM_PARENTNOTIFY messages with SetFocus.. on my window but didn´t work.
This both interfaces IOleComponent and IOleComponentManager were new to me. Didn´t find the header files, but could write and implement from the descriptions at the link you shared.
How it worked for me was to register my IOleComponent implementation on every WM_SETCURSOR e WM_IME_SETCONTEXT at my Form Window. (I am not sure if this is exactly the best messages, but did work for me) and Revoke the component on every click back at EXCEL7 window.
The MSOCRINFO options I used to register was msocrfPreTranslateKey and msocadvfModal.
Hope that with this answer I will not receive tons of criticism. I know that it is a very specific issue, the question was with a -1 status when I read it, but was exactly what I needed to finish with this point. So I am just trying to be honest and share back something.
I finally found the solution to this after I decided to have another look at this...
Seems I was on the right track about needing IMsoComponentManager and IMsoComponent.
So first we need to retrieve the ComponentManager:
function GetMsoComponentManager(out ComponentManager: IMsoComponentManager): HRESULT;
var
MessageFilter: IMessageFilter;
ServiceProvider: IServiceProvider;
begin
MessageFilter := nil;
// Get the previous message filter by temporarily registering a new NULL message filter.
Result := CoRegisterMessageFilter(nil, MessageFilter);
if Succeeded(Result) then
begin
CoRegisterMessageFilter(MessageFilter, nil);
if (MessageFilter <> nil) then
begin
try
ServiceProvider := MessageFilter as IServiceProvider;
Result := ServiceProvider.QueryService(IID_IMsoComponentManager,
SID_SMsoComponentManager, ComponentManager);
if Assigned(ComponentManager) then
begin
end;
except
on E: Exception do
begin
Result := E_POINTER;
end;
end;
end;
end;
end;
Then we need to register a dummy component using msocrfPreTranslateAll (or msocrfPreTranslateKey)
procedure TVCLForm.RegisterComponent;
var
RegInfo: MSOCRINFO;
//MsoComponentManager: IMsoComponentManager;
hr: HRESULT;
bRes: Boolean;
begin
if FComponentId = 0 then
begin
FDummyMsoComponent := TDummyMsoComponent.Create;
ZeroMemory(#RegInfo, SizeOf(RegInfo));
RegInfo.cbSize := SizeOf(RegInfo);
RegInfo.grfcrf := msocrfPreTranslateAll or msocrfNeedIdleTime;
RegInfo.grfcadvf := DWORD(msocadvfModal);
bRes := ComponentManager.FRegisterComponent(FDummyMsoComponent, RegInfo,
FComponentId);
Memo1.Lines.Add(Format('FMsoComponentManager.FRegisterComponent: %s (Component ID: %d)', [BoolToStr(bRes, True), FComponentId]));
end
else begin
Memo1.Lines.Add(Format('Component with ID %d was already registered', [FComponentId]));
end;
if FComponentId > 0 then
begin
bRes := ComponentManager.FOnComponentActivate(FComponentId);
Memo1.Lines.Add(Format('FMsoComponentManager.FOnComponentActivate: %s (Component ID: %d)', [BoolToStr(bRes, True), FComponentId]));
end;
end;
Now in the Dummy Component implementation class we must handle FPreTranslateMessage:
function TDummyMsoComponent.FPreTranslateMessage(MSG: pMsg): BOOL;
var
hWndRoot: THandle;
begin
// this is the magic required to make sure non office owned windows (forms)
// receive Window messages. If we return True they will not, however if we
// return False, they will -> so we check if the message was meant for the
// window owner
hWndRoot := GetAncestor(MSG^.hwnd, GA_ROOT);
Result := (hWndRoot <> 0) and (IsDialogMessage(hWndRoot, MSG^));
end;
Finally a good place to to (un)register the Dummy component is when receiving WM_ACTIVATE. For example:
procedure TVCLForm.OnActivate(var Msg: TMessage);
var
bRes: Boolean;
begin
case Msg.WParam of
WA_ACTIVE:
begin
Memo1.Lines.Add('WA_ACTIVE');
RegisterComponent;
end;
WA_CLICKACTIVE:
begin
Memo1.Lines.Add('WA_CLICKACTIVE');
RegisterComponent;
end;
WA_INACTIVE:
begin
Memo1.Lines.Add('WA_INACTIVE');
UnRegisterComponent;
end
else
Memo1.Lines.Add('OTHER/UNKNOWN');
end;
end;
This all seems to work well and does not require intercepting WM_SETCURSOR or WM_IME_SETCONTEXT nor does it need subclassing of the Excel Window.
Once cleaned up will probably write a blog and place all the complete code on Github.
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).
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 a question about threads and controls. I made a syncing modal dialog. There's three progressbars and couple of captions. If application is opened for a first time, then it will open syncing dialog and do the update thing. On dialog Show() method I create three different threads. Each thread controls data download, xml parsing and database inserting. And then shows progress on progressbar. All described, is working fine.
Now the problem - I want to close this dialog automatically when all items are downloaded, parsed and inserted to database. I tried to check if progressbar.position equals to progressbar.max and I tried check if threads are terminated.If I go with the progressbar way, dialog closes too early and one progressbar isn't totally ended. If I go with the thread checking way, then progressbars stop in the middle of process and that's all.
Maybe you have done it and tell the Delphi n00b, how is the best way to do it.
Thanks in advance...
For this simple thing, you can use the thread OnTerminate event (which runs in the context of the main thread) just to decrement a "thread count" variable initialized to 3 at thread creation moment.
When the thread count reaches 0, you can safely close the form.
begin
//..thread creation, stuff
FThreadCount := 3;
DownloadThread.OnTerminate := DecThreadCount;
ParseThread.OnTerminate := DecThreadCount;
InsertThread.OnTerminate := DecThreadCount;
//resume threads and other stuff
end;
procedure TForm1.DecThreadCount(Sender: TObject);
begin
Dec(FThreadCount);
if FThreadCount = 0 then
Close;
end;
Are you using Windows Vista or Windows 7? Microsoft changed the way progress bars work in Vista, so that instead of immediately jumping to the indicated position, it gradually slides towards it. This means that your progress can actually be finished, but the bar won't indicate that for another second or so, so it looks like the dialog is closed before you're done, especially if the bar has a small number of progress steps.
It's kinda ugly, but you can work around this by using a helper function that does something like this:
procedure UpdateProgressBar(bar: TProgressBar);
begin
bar.StepIt;
bar.Max := bar.Max + 1;
bar.Max := bar.Max - 1;
end;
This will ensure that it immediately jumps to the correct position.
EDIT: Details in How do I make TProgressBar stop lagging?
I'd get your threads to post a message back to the dialog when they complete. Once all three messages have been received you can close the dialog.