How to check if a recursive method has ended? - c#

I've just finished making this recursive method:
/// <summary>
/// Recursively process a given directory and add its file to Library.xml
/// </summary>
/// <param name="sourceDir">Source directory</param>
public void ProcessDir(string sourceDir)
{
string[] fileEntries = Directory.GetFiles(sourceDir, "*.mp3");
foreach (string fileName in fileEntries)
{
Song newSong = new Song();
newSong.ArtistName = "test artist";
newSong.AlbumName = "test album";
newSong.Name = "test song title";
newSong.Length = 1234;
newSong.FileName = fileName;
songsCollection.Songs.Add(newSong);
}
string[] subdirEntries = Directory.GetDirectories(sourceDir);
foreach (string subdir in subdirEntries)
{
if ((File.GetAttributes(subdir) & FileAttributes.ReparsePoint) != FileAttributes.ReparsePoint)
{
ProcessDir(subdir);
}
}
}
Everything is working as expected, the only problem I'm having is: How do I know when this method finishes execution? Is there something made for that very purpose in .NET?

There's nothing special in .NET that tells you this... Basically, the first call to ProcessDir will return after the recursion has ended.

Well, you could always put a line of code indicating the end of execution after your initial ProcessDir call:
ProcessDir("MyDir");
Console.WriteLine("Done!");

You could try using a global variable to keep track of it.
private int _processDirTrack = 0;
public void ProcessDir(string sourceDir)
{
_processDirTrack++; // Increment at the start of each
string[] fileEntries = Directory.GetFiles(sourceDir, "*.mp3");
foreach (string fileName in fileEntries)
{
Song newSong = new Song();
newSong.ArtistName = "test artist";
newSong.AlbumName = "test album";
newSong.Name = "test song title";
newSong.Length = 1234;
newSong.FileName = fileName;
songsCollection.Songs.Add(newSong);
}
string[] subdirEntries = Directory.GetDirectories(sourceDir);
foreach (string subdir in subdirEntries)
{
if ((File.GetAttributes(subdir) & FileAttributes.ReparsePoint) != FileAttributes.ReparsePoint)
{
ProcessDir(subdir);
}
}
_processDirTrack--; // Decrement after the recursion. Fall through means it got to
// the end of a branch
if(_processDirTrack == 0)
{
Console.WriteLine("I've finished with all of them.");
}
}

I'm assuming the (albeit correct) answer RQDQ provided isn't the one you are looking for?
In case you have a long running task of which you want to check how far it is along you can use a BackgroundWorker.
Ofcourse this background worker doesn't magically know how much more files to process, so you have to call ReportProgress whenever you can give an estimate of how far along you are.
In order to try to estimate how much longer the processing will take, you could try the following:
Check for total disk space the folder occupies, and keep track of much you already processed.
Check for amount of files you have to process vs. how much you still have to process.

If you're wanting to know when it ends to signal/start another process in your application, you could raise an event. This may be over kill, but hey, it's another way to look at it if it suits your need. You just need to add an event to the class where ProcessDir() is a member.
private int _processDirTrack = 0;
public event EventHandler DirProcessingCompleted;
At the end of your method you would raise your event like so
DirProcessingCompleted(this, new EventArgs());
You subscribe to these events with an eventhandler somewhere else in your code
myClass.DirProcessingCompleted += new EventHandler(ProcessingComplete_Handler);
Note: you don't have to subscribe to an event in this manner; you could also subscribe with a delegate or lambda expression instead.
To wrap it all up you create your method that is called whenever an event is raised by the object.
private void ProcessingComplete_Handler(object sender, EventArgs e)
{
// perform other operations here that are desirable when the event is raised ...
}

I'm guessing what you're trying to figure out is how to know that you've reached the deepest level of recursion that is going to occur.
In this case, you'll know that you're in the last recursive call of the function when subdirEntries is empty, since the function will no longer recurse at that point. That's the best you can do, there's no universal answer as to how to know when a function will cease to recurse. It's entirely dependent upon what the conditions to recurse are.
Edit: Wanted to clarify. This will check each time you end a single chain of recursions. Considering your code can recurse multiple times per call, my solution will only signify the end of a single chain of recursions. In the case of recursively navigating a tree, this will occur at every leaf node, not at the deepest level of recursion.

Related

How can I make GUI responsive during long iteration?

