My goal is to force a process to run for at least 5 seconds (any amount of time really). I am working of the .NET Framework 3.5, with Service Pack 1. The idea is that the document holds some information that the user must see, so to safe guard against them immediately clicking to close the document, we can force it to stay open for some time. I developed a small test UI, consisting of a button, and then three radio buttons (one for each document). Here is my code behind...
I define the strings for the file paths, the string for the chosen file's path, and int to store the process's ID, a boolean for if they can exit the program, and the thread and timer declarations such as..
string WordDocPath = #"Some file path\TestDoc_1.docx";
string PowerPointPath = #"Some file path\Test PowerPoint.pptx";
string TextFilePath = #"Some file path\TestText.txt";
string processPath;
int processID;
bool canExit = false;
System.Threading.Thread processThread;
System.Timers.Timer processTimer;
In the constructor, I initialize the thread and timer, setting the thread's start method to a method called TimerKeeper(), and then I start the thread.
processTimer = new System.Timers.Timer();
processThread = new System.Threading.Thread(new System.Threading.ThreadStart(timeKeeper));
processThread.Start();
I have the timer set to count to 5 seconds, upon which it will set the canExit boolean to true.
public void timeKeeper()
{
processTimer.Elapsed += new System.Timers.ElapsedEventHandler(processTimer_Elapsed);
processTimer.AutoReset = false;
processTimer.Interval = 5000; //5000 milliseconds = 5 seconds
}
void processTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
canExit = true;
}
The rest is my button's click event, which deicides which file path to use to start the process, starts the timer, and then starts the process itself..
private void button1_Click(object sender, RoutedEventArgs e)
{
if ((bool)PowerPointRadioButton.IsChecked)
{
processPath = PowerPointPath;
}
if ((bool)WordDocRadioButton.IsChecked)
{
processPath = WordDocPath;
}
if ((bool)TextDocRadioButton.IsChecked)
{
processPath = TextFilePath;
}
try
{
canExit = false;
processTimer.Start();
while (!canExit)
{
processID = System.Diagnostics.Process.Start(processPath).Id;
System.Diagnostics.Process.GetProcessById(processID).WaitForExit();
if (!canExit)
{
processTimer.Stop();
MessageBox.Show("Document must remain open for at least 5 seconds.", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
processTimer.Start();
}
}
}
catch (Exception ex)
{
MessageBox.Show("Error dealing with the process.\n" + ex.Message.ToString());
}
This actually works, for the most part. The user still can close the document, but if it has not been 5 seconds, it will reopen. Except for the word document (.docx). Things go smoothly for the powerpoint and text files, but the word document has some strange behavior (please note that all 3 files are in the same file directory). When I choose the word documents radio button and click the button, the word document opens, BUT I am also prompted with the message box from the catch block, alerting me that a "Object reference not set to an instance on an object" exception was thrown. This only occurs for the word document. Like I said, the word document still opens (I can see it's contents, just like the powerpoint or textfile). The exception causes the lines that check to see if they can exit to be skipped, so the document can close immediately, which is a problem.
Can anyone see my issue here? Or if there is a better way to doing all of this (I am a wpf/c# newbie)? I just don't understand why this only occurs for the word document, and not the powerpoint and text files.
If this is run on the user's desktop you are subject to the proper app being installed (e.g. Word) and how it is configured. If these are read only files on a share then I could convert them to XPS so you could show them in a DocumentViewer. And rather than force them to wait 5 seconds to click make them say yes to a dialog box that they have read and understand the document. Or have this on a page with an "I agree" button as MilkyWayJoe suggested.
The problem could be that the associated application is not the word application itself, but some intermediate application that launches word on your behalf.
To find out, keep a reference to the process object, and check if it has already terminated, what it's executable path is.
Having said that, why do you need this annoying behavior? You cant stop people from looking the other way. Is it supossed to fullfill some legal requirement or something?
Related
I'm trying to delete .xlsx or .pdf files after using them. When files are created I display them, but then users want automatic file deletion after closing them.
I've tried couple of things, but none of them seem to work properly. Issue:
When opened multiple files (.xlsx or .pdf) I can't terminate a single process, like just a single file. Instead what happens is that file get's deleted only when I close all same processes (Excel or PDF files). As I investigated this happens because Excel or PDF works as one instance only. However code works as expected when I have only one file opened...
This is what I have so far:
var process= Process.Start(file_path); //file_path is global variable
Set_event(process);
private void Set_event(Process process)
{
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(Delete_File);
}
public void Delete_File(object sender, EventArgs e)
{
//Delete file on close
File.Delete(file_path);
}
I've also tried with DeleteOnClose method of FileOptions, but unfortunally that doesn't display file to user and doesn't quite delete file immediately after using them, only after my win app is closed. That isn't my desired output, but at least files are deleted, so If I could fix that I would be partially satisfied too. Here is my line for that:
var open_file = new FileStream(file_path,FileMode.Open, FileAccess.ReadWrite,FileShare.ReadWrite, 512, FileOptions.DeleteOnClose);
With all that said, are there any other options I missed ? Thanks for help in advance.
I've tried almost everything I could find (different variations of Exited_Event for Process, monitoring with FileSystemWatcher, creating files with DeleteOnClose - even API), but none of them worked as expected.
Everything ends or fails with issue I described in first place - some apps, like Microsoft Excel or Adobe Acrobat uses one instance to open a file (.pdf or .xls/.xlsx), so you can't just reference a single file as object while you have opened more files. That means you either end up with an error when trying to assign Exited_event to single file, or no error but file gets deleted only when you close all files with same type...
BUT fortunate enough I figured out one thing: WHEN you have opened more than one file in question (.pdf or .xlsx) something happens in background of OS: If you loop through processes of same type at that time, you'll get a list of particular instance that is in use.
In other words, while you have 2 Excel files opened, loop through processes is showing you only a file which is currently active for "EXCEL" process.
So, that leaded me to a completely new approach that might solve this issue. In order to have a complete solution for this you have to:
1. Create a method to check whether file is no longer in use.
2. Set a Timer with a delay of 2 seconds, to make sure process really ends. Maybe this should be incremented for different purposes...
3. Set a Timer_tick event, where you loop processes to see whether particular file is listed as active, and If user has already closed this file. As described by other users this method isn't quite accurate, but with setting delay for Timer I think there shouldn't be any problems anymore.
Here is a complete code for this (for .pdf and .xlsx - that is what I needed):
//as global variable
System.Windows.Forms.Timer delete_file = new System.Windows.Forms.Timer();
Process.Start(file_path); //file_path is global variable
delete_file.Tick += new EventHandler(timer_Tick);
delete_file.Interval = (2000);
delete_file.Enabled = true;
delete_file.Start();
private void timer_Tick(object sender, EventArgs e)
{
Boolean file_is_opened = false;
// Loop processes and list active files in use
foreach (var process in Process.GetProcesses())
{
if (process.MainWindowTitle.Contains(Path.GetFileName(file_path)))
{
file_is_opened = true;
}
}
//If our file is not listed under active processes we check
//whether user has already closed file - If so, we finally delete It
if (file_is_opened==false)
{
if (!File_In_Use(new FileInfo(file_path)))
{
File.Delete(file_path);
delete_file.Enabled = false;
delete_file.Stop();
return;
}
}
}
private bool File_In_Use(FileInfo file)
{
//Method to check whether file is in use
FileStream stream = null;
try
{
//If file doesn't exist
if (!file.Exists)
{
return false;
}
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
//File is unavailable:
//because someone writes to It, or It's being processed
return true;
}
finally
{
if (stream!=null)
{
stream.Close();
}
}
//File not locked
return false;
}
This is how I did It. It might not be a perfect solution, but that works for me on Win 10 with no errors so far.
If someone has a suggestion to fix upper code, please let me know. Otherwise I hope this will help someone in future as I noticed there were already some questions about this in past, with no proper answer.
I'm making a program that controls a game server. One of the functions I'm making, is a live server logfile monitor.
There is a logfile (a simple textfile) that gets updated by the server as it runs.
How do I continuously check the logfile and output it's content in a RichTextBox?
I did this simple function just try and get the content of the log. It will of course just get the text row by row and output it to my textbox. Also it will lock the program for as long as the loop runs, so I know it's useless.
public void ReadLog()
{
using (StreamReader reader = new StreamReader("server.log"))
{
String line;
// Read and display lines from the file until the end of the file is reached.
while ((line = reader.ReadLine()) != null)
{
monitorTextBox.AppendText(line + "\n");
CursorDown();
}
}
}
But how would you go about solving the live monitoring as simple as possible?
*** EDIT ***
I'm using Prescots solution. great stuff.
At the moment I'm using a sstreamreader to put the text from the file to my textbox. I ran into the problem is that, whenever I tried to access any of the gui controls in my event handler the program just stopped with no error or warnings.
I found out that it has to do with threading. I solved that like this:
private void OnChanged(object source, FileSystemEventArgs e)
{
if (monitorTextField.InvokeRequired)
{
monitorTextField.Invoke((MethodInvoker)delegate { OnChanged(source, e); });
}
else
{
StreamReader reader = new StreamReader("file.txt");
monitorTextField.Text = "";
monitorTextField.Text = reader.ReadToEnd();
reader.Close();
CursorDown();
}
}
Now my only problem is that the file.txt is used by the server so I can't access it, since it's "being used by another process". I can't control that process, so maybe I'm out of luck.
But the file can be opened in notepad while the server is running, so somehow it must be possible. Perhaps I can do a temp copy of the file when it updates and read the copy. I don't know.
Check out the System.IO.FileSystemWatcher class:
public static Watch()
{
var watch = new FileSystemWatcher();
watch.Path = #"D:\tmp";
watch.Filter = "file.txt";
watch.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite; //more options
watch.Changed += new FileSystemEventHandler(OnChanged);
watch.EnableRaisingEvents = true;
}
/// Functions:
private static void OnChanged(object source, FileSystemEventArgs e)
{
if(e.FullPath == #"D:\tmp\file.txt")
{
// do stuff
}
}
Edit: if you know some details about the file, you could handle the most efficent way to get the last line. For example, maybe when you read the file, you can wipe out what you've read, so next time it's updated, you just grab whatever is there and output. Perhaps you know one line is added at a time, then your code can immediately jump to the last line of the file. Etc.
Although the FileSystemWatcher is the most simple solution I have found it to be unreliable in reality.. often a file can be updated with new contents but the FileSystemWatcher does not fire an event until seconds later and often never.
The only reliable way I have found to approach this is to check for changes to the file on a regular basis using a System.Timers.Timer object and checking the file size.
I have written a small class that demonstrates this available here:
https://gist.github.com/ant-fx/989dd86a1ace38a9ac58
Example Usage
var monitor = new LogFileMonitor("c:\temp\app.log", "\r\n");
monitor.OnLine += (s, e) =>
{
// WARNING.. this will be a different thread...
Console.WriteLine(e.Line);
};
monitor.Start();
The only real disadvantage here (apart from a slight performance delay caused by file size checking) is that because it uses a System.Timers.Timer the callback comes from a different thread.
If you are using a Windows Forms or WPF app you could easily modify the class to accept a SynchronizingObject which would ensure the event handler events are called from the same thread.
As #Prescott suggested, use a FileSystemWatcher. And make sure, you open the file with the appropriate FileShare mode (FileShare.ReadWrite seems to be appropriate), since the file might still be opened by the server. If you try to open the file exclusively while it is still used by another process, the open operation will fail.
Also in order to gain a bit of performance, you could remember the last position up to which you already have read the file and only read the new parts.
Use this answer on another post c# continuously read file.
This one is quite efficient, and it checks once per second if the file size has changed.
You can either run it on another thread (or convert to async code), but in any case you would need to marshall the text back to the main thread to append to the textbox.
Try adding a Timer and have the Timer.Tick set to an Interval of 1 second. On Timer.Tick you run the function.
private void myTimer_Tick(object sender, EventArgs e)
{
ReadLog();
}
I am creating an C# desktop application.
I want to open only one document at a time.
how can i know that already opened word document is closed by the user before i opens next.
The user has to close opened word document.
Any idea or suggestion would be appreciated.
Can you explain how you application is interacting with Word documents? If your application is the only way for a user to open a word document, then once a document is open set a variable:
bDocOpen = true;
Once the document has been closed set it to false. Don't allow opening documents if bDocOpen equals true.
EDIT
You could launch the document and wait for the process to finish. This is not threaded, so your app will be unresponsive until the document is closed. I guess this behavior could be a mechanism to keep multiple documents from being opened. However leaving your app unresponsive is not a good practice, the user will think something is wrong.
bool bDocOpen = false;
private void btnOpenDoc(object sender, EventArgs e)
{
if (!bDocOpen)
{
ProcessStartInfo pInfo = new ProcessStartInfo();
pInfo.FileName = #"C:\temp\word.doc";
Process p = Process.Start(pInfo);
p.WaitForInputIdle();
p.WaitForExit();
//Will not get here till process exits
MessageBox.Show("Document closed");
bDocOpen = false;
}
}
My application uses Word interop for "active reporting" functionality. When the Word document is launched from the application, we set a EventWaitHandle to pause the application (creating a 'modal' effect) until the document is closed:
wh.WaitOne();
We have set an event on the word Application Quit event, where we then set the wait EventWaitHandle for the application to continue
wordGenerator.WordApplication.ApplicationEvents2_Event_Quit += WordApplication_ApplQuit;
private void WordApplication_ApplQuit()
{
wh.Set(); // signal that word has closed
wordGenerator.Dispose();
wordGenerator = null;
}
After this is called, the application then reads the document from the location it was stored and saves it into our database. All works great. UNLESS... the user makes changes in the document and doesn't CTRL+S but rather clicks close and gets prompted with the "would you like to save changes" prompt.
What happens in this instance that the quit event is fired as soon as you click close in Word, but Word is still open whilst the dialog to save changes is there. The application then continues to run and gets IO exceptions "Document is in use by another process" when trying to read the document to save to the database. Even waiting and retrying doesn't work as it seems that Word and the Application are waiting on eachother.
Is there another event I can use? I can't bypass the alert and automatically save as perhaps the user doesn't want to save.
Problem solved... easy one this time. Moved the dispose code above the .Set().
private void WordApplication_ApplQuit()
{
wordGenerator.Dispose();
wordGenerator = null;
wh.Set(); // signal that word has closed
}
I have an application that uses MSWord automation to edit some documents, after they save and close word I need to grab the modified file and put it back on the repository, there is only one scenario where I can't get it to work and that is
when the user makes changes to the file, selects to close word and selects yes to save the file
there are 2 events that I'm using:
DocumentBeforeSave
Quit
on the Quit event I'm trying to load the .docx file from disk but on this particular scenario I get an IOException because the file is still in use, somehow I need to wait until after the Quit event has been processed, which is when Word is actually closed and the file is no longer being used
right now I have it working using this
word.Visible = true;
while (!wordDone) { //gets changed to true on the Quit event
System.Threading.Thread.Sleep(100);
}
bool error = false;
do {
try { //need to load the contents of the modified file
ls.Content = System.IO.File.ReadAllBytes(provider.GetFileName());
error = false;
}
catch (System.IO.IOException) {
error = true;
System.Threading.Thread.Sleep(200);
}
} while (error);
while this works it is very ugly, I need a way to fire an event after the Quit event has been handled, or block the current thread while word is still running, or get an event after the document has been saved, the bottom line is I need a clean way to load the file after it has been saved and word is closed. DocumentAfterSave would be awesome, but doesn't seem to exist.
I Also tried unhooking the Quit handler and calling word.Quit on the Quit handler, that made no difference
I'm also investigating the use of ManualResetEvent or related classes, so far it almost works, but I still need to pause after it has been signaled to make sure word is closed and the file is no longer in use
I faced similar problem in the past as well. I dont think there is any nice clean way but instead of doing it like your above, how about considering this (will suit if you have a controlled environment)
Create word app
Get the Process ID immediately by using GetProcesses matching Winword and the last one in the list return should be the one you are after. This is not 100% reliable in multiuser environment.
After word quit, use the Thread.Sleep loop to ensure the PID no longer exist.
Reading the docx for your custom operations
I used to have the same problem. Using ReleaseComObject on all COM-related objects did the trick (that is, on your Word document object and your Word.Application object). That way you ensure that all dirty locks are removed after the COM object has been destroyed. Close the document and application with the Interop API. I use:
var localWordapp = new Word.Application();
localWordapp.Visible = false;
Word.Document doc = null;
// ...
if (doc != null)
{
doc.Close();
System.Runtime.InteropServices.Marshal.ReleaseComObject(doc);
}
localWordapp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(localWordapp);