Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Detect when user closes an app

dboggs
New Contributor I
949 Views

My program opens an application using the ShellExecute command, e.g.

irtn = ShellExecute (hwnd=null, lpOperation = 'open'C, lpFile = 'edit_this.txt', ...)

In my case, txt files are associated with the app TextPad, so this line causes the system to open TextPad with the file edit_this. This much works fine.

Then, my program should wait until the user closes TextPad to continue. How can my program determine when TextPad has been closed?

0 Kudos
22 Replies
Steven_L_Intel1
Employee
876 Views

You can't do this with ShellExecute. If you use ShellExecuteEx instead, you probably can. You will have to set the SEE_MASK_NOCLOSEPROCESS flag in the fMask component of the SHELLEXECUTEINFO structure. When the call returns, see if the hProcess component is nonzero. If it is, do a WaitForSingleObject on that handle to wait for it to close. I suggest you read all the notes in MSDN on this API and structure, and consider that in some cases you may not be able to tell when the text editor closes. For example, the user may tell TextPad to close the file but leave the application open.

0 Kudos
dboggs
New Contributor I
876 Views

How depressing! But it doesn't surprise me, after all this is Windows.

In anticipation, I decided to take the alternate route and have my program close the app instead of the user. Surely, I thought, this can be done within my program and obviously then the program would know about it. Silly me, I hoped this might be as simple as calling ShellExecute with the argument 'Close', or something like that, instead of 'Open'. That didn't work either, but maybe there is a simple way?

Or, I might give up the utility of ShellExecute (which I am using just because it can sense the application which the user has already associated with .txt files), and force the use of a specific app (like TextPad), located in a specific directory, using a call such as SYSTEM or SYSTEMQQ. Would that make it easier to either (a) sense when a user closes the app, or (b) close the app programmatically?

0 Kudos
IanH
Honored Contributor II
876 Views

If a third party program arbitrarily decided to close my text editor I would get very annoyed.  Think of what that means in terms of the potential for lost work - the user may have other files open in the text editor that are unrelated to the third party program.

A typical approach to this, assuming that the reason the text editor is being displayed is to allow the user to edit/provide text that your program requires in order to continue executing (versus just displaying text), is to use, by default, a text editor that is known to only edit a single file per text editor process (e.g. notepad on Windows) but then permit the user to explicitly specify an alternative command line for an editor that lets them use their preferred tool, but still under the constraint of one file per editor process.  Most text editors that ordinarily permit more than one file to be open at once will have a command line option that enforces this constraint.

Your application then just needs to wait for the editor process to finish, which you can do asynchronously if you have the process handle (out of ShellExecuteEx, as Steve describes, or CreateProcess).  If your program is suspended while the text editor runs then there are even more options - including the standard F2008 intrinsic EXECUTE_COMMAND_LINE.

This problem is not specific to Windows.

If you are just displaying text to the user, then just throw the text file up in whatever editor the user prefers through ShellExecute, and move on. 

0 Kudos
Steven_L_Intel1
Employee
876 Views

I don't know what you're expecting the user to do with the text but I'd think the usual approach is to open a dialog box with a text eidit field, initialize it with the text you want and have a Close or Save button when the user is done with it. Using an external editor for this purpose is fraught with peril.

0 Kudos
LRaim
New Contributor I
876 Views

If your application has to detect that the editing of a file has completed, you can:

  • ​try to open this file with an EXCLUSIVE option
  • if the file is owned by another app this will not succeed
  • wait for definite time interval

then repeat the cycle until you can open the file.

 

 

 

0 Kudos
dboggs
New Contributor I
876 Views

The EXCLUSIVE option sounds like what I need, but I am not familiar with it and can't find any documentationt. Can you give an example OPEN statement?

0 Kudos
Steven_L_Intel1
Employee
876 Views

SHARE="DENYRW" is the OPEN option.

0 Kudos
IanH
Honored Contributor II
876 Views

Attempting to lock the file in that way assumes that the text editor keeps the file open while the user is editing it.  This isn't the case for most of the text editors that I use.
 

0 Kudos
andrew_4619
Honored Contributor II
876 Views

In one program I use system from ifport.