To begin with, I'm relatively new to programming. I went through some introductory C# training for my new job, and it's the first language I've worked with.
I recently had a business problem that I decided to solve using C#, both to save time (I had hoped) and to learn more C# in the process. The business problem I mentioned was this: I had 600+ Word files that I needed to audit. For each document, I had to make sure that...
There was no text with strike-through anywhere in the document.
Track Changes was disabled.
There were no pending changes (as in changes that were made while
Track Changes was enabled and have yet to be accepted or
rejected).
There were no comments.
It would have been fastest to have my program iterate through all of the documents, making changes as it went along. But because of the nature of this assignment I wanted to make the changes manually, limiting the program's use to generating a list of files (out of the 600) where changes were necessary, and detailing what changes needed to be made for each of those files.
So, I have a button that calls up a FolderBrowserDialog.
private void AddFolderButtonClick(object sender, EventArgs e)
{
var folderBrowser = new FolderBrowserDialog();
if (folderBrowser.ShowDialog() != DialogResult.OK)
{
return;
}
this.progressBar1.Visible = true;
this.progressBar1.Style = ProgressBarStyle.Marquee;
this.Cursor = Cursors.WaitCursor;
var args = new List<string>(Directory.EnumerateDirectories(folderBrowser.SelectedPath));
// Get list of files in selected directory, adding to list of directories
args.AddRange(Directory.EnumerateFiles(folderBrowser.SelectedPath));
this.displayListBox.BeginUpdate();
foreach (string path in args)
{
if (File.Exists(path))
{
// This path is a file
this.ProcessFile(Path.GetFullPath(path));
}
else if (Directory.Exists(path))
{
// This path is a directory
this.ProcessDirectory((Path.GetFullPath(path)));
}
else
{
Console.WriteLine(Resources.Finder_Invalid_File_Or_Directory, path);
}
}
this.displayListBox.EndUpdate();
this.progressBar1.Visible = false;
this.progressBar1.Style = ProgressBarStyle.Continuous;
this.Cursor = Cursors.Default;
}
Together, the following two methods iterate through all subdirectories and files to create a full list of all files below the top level directory selected through the FolderBrowserDialog:
private void ProcessDirectory(string targetDirectory)
{
// Process the list of files found in the directory.
string[] fileEntries = Directory.GetFiles(targetDirectory);
foreach (string fileName in fileEntries)
{
this.ProcessFile(fileName);
}
// Recurse into subdirectories of this directory.
string[] subdirectoryEntries = Directory.GetDirectories(targetDirectory);
foreach (string subdirectory in subdirectoryEntries)
{
this.ProcessDirectory(subdirectory);
}
}
private void ProcessFile(string path)
{
Console.WriteLine(Resources.Finder_File_Processed, path);
string fileName = Path.GetFileName(path);
if (fileName == null || fileName.StartsWith(#"~$") || this.selectedFilesList.Contains(path))
{
return;
}
this.selectedFilesList.Add(path);
this.filePathsCountLabel.Text = (#"Count: " + this.selectedFilesList.Count);
this.displayListBox.Items.Add(path);
}
Once all this code has run, I get a full list of documents. I click a button and the program does what it's supposed to from here on out. Okay, cool. I mentioned before that half of the reason I chose to use C# to solve this was for the sake of learning. At this point I've got everything I need but what I really want to know is how can I implement threading to make the GUI responsive while the list of files is being generated? I've looked through several examples. They made sense. For some reason I just can't get my head around it for this application though. How can I make the whole process of processing subdirectories and files happen without locking up the GUI?
I believe what you need could be found here.
In short, to use a backgroundworker which does all the work on a separate thread thus prevents GUI freezes, first you instantiate BackgroundWorker and handle the DoWork event. Then you call RunWorkerAsync, optionally with an object argument.
As a skeleton code:
class myClass
{
static BackgroundWorker myBw = new BackgroundWorker();
static void Main()
{
myBw .DoWork += myBw_DoWork;
myBw .RunWorkerAsync ("an argument here");
Console.ReadLine();
}
static void myBw_DoWork (object sender, DoWorkEventArgs e)
{
// This is called on the separate thread, argument is called as e.Argument
// Perform heavy task...
}
}
You have to create a separate thread to process your work. Look at this if you are using .NET 4.0+ or this for older versions.
With Task, you can write
Task.Factory.StartNew(() => DoAction()
where DoAction is your function that starts to process data.
But do not forget to use Invoke, if you want to act with GUI from separate thread. For example, if you want to update some Label text from separate thread, you have to do this
label1.Invoke(() => label1.Text = "Some Text");

Silverlight C# - How to hold loop progress until an event completes?

I'm trying to loop through the text in a textbox by word in order to spellcheck it. I've split the contents of the textbox into an array, and loop through each word in the array and run it through the spellchecker. When a misspelling is found, I have a popup with a listbox inside it display so that you can choose the correction.
The issue that I'm having, is that it just loops through the whole array and only ends up showing the last correction that needs to be done.
How do I pause the loop so that it waits for a selection to be made and then resume?
Here's the code for the loop:
foreach(string checkedWord in articleWords)
{
bool success = _spellChecker.CheckWord(checkedWord);
List<string> suggest;
if (!success)
{
suggest = _spellChecker.GetSuggestions(checkedWord);
SpellChecklistBox.Items.Clear();
foreach (string s in suggest)
{
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = s });
}
SpellCheckerPopup.IsOpen = true;
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = " ----------------------" });
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = "Ignore" });
}
}
When the SpellCheckerPopup displays, I have an event trigger in the listbox on SelectionChange.
Basically, I need to pause the loop somehow, and then when the SelectionChange event does it's thing, have the loop resume.
Thanks in advance!
-Sootah
If I'm not misunderstanding, currently you are going to:
(1) Check each word in the loop
(2) Pause the loop when an error is found and pop up a suggestion window
(3) User select a suggestion word and resume the loop
I think it's better and easier if the solution is:
(1) Check the word from the first one
(2) Quit the check method with an error flag, and store the position in a variable, pop up a suggestion window
(3) User selects a suggestion word and when User has confirmed the suggestion(e.g. pressing OK on the suggestion window), start the CheckWordMethod again from the stored position
(4) Until step (2) quits with no error flag, which means all words are correct now (but make sure in the whole progress, users can only modify the words by your suggestion window)
#The Smartest: Your answer lead me in the correct direction; actually ended up learning a new datatype out of it! Never used a Queue before. (Which made it a HELL of a lot simpler than having to track where in the array I was at, as I first figured I thought I'd have to.. :)
Anyway, I'll accept your answer, but here's the code I ended up doing: (The actual replacing of the word in the textbox I've not implemented yet.)
private void btnSpelling_Click(object sender, RoutedEventArgs e)
{
SpellChecklistBox.Items.Clear();
string[] articleWordsArray = txtArticle.Text.Split(' ');
foreach (string word in articleWordsArray)
{
articleWords.Enqueue(word);
}
CorrectWord();
}
private void SpellChecklistBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SpellCheckerPopup.IsOpen = false;
}
private void SpellCheckerPopup_Closed(object sender, EventArgs e)
{
CorrectWord();
SpellChecklistBox.Items.Clear();
}
Queue<string> articleWords = new Queue<string>();
private void CorrectWord()
{
if (articleWords.Count() > 0)
{
string checkedWord = articleWords.Dequeue();
bool success = _spellChecker.CheckWord(checkedWord);
List<string> suggest;
if (!success)
{
suggest = _spellChecker.GetSuggestions(checkedWord);
foreach (string s in suggest)
{
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = s });
}
SpellCheckerPopup.IsOpen = true;
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = " ----------------------" });
SpellChecklistBox.Items.Add(new ListBoxItem() { Content = "Ignore" });
SpellCheckerPopup.IsOpen = true;
}
}
}
It's all pretty straight forward courtesy of the Queue datatype. When the spelling button gets clicked it loads the TextBox into an array, and then I loop through the array to enqueue the items into the articleWords queue, after which it calls CorrectWord(). CorrectWord() then loads the relevant list after dequeueing from articleWords, and on the PopUp. Closed event it clears the ListBox and calls CorrectWord() which will keep bringing back the PopUp until there are no more words to be corrected. :)

