Get WM_LBUTTONDOWN message while ShellExecAndWait? - delphi

In the Delphi IDE, create a VCL Forms Application. Then add a TApplicationEvents component and a TButton on the form. Then add these two event-handlers:
uses
JclShell;
procedure TForm3.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
begin
if Msg.Message = WM_LBUTTONDOWN then
begin
Self.Caption := 'WM_LBUTTONDOWN';
end
else if Msg.Message = WM_LBUTTONUP then
begin
Self.Caption := 'WM_LBUTTONUP';
end
end;
procedure TForm3.Button1Click(Sender: TObject);
begin
JclShell.ShellExecAndWait('C:\Windows\system32\notepad.exe');
Self.Caption := 'Notepad closed';
end;
Now click on the button. The following happens:
"WM_LBUTTONDOWN" appears on the form's title-bar.
"WM_LBUTTONUP" appears on the form's title-bar.
Notepad is executed.
Then click again on the button which starts another instance of Notepad BUT this time without writing anything on the form's title-bar.
Obviously, the program is stuck in JclShell.ShellExecAndWait which returns only when Notepad is closed. So when Notepad is closed any mouse-click again writes to the form's title-bar.
So we can see that while Notepad is running in JclShell.ShellExecAndWait everything in the program works normally: You could even make mathematical calculations while Notepad is running in JclShell.ShellExecAndWait. Only the ApplicationEvents1Message is not triggered while Notepad is running.
So how can I get a WM_LBUTTONDOWN message while Notepad is running in JclShell.ShellExecAndWait?

While JclShell.ShellExecAndWait is waiting for the spawned process to exit it uses following basic message pump:
while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do
repeat
Msg.hwnd := 0;
Res := PeekMessage(Msg, Sei.Wnd, 0, 0, PM_REMOVE);
if Res then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
until not Res;
CloseHandle(Sei.hProcess);
I say basic, because the looped part of the VCLs message pump looks like this:
if PeekMessage(Msg, 0, 0, 0, PM_NOREMOVE) then
begin
Unicode := (Msg.hwnd = 0) or IsWindowUnicode(Msg.hwnd);
if Unicode then
MsgExists := PeekMessageW(Msg, 0, 0, 0, PM_REMOVE)
else
MsgExists := PeekMessageA(Msg, 0, 0, 0, PM_REMOVE);
if MsgExists then
begin
Result := True;
if Msg.Message <> WM_QUIT then
begin
Handled := False;
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
if not IsPreProcessMessage(Msg) and not IsHintMsg(Msg) and
not Handled and not IsMDIMsg(Msg) and
not IsKeyMsg(Msg) and not IsDlgMsg(Msg) then
begin
TranslateMessage(Msg);
if Unicode then
DispatchMessageW(Msg)
else
DispatchMessageA(Msg);
end;
end
else
begin
{$IF DEFINED(CLR)}
if Assigned(FOnShutDown) then FOnShutDown(self);
DoneApplication;
{$IFEND}
FTerminate := True;
end;
end;
The code line
if Assigned(FOnMessage) then FOnMessage(Msg, Handled);
is what will hand the message to your TApplicationEvents.OnMessage via the TMultiCaster that TApplicationEvents has assigned to Application.OnMessage. This is not done in the JCL source.
Additional issues might be that JCL is not UniCode aware regarding messages at this point and there is no handling for WM_QUIT.
What to do about this is depending on what you want to achieve, too. Why do you want to receive this messages in the first place?
I mean it is possible to alter the JCL source - if you are willing to do so - and to add VCL.Forms to the uses, and then call the event handler if assigned like the VCL does:
while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do
repeat
Msg.hwnd := 0;
Res := PeekMessage(Msg, Sei.Wnd, 0, 0, PM_REMOVE);
if Res then
begin
Handled := False;
if Assigned(Application.OnMessage) then
Application.OnMessage(Msg, Handled);
if not Handled then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
until not Res;
CloseHandle(Sei.hProcess);
Or even call Application.ProcessMessages, to have the same message processing in place the VCL does:
while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do
Application.ProcessMessages;
CloseHandle(Sei.hProcess);
This is working, I could not see any side effects like commenters suggested. But before changing the JCL source in this way I would probably implement my own ShellExecAndWait. Depending on what you would like to achieve, the normal dispatching of messages should still work. So if your TFrom has e.g.
procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
implemented, it should be called. This is, however, only the case if the message was directed at your form. If the button is clicked, the resulting messages will be directed to the button itself. You would need to implement your own descendant class then.
Maybe a completely different suggestion?
If your goal is to let certain controls not to be clicked while JclShell.ShellExecAndWait is waiting, and using message handling is your approach to make this possible, there might be other things you can try.
Why not disable them? This will also give the user a visible indication that clicking is a no-go.
Button1.Enabled := False;
try
JclShell.ShellExecAndWait('C:\Windows\system32\notepad.exe');
Self.Caption := 'Notepad closed';
finally
Button1.Enabled := True;
end;
If you are concerned about multiple controls create a TAction, assign it to every control you want to disable and disable all of them together with the Enabled property of the TAction.
Or, just hide your form while notepad.exe is opened?
Hide;
try
JclShell.ShellExecAndWait('C:\Windows\system32\notepad.exe');
Self.Caption := 'Notepad closed';
finally
Show;
end;
Or you could remove the event handlers:
var
ATmpOnClick: TNotifyEvent;
begin
ATmpOnClick := Button1.OnClick;
Button1.OnClick := nil;
try
JclShell.ShellExecAndWait('C:\Windows\system32\notepad.exe');
Self.Caption := 'Notepad closed';
finally
Button1.OnClick := ATmpOnClick;
end;
end;

