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
Related
I am very new to coding and I'm writing an application which will rename files
by appending milliseconds to the existing filename of files which have been scanned in from an MFD.
The folder is a shared folder and the renamed file should stay within it and not be copied elsewhere.
From doing a lot of searching, I know that File.Move is my way forward, however, I can't seem to get it to work.
Here's my code:
private void MonitorToggle_Click(object sender, EventArgs e)
{
// Create a new FileSystemWatcher object.
FileSystemWatcher fsWatcher = new FileSystemWatcher();
switch (MonitorToggle.Text)
{
// Start Monitoring…
case startMonitoring:
if (!FilePathField.Text.Equals(String.Empty))
{
//Set the watched folder path equal to the file path variable
fsWatcher.Path = FilePathField.Text;
// Set Filter.
fsWatcher.Filter = (FileTypeField.Text.Equals(String.Empty))? "*.*" : FileTypeField.Text;
// Monitor files and subdirectories.
fsWatcher.IncludeSubdirectories = true;
// Monitor all changes specified in the NotifyFilters.
fsWatcher.NotifyFilter = NotifyFilters.LastWrite;
fsWatcher.EnableRaisingEvents = true;
// Raise Event handlers.
fsWatcher.Changed += new FileSystemEventHandler(OnChanged);
fsWatcher.Created += new FileSystemEventHandler(OnCreated);
}
else
{
MessageBox.Show("Please select a folder to monitor.", "Warning",MessageBoxButtons.OK, MessageBoxIcon.Warning );
}
break;
// Stop Monitoring…
case stopMonitoring:
default:
fsWatcher.EnableRaisingEvents = false;
fsWatcher = null;
break;
}
}
public void OnChanged (object sender, FileSystemEventArgs e)
{
FileInfo file = new FileInfo(e.Name);
string dateStamp = DateTime.Now.ToString("fff");
string fName = file.Name;
string newFile = string.Concat(fName, dateStamp);
File.Move(fName,newFile);
}
public void OnCreated(object sender, FileSystemEventArgs e)
{
FileInfo file = new FileInfo(e.Name);
string dateStamp = DateTime.Now.ToString("fff");
string fName = file.Name;
string newFile = string.Concat(fName, dateStamp);
File.Move(fName, newFile);
}
private void BrowseButton_Click(object sender, EventArgs e)
{
// Create FolderBrowserDialog object.
FolderBrowserDialog folderBrowserDialog = new FolderBrowserDialog();
// Show a button to create a new folder.
folderBrowserDialog.ShowNewFolderButton = true;
DialogResult dialogResult = folderBrowserDialog.ShowDialog();
// Get selected path from FolderBrowserDialog control.
if (dialogResult == DialogResult.OK)
{
FilePathField.Text = folderBrowserDialog.SelectedPath;
Environment.SpecialFolder root = folderBrowserDialog.RootFolder;
}
}
Whenever I create a new file in the folder I'm watching, it does absolutely nothing. At first I thought it may be because I only had the "OnCreated" method, so I then copied it into the "OnChanged" method (I wasn't sure if copying an existing file into the folder counted as it being "created" but I've had no luck).
Out of interest, I'm also getting an exception thrown if I don't specify a type in the filter, but that's much less pressing at the minute.
If anyone can offer any pointers of where I may be going wrong, it'd be greatly appreciated.
There are a few problems with your code.
First, you should use e.FullPath instead of e.Name, otherwise the code will try to rename the file in the current directory, instead of watched directory.
Second, to receive Created event you should include NotifyFilters.FileName.
However, this will not help you much, because the file is locked in the created and changed events until the file is copied, so you'll get an exception. Also, you'll probably receive more than one Changed event (in my tests I always receive two, regardless of the file size, but it may be different on the different versions of Windows or .Net framework).
To fix this, you may use timers or threads to accomplish the task. Here's an example using ThreadPool thread. Whenever created is fired, you create a new thread. In the thread you check whether a file is locked (trying to open file), and when the file is unlocked, do the rename.
public class FileMon
{
public static void Run()
{
FileSystemWatcher fsWatcher = new FileSystemWatcher();
fsWatcher.Path = #"C:\Test\";
fsWatcher.Filter = "*.*" ;
fsWatcher.IncludeSubdirectories = true;
// Monitor all changes specified in the NotifyFilters.
fsWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName ;
fsWatcher.EnableRaisingEvents = true;
// Raise Event handlers.
fsWatcher.Changed += OnChanged;
fsWatcher.Created += OnCreated;
Console.WriteLine("[Enter] to end"); Console.ReadLine();
fsWatcher.EnableRaisingEvents = false;
}
static void Worker(object state)
{
FileSystemEventArgs fsArgs = state as FileSystemEventArgs;
bool done = false;
FileInfo fi = new FileInfo(fsArgs.FullPath);
do
{
try
{
using (File.Open(fsArgs.FullPath, FileMode.Open))
{
done = true;
}
}
catch
{
done = false;
}
Thread.Sleep(1000);
} while (!done);
Console.WriteLine("DOne");
string dateStamp = DateTime.Now.ToString("fff");
string fName = fi.FullName;
string newFile = fsArgs.FullPath + dateStamp;
File.Move(fsArgs.FullPath, newFile);
}
private static void OnCreated(object sender, FileSystemEventArgs e)
{
Console.WriteLine($"Created {e.ChangeType} : {e.Name}");
ThreadPool.QueueUserWorkItem(Worker, e);
}
static void OnChanged(object sender, FileSystemEventArgs e)
{
Console.WriteLine($"{e.ChangeType} : {e.FullPath}");
}
}
I need to watch several file at different time and sometimes at the same time.
I am using this as a test:
namespace FilewatcherTest
{
public partial class Form1 : Form
{
private System.IO.FileSystemWatcher FSWatcherTest;
public Form1()
{
InitializeComponent();
FSWatcherTest = new FileSystemWatcher();
EventHandling();
FSWatcherTest.Path = #"d:\tmp";
FSWatcherTest.Filter = "file.txt";
// Begin watching.
FSWatcherTest.EnableRaisingEvents = true;
}
protected void EventHandling()
{
FSWatcherTest.Changed += FSWatcherTest_Changed;
FSWatcherTest.Deleted += FSWatcherTest_Deleted;
FSWatcherTest.Renamed += FSWatcherTest_Renamed;
FSWatcherTest.Created += FSWatcherTest_Created;
}
private void FSWatcherTest_Changed(object sender, System.IO.FileSystemEventArgs e)
{
WriteToLog("File Changed");
}
private void FSWatcherTest_Created(object sender, System.IO.FileSystemEventArgs e)
{
WriteToLog("File Created");
}
private void FSWatcherTest_Deleted(object sender, System.IO.FileSystemEventArgs e)
{
WriteToLog("File Deleted");
}
private void FSWatcherTest_Renamed(object sender, System.IO.RenamedEventArgs e)
{
WriteToLog("File Renamed");
}
private void WriteToLog(string message)
{
using (var sw = new StreamWriter(#"d:\tmp\service.log", true))
{
sw.WriteLine(string.Format("{0} {1}", DateTime.Now,message));
}
}
}
}
Of course I'll change the hardcoded paths once I have something in place since this is going into a service I created.
My question is, can I use the same file watcher or should I use a unique one for each file?
If I use the same one, how do I know which file is raising the event?
Thanks!!
EDIT
Sorry I haven't used filesystemwatcher before and didn't know it mattered but the files will be in different directories and not of the same file type.
can I use the same file watcher or should I use a unique one for each file?
In your case, I don't think there is a reason to create a new instance of FileSystemWatcher for every file you're watching. Yes, you can use the same one. You can use a filter such as "*.txt" or whatever you need to watch a set of files...
If I use the same one, how do I know which file is raising the event?
The FileSystemEventArgs has a Name property which returns the name of the file that triggered the event.
So for example:
private void FSWatcherTest_Created(object sender, System.IO.FileSystemEventArgs e)
{
string fileName = e.Name;
WriteToLog("File Created: " + fileName);
}
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.
I'm creating a List of FileSystemWatchers.
List<ExtSystemFileWatcher> fswMonitors = new List<FileSystemWatcher> ();
The number of them in the list is dynamic depending on the user. This is done from a INI file and an array of Monitor objects from my own Monitor class are created. The class simply has varibles like the Montior number, Path to monitor, Ext to look for etc.
if (iNumberMonitors > 0)
{
obMonitors = ReadMonitors(iNumberMonitors);
for (int iCounter = 0; iCounter < iNumberMonitors; iCounter++)
{
FileSystemWatcher fswCurrent = new FileSystemWatcher();
fswCurrent.Path = obMonitors[iCounter].strMonPath;
fswCurrent.EnableRaisingEvents = true;
fswCurrent.NotifyFilter = NotifyFilters.FileName;
fswCurrent.Filter = "*." + obMonitors[iCounter].strMonExt;
fswCurrent.Deleted += OnDelete;
fswMonitors.Add(fswCurrent);
}
}
In the 'OnDelete' Method that each FileSystemWatcher calls if the Delete event fires I need to know which of the FileSystemWatchers is calling it.
My question is how can I know which FileSystemMonitor in the List is calling the method?
Do you need anything else more than just checking sender in your eventHandler?
private void OnDelete(object sender, ...)
{
var watcher = (FileSystemWatcher) sender;
// probably list.IndexOf here if you really need an index
}
You can use a closure where you hook your "Delete event"
instead of passing the method itself:
fswCurrent.Deleted += OnDelete;
you pass something like
fswCurrent.Deleted += (sender, e) => OnDelete(sender, e, iCounter)
of course you will need to change the signature of OnDelete to take the additional Index.
After reading your comment you might don't need it though, as the other answers suggests.
The event handler has a sender parameter that is a reference to the FileSystemWatcher that raised the event.
private static void OnDeleted(object source, FileSystemEventArgs e)
{
FileSystemWatcher watcher = source as FileSystemWatcher;
if(watcher != null)
{
string deletedFile = e.FullPath;
//Update db with watcher and deletedFile
}
}
"sender" parameter in OnDelete method will point to the originator of the event
void OnDelete(object sender, EventArgs e)
{
var watcher = ((FileSystemWatcher) sender);
.....
}
Just cast the sender parameter of the event handler which you attach to the Deleted event.
For example:
class Program
{
static void Main(string[] args)
{
var watcher = new FileSystemWatcher { Path = #"c:\temp", Filter = "*.txt" };
watcher.Deleted += watcher_Deleted;
watcher.EnableRaisingEvents = true;
Console.ReadLine();
}
static void watcher_Deleted(object sender, FileSystemEventArgs e)
{
var watcher = sender as FileSystemWatcher;
}
}
May be check if each of these pooled objects might have an hashcode in it. Which should be unique.
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); }));
}