A little help needed catching events being raised inside a seperate thread

To begin, I'm working on a pretty high level file system where I need to be able to (with very pin point accuracy) identify and process files in a timely manner. With that said, I am working on a system that will be using the FileSystemWatcher. The whole problem with the watcher though is the fact that it tends to have issues when throwing events when there are large files involved.
To remedy this I'm working on an abstract class that can handle the files individualy once they are created by the file system watcher.
The current road block that I am running into is that my out of process validation on the file is throwing an event, I'm just having problems catching it.
public abstract class absFile
{
public delegate void FileAvailable(object sender, FileEventArgs e);
public event FileAvailable OnFileAvailable;
public void isAvailable()
{
// Create a new threaded instance of the AvailableCheck private void
// This method will be run out of process to allow other operations to continue.
Thread toAvailableCheck = new Thread(new ThreadStart(AvailableCheck));
// Once the threaded object is created, start it.
toAvailableCheck.Start();
}
private void AvailableCheck()
{
// Declaring the file stream toThisFile to be used later with the File.Open
FileStream toThisFile;
// Declaring and instantiating the attempt counter for the loop
int tiAttemptNumber = 0;
// Declaring the event args for returning the events that are
// used by this object.
FileEventArgs toFileEventArgs = new FileEventArgs();
do {
try
{
// Attempt to open the file. If this fails the try
// will interrupt the processing and it will be caught.
toThisFile = File.Open(this.FilePath, FileMode.Open);
// If the file.open method does not fail, the isFileAvailable
// property will be set to true by updating the mbIsAvailable
// private boolean.
mbIsAvailable = true;
// populate the file event args to send back
// the number of attempts made at the file and the pause lenght
toFileEventArgs.Attempts = tiAttemptNumber;
toFileEventArgs.Pause = this.AttemptPause / 1000;
// This event is called when the file is complete.
// The client application will be able to handle this event.
OnFileAvailable(this, toFileEventArgs);
// close and dispose of the filestream.
toThisFile.Close();
toThisFile.Dispose();
}
catch (Exception toException)
{
// Since the open failed, add 1 to the counter so that
// it will eventually time out.
tiAttemptNumber++;
// Set the isFileAvailable property to false. This property
// will default as false, but as a part of standard, make sure that
// if the open fails that the flag IS set to false by updating the
// mbIsAvailable private boolean.
mbIsAvailable = false;
// Put the thread to sleep for the ammount of time specified
// by the AttemptPause. This will give the file time to finish
// whatever process it is involved in.
Thread.Sleep(this.AttemptPause);
}
// Continue to make attempts until either the file is marked as available
// or the number of current attempts is the same as or greater than the
// AccessAttempts property.
} while (!this.isFileAvailable && this.AccessAttempts > tiAttemptNumber);
}
this is the code that I am running as you can see in the private void AvailableCheck, OnfileAvailable is the delegate called passing back this and the file event args.
now i have inherited this abstract class, and i need to be able to catch that event.
toWatcher.Created += new FileSystemEventHandler(OnCreated);
is called in the main and farther down the code is the following method
private void OnCreated(object source, FileSystemEventArgs e)
{
lstStatus.Invoke(new MethodInvoker(delegate {
lstStatus.Items.Add(DateTime.Now.ToString("g") + " - " + e.Name + " - File Created Event Detected for: " + e.FullPath);
lstStatus.TopIndex = lstStatus.Items.Count - 1;
tgtFile ThisFile = new tgtFile(e.FullPath);
lstStatus.Items.Add(DateTime.Now.ToString("g") + " - " + e.Name + " - Creating tgtFile Object");
}));
}
The instatiation of the tgtFile object is passed the path which makes its way down the pike to the is available method.
as you can see the chance exists for the OnFileAvailable event to be fired from the tgtFile object.
Also as you can see, the the possibility for multiple tgtFile objects to exist in memory at the same time is there as well based on the threading design of the filesystemwatcher.
in my main application then i want to be able to do something like:
public tgtFile ThisFile;
ThisFile.OnFileAvailable += new EventHandler(OnFileAvailable);
but the EventHandler errors out, and that is where I am stuck.
If it is giving you a compiler error, it's probably because your "OnFileAvailable" method referenced in this line (from the bottom of your post):
ThisFile.OnFileAvailable += new EventHandler(OnFileAvailable);
is not expecting an EventHandler - it's expecting a FileAvailable delegate. Change it to:
ThisFile.OnFileAvailable += new absFile.FileAvailable(OnFileAvailable);
//note that this can also just be ThisFile.OnFileAvailable += OnFileAvailable;
and make sure OnFileAvailable looks like this:
public void OnFileAvailable(object sender, FileEventArgs e)
{
//...
}
First things first... Ensure the event is subscribed, before calling it. Wrap the event call in an IF statement:
if (OnFileAvailable != null)
OnFileAvailable(this, toFileEventArgs);

Is is possible to know for sure if a WebBrowser is navigating or not?

I'm trying to find a way for my program to know when a WebBrowser is navigating and when is not. This is because the program will interact with the loaded document via JavaScript that will be injected in the document. I don't have any other way to know when it starts navigating than handling the Navigating event since is not my program but the user who will navigate by interacting with the document. But then, when DocumentCompleted occurs doesn't necessarily mean that it have finished navigating. I've been googling a lot and found two pseudo-solutions:
Check for WebBrowser's ReadyState property in the DocumentCompleted event. The problem with this is that if not the document but a frame in the document loads, the ReadyState will be Completed even if the main document is not completed.
To prevent this, they advise to see if the Url parameter passed to DocumentCompleted matches the Url of the WebBrowser. This way I would know that DocumentCompleted is not being invoked by some other frame in the document.
The problem with 2 is that, as I said, the only way I have to know when a page is navigating is by handling the Navigating (or Navigated) event. So if, for instance, I'm in Google Maps and click Search, Navigating will be called, but just a frame is navigating; not the whole page (on the specific Google case, I could use the TargetFrameName property of WebBrowserNavigatingEventArgs to check if it's a frame the one that is navigating, but frames doesn't always have names). So after that, DocumentCompleted will be called, but not with the same Url as my WebBrowsers Url property because it was just a frame the one that navigated, so my program would thing that it's still navigating, forever.
Adding up calls to Navigating and subtracting calls to DocumentCompleted wont work either. They are not always the same. I haven't find a solution to this problem for months already; I've been using solutions 1 and 2 and hoping they will work for most cases. My plan was to use a timer in case some web page has errors or something but I don't think Google Maps has any errors. I could still use it but the only uglier solution would be to burn up my PC.
Edit: So far, this is the closest I've got to a solution:
partial class SafeWebBrowser
{
private class SafeNavigationManager : INotifyPropertyChanged
{
private SafeWebBrowser Parent;
private bool _IsSafeNavigating = false;
private int AccumulatedNavigations = 0;
private bool NavigatingCalled = false;
public event PropertyChangedEventHandler PropertyChanged;
public bool IsSafeNavigating
{
get { return _IsSafeNavigating; }
private set { SetIsSafeNavigating(value); }
}
public SafeNavigationManager(SafeWebBrowser parent)
{
Parent = parent;
}
private void SetIsSafeNavigating(bool value)
{
if (_IsSafeNavigating != value)
{
_IsSafeNavigating = value;
OnPropertyChanged(new PropertyChangedEventArgs("IsSafeNavigating"));
}
}
private void UpdateIsSafeNavigating()
{
IsSafeNavigating = (AccumulatedNavigations != 0) || (NavigatingCalled == true);
}
private bool IsMainFrameCompleted(WebBrowserDocumentCompletedEventArgs e)
{
return Parent.ReadyState == WebBrowserReadyState.Complete && e.Url == Parent.Url;
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, e);
}
public void OnNavigating(WebBrowserNavigatingEventArgs e)
{
if (!e.Cancel) NavigatingCalled = true;
UpdateIsSafeNavigating();
}
public void OnNavigated(WebBrowserNavigatedEventArgs e)
{
NavigatingCalled = false;
AccumulatedNavigations++;
UpdateIsSafeNavigating();
}
public void OnDocumentCompleted(WebBrowserDocumentCompletedEventArgs e)
{
NavigatingCalled = false;
AccumulatedNavigations--;
if (AccumulatedNavigations < 0) AccumulatedNavigations = 0;
if (IsMainFrameCompleted(e)) AccumulatedNavigations = 0;
UpdateIsSafeNavigating();
}
}
}
SafeWebBrowser inherits WebBrowser. The methods OnNavigating, OnNavigated and OnDocumentCompleted are called on the corresponding WebBrowser overridden methods. The property IsSafeNavigating is the one that would let me know if it's navigating or not.
Waiting till the document has loaded is a difficult problem, but you want to continually check for .ReadyState and .Busy (dont forget that). I will give you some general information you will need, then I will answer your specific question at the end.
BTW, NC = NavigateComplete and DC = DocumentComplete.
Also, if the page you are waiting for has frames, you need to get a ref to them and check their .busy and .readystate as well, and if the frames are nested, the nested frames .readystate and .busy as well, so you need to write a function that recursively retreives those references.
Now, regardless of how many frames it has, first fired NC event is always the top document, and last fired DC event is always that of the top (parent) document as well.
So you should check to see if its the first call and the pDisp Is WebBrowser1.object (literally thats what you type in your if statement) then you know its the NC for top level document, then you wait for this same object to appear in a DC event, so save the pDisp to a global variable, and wait until a DC is run and that DC's pDisp is equal to the Global pDisp you've saved during the first NC event (as in, the pDisp that you saved globally in the first NC event that fired). So once you know that pDisp was returned in a DC, you know the entire document is finished loading.
This will improve your currect method, however, to make it more fool proof, you need to do the frames checking as well, since even if you did all of the above, it's over 90% good but not 100% fool proof, need to do more for this.
In order to do successful NC/DC counting in a meaningful way (it is possible, trust me) you need to save the pDisp of each NC in an array or a collection, if and only if it doesn't already exist in that array/collection. The key to making this work is checking for the duplicate NC pDisp, and not adding it if it exists. Because what happens is, NC fires with a particular URL, then a server-side redirect or URL change occurs and when this happens, the NC is fired again, BUT it happens with the same pDisp object that was used for the old URL. So the same pDisp object is sent to the second NC event now occuring for the second time with a new URL but still all being done with the exact same pDisp object.
Now, because you have a count of all unique NC pDisp objects, you can (one by one) remove them as each DC event occurs, by doing the typical If pDisp Is pDispArray(i) Then (this is in VB) comparison wrapped in a For Loop, and for each one taken off, your array count will get closer to 0. This is the accurate way to do it, however, this alone is not enough, as another NC/DC pair can occur after your count reaches 0. Also, you got to remember to do the exact same For Loop pDisp checking in the NavigateError event as you do in the DC event, because when a navigation error occurs, a NavigateError event is fired INSTEAD of the DC event.
I know this was a lot to take, but it took me years of having to deal with this dreaded control to figure these things out, I've got other code & methods if you need, but some of the stuff I mentioned here in relation to WB Navigation being truely ready, haven't been published online before, so I really hope you find them useful and let me know how you go. Also, if you want/need clarification on some of this let me know, unfortunately, the above isn't everything if you want to be 100% sure that the webpage is done loading, cheers.
PS: Also, forgot to mention, relying on URL's to do any sort of counting is inaccurate and a very bad idea because several frames can have the same URL - as an example, the www.microsoft.com website does this, there are like 3 frames or so calling MS's main site that you see in the address bar. Don't use URL's for any counting method.
First I've converted the document to XML and then used my magic method:
nodeXML = HtmlToXml.ConvertToXmlDocument((IHTMLDocument2)htmlDoc.DomDocument);
if (ExitWait(false))
return false;
conversion:
public static XmlNode ConvertToXmlDocument(IHTMLDocument2 doc2)
{
XmlDocument xmlDoc = new XmlDocument();
IHTMLDOMNode htmlNodeHTML = null;
XmlNode xmlNodeHTML = null;
try
{
htmlNodeHTML = (IHTMLDOMNode)((IHTMLDocument3)doc2).documentElement;
xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration("1.0", ""/*((IHTMLDocument2)htmlDoc.DomDocument).charset*/, ""));
xmlNodeHTML = xmlDoc.CreateElement("html"); // create root node
xmlDoc.AppendChild(xmlNodeHTML);
CopyNodes(xmlDoc, xmlNodeHTML, htmlNodeHTML);
}
catch (Exception err)
{
Utils.WriteLog(err, "Html2Xml.ConvertToXmlDocument");
}
magic method:
private bool ExitWait(bool bDelay)
{
if (m_bStopped)
return true;
if (bDelay)
{
DateTime now = DateTime.Now;
DateTime later = DateTime.Now;
TimeSpan difT = (later - now);
while (difT.TotalMilliseconds < MainDef.IE_PARSER_DELAY)
{
Application.DoEvents();
System.Threading.Thread.Sleep(10);
later = DateTime.Now;
difT = later - now;
if (m_bStopped)
return true;
}
}
return m_bStopped;
}
where m_bStopped is false by default, IE_PARSER_DELAY is a timeout value.
I hope this helps.

