[RESOLVED] How to UseWaitCursor correctly - Page 3
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Page 3 of 3 FirstFirst 123
Results 31 to 40 of 40

Thread: [RESOLVED] How to UseWaitCursor correctly

  1. #31
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,585

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Alex F View Post
    How progress information is sent to the main thread?
    Like that:

    Code:
    System::Void Form1::DoCountWorker(FileStream ^fsInfile,BackgroundWorker ^bw,DoWorkEventArgs ^e)
    {
      BinaryReader brInfile(fsInfile);
      const int nBufferSize=512;
      array<Byte> ^abBuffer;
      int nPercent=0,nLastPercent=-1;
    
      do {
        abBuffer=brInfile.ReadBytes(nBufferSize);
        for (int i=0;i<abBuffer->Length;++i)
          ++aulCounts[abBuffer[i]];
        if ((nPercent=(100*fsInfile->Position)/fsInfile->Length)!=nLastPercent) {
          bw->ReportProgress(nPercent);
          nLastPercent=nPercent;
        }
      } while (!bw->CancellationPending && abBuffer->Length==nBufferSize);
      brInfile.Close();
      if (bw->CancellationPending) e->Cancel=true;
    }
    
    System::Void Form1::bwDoCount_ProgressChanged(System::Object^  sender, System::ComponentModel::ProgressChangedEventArgs^  e)
    {
      progress->Text=String::Format("Processing file... {0}&#37;",e->ProgressPercentage);
      progress->progressBar1->Value=e->ProgressPercentage;
    }
    Where progress is the progress dialog form object.

    By debugging I found out that the bwDoCount_ProgressChanged() event handler runs in the context of the GUI thread, so IMO inter-thread communication is done internally by BackgroundWorker.

    But the progress display doesn't cause the deadlock anyway, the problem was only exposed when I clicked the Cancel button. Cancellation is handled by this simple event handler in the progress dialog's form class:

    Code:
    System::Void Progress::bnCancel_Click(System::Object^  sender, System::EventArgs^  e)
    {
      bwParentWorker->CancelAsync();
    }
    Where bwParentWorker is a tracking handle to the BackgroundWorker object that gets passed (i.e. assigned) from the main form object to the progress dialog's form object.

    Closing the progress dialog in case I did not click the cancel button is donne by the completion handler (and works):

    Code:
    System::Void Form1::bwDoCount_RunWorkerCompleted(System::Object^  sender, System::ComponentModel::RunWorkerCompletedEventArgs^  e)
    {
      if (e->Error) MessageBox::Show(this,e->Error->Message,"Error during file processing",MessageBoxButtons::OK,MessageBoxIcon::Error);
      bCountCancelled=e->Cancelled;
      if (progress->Visible) progress->Close();  // Progress dialog is still open: close it
      Cursor->Current=csrSaved;  // This has just been added in response to Arjay's latest post
      ewhWorkerReallyFinished->Set();
    }
    But, as described above, this handler doesn't even get called when I click Cancel.
    Last edited by Eri523; September 19th, 2010 at 07:04 AM.

  2. #32
    Join Date
    Jul 2002
    Posts
    2,512

    Re: How to UseWaitCursor correctly

    It is not clear from your post what is bw->ReportProgress(nPercent) and how it calls bwDoCount_ProgressChanged. Some code is missing to understand this.
    Anyway, comment off ReportProgress line and try to reproduce the cancelling problem: what happens?

  3. #33
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,585

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Alex F View Post
    It is not clear from your post what is bw->ReportProgress(nPercent) [...].
    Oops! I tried to reduce the amount of code posted. Maybe I reduced a bit too much...

    bw is a BackgroundWorker ^, passed as a parameter to DoCountWorker() from the worker event handler:

    Code:
    System::Void Form1::bwDoCount_DoWork(System::Object^  sender, System::ComponentModel::DoWorkEventArgs^  e)
    {
      DoCountWorker(safe_cast<FileStream ^>(e->Argument),dynamic_cast<BackgroundWorker ^>(sender),e);
    }
    And the event handler in turn gets the handle from the BackgroundWorker object itself.

    Thus, ReportProgress() is the BackgroundWorker method as described on MSDN and bwDoCount_ProgressChanged() is the event handler called by the BackgroundWorker as in response to my call to that method.

    Some code is missing to understand this.
    As you can conclude from the above, I don't have that code either; MS has.

    Anyway, comment off ReportProgress line and try to reproduce the cancelling problem: what happens?
    Interesting thing to try. Result: same problem as before (assertion failure as shown in the screenshot in post #21, fired from within Form1::DoCount(FileStream ^)).

  4. #34
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,286

    Re: How to UseWaitCursor correctly

    Is the attached project code up to date? If not, updated it and I'll take another look at it.

    As compared to C#, working in managed C++ is a real PITA, so I want to be sure the latest is posted before I look at it again.

  5. #35
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,585

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Arjay View Post
    Is the attached project code up to date? If not, updated it and I'll take another look at it.
    No. I'll attach an updated one to this post and remove the one from the previous post.

    Please let me know if it contains any unwanted files, so I can adjust my clean-up settings.

    As I already mentioned, the input file used for testing doesn't really matter. The Test.txt attached to the previous post was used to generate the screenshot, however.

    TIA again for your efforts!
    Attached Files Attached Files

  6. #36
    Join Date
    Jul 2002
    Posts
    2,512

    Re: How to UseWaitCursor correctly

    This thread is really too long... The problem was that thread hangs, and now assertion failure. I am completely lost. Just make small threading exercise without BackgroundWorker, better in C#, and you will understand everything.

  7. #37
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,585

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Alex F View Post
    This thread is really too long... The problem was that thread hangs, and now assertion failure. I am completely lost.
    Yes, well, the two are related, however: The assertion failure is caused by a wait on an EventWaitHandle that is supposed to be set by the BackgroundWorker (more particular: its RunWorkerCompleted() event handler) timing out.

    Sorry for confusing you...

  8. #38
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,286

    Re: How to UseWaitCursor correctly

    The main problem was you were blocking the main UI thread by waiting on an event. I removed the DoEvent( ) hacks because they aren't necessary.

    I also restructured the code to use a modeless progress form rather than the modal one.
    I removed the Diagnostic timer methods. If you want to put them back in, put the start in the DoCount and the stop in the BackgroundCompleted handler.

    Here's the updated code:

    Code:
    #include "stdafx.h"
    #include "Form1.h"
    
    using namespace MCppTest;
    
    System::Void Form1::bnAbout_Click(System::Object^  sender, System::EventArgs^  e)
    {
      MessageBox::Show("Eri's erstes .NET-Programm! <puh>");
    }
    
    System::Void Form1::bnCount_Click(System::Object^  sender, System::EventArgs^  e)
    {
    	if (openFileDialog1->ShowDialog(this)==System::Windows::Forms::DialogResult::OK)
    	{
    		DoCount((FileStream ^)openFileDialog1->OpenFile());
    	}
    }
    
    System::Void Form1::bnClose_Click(System::Object^  sender, System::EventArgs^  e)
    {
      Close();
    }
    
    System::Void Form1::DoCountWorker(FileStream ^fsInfile,BackgroundWorker ^bw,DoWorkEventArgs ^e)
    {
    	BinaryReader brInfile(fsInfile);
    	
    	const int nBufferSize=512;
    
    	array<Byte> ^abBuffer;
    	int nPercent = 0, nLastPercent = -1;
    
    	do
    	{
    		abBuffer = brInfile.ReadBytes( nBufferSize );
        
    		for ( int i = 0; i < abBuffer->Length; ++i )
    			++aulCounts[abBuffer[i]];
    
    		if ( ( nPercent = ( 100 * fsInfile->Position ) / fsInfile->Length ) != nLastPercent )
    		{
    			bw->ReportProgress( nPercent );
    			nLastPercent=nPercent;
    			System::Threading::Thread::Sleep( 250 );  // Slow the reading down so we can test it (remove for production)
    		}
    	}
    	while ( !bw->CancellationPending && abBuffer->Length == nBufferSize );
      
    	brInfile.Close();
    	fsInfile->Close( );
    
    	if (bw->CancellationPending)
    		e->Cancel = true;
    }
    
    System::Void Form1::DoCount(FileStream ^fsInfile)
    {
    	// Disable the buttons
    	bnAbout->Enabled = false;
    	bnCount->Enabled = false;
    	bnClose->Enabled = false; 
    
    	// Save off the file name
    	_fileName = fsInfile->Name;
    
    	// Reset the chart text
    	chart1->Titles[0]->Text = L"Byte frequency histogram";
    
    	// Zero out the chart data
    	Series ^serTarget = chart1->Series["Byte frequencies"];
    	serTarget->Points->Clear();
    
    	// Zero out the array
    	for ( int i=0; i < 256; ++i )
    		aulCounts[i]=0;
    	
    	// Save off the current Cursor
      	_csrSaved = Cursor;
    
    	// Set the wait cursor on the main form (not the progress form)
    	Cursor = Cursors::WaitCursor;
    
    	// To enable calling back to the BackgroundWorker object
    	progress->bwParentWorker=bwDoCount;  
    
      	progress->Show( this );
    
    	bwDoCount->RunWorkerAsync(fsInfile);
    }
    
    System::Void Form1::DoCount(String ^strInfile) {
      DoCount(gcnew FileStream(strInfile,FileMode::Open,FileAccess::Read));
    }
    
    System::Void Form1::Form1_DragEnter(System::Object^  sender, System::Windows::Forms::DragEventArgs^  e)
    {
      if(e->Data->GetDataPresent(DataFormats::FileDrop))
        e->Effect = DragDropEffects::All;
      else
        e->Effect = DragDropEffects::None;
    }
    
    System::Void Form1::Form1_DragDrop(System::Object^  sender, System::Windows::Forms::DragEventArgs^  e)
    {
      // Always use only the first file dropped and pass it to counting function:
      DoCount(((array<String ^> ^) e->Data->GetData(DataFormats::FileDrop, false))[0]);
    }
    
    System::Void Form1::bwDoCount_DoWork(System::Object^  sender, System::ComponentModel::DoWorkEventArgs^  e)
    {
      DoCountWorker(safe_cast<FileStream ^>(e->Argument),dynamic_cast<BackgroundWorker ^>(sender),e);
    }
    
    System::Void Form1::bwDoCount_ProgressChanged(System::Object^  sender, System::ComponentModel::ProgressChangedEventArgs^  e)
    {
      progress->Text=String::Format("Processing file... {0}&#37;", e->ProgressPercentage );
      progress->progressBar1->Value = e->ProgressPercentage;
    }
    
    System::Void Form1::bwDoCount_RunWorkerCompleted(System::Object^  sender, System::ComponentModel::RunWorkerCompletedEventArgs^  e)
    {
    	if (e->Error)
    	{
    		MessageBox::Show( this
    			, e->Error->Message
    			, "Error during file processing"
    			, MessageBoxButtons::OK
    			, MessageBoxIcon::Error );
    	}
    
    	// Progress dialog is still open: close it
    	if (progress->Visible)
    		progress->Hide();  
      
        if (!e->Cancelled)
    	{
    		chart1->Titles[0]->Text=String::Concat("Byte frequency histogram of ", _fileName);
    		Series ^serTarget=chart1->Series["Byte frequencies"];
    
    		for ( int i=0; i < 256; ++i )
    		{
    			serTarget->Points->AddXY( i, aulCounts[i] );
    		}
    	}
    
    	// Restore the cursor
    	Cursor = _csrSaved;
    
    	// Restore the buttons
    	bnAbout->Enabled = true;
    	bnCount->Enabled = true;
    	bnClose->Enabled = true; 
    }
    Lastly, I'm a firm believer in the amount of whitespace used in formatting is directly proportional to the developer's ability to track down issues.

    As such, I reformatted the code to add some whitespace.
    Last edited by Arjay; September 21st, 2010 at 11:41 PM.

  9. #39
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,585

    Cool Re: How to UseWaitCursor correctly

    Thanks for correcting my code, Arjay. Great work, really invaluable! And your implementation is so pretty straightforward. I couldn't imagine that it could be done that simple.

    After adding the two new members you introduced to the Form1 class and re-adding the instantiation of the Progress object in DoCount(FileStream ^) that got lost somehow it worked instantly. I then could remove three or so members from the Form1 class that were unused now.

    Did you add the Sleep() call in the worker function because you didn't have a 731 MB file to test the app with (or have a system where even processing that doesn't take a notable amount of time)? I found out that I could do some debugging pretty well without it.

    I think one of the reasons why I got stuck with my screwed-up code was that I was stubbornly clinging too much to the concept of the modal progress dialog and trying to keep most work out of the RunWorkerCompleted() handler.

    I must admit my "whitespace allergy" is most likely one of these "bad habits are hard to break" kind of problems. I started programming on a system that had a text display of 64 x 16 characters (and a "graphics" resolution of 128 x 48 brick-size "pixels", bi-level). (Not counting my previous experiments with programmable calculators.) Looks like I somehow still are stuck with this way of viewing things...

    Now that the program is running fine, I've got only one question remaining before finally marking the thread resolved: As I (think I've) understood it, all windows of one GUI thread share a single common message pump. So, if my progress dialog could process messages, why then couldn't the main form?

    Ah, and... Of course you and Alex F are now credited in that app's About box.
    Last edited by Eri523; September 22nd, 2010 at 10:39 PM.

  10. #40
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,286

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Eri523 View Post
    After adding the two new members you introduced to the Form1 class and re-adding the instantiation of the Progress object in DoCount(FileStream ^) that got lost somehow it worked instantly. I then could remove three or so members from the Form1 class that were unused now.
    I just renamed them by prepending an '_' to the existing variables. That way I could see identify them as a class field rather than a method param or local variable.

    Quote Originally Posted by Eri523 View Post
    Did you add the Sleep() call in the worker function because you didn't have a 731 MB file to test the app with (or have a system where even processing that doesn't take a notable amount of time)? I found out that I could do some debugging pretty well without it.
    I think I commented it, but you definitely want to remove the Sleep. I added that because the file I was working with didn't give me enough time to test the cancel and cursor functionality.

    Quote Originally Posted by Eri523 View Post
    I think one of the reasons why I got stuck with my screwed-up code was that I was stubbornly clinging too much to the concept of the modal progress dialog and trying to keep most work out of the RunWorkerCompleted() handler.
    Essentially with threading you need to do any UI work before the thread starts (like disable buttons) and do any 'post' thread operations (like reenabling buttons) in the backgroundCompleted handler.[

    Quote Originally Posted by Eri523 View Post
    I must admit my "whitespace allergy" is most likely one of these "bad habits are hard to break" kind of problems. I started programming on a system that had a text display of 64 x 16 characters (and a "graphics" resolution of 128 x 48 brick-size "pixels", bi-level). (Not counting my previous experiments with programmable calculators.) Looks like I somehow still are stuck with this way of viewing things...
    I really find code without whitespace very hard to read. I think that's why I missed that you were waiting on the event in the UI thread the first time around.

    Quote Originally Posted by Eri523 View Post
    Now that the program is running fine, I've got only one question remaining before finally marking the thread resolved: As I (think I've) understood it, all windows of one GUI thread share a single common message pump. So, if my progress dialog could process messages, why then couldn't the main form?
    The reason was (and probably why the Cursor didn't work as well) because you were waiting on the thread completed [manual or auto-reset] event in the main UI thread. When you wait on an event with WaitOne, it blocks the main thread and the message pump doesn't run. You kind of overcame this partially by putting in a few DoEvent calls. I think Alex and I told you that DoEvent wasn't required (and having it in there indicated a problem).

    I tell you coding in Managed C++ is a bit of a pain compared to coding in C#. The syntax for variables and namespaces is goofy. Something like:
    Code:
    using System::Threading;
    
    private: System::Threading::AutoResetEvent^ _completedEvent;
    In C#, the equivalent is:
    Code:
    using System.Threading;
    
    private AutoResetEvent _completedEvent;
    C# looks cleaner because there isn't all that namespace tomfoolery.

    I was surprised to find out that intellisense doesn't work in managed C++. When you understand what needs to be done, but don't know the exact syntax (like me being unfamiliar with Managed C++), intellisense is sure handy.

    Lastly, managed C++ organizes the classes into the std .h/.cpp file format, but I have to tell you, I find the project is really cluttered with both these files. In C#, there is only one file (.cs) for both the declaration and implementation - it's like half as many files to keep track of.

    As a long time C++ developer that works a lot in C#, I tend to notice these sorts of things when switching between C++ and C#.

Page 3 of 3 FirstFirst 123

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center