I want to write a screenshot app that grabs a screen shot, saves it to a file, opens Paint.NET with that file, then uploads the edited file to a service when the user is finished editing the file in Paint.NET.
I have the other stuff sort of covered. How can I detect when the image is finished being edited in Paint.NET?
I'm using .NET 3.5, C#.
I can use a FileSystemWatcher to detect changes in a specific file. But the first time a file changes does not necessarily indicate that Paint.NET is finished. I can wait for Paint.NET to exit - I suppose by examining the list of processes in Windows and detecting when Paint.NET is no longer there. But a user may finish editing a file without actually closing Paint.NET.
If I have to tell the user to close Paint.NET, to signal that the file is ready for upload, I guess I could do that. But I'm hoping to avoid that sort of extra requirement.
If Paint.NET holds a file open for read while it is being edited, then I suppose I could try watching for that. But how? Maybe by polling, trying to open the file with FileShare.None.
Is there a better way?
EDIT - no, Paint.NET does not keep the file open, not even for reading. I can open an image file no problem with FileShare.None, even while it is being displayed/edited by Paint.NET. So that idea won't work.
Does Paint.NET have a Remoting interface, where I can interrogate it for which files it has open? that would suit my purposes.
After fiddling with this a little bit, I think UI Automation will satisfy. Using the System.Windows.Automation classes that were new in .NET 3.0, I can inquire about the contents of other Windows on the machine, and I can find a windows for a given process ID. So it's a matter of
I haven't tested this very much, but, this code seems to work for my purposes:
public void Run()
{
var shortFileName = Path.GetFileName(_filename);
System.Console.WriteLine("Waiting for PDN to finish with {0}", shortFileName);
var s= from p in Process.GetProcesses()
where p.ProcessName.Contains("PaintDotNet.exe")
select p;
if (s.Count()==0)
{
System.Console.WriteLine("PDN is not running.");
return;
}
var process = s.First();
var window = AutomationElement.RootElement.FindChildByProcessId(process.Id);
string name =
window.GetCurrentPropertyValue(AutomationElement.NameProperty) as string;
if (!name.StartsWith(shortFileName))
{
System.Console.WriteLine("PDN appears to NOT be editing the file.");
}
else
{
try
{
int cycles = 0;
do
{
System.Threading.Thread.Sleep(800);
name = window.GetCurrentPropertyValue(AutomationElement.NameProperty) as string;
if (!name.StartsWith(shortFileName)) break;
cycles++;
System.Console.Write(".");
} while (cycles < 24);
if (!name.StartsWith(shortFileName))
System.Console.WriteLine("PDN is done.");
else
System.Console.WriteLine("Timeout.");
}
catch (ElementNotAvailableException)
{
System.Console.WriteLine("PDN has exited.");
}
}
}
The FindChildByProcessId
method is an extension method from this blog post. It looks like this:
public static class AutomationExtensions
{
public static AutomationElement FindChildByProcessId(this AutomationElement element, int pid)
{
var cond = new PropertyCondition(AutomationElement.ProcessIdProperty, pid);
var result = element.FindChildByCondition(cond);
return result;
}
public static AutomationElement FindChildByCondition(this AutomationElement element, Condition cond)
{
var result = element.FindFirst(TreeScope.Children, cond);
return result;
}
}
This seems a little unorthodox, but, it just works. The trickiest part is the timing - you need to wait long enough to let the app start up, which can be 1 second or it could be 7 seconds. Then try to attach to it with the UIAutomation classes.