The mission seems simple: In a desktop application, start an external app (such as Word, Excel, Acrobat, etc) to view or edit a certain file, and wait for the application to exit or modify the file, and then load the modified file back into the application.
This turned out to be more complicated than expected.
Attempt 1: Process WaitForExit
The initial, obvious solution appears to be to start the file using Process.Start, which returns a Process object. In turn, the Process object has a WaitForExit method. However, this idea ran into a significant impediment: Both Windows and applications re-use processes. In some cases, Process.Start returns null. Some proposed solutions recommend attaching to the existing process, but, this requires knowing what specific process it is and then is still dependent on that process itself not being further reused.
Even in those cases where Process.Start returned a value, sometimes the process exited almost immediately when it found another instance of the correct application running and simply handed the file off to it. Thus, the WaitForExit method call would complete very quickly even though the application was still in use.
Attempt 2: Wait for Application Window Re-Activation
Since file editing is expected to open in another window, and continue until that file is closed, it makes some sense to attach an Activated event listener to the calling application’s window. When the event is called, the handler can be de-registered and the application may assume work on the file has completed.
This, however, also doesn’t work, as some applications go through phases on start up and cause the activated event to be fired.
I didn’t pursue further “hacks” (such as waiting a few seconds before enabling the activated event) as the whole idea seems pretty unreliable, since other applications are not modal.
New Approach
Ok, forget the idea of waiting for an application to close. Instead, continue to monitor the file in the background watching for changes. If any changes are detected, load the file back into the application.
Attempt 3: FileSystemWatcher
.NET has a class apparently just for this kind of purpose, the FileSystemWatcher. Provide a directory and/or filename to the watcher, and it will trigger an event when anything happens to the file.
Seems easy and fun, but there’s a few problems. It can be difficult to use, and when notifications come in, sometimes the file would still be locked by the editing application, causing exceptions when the application tried to load the file back in. In order to handle this, a timer and queue must be manually created. Even worse, the FileSystemWatcher sometimes fails to notify of changes all together.
There are various projects which wrap or extend the FileSystemWatcher to make it usable. Some of these might be sufficient to address these issues.
[My] Solution: FileChangeMonitor
Given that significant custom infrastructure was needed regardless (timer, queue), I decided to complete a custom file change monitoring solution. The monitoring system allows individual files to be added to an internal list. Using a timer, the list is monitored for file changes using a hash. If a change is detected in a readable file (not locked), one event per file is generated.
Additionally, the class supports an arbitrary data “tag” to accompany each file which will be provided when a change event occurs, and it also supports a synchronizing object for performing change events on the UI thread, if desired.
The critical signatures are:
/// <summary>
/// When a change occurs, the event call will be synchronized to this
/// object's thread, if given.
/// </summary>
public ISynchronizeInvoke SynchronizingObject { get; set; }
/// <summary>
/// Fired when one of the monitored files changes content
/// </summary>
public event EventHandler<FileChangedEventArgs> FileChanged;
/// <summary>
/// Adds this file to be monitored for changes. If the file is already
/// being monitored, does nothing.
/// If data is specifed, that object is stored and will be provided in
/// the event args when the file is updated.
/// </summary>
public void Add(string filename, object data = null);
/// <summary>
/// Remove this file from monitoring
/// </summary>
public void Remove(string filename);
/// <summary>
/// Remove all files from monitorings
/// </summary>
public void Clear();