Program exits when FileSystemWatcher.Changed event is raised (C#) - c#

So, I'm trying to make a file changed notifier, and I need to make it so the text in a textbox updates whenever the contents of the file are changed. This is what I have so far:
string path = "C:/Users/Max/Dropbox/Public/IM.txt";
StringBuilder b = new StringBuilder();
private void Window_Loaded(object sender, EventArgs e)
{
TB.Text = File.ReadAllText(path);
b.Append(TB.Text);
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = path.Remove(path.Length - 6, 6);
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Filter = "*.txt";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
TB.SelectionStart = TB.Text.Length;
TB.ScrollToCaret();
}
private void OnChanged(object source, FileSystemEventArgs e)
{
TB.Text = File.ReadAllText(path);
}
This seems to raise the event correctly, but as soon as it touches the code in the OnChanged event, the program exits, no errors or anything, just closes. I have tried to stop it from closing, I have even tried putting e.Cancel under the formclosing event, but nothing seems to work. Any ideas? I can provide more info if needed.

Have you tried wrapping the code in try catch
private void OnChanged(object source, FileSystemEventArgs e)
{
try
{
TB.Text = File.ReadAllText(path);
}catch(Exception e)
{
//Show exception in messagebox or log to file.
}
}

Try this in your Changed method
if (TB.InvokeRequired)
{
TB.Invoke(new MethodInvoker(delegate { TB.Text = File.ReadAllText(path); }));
}

Related

Update textbox from txt file during runtime without clicking "refresh"

In wpf, I have a textbox, the content of it coming from a txt file.
in code behine it looks like:
public MyWindow()
{
InitializeComponent();
ReadFromTxt();
}
void Refresh(object sender, RoutedEventArgs e)
{
ReadFromTxt();
}
void ReadFromTxt()
{
string[] lines = System.IO.File.ReadAllLines(#"D:\Log.txt");
foreach (string line in lines)
{
MyTextBox.AppendText(line + Environment.NewLine);
}
}
The thing is that the txt file content is changing during the runtime, is it possible to be synchronized with those changes? I mean that the textbox content would be change if the txt file is changed. if It's possible please give some code example i c# or XAML.
My solution for now is that I made a "Refresh" button that reads again from the txt file
You can configure FileSystemWatcher. I added FileWatcherConfigure function as an example.
Whenever file changes, FileChanged event will be published. You should trigger the RefreshTextbox method with that event.
FileSystemWatcher fileWatcher = new FileSystemWatcher();
string filePath = #"./Log.txt";
public MainWindow()
{
InitializeComponent();
ReadFromTxt();
FileWatherConfigure();
}
public void FileWatherConfigure()
{
fileWatcher.Path = System.IO.Path.GetDirectoryName(filePath);
fileWatcher.Filter = System.IO.Path.GetFileName(filePath);
fileWatcher.Changed += FileWatcher_Changed;
fileWatcher.EnableRaisingEvents = true;
}
private void FileWatcher_Changed(object sender, FileSystemEventArgs e)
{
Thread.Sleep(TimeSpan.FromSeconds(1));
ReadFromTxt();
}
void ReadFromTxt()
{
string[] lines = System.IO.File.ReadAllLines(filePath);
MyTextBox.Dispatcher.Invoke(() => { this.MyTextBox.Text = string.Join(Environment.NewLine, lines); });
}
You need a FileSytemWatcher that will trigger an event that you can use to refresh the content of your textbox automatically.
I am using the following in a project I am working on to detect new and updated file.
I watch for the:
LastAccess: To get an event when the file has been changed
LastWrite: To get an event when the file has been changed
FileName: To get an event when a file is renamed that would match
Please note that I am clearing the textbox each time to avoid duplicated lines (your code would append the whole file to the existing content)
public class MyWindow : Window
{
public MyWindow()
{
InitializeComponent();
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = #"D:\";
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName;
watcher.Filter = "Log.txt";
// Add event handlers.
watcher.Changed += new FileSystemEventHandler(OnFileChanged);
watcher.Created += new FileSystemEventHandler(OnFileChanged);
watcher.Renamed += new RenamedEventHandler(OnFileRenamed);
// Begin watching.
watcher.EnableRaisingEvents = true;
ReadFromTxt();
}
private void OnFileChanged(object source, FileSystemEventArgs e)
{
ReadFromTxt();
}
private void OnFileRenamed(object source, RenamedEventArgs e)
{
ReadFromTxt();
}
private void ReadFromTxt()
{
// The easiest way is to just replace the whole text
MyTextBox.Text = "";
if (System.IO.File.Exists(#"D:\Log.txt") == false)
{
return;
}
string[] lines = System.IO.File.ReadAllLines(#"D:\Log.txt");
foreach (string line in lines)
{
MyTextBox.AppendText(line + Environment.NewLine);
}
}
}
Just to clarify on other answers, FileSystemWatcher can be an option but you should definately consider polling as an alternative. For readability (opinion-based) and reliability (see this thread). Note that there is many other posts that underline issues concerning the use of FileSystemWatcher.
And by polling I mean look for changes on specific periodic periods. An option would be to use a Timer and look for changes and act accordingly.
View :
<TextBox Text="{Binding myText}"/>
Model :
private string _myText;
public string MyText{
get { return _myText; }
set { _myText = value;
NotifyPropertyChanged();
}
Implement INotifyPropertyChangedto your Window
And change the property MyText and not MyTextBox

Exit File Watcher When a file is created

I am using a file watcher. And when a file is created the file watcher should stop until it is called again. I have got the following code which is watching and the loop doesn't stop.
public void startwatch()
{
string path = "C:\\testwatcher\\";
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.EnableRaisingEvents = true;
watcher.IncludeSubdirectories = false;
watcher.Filter = "*.*";
watcher.Created += watcher_Created;
while (true) ;
}
static void watcher_Created(object sender, FileSystemEventArgs e)
{
string FileName = ("C:\\testfilescrated\\");
string text = File.ReadAllText(e.FullPath);
File.WriteAllText(FileName, text);
}
What i want to do is while loop should end when a file is created.
Don't use a busy loop, this will unnecessarily eat CPU. You could use a wait handle, for example using a ManualResetEvent's WaitOne() method:
ManualResetEvent _resetEvent;
public void StartWatch()
{
// Start watching...
// Wait until signaled.
_resetEvent = new ManualResetEvent(false);
_resetEvent.WaitOne();
}
void watcher_Created(object sender, FileSystemEventArgs e)
{
// Handle your file...
// And signal.
_resetEvent.Set();
}

FileSystemWatcher Events Not Firing

My FileSystemWatcher isn't throwing any events. I've looked at these similar questions, none seem to be an answer for my problem:
*Edit: My goal is to capture when an XLS file is copied to or created in a directory.
Filesystemwatcher doesn't trigger event
FileSystemWatcher - event not firing the second time
FileSystemWatcher Changed event doesn't fire
FileSystemWatcher - only the change event once firing once?
Monitor class:
public class Monitor
{
FileSystemWatcher watcher = new FileSystemWatcher();
readonly string bookedPath = #"\\SomeServer\SomeFolder\";
public delegate void FileDroppedEvent(string FullPath);
public event FileDroppedEvent FileDropped;
public delegate void ErrorEvent(Exception ex);
public event ErrorEvent Error;
public Monitor()
{
watcher.Path = bookedPath;
watcher.Filter = "*.xls";
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Changed += new FileSystemEventHandler(watcher_Changed);
watcher.Error += new ErrorEventHandler(watcher_Error);
}
void watcher_Error(object sender, ErrorEventArgs e)
{
Error(e.GetException());
}
void watcher_Changed(object sender, FileSystemEventArgs e)
{
if (e.ChangeType != WatcherChangeTypes.Created) return;
FileDropped(e.FullPath);
}
public void Start()
{
watcher.EnableRaisingEvents = true;
}
public void Stop()
{
watcher.EnableRaisingEvents = false;
}
}
Simple form with Listbox:
public partial class Form1 : Form
{
Monitor monitor = new Monitor();
public Form1()
{
InitializeComponent();
FormClosing += new FormClosingEventHandler(Form1_FormClosing);
Load += new EventHandler(Form1_Load);
monitor.FileDropped += new Monitor.FileDroppedEvent(monitor_FileDropped);
monitor.Error += new Monitor.ErrorEvent(monitor_Error);
}
void Form1_Load(object sender, EventArgs e)
{
monitor.Start();
}
void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
monitor.Stop();
}
void monitor_Error(Exception ex)
{
listBox1.Items.Add(ex.Message);
}
void monitor_FileDropped(string FullPath)
{
listBox1.Items.Add(FullPath);
}
}
What am I doing wrong?
Try this out. Works for me for a very similar task.
watcher.NotifyFilter = NotifyFilters.FileName;
watcher.Created += new FileSystemEventHandler(handler);
watcher.Renamed += new RenamedEventHandler(handler);
This may be because the file metadata hasn't been updated yet. This may happen if you are continuously writing to the file.
Have you tried the following:
watcher.Path = directory name;
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Filter = "*.xls";
watcher.Changed += OnDirectoryChange;
watcher.Error += OnError;
watcher.EnableRaisingEvents = true;
// Watch only files not subdirectories.
watcher.IncludeSubdirectories = false;
Your issue is with the filters as well as your events I believe. NotifyFilters.LastAccess will only trigger when a file is opened. Try using:
NotifyFilters.LastWrite | NotifyFilters.CreationTime
This will watch for written/created files. Next, hook up to the Created delegate to handle newly created files:
watcher.Created += YourDelegateToHandleCreatedFiles
The way FileSystemWatcher works is to first use the NotifyFilters to limit the event triggers. Then, you use the actual events to do the work. By hooking into the Created event you'll only do work when a file is created.

cross-thread calls?

This is mt first time trying to write a not web based program, and my first time writing anything in C#.
I need a program that monitors folders, but I can't get it to work.
I have used the example from this post Using FileSystemWatcher with multiple files but is trying to make it a form.
My current problem comes in the ProcessQueue function where fileList apparently is defined in another thread.
Whenever a file is actually submitted to the watched folder I get an error that using fileList is a cross thread call
Can anyone explain this error to me, and how to fix it?
namespace matasWatch
{
public partial class Form1 : Form
{
private int n = 1;
private bool isWatching = false;
private List<string> filePaths;
private System.Timers.Timer processTimer;
private string watchedPath;
private FileSystemWatcher watcher;
public Form1()
{
filePaths = new List<string>();
watchedPath = "C:\\Users\\username\\Desktop\\test";
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (!isWatching)
{
button1.Text = "Stop";
isWatching = true;
watcher = new FileSystemWatcher();
watcher.Filter = "*.*";
watcher.Created += Watcher_FileCreated;
watcher.Error += Watcher_Error;
watcher.Path = watchedPath;
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
}
else {
button1.Text = "Watch";
isWatching = false;
watcher.EnableRaisingEvents = false;
watcher.Dispose();
watcher = null;
}
}
private void Watcher_Error(object sender, ErrorEventArgs e)
{
// Watcher crashed. Re-init.
isWatching = false;
button1_Click(sender, e);
}
private void Watcher_FileCreated(object sender, FileSystemEventArgs e)
{
filePaths.Add(e.FullPath);
if (processTimer == null)
{
// First file, start timer.
processTimer = new System.Timers.Timer(2000);
processTimer.Elapsed += ProcessQueue;
processTimer.Start();
}
else{
// Subsequent file, reset timer.
processTimer.Stop();
processTimer.Start();
}
}
private void ProcessQueue(object sender, ElapsedEventArgs args)
{
try
{
fileList.BeginUpdate();
foreach (string filePath in filePaths)
{
fileList.Items.Add("Blaa");
}
fileList.EndUpdate();
filePaths.Clear();
}
finally
{
if (processTimer != null)
{
processTimer.Stop();
processTimer.Dispose();
processTimer = null;
}
}
}
}
}
I assume that fileList is a windows forms control. The ProcessQueue method is called from a timer thread which is by default a background thread. The fileList control resides in the UI thread. You need to use the Invoke() method of the form passing it in a delegate the updates the fileList control.
Invoke(new Action(() =>
{
fileList.BeginUpdate();
foreach (string filePath in filePaths)
{
fileList.Items.Add("Blaa");
}
fileList.EndUpdate();
filePaths.Clear();
}));
Try using System.Windows.Forms.Timer instead of System.Timers.Timer so the timer tick event is executed on the UI thread.
See here for more details.

FileSystemWatcher + Dialog

I wanna show a dialog everytime some file has been changed... But everytime that dialog shows up, my app freeze. How can I do this with another thread? Any ideas?
protected virtual void CreateWatcher (object path)
{
if (watcher != null)
{
watcher.EnableRaisingEvents = false;
watcher.Dispose ();
}
//Create a new FileSystemWatcher.
watcher = new FileSystemWatcher ();
//Set the filter to only catch TXT files.
watcher.Filter = "*.txt";
watcher.IncludeSubdirectories = true;
watcher.NotifyFilter = NotifyFilters.LastWrite;
//Subscribe to the Created event.
watcher.Changed += new FileSystemEventHandler (OnChanged);
watcher.Created += new FileSystemEventHandler (OnChanged);
//watcher.Deleted += new FileSystemEventHandler (OnChanged);
//watcher.Renamed += new RenamedEventHandler (OnRenamed);
//Set the path to C:\\Temp\\
watcher.Path = #path.ToString();
//Enable the FileSystemWatcher events.
watcher.EnableRaisingEvents = true;
}
void OnChanged (object source, FileSystemEventArgs e)
{
NovaInteracaoMsg();
}
protected virtual void NovaInteracaoMsg ()
{
novaInteracao = new MessageDialog (this, DialogFlags.DestroyWithParent, MessageType.Question, ButtonsType.YesNo, "Foi detectada a mudança nos arquivos do modelo. Deseja inserir uma nova interação?");
ResponseType result = (ResponseType)novaInteracao.Run ();
if (result == ResponseType.Yes) {
OpenInfoWindow (novaInteracaoPath);
return;
}
else {
novaInteracao.Destroy ();
}
}
void OnRenamed (object source, RenamedEventArgs e)
{
//Console.WriteLine ("File: {0} renamed to\n{1}", e.OldFullPath, e.FullPath);
}
protected virtual void OpenInfoWindow (string path)
{
ModMemory.Iteration iterWin = new ModMemory.Iteration (path);
iterWin.Modal = true;
iterWin.Show ();
iterWin.Destroyed += delegate {
// TODO: Funções para executar quando a janela for fechada
// Possivelmente atualizar o número de interações realizadas
Console.WriteLine ("Janela modal destruída");
};
}
The problem is that you are already using another thread. Try one of the following approaches
Set the FileSystemWatcher.SynchronizingObject property so it
raises events on your UI thread. Now you can show a UI that won't
freeze or
Use Control.BeginInvoke() in the event handler.
This was a psychic debugging attempt, there was nothing in your question that helped me be sure that's the correct answer.
Call Show() instead of ShowDialog() in your form.
http://msdn.microsoft.com/en-us/library/system.windows.forms.control.show.aspx
Edit - MessageBox classes are Modal, if you want a modeless dialog window you'll have to create it yourself.

Categories