Related

How to close all windows with the same title

i have thread in my application shows messageboxs in another application with title 'Test' on every event the thread create it,by the end of this thread i wanna close all of this messages.
i tried to create loop like this
while FindWindow(Nil,PChar('Test')) <> 0 do
begin
Sleep(5); //if i remove the sleep the application will hanging and froze.
SendMessage(FindWindow(Nil,PChar('Test')), WM_CLOSE, 0, 0); // close the window message
end;
but this loop works only if i close the last message manually
Note: the messageboxs comes from another applaction not in the same application have this thread.
Try this instead:
var
Wnd: HWND;
begin
Wnd := FindWindow(Nil, 'Test');
while Wnd <> 0 do
begin
PostMessage(Wnd, WM_CLOSE, 0, 0);
Wnd := FindWindowEx(0, Wnd, Nil, 'Test');
end;
end;
Or:
function CloseTestWnd(Wnd: HWND; Param: LPARAM): BOOL; stdcall;
var
szText: array[0..5] of Char;
begin
if GetWindowText(Wnd, szText, Length(szText)) > 0 then
if StrComp(szText, 'Test') = 0 then
PostMessage(Wnd, WM_CLOSE, 0, 0);
Result := True;
end;
begin
EnumWindows(#CloseTestWnd, 0);
end;
Your logic seems to be somewhat... off. :-) You may or may not be sending the WM_CLOSE to the same window, since you're using one FindWindow to see if it exists and a different call to FindWindow to send the message.
I'd suggest doing it more like this:
var
Wnd: HWnd;
begin
Wnd := FindWindow(nil, 'Test'); // Find the first window (if any)
while Wnd <> 0 do
begin
SendMessage(Wnd, WM_CLOSE, 0, 0); // Send the message
Sleep(5); // Allow time to close
Wnd := FindWindow(nil, 'Test'); // See if there's another one
end;
end;
Depending on what the other application is doing, you may need to increase the Sleep time in order to allow the window time to receive and process the WM_CLOSE message; otherwise, you'll be simply sending it multiple times to the same window. (I'm suspecting that 5 ms is far too little time.)

Delphi check if Tabsheet has finished loading data before making a clipboard picture