Using a Background Worker - Update a ProgressBar on the progress of a Recursive Method

Below is a method that I want to ship off into a background worker but I am struggling how to do it based on how created my method. As you can it doesn't return anything which is ok but it expects a directoryInfo object everytime it is recalled.
private void getSizeForTargetDirectory(DirectoryInfo dtar)
{
// generate a collection of objects. files comes first and then directories.
foreach (Object item in collection )
{
if (item == file)
{
track the size of the files as you encounter.
}
else if (item == directory)
{
// found a new directory, recall the method. !!!
}
}
}
This is my first time using a background worker so I'm a little stuck, I tried implementing something thanks to the help found here but got stuck when I realised my method was recursive.
How do I display progress during a busy loop?
I implemented a doWork event handler method but noticed that i needed to somehow recall the method if I had more files and folders to process on lower sub levels.
I have a simple button click event handler that calls my 'getSizeForTargetDirectory()' method when the current selected node is a directory.
private void retrieveInfoButton_Click(object sender, EventArgs e)
{
// check to see if the path is valid
// reset the labels and textfields.
string fullPath = treeDrives.SelectedNode.FullPath;
string sNodesName = treeDrives.SelectedNode.Text;
if (directory) // Enter here if its a directory.
{
string parentPath = treeDrives.SelectedNode.Parent.FullPath;
DirectoryInfo[] dirArray = populateFoldersArray(parentPath);
for (int i = 0; i < dirArray.Length; i++)
{
if (dirArray[i].Name == sNodesName)
{
getSizeForTargetDirectory(dirArray[i]);
// do work !
Hopefully that explains what I am trying to do and how I am doing it. Question is how can i use the report progress feature of the background worker class when the bulk of the work I am trying to ship is coming from a recursive method.
Through early testing I noticed that my getSize method was incredibly efficient after a few tweaks and reported size information for the current supplied folder very quickley but then again I use quite a powerful dev machine so this may not be true for all users.
Thanks For Reading, Hope someone can help !!!
I think it is much simpler to use the built-in methods on either Directory or DirectoryInfo to obtain all directories, or files, using the recursive search option:
public partial class Form1 : Form
{
private Action<float> updateProgMethod;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
updateProgMethod = UpdateProgress;
}
private void GetDirectorySizeAsync(string path)
{
backgroundWorker.RunWorkerAsync(path);
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
DirectoryInfo di = new DirectoryInfo((string)e.Argument);
di.GetTotalSize(ProgressCallback);
}
// Takes callbacks from the GetTotalSize() method
private void ProgressCallback(float p)
{
// Invokes update progress bar on GUI thread:
this.BeginInvoke(updateProgMethod, new object[] { p });
}
// Actually updates the progress bar:
private void UpdateProgress(float p)
{
progressBar.Value = (int)(p * (progressBar.Maximum - progressBar.Minimum)) + progressBar.Minimum;
}
}
public static class IOExtensions
{
public static long GetTotalSize(this DirectoryInfo directory, Action<float> progressCallback)
{
FileInfo[] files = directory.GetFiles("*.*", SearchOption.AllDirectories);
long sum = 0;
int countDown = 0;
for (int i = 0; i < files.Length; i++)
{
sum += files[i].Length;
countDown--;
if (progressCallback != null && countDown <= 0)
{
countDown = 100;
progressCallback((float)i / files.Length);
}
}
return sum;
}
}
It's hard to guess progress without knowing the number of files or folders first!
EDIT: I've improved the code a little.
If, when you call a method, you don't know how long the method is going to take or how many discrete steps are going to be involved, then there is no way to display a progress bar while the method is executing.
In my opinion, the purpose of a progress bar is not to give reliable information about when a task is going to be completed. Rather, the purpose is to keep the user from freaking out and cancelling the whole operation because they think your program has locked up and isn't doing anything at all.
Since you're iterating through directories and sub-directories, a simpler approach here might be to just display the current directory in a Label. This would give the user a relaxing sense that things are happening, and if the directories are all ordered alphabetically, they can even gauge for themselves the overall progress of the operation.
I would report how far you have gotten since you don't know the goal until you get there. I would do it once per invocation. Perhaps # of files and # of directories seen so far.

Categories

Resources