iret = system('notepad.exe '//gfile)

To open the file gfile in notepad. The is blocking ie the thread that makes the close is suspended until the notepad is closed. Notepad can only open a single file at once. That works on all window from 95 onwards....

 

 

 

 

0 Kudos
Steven_L_Intel1
Employee
876 Views

That wouldn't work on my system where TextPad replaces notepad. (You can also use EXECUTE_COMMAND_LINE instead of system nowadays.)

0 Kudos
andrew_4619
Honored Contributor II
876 Views

Steve Lionel (Intel) wrote:

That wouldn't work on my system where TextPad replaces notepad. (You can also use EXECUTE_COMMAND_LINE instead of system nowadays.)

I would still expect that to work unless you actually removed the notepad.exe from the windows install folder. 

I have used EXECUTE_COMMAND_LINE also but from memory I am unsure of the exact behaveour I think it is the same if WAIT=.true.

0 Kudos
Steven_L_Intel1
Employee
876 Views

While notepad.exe is not replaced, running notepad.exe using SYSTEM or even typing it from the command line starts TextPad. I am not sure how that is accomplished - maybe some registry redirection.

0 Kudos
Paul_Curtis
Valued Contributor I
876 Views

Here is how I launch a subprocess from my main program.  In my case, the main program keeps working in the background (this is, after all, a multitasking opsys), but one could easily put an existance test on the child process' window handle, and pause the parent program while the child process is running, to resume when the child window handle is found to be invalid as evidence that the child process has been terminated.  (In this code, the handles hwnd_ChildProcess and ghwndMain are made available through an included module.)

SUBROUTINE Launch (progname, classname)
	IMPLICIT NONE
	CHARACTER(LEN=*), INTENT(IN)   :: progname, classname
	
	INTEGER                        :: rval
	CHARACTER(LEN=40)              :: emsg
	TYPE(T_STARTUPINFO)			   :: si
	TYPE(T_PROCESS_INFORMATION)	   :: pi

	!     TYPE T_PROCESS_INFORMATION
	!     SEQUENCE
	!       integer(HANDLE) hProcess ! knowns  HANDLE 
	!       integer(HANDLE) hThread ! knowns  HANDLE 
	!       integer(DWORD) dwProcessId ! knowns  DWORD 
	!       integer(DWORD) dwThreadId ! knowns  DWORD 
	!     END TYPE

	!	disallow multiple child processes; previous shellouts
	!	must have been already terminated by the user
	IF (IsWindow(hwnd_ChildProcess) == 0) THEN

		CALL ZeroMemory (LOC(pi), SIZEOF(pi))
		CALL ZeroMemory (LOC(si), SIZEOF(si))

		si%cb		   = SIZEOF(si)
		si%dwFlags	   = STARTF_USESHOWWINDOW
		si%wShowWindow = SW_SHOWNORMAL

		IF (CreateProcess (NULL,						& ! process name
						   progname,					& ! command line		
						   NULL_SECURITY_ATTRIBUTES,	& ! security attributes
						   NULL_SECURITY_ATTRIBUTES,	& ! thread attributes
						   FALSE,						& ! handle inheritance
						   0,							& ! creation flags
						   NULL,						& ! environment block
						   NULL,						& ! initial working pat
						   si,							& ! startup info
						   pi) ) THEN  					  ! process info		

			!	let the process load itself
			rval = WaitForInputIdle (pi%hProcess, 2000)

			!	get its window handle from the classname
			hwnd_ChildProcess = FindWindow (classname, NULL)
			
			rval = CloseHandle (pi%hProcess)
			rval = CloseHandle (pi%hThread)
		
		!	problem creating new process
		ELSE
			rval = GetLastError()
			SELECT CASE (rval)
			CASE (ERROR_FILE_NOT_FOUND)
			    emsg = 'FILE NOT FOUND'C
			CASE (ERROR_DIRECTORY)
				emsg = 'DIRECTORY NOT FOUND'C
			CASE (ERROR_PATH_NOT_FOUND)
				emsg = 'PATH NOT FOUND'C
			CASE (ERROR_BAD_PATHNAME)
				emsg = 'BAD PATH NAME'C
			CASE DEFAULT
				emsg = 'Cannot create process'c
			END SELECT
            rval = MessageBox (ghwndMain, emsg, "Launch Error"C, &
                               IOR(MB_OK,MB_ICONERROR))
		END IF
	END IF
END SUBROUTINE Launch

 

0 Kudos
dboggs
New Contributor I
876 Views

To #6 and #8:

Thank you for this; I hadn't recognized this alternative spelling of EXCLUSIVE (!).

But I have now tried it, and it doesn't work. Apparently, opening a file with SHARE='DENYRW' works fine even if the file is currently open to another app; what this option really means is that another application will not be able to subsequently open the file. IOW, it controls the future behavior of the file whereas I need an option that depends on the past--if a file has already been opened by another app, then I need the open statement to fail.

I also thank users for discouraging me from closing the app programmatically, which might be infuriating to a user who may have had the app open for some unrelated prupose.

So I am back to my original plan: instead of using ShellExecute to open the editor (whichever one is registered to a .txt file), I will use
      RUNQQ ('Textpad', filespec)
since program execution automatically waits until the user closes Textpad. The only disadvantage is that the location of Textpad must either be on the system Path, or else it must be included with the RUNQQ argument. This I can live with, although it may be quite inconvenient for my customers.

0 Kudos
dboggs
New Contributor I
876 Views

To #6 and #8:

Thank you for this; I hadn't recognized this alternative spelling of EXCLUSIVE (!).

But I have now tried it, and it doesn't work. Apparently, opening a file with SHARE='DENYRW' works fine even if the file is currently open to another app; what this option really means is that another application will not be able to subsequently open the file. IOW, it controls the future behavior of the file whereas I need an option that depends on the past--if a file has already been opened by another app, then I need the open statement to fail.

I also thank users for discouraging me from closing the app programmatically, which might be infuriating to a user who may have had the app open for some unrelated purpose.

So I am back to my original plan: instead of using ShellExecute to open the editor (whichever one is registered to a .txt file), I will use
      RUNQQ ('Textpad', filespec)
since program execution automatically waits until the user closes Textpad. The only disadvantage is that the location of Textpad must either be on the system Path, or else it must be included with the RUNQQ argument. This I can live with, although it may be quite inconvenient for my customers.

0 Kudos
Steven_L_Intel1
Employee
876 Views

As TextPad is a commercial, paid product, how can you assume that all your users will have it? What if they use some other text editor replacement (Notepad++, etc.)? I think using ShellExecuteEx to "open" the file, not run a particular program, is best. You can put up a message instructing the user to close the program when done with the file. (Probably best handled by a non-modal dialog with a Cancel button.)

0 Kudos
dboggs
New Contributor I
876 Views

Yes Steve, I am well aware of these drawbacks to code for a specific editor app. That's really what this whole thread is about. So my program development to date has been using ShellExecute for the very reason you state. But I have developed second thoughts with the realization that I have no control over the application that it launches; I cannot close the application myself, and I cannot even detect if the user has closed it. Yes my program can ASK the user to close the app, but it cannot MAKE him close it. Code to handle this in the best possible way (still far from a very good way) becomes somewhat clunky. I just have to weigh this against the clunkiness of assuming the existence (and location) of a specific editor app.

My program is very simple. It is a console program, and contains just one modal dialog. I am not adept at handling multiple dialogs, modeless dialogs, message loops, etc. I know I should learn but it is not likely to happen for this first edition of the program. Is there a good working example code you could point me to? I definitely don't want a Windows project type (console or Quickwin is OK).

0 Kudos
Steven_L_Intel1
Employee
876 Views

Tell us what you're using the text editor for. Is it to display text or for the user to modify it? If it's just display you can use MESSAGEBOXQQ from any program type.

0 Kudos
dboggs
New Contributor I
876 Views

The text editor shows a .txt file to the user, who is then able to edit, add, or delete text in it.

MESSAGEBOXQQ will therefore not work. In addition, btw, it is limited to Quickwin projects (where I use it frequently).

0 Kudos
Steven_L_Intel1
Employee
791 Views

Then you want a dialog box with a text control.

0 Kudos
Reply