I found a well-written CSV parser/reader from this link written by Sebastien Lorion.
What I like of this CSV parser is that I could easily bind it to a DataGrid like:
using (CachedCsvReader csv = new
CachedCsvReader(new StreamReader(txtChosenFile.Text), true))
{
dataGridView1.DataSource = csv;
}
Which is what I need in my project because I want my users to preview it before committing it to the Database.
However, since it takes a while to load a file I need to provide at least a feedback to my user using a Progress Bar. Unfortunately, it is only one liner to get the CachedCsvReader class which make it difficult for me to link or update a progress bar as the reading of the csv file progresses.
If it is just a simple CsvReader class it would be easy to update my progress Bar like:
using (StreamReader sr = new StreamReader(openFileDialog1.FileName))
{
using (CsvReader csv = new
CsvReader(sr, true))
{
double progress = (double) sr.BaseStream.Position / (double) sr.BaseStream.Length;
progressBar1.Value = (int)progress*100;
}
}
However, since I am using a CachedCsvReader and it is only one liner (or two) to get upload the csv reader without having an information on the stream position and length then I could not update my progress bar.
So, what would be the best way to connect my progress bar to my CachedCsvReader?
Assuming you are initiating the read from a method called Open, following should work. It uses a timer control to poll the read position every 1 sec.:
private StreamReader sr;
public void Open()
{
Timer timer = new Timer();
timer.Interval = 1000;
timer.Tick += new EventHandler(timer_Tick);
timer.Enabled = true;
timer.Start();
using (this.sr = new StreamReader(openFileDialog1.FileName))
{
using (CachedCsvReader csv = new CachedCsvReader(sr, true))
{
dataGridView1.DataSource = csv;
}
}
timer.Stop();
timer.Enabled = false;
timer.Tick -= new EventHandler(timer_Tick);
}
void timer_Tick(object sender, EventArgs e)
{
if (null != this.sr)
{
double progress = (double)sr.BaseStream.Position / (double)sr.BaseStream.Length;
progressBar1.Value = (int)progress * 100;
}
}
Related
StreamReader sr = new StreamReader("C:/CR EZ Test/Log.txt"); //use with IF
private void timer2_Tick(object sender, EventArgs e)
{
if ((line = sr.ReadLine()) != null)
{
//FileStream fs = File.Open("C:/CR EZ Test/Log.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
//StreamReader sr = new StreamReader(fs); //use with While can't use with }else{
//while ((line = sr.ReadLine()) != null)
//{
string[] dataLog = line.Split(new[] { ',' }, StringSplitOptions.None);
mpa = (dataLog[1]);
ml = (dataLog[2]);
lph = (dataLog[3]);
elapsedTime = float.Parse(dataLog[4]) / 1000;
if (testStatus > 0) time = elapsedTime.ToString("0.0");
tb2.Value = int.Parse(dataLog[6]);
if (chart1.Series[0].Points.Count > tb1.Value && tb1.Value > 0)
{
chart1.Series[0].Points.RemoveAt(0);
chart1.Series[1].Points.RemoveAt(0);
}
chart1.Series[0].Points.AddXY(dataLog[5], int.Parse(dataLog[1]));
chart1.Series[1].Points.AddXY(dataLog[5], int.Parse(dataLog[6]));
//}
}
else
{
sr.DiscardBufferedData();
sr.BaseStream.Seek(0, SeekOrigin.Begin);
sr.BaseStream.Position = 0;
//sr.Close();
//alertTB.Text = "";
timer2.Enabled = false;
}
alertTB.ForeColor = Color.Red;
alertTB.Text = "Data Log Viewing In Progress";
}
The issue is I am reading a text file full of variables back through a GUI, like replaying a video. As the code is shown, it works and I can control the timer tick to change the replay speed. The issue is the file is in use, so I can't write to or delete the text while the file is in use, without closing it first. I would like to either be able to find a workaround of the Streamreader, or use the Filestream to Streamreader code that will allow me to edit the file while it is in use. The issue there is, I can't figure out how to make it work with the timer, it just reads the entire file very quickly. Any help or ideas are greatly appreciated.
The issue here is how to have the commented out code to:
read a line of the text file,
have the timer to tick
then read the next line of the text file, and so on. Obviously handling the data as it arrives.
Opening a file while it is in use
I think what you are looking for is FileStream with FileShare.ReadWrite for the instance of your StreamReader (not the instance you have commented out),
var fs = new FileStream("C:\foo.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var sr = new StreamReader(fs);
Setting the position of the stream
It also seems like based on your comments, you are having trouble with positioning the stream, this is how you could do that...
fs.Position = 0; // note this is the FileStream not the StreamReader!
// alternatively, you could use Seek
Difference between sequential and random access
Lastly, you might want to take a look below to see the difference between sequential and random access
A Potential Solution
Here is a class called FileMonitor that will check the file and update the list whenever the file is changed / updated.
I understand that you want a timer to poll the data in the text file, but in case the timer is very fast, I have optimized the FileMonitor to watch the file for changes and only extract when there is a change.
Please note that this only continues to read where it was left off, based on the position of the stream. So, it will not work if lines are deleted or modified prior to getting "extracted". This means it only functions based on your requirements and is not improved to handle a lot of other scenarios, but it should adequately cover your requirements.
public class FileMonitor : IDisposable
{
private readonly FileStream _file;
private readonly StreamReader _reader;
private long _position;
private List<string> _lines;
public FileMonitor(string file)
{
if (String.IsNullOrEmpty(nameof(file))) throw new ArgumentNullException(nameof(file));
_lines = new List<string>();
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = Path.GetDirectoryName(file);
watcher.Filter = Path.GetFileName(file);
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Changed += new FileSystemEventHandler(OnChanged);
//watcher.Created += new FileSystemEventHandler(OnCreated);
//watcher.Deleted += new FileSystemEventHandler(OnDeleted);
//watcher.Renamed += new RenamedEventHandler(OnRenamed);
// begin watching.
watcher.EnableRaisingEvents = true;
// begin reading
_file = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
_reader = new StreamReader(_file);
_lines = ReadLines(_reader).ToList();
_position = _file.Position;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
List<string> update = ReadLines(_reader).ToList();
// fix to remove the immidate newline
if (update.Count() > 0 && String.IsNullOrEmpty(update[0])) update.RemoveAt(0);
_lines.AddRange(update);
_position = _file.Position;
// just for debugging, you should remove this
Console.WriteLine($"File: {e.FullPath} [{e.ChangeType}]");
}
public IEnumerable<string> Lines { get { return _lines; } }
public void Reset()
{
_file.Position = 0;
_position = _file.Position;
_lines.Clear();
}
private static IEnumerable<string> ReadLines(StreamReader reader)
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
public void Dispose()
{
_reader.Dispose();
_file.Dispose();
}
}
Here is how you could use it with your timer
private IEnumerable<string> _lines; // holds all the lines "extracted"
void Main()
{
string file = #"C:\Data\foo.txt";
using (var timer = new System.Timers.Timer())
{
timer.Interval = 2000; // 2 second interval
timer.Elapsed += OnTimedEvent; // attach delegate
timer.Enabled = true; // start the timer
// open the file
using (var monitor = new FileMonitor(file))
{
_lines = monitor.Lines;
// loop forever, remove this
while (true) { }
}
}
}
public void OnTimedEvent(object sender, EventArgs e)
{
// just for debugging, you should remove this
Console.WriteLine($"current count: {_lines.Count()}");
}
If it isn't clear, the data extracted is held in a list of strings. Above, you can grab the "extracted" data from the monitor using the monitor.Line property.
A Proven Working Solution
string line;
if (!File.Exists(logFile))
{
viewLog.Text = "Play";
alertTB.ForeColor = Color.Red;
alertTB.Text = "File Does Not Exist | Log Data To Create File";
chart.Text = "Scope On";
}
if (File.Exists(logFile))
{
var lineCount = File.ReadLines(logFile).Count();//read text file line count to establish length for array
if (lineCount < 2)
{
viewLog.Text = "Play";
alertTB.ForeColor = Color.Red;
alertTB.Text = "File Exists | No Data Has Been Recorded";
chart.Text = "Scope On";
}
if (counter < lineCount && lineCount > 0)//if counter is less than lineCount keep reading lines
{
line = File.ReadAllLines(logFile).Skip(counter).Take(lineCount).First();
string[] dataLog = line.Split(new[] { ',' }, StringSplitOptions.None);
//-----------------------------------------Handling my data
counter++;
}
else
{
counter = 0;
timer2.Enabled = false;
}
}
This is the fix I arrived at, it allows editing the file or deleting the contents of the file. I get the line count before trying to load the file. I then use the counter to iterate through the lines. I can change the delay between the next line read based upon the timer tick interval, pause it, or stop it.
In my C# WinForms app, I have the MainForm in which I am using a BackgroundWorker to read a large Text file.
I am also using a second Form to display a Marquee ProgressBar to inform user that they must wait until file has been completely read.
The problem I am having is the Form with the progressbar (SimpleProgressBar) is frozen until the file is read. Where the BW should be on a separate thread to not let this happen.
I left the code that reads the file as it may be relevant to my problem. However all the code actually works its just that the Form to display the ProgressBar is frozen.
SimpleProgressBar.cs (Form)
//Simple Progress Bar set to Marquee
public partial class SimpleProgressBar : Form
{
public SimpleProgressBar()
{
InitializeComponent();
//ProgressBar is setup in the designer.
/*
System.Windows.Forms.ProgressBar progressBar1;
this.progressBar1.Location = new System.Drawing.Point(16, 65);
this.progressBar1.Name = "progressBar1";
this.progressBar1.Size = new System.Drawing.Size(350, 23);
this.progressBar1.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressBar1.TabIndex = 1;
*/
}
}
MainForm.cs (Form)
//Class variable
private SimpleProgressBar wait = new SimpleProgressBar();
private void generatePreview()
{
//Setup BW Thread
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += worker_DoWork;
worker.ProgressChanged += worker_ProgressChanged;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
//Start procesing file
worker.RunWorkerAsync();
//Show the dialog
wait.ShowDialog(); //perhaps .Show() would be better ?
}
//Once completed put the text into the textbox
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
textBox1.Text = ((StringBuilder)e.Result).ToString();
}
//Report progress here
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = (BackgroundWorker)sender;
int bufferSize = 1024;
var sb = new StringBuilder();
var buffer = new Char[bufferSize];
var length = 0L;
var totalRead = 0L;
var count = bufferSize;
using (var sr = new StreamReader("c:\200mb_text_file.txt"))
{
if (bw.CancellationPending)
{
}
else
{
length = sr.BaseStream.Length;
while (count > 0)
{
count = sr.Read(buffer, 0, bufferSize);
sb.Append(buffer, 0, count);
totalRead += count;
}
}
}
e.Result = sb;
}
UPDATE
So essentially I wanted to create a generic 2nd Form to use a a progress indicator that can be used for multiple purposes.
However it looks like that the BW MUST be on the same thread that hold the UI elements that need updating.
ShowDialog shows a modal dialog that you'll have to explicitly close. When you call ShowDialog, the program will not continue to execute beyond that point. Your background worker's completion event will have to close the dialog.
In addition, you're reading 1,024 bytes at a time and then calling the progress event. Every call to the progress event requires marshaling to the UI thread. It takes approximately zero time to read 1,024 bytes, which means that the progress event is being called continually, which in turn means that the UI thread is nearly 100% occupied with your progress update.
If you really need to report progress as the thing is loading, then use a larger buffer. You'll get better read performance anyway with a 64 Kilobyte buffer. That is:
int bufferSize = 65536;
But your files must be huge. You should be able to read at least 50 megabytes per second unless you have a really slow disk or you're reading over a slow network.
Progress Bar needs to be updated in another Thread.Updating the progress bar from the UI thread will cause freezing issues. Just put the code to update the progres bar in the Backgroudnworker's DOWork Method rather than reporting it.
void worker_DoWork(object sender, DoWorkEventArgs e)
{
stuffdone()
progressBar1.PerformStep();
}
Before using this you will need to set Form.CheckForIllegalCrossThreadCalls = false; so that BW can access the UI
I have a simple one-threaded windows forms .NET 4.5 app where user listens to spoken words (wav files) and then selects the correct picture that represents the word.
The problem is that the clip will sometimes (very rarely - about 1% of the time and completelly at random) not play...
This is the method for playing clips:
public static void PlayWordAudio(Word word, AxWMPLib.AxWindowsMediaPlayer player)
{
string tempFile = Path.GetTempFileName() + ".wav";
MemoryStream stream = new MemoryStream(word.Audio);
using (Stream fileStream = File.OpenWrite(tempFile))
{
stream.WriteTo(fileStream);
}
player.URL = tempFile;
File.Delete(tempFile);
}
Can someone please suggest a solution to this problem? Maybe I shouldn't delete the file at the end of the method? But then temp files would pile up...
I am on Windows 7...
I guess the file is being deleted quicker than it can get played.
Can you try this in stead of File.Delete(tempFile); utilizing the PlayStateChange event
player.PlayStateChange += (snd, psce) => {
switch (psce.newState)
{
case 1: // Stopped (maybe use 12 => Last )
File.Delete(tempFile);
break;
default:
Debug.WriteLine(psce.newState);
break;
}
};
You might have to unsubscribe the event if you keep the player object around a long time.
It seems that I solved the problem... it was in fact the deletion of file that caused this...
solution:
public static void PlayWordAudio(Word word, AxWMPLib.AxWindowsMediaPlayer player)
{
string tempFile = Path.GetTempFileName() + ".wav";
MemoryStream stream = new MemoryStream(word.Audio);
using (Stream fileStream = File.OpenWrite(tempFile))
{
stream.WriteTo(fileStream);
}
player.URL = tempFile;
RunDelayed(5000, File.Delete, tempFile); //if we delete file immediately then clip sometimes would not be played
}
public delegate void DelayedFuncion(string param);
public static void RunDelayed(int delay, DelayedFuncion function, string param = null)
{
System.Windows.Forms.Timer timer = new System.Windows.Forms.Timer();
DelayedArgs args = new DelayedArgs() { delayedFunction = function, param = param };
timer.Tag = args;
timer.Tick += TimerElapsed;
timer.Interval = delay;
timer.Start();
}
private static void TimerElapsed(object sender, EventArgs e)
{
System.Windows.Forms.Timer timer = sender as System.Windows.Forms.Timer;
timer.Stop();
DelayedArgs args = timer.Tag as DelayedArgs;
args.delayedFunction(args.param);
}
class DelayedArgs
{
public Util.DelayedFuncion delayedFunction;
public string param;
}
Here is the architecture of my app. I have a Perl script that is called using the method below on click of a button.
ProcessStartInfo psStartInfo = new ProcessStartInfo("perl.exe");
psStartInfo.Arguments = paramStr;
psStartInfo.UseShellExecute = false;
psStartInfo.RedirectStandardOutput = true;
psStartInfo.RedirectStandardError = true;
psStartInfo.CreateNoWindow = false;
ps.StartInfo = psStartInfo;
ps.Start();
string os = ps.StandardOutput.ReadToEnd();
The above code executes successfully. There is a text file which is generated using the Perl script I fired in the above mentioned code. I have to read that file and show everything in that in my WPF textbox. That Perl file is updated after every 5 to 10 secs. I use the below mentioned code to read the file and show it in my textbox.
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 30);
dispatcherTimer.Start();
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
txtExecLog.Text = "";
if (File.Exists("C:\\FlashAuto\\Execution_Logs\\log.txt"))
{
File.Copy("C:\\FlashAuto\\Execution_Logs\\log.txt", "C:\\FlashAuto\\Temp\\log.txt", true);
TextReader readLogs = new StreamReader("C:\\FlashAuto\\Temp\\log.txt");
string line = readLogs.ReadLine();
while (line != null)
{
txtExecLog.Text += "\n" + line;
line = readLogs.ReadLine();
txtExecLog.ScrollToEnd();
}
CountLines = txtExecLog.LineCount - 1;
readLogs.Close();
// Forcing the CommandManager to raise the RequerySuggested event
txtExecLog.ScrollToEnd();
CommandManager.InvalidateRequerySuggested();
readLogs.Dispose();
}
else
{
txtExecLog.Text += "log file not found at: "+DateTime.Now.ToString();
}
}
Here is the problem:
Reading and writing to the textbox is successfully done but there is a huge chunk of memory that is eaten up by my app. If I disable the logging, memory usage is optimal.
I am making an audio recorded using NAudio in C# and i need to remove the stop button used and simply stop the recording on its own after some time delay.
The code for the record event is
private void cmbRecord_Click(object sender, EventArgs e)
{
outputFilename = "file address";
waveInStream = new WaveIn(44100,2);
writer = new WaveFileWriter(outputFilename, waveInStream.WaveFormat);
waveInStream.DataAvailable += new EventHandler<WaveInEventArgs>(waveInStream_DataAvailable);
waveInStream.StartRecording();
// Just controling the objects on the screen.
cmbRecord.Enabled = false;
cmbStop.Enabled = true;
}
void waveInStream_DataAvailable(object sender, WaveInEventArgs e)
{
writer.WriteData(e.Buffer, 0, e.BytesRecorded);
int secondsRecorded = (int)(writer.Length / writer.WaveFormat.AverageBytesPerSecond);
}
The stop button is given as
private void cmbStop_Click(object sender, EventArgs e)
{
waveInStream.StopRecording();
waveInStream.Dispose();
waveInStream = null;
writer.Close();
writer = null;
cmbRecord.Enabled = true;
cmbStop.Enabled = false;
}
I need to stop the recording automatically inside the cmbRecord_Click event.
Thanks in advance.
use a Timer, set the Interval and copy the code in cmbStop_Click event over to timer's OnTick event. Enable the timer in the mbRecord_Click event and & remember to disable the timer in cmbStop_Click event
Edit:
Create a new timer and set its value
//put this line in your form class level
System.Windows.Forms.Timer mytimer=new System.Windows.Forms.Timer(); //create a new Timer
//put these two into your form constructor just after InitializeComponent();
mytimer.Interval=1000; //set the interval to 1 second.
mytimer.Tick += new EventHandler(mytimer_Tick);
Enable the timer in the mbRecord_Click event
private void cmbRecord_Click(object sender, EventArgs e)
{
outputFilename = "file address";
waveInStream = new WaveIn(44100,2);
writer = new WaveFileWriter(outputFilename, waveInStream.WaveFormat);
waveInStream.DataAvailable += new EventHandler<WaveInEventArgs>(waveInStream_DataAvailable);
waveInStream.StartRecording();
// Just controling the objects on the screen.
cmbRecord.Enabled = false;
cmbStop.Enabled = true;
//Enable the timer to fire
mytimer.Enabled = true;
}
Stop recording after 1 second..
void mytimer_Tick(object sender, EventArgs e)
{
waveInStream.StopRecording();
waveInStream.Dispose();
waveInStream = null;
writer.Close();
writer = null;
cmbRecord.Enabled = true;
cmbStop.Enabled = false;
//disable the timer here so it won't fire again...
mytimer.Enabled = false;
}
One thing you may want to bear in mind - there will be a DataAvailable callback after the call to StopRecording (or during, depending on the callback model used), so you might want to delay closing the WaveFileWriter until you have written everything.
Have a look at the VoiceRecorder sample project which uses NAudio and stops recording after 60 seconds. I explain in this article how recording is automatically stopped.
long maxFileLength = this.recordingFormat.AverageBytesPerSecond * 60;
int toWrite = (int)Math.Min(maxFileLength - writer.Length, bytesRecorded);
if (toWrite > 0)
writer.WriteData(buffer, 0, bytesRecorded);
else
Stop();