I am working in Delphi XE3. I have made a loop that goes through a Pagecontrol with 6 tabsheets, that has frames with a lot of edit boxes which load mdb data.
When looping through the pages I make a "screen cut" image of the active tab and place it on an image in fastreport.
Everything works great but when testing on some slower computers it makes all the frames but the data has not been loaded. How do I check that all data is loaded in frame edit components that is placed on the tab before going to next page?
The code looks like this:
begin
Screen.Cursor := crHourGlass;
p := PageControlKalkyl.ActivePageIndex; // Get page index
for i := 0 to 7 do begin
MyPage := frxReport1.FindObject('Page' + IntToStr(i)) as TfrxPage;
MyPage.Visible := True;
end;
try
for i := 0 to PageControlKalkyl.PageCount - 1 do
If PageControlKalkyl.Pages[i].TabVisible then
Begin
PageControlKalkyl.ActivePageIndex := i;
PageControlKalkyl.ActivePage.Repaint;
Bilder := 'Pic' + IntToStr(i);
if FLaddardata = False then //Check if page changed
Try
Bitmap := TBitmap.Create;
Assert(HandleAllocated);
DC := GetWindowDC(Handle);
Win32Check(DC <> 0);
Bitmap.SetSize(Width, Height);
Win32Check(BitBlt(Bitmap.Canvas.Handle, 0, 0, Width, Height, DC, 0, 0, SRCCOPY));
//Load data in to Images in Fastreport
if PageControlKalkyl.ActivePageIndex > 0 then
Begin
Ver:= 'Version NR: ' + Trim(DataModuleTrakop.ADOTableKALKYL.FieldByName('VERSION').AsString);
Raid:= 'Kalkyl ID: ' + Trim(DataModuleTrakop.ADOTableKALKYL.FieldByName('DENH').AsString);
RepImage := frxReport1.FindObject('Pic'+IntTostr(i)) as TfrxPictureView;
RepImage.Picture.Assign(Bitmap);
Rappid := frxReport1.FindObject('Rapdata' + IntToStr(i)) as TfrxMemoView;
Rappid.Font.Style:= [fsBold];
Rappid.Text := Ver +' '+Raid;
end;
Finally
ReleaseDC(Handle, DC);
Bitmap.Free;
End;
end
else
begin
MyPage := frxReport1.FindObject('Page' + IntToStr(i)) as TfrxPage;
MyPage.Visible := False;
end;
if Fskaparapport = True then
begin
Fskaparapport := False;
frxReport1.PrepareReport;
if FEpost = False then
frxReport1.ShowPreparedReport;
Screen.Cursor := crDefault;
end;
PageControlKalkyl.ActivePageIndex := p;
except
on E: Exception do
ShowMessage(E.Message);
end;
end;
Since you're using TADOTable, I suspect your table is configured to operate asynchronously.
This can be done via property ExecuteOptions: TExecuteOptions;
Of course, if you set ExecuteOptions := [];, your data should load synchronously, but with the unpleasant side-effect of blocking your UI.
The 'friendlier' option would be to hook the OnFetchComplete event which is decalred as follows: procedure (DataSet: TCustomADODataSet; const Error: Error; var EventStatus: TEventStatus) of object;
The exact specifics requires more information on what exactly you're trying to achieve. You could:
Call your report method directly from the OnFetchComplete handler.
Use your handler to enable a menu option / button / action that is disabled while the data is loading.
Use a synchronisation object (such as TSimpleEvent) and signal the event inside the OnFetchComplete handler. Then other code can simply call the WaitFor method blocking code until the event has been signalled.

Close Delphi dialog after [x] seconds

Is it possible to get Delphi to close a ShowMessage or MessageDlg Dialog after a certain length of time?
I want to show a message to the user when the application is shut down, but do not want to stop the application from shutting down for more than 10 seconds or so.
Can I get the default dialog to close after a defined time, or will I need to write my own form?
Your application is actually still working while a modal dialog or system message box or similar is active (or while a menu is open), it's just that a secondary message loop is running which processes all messages - all messages sent or posted to it, and it will synthesize (and process) WM_TIMER and WM_PAINT messages when necessary as well.
So there's no need to create a thread or jump through any other hoops, you simply need to schedule the code that closes the message box to be run after those 10 seconds have elapsed. A simple way to do that is to call SetTimer() without a target HWND, but a callback function:
procedure CloseMessageBox(AWnd: HWND; AMsg: UINT; AIDEvent: UINT_PTR;
ATicks: DWORD); stdcall;
var
Wnd: HWND;
begin
KillTimer(AWnd, AIDEvent);
// active window of the calling thread should be the message box
Wnd := GetActiveWindow;
if IsWindow(Wnd) then
PostMessage(Wnd, WM_CLOSE, 0, 0);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
TimerId: UINT_PTR;
begin
TimerId := SetTimer(0, 0, 10 * 1000, #CloseMessageBox);
Application.MessageBox('Will auto-close after 10 seconds...', nil);
// prevent timer callback if user already closed the message box
KillTimer(0, TimerId);
end;
Error handling ommitted, but this should get you started.
You can try to do it with a standard Message dialog. Create the dialog with CreateMessageDialog procedure from Dialogs and after add the controls that you need.
In a form with a TButton define onClick with this:
procedure TForm1.Button1Click(Sender: TObject);
var
tim:TTimer;
begin
// create the message
AMsgDialog := CreateMessageDialog('This is a test message.',mtWarning, [mbYes, mbNo]) ;
lbl := TLabel.Create(AMsgDialog) ;
tim := TTimer.Create(AMsgDialog);
counter := 0;
// Define and adding components
with AMsgDialog do
try
Caption := 'Dialog Title' ;
Height := 169;
// Label
lbl.Parent := AMsgDialog;
lbl.Caption := 'Counting...';
lbl.Top := 121;
lbl.Left := 8;
// Timer
tim.Interval := 400;
tim.OnTimer := myOnTimer;
tim.Enabled := true;
// result of Dialog
if (ShowModal = ID_YES) then begin
Button1.Caption := 'Press YES';
end
else begin
Button1.Caption := 'Press NO';
end;
finally
Free;
end;
end;
An the OnTimer property like this:
procedure TForm1.MyOnTimer(Sender: TObject);
begin
inc(counter);
lbl.Caption := 'Counting: ' + IntToStr(counter);
if (counter >= 5) then begin
AMsgDialog.Close;
end;
end;
Define the variables and procedure:
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
AMsgDialog: TForm;
lbl:TLabel;
counter:integer;
procedure MyOnTimer(Sender: TObject);
end;
And test it.
The form close automatically when the timer final the CountDown. Similar this you can add other type of components.
Regards.
Try this:
function MessageBoxTimeOut(hWnd: HWND; lpText: PChar; lpCaption: PChar;
uType: UINT; wLanguageId: WORD; dwMilliseconds: DWORD): integer;
stdcall; external user32 name 'MessageBoxTimeoutA';
I've been using this for quite some time; it works a treat.
OK. You have 2 choices:
1 - You can create your own MessageDialog form. Then, you can use it and add a TTimer that will close the form when you want.
2 - You can keep using showmessage and create a thread that will use FindWindow (to find the messadialog window) and then close it.
I recommend you to use you own Form with a timer on it. Its cleaner and easier.
This works fine with windows 98 and newers...
I don't use the " MessageBoxTimeOut" because old windows 98, ME, doesn't have it...
this new function works like a "CHARM"..
//add this procedure
procedure DialogBoxAutoClose(const ACaption, APrompt: string; DuracaoEmSegundos: Integer);
var
Form: TForm;
Prompt: TLabel;
DialogUnits: TPoint;
ButtonTop, ButtonWidth, ButtonHeight: Integer;
nX, Lines: Integer;
function GetAveCharSize(Canvas: TCanvas): TPoint;
var
I: Integer;
Buffer: array[0..51] of Char;
begin
for I := 0 to 25 do Buffer[I] := Chr(I + Ord('A'));
for I := 0 to 25 do Buffer[I + 26] := Chr(I + Ord('a'));
GetTextExtentPoint(Canvas.Handle, Buffer, 52, TSize(Result));
Result.X := Result.X div 52;
end;
begin
Form := TForm.Create(Application);
Lines := 0;
For nX := 1 to Length(APrompt) do
if APrompt[nX]=#13 then Inc(Lines);
with Form do
try
Font.Name:='Arial'; //mcg
Font.Size:=10; //mcg
Font.Style:=[fsBold];
Canvas.Font := Font;
DialogUnits := GetAveCharSize(Canvas);
//BorderStyle := bsDialog;
BorderStyle := bsToolWindow;
FormStyle := fsStayOnTop;
BorderIcons := [];
Caption := ACaption;
ClientWidth := MulDiv(Screen.Width div 4, DialogUnits.X, 4);
ClientHeight := MulDiv(23 + (Lines*10), DialogUnits.Y, 8);
Position := poScreenCenter;
Prompt := TLabel.Create(Form);
with Prompt do
begin
Parent := Form;
AutoSize := True;
Left := MulDiv(8, DialogUnits.X, 4);
Top := MulDiv(8, DialogUnits.Y, 8);
Caption := APrompt;
end;
Form.Width:=Prompt.Width+Prompt.Left+50; //mcg fix
Show;
Application.ProcessMessages;
finally
Sleep(DuracaoEmSegundos*1000);
Form.Free;
end;
end;
////////////////////////////How Call It//////////////////
DialogBoxAutoClose('Alert'', "This message will be closed in 10 seconds',10);
/////////////////////////////////////////////////////////
MessageBox calls this function internally and pass 0xFFFFFFFF as timeout parameter, so the probability of it being removed is minimal (thanks to Maurizio for that)
I thought about using a separate thread, but it's probably going to get you into a lot of unnecessary code etc. Windows dialogs were simply not made for this thing.
You should do your own form. On the good side, you can have custom code/UI with a countdown like timed dialog boxes do.
No. ShowMessage and MessageDlg are both modal windows, which means that your application is basically suspended while they're displayed.
You can design your own replacement dialog that has a timer on it. In the FormShow event, enable the timer, and in the FormClose event disable it. In the OnTimer event, disable the timer and then close the form itself.
You can hook up the Screen.OnActiveFormChange event and use Screen.ActiveCustomForm if it is a interested form that you want to hook up the timer to close it
{code}
procedure abz.ActiveFormChange(Sender: TObject);
var
Timer: TTimer;
begin
if (Screen.ActiveCutomForm <> nil) and //valid form
(Screen.ActiveCutomForm.Tag = 0) and //not attached a timer yet
(Screen.ActiveCutomForm.ClassName = 'TMessageForm') //any interested form type check
then
begin
Timer := TTimer.Create(Screen.ActiveCutomForm); // let the form owned so it will be freed
Timer.Enabled := False;
Timer.Tag := Integer(Screen.ActiveCutomForm); // keep track to be used in timer event
.... setup any timer interval + event
Screen.ActiveCutomForm.Tag := Integer(Timer);
Timer.Enabled := True;
end;
end;
{code}
enjoy
Best way is to use a stayontop form and manage a counter to disappear using the alfpha blend property of the form, at the end of the count just close the form, but
the control will be passed to the active control needed before showing the form, this way, user will have a message which disappears automatically and wont prevent the usage of the next feature, very cool trick for me.
You can do this with WTSSendMessage.
You can find this in the JWA libraries, or call it yourself.

WM_SysCommand Preventing window move in delphi

I use the following code to capture when the minimise button of my program is pressed so that I can hide the form from the taskbar, and as such minimise to the system tray.
procedure TForm1.WMSysCommand;
begin
if (Msg.CmdType = SC_MINIMIZE) then
begin
form1.Hide;
show1.Checked :=false;
hide1.Checked :=true;
end;
if (Msg.CmdType = SC_CLOSE) then form1.Close;
end;
I have had to put the capture in for the close button too as this code was preventing the program closing via the close button. What I need help with is how to fix dragging the program window by the caption bar which has stopped working with this code.
If you override the processing of a Windows message you need to take care to either handle all possible cases, or to call the inherited code for all unhandled cases:
procedure TForm1.WMSysCommand(var Msg: TWMSysCommand);
begin
if (Msg.CmdType = SC_MINIMIZE) then
begin
Hide;
show1.Checked := False;
hide1.Checked := True;
Msg.Result := 0;
exit;
end;
inherited;
end;

Minimize a external application with Delphi

Is there a way to Minimize an external application that I don't have control over from with-in my Delphi application?
for example notepad.exe, except the application I want to minimize will only ever have one instance.
You can use FindWindow to find the application handle and ShowWindow to minimize it.
var
Indicador :Integer;
begin
// Find the window by Classname
Indicador := FindWindow(PChar('notepad'), nil);
// if finded
if (Indicador <> 0) then begin
// Minimize
ShowWindow(Indicador,SW_MINIMIZE);
end;
end;
I'm not a Delphi expert, but if you can invoke win32 apis, you can use FindWindow and ShowWindow to minimize a window, even if it does not belong to your app.
Thanks for this, in the end i used a modifyed version of Neftali's code, I have included it below in case any one else has the same issues in the future.
FindWindow(PChar('notepad'), nil);
was always returning 0, so while looking for a reason why I found this function that would find the hwnd, and that worked a treat.
function FindWindowByTitle(WindowTitle: string): Hwnd;
var
NextHandle: Hwnd;
NextTitle: array[0..260] of char;
begin
// Get the first window
NextHandle := GetWindow(Application.Handle, GW_HWNDFIRST);
while NextHandle > 0 do
begin
// retrieve its text
GetWindowText(NextHandle, NextTitle, 255);
if Pos(WindowTitle, StrPas(NextTitle)) <> 0 then
begin
Result := NextHandle;
Exit;
end
else
// Get the next window
NextHandle := GetWindow(NextHandle, GW_HWNDNEXT);
end;
Result := 0;
end;
procedure hideExWindow()
var Indicador:Hwnd;
begin
// Find the window by Classname
Indicador := FindWindowByTitle('MyApp');
// if finded
if (Indicador <> 0) then
begin
// Minimize
ShowWindow(Indicador,SW_HIDE); //SW_MINIMIZE
end;
end;
I guess FindWindow(PChar('notepad'), nil) should be FindWindow(nil, PChar('notepad')) to find the window by title.

Resources