Unable to find out problem in NSProgressIndicator

Udit1Udit1 Member ✭✭

I am a C++ developer. Xamarin and C# is new for me. I was using NSProgressIndicator to display progress of process. For this I had set minimum and maximum value and incrementing progressValue by 1 in each loop cycle. For example

    var progressBar = new NSProgressIndicator (new CGRect (60, 0, 500, 300)) 
        {
                DoubleValue = 0,
                Indeterminate = false
         }

    double progressValue = 0;
    progressBar.MinValue = 0;
    progressBar.MaxValue = 228;//it can be any integer value.

    while(progressValue < progressBar.MaxValue)
        {
                progressValue += 1;
                progressBar.DoubleValue = progressValue;
                mainWinController.Window.ContentView.AddSubview (progressIndicator);
         }
    //mainWinController.Window.ContentView.AddSubview (progressIndicator);

In above logic I am running my while loop till MaxValue of NSProgressIndicator and in every running step of loop incrementing progressValue by 1. But Progress indicator is displaying few increment and not being complete. According to logic it must be complete as loop is running 228(MaxValue) times.

Tagged:

Answers

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai
    edited June 2018

    This code is not doing what you expect. To begin with, you are adding the progressIndicator to the visual tree with:

                mainWinController.Window.ContentView.AddSubview (progressIndicator);
    

    every single iteration of the while loop. In addition, you are updating the progressValue on the main thread "returning", so the UI never has time to draw the updates.

    You likely want something like this (simplified example):

        public override void ViewDidLoad ()
        {
            base.ViewDidLoad ();
    
            // Create View
            var progressBar = new NSProgressIndicator (new CGRect (60, 0, 500, 300)) {
                DoubleValue = 0,
                Indeterminate = false,
                MinValue = 0,
                MaxValue = 100
            };
    
            // Add to Visual Tree
            View.AddSubview (progressBar);
    
            // Startup Worker Thread
            NSThread.Start (() => {
    
                // For some amount of work
                for (int i = 0; i < 100; ++i)
                {
                    // Update UI
                    BeginInvokeOnMainThread (() => {
                        progressBar.DoubleValue = i;
                    });
    
                    NSThread.SleepFor (.1);
                }
            });
        }
    

    Where you do work on some secondary thread and update the progress bar every so often.

    Do note the BeginInvokeOnMainThread, it is invalid to touch almost any Cocoa object off the UI thread.

  • Udit1Udit1 Member ✭✭

    I tried your code. But not able to find out solution of the problem. Seems I should explain the problem briefly.

    Im my project I have a class “ProgressInfoController : NSWindowController” in ProgressInfoController.cs file. In this class I am creating an object of NSProgressIndicator like following

    progressIndicator = new NSProgressIndicator(new CoreGraphics.CGRect(20, 40, 300, 100))
         
    {
             
            DoubleValue = 0,
             
        Indeterminate = false
         
    };
    

    In this class I am not initialising NSProgressIndicator’s MinValue and MaxValue of because I get MaxValue in another class name is MainWindowController which is declaired in MainWindowController.cs

    I am attaching an image 10.png . In this you will see three pane. Left pane in bold oval is “Outline View” and middle pane is “Table View”. For this Outline View I have a class name is TreeOutlineDelegate in TreeOutlineDelegate.cs . In this class I have following function

        SelectionDidChange(NSNotification notification)
        {
              
            var table = notification.Object as NSOutlineView;
              
            var row = table.SelectedRow;

              
            if(row < 0)
                 
                return;
                
            var obj = (Node)table.ItemAtRow((int)row);

                
            Global.mainWindowController.Parse(ref obj);
         
        }
    

    In above code I am calling Parse(ref obj) which is define in class MainWindowController. In Parse(ref obj) function I am performing a time consuming task. Whenever I perform a selection change in Outline View then Parse(ref obj) will be call and Table View in middle pane will be populate. Table View in middle pane has own’s MailDataSource and MailDataDelegate class.

    In function Parse(ref obj) I am in initialising MinValue, MaxValue and adding NSProgressIndicator in ProgressInfoController window. For this I am using following code

        progressInfo.progressIndicator.MinValue = 0
        progressInfo.progressIndicator.MaxValue = totalItems;
 
        progressInfo.Window.ContentView.AddSubview(progressInfo.progressIndicator);

    

    As I told you in function Parse(ref obj) performing time consuming task. So I am displaying ProgressInfoController window to show the progress of time consuming task. Here I am facing problems to display the progress. I also used your code with my code but ProgressInfoController window is not displaying and this time face another problem that is middle pane(TableView) is also not being populate. You can see attached image 10.png in which “All Mail” is selected but middle pane is not being populate. Without you code it was being populate as in image 11.png. I also tried to display ProgressInfoController window as sheet but still the same problem. Following is Parse(ref obj) function…

    public void Parse(ref Node objParsed)
        {
            if(!string.IsNullOrEmpty(objParsed.Path))
                {
                    FileAttributes attr = File.GetAttributes(objParsed.Path);
                    MailDataSource emailDataSource = new MailDataSource();
                    this.RightTableView.DataSource = emailDataSource;
                    //detect whether its a directory or file
                    if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
                    {
                    }
                    else
                    {
                            RightTableView.Delegate = new MailDataDelegate(emailDataSource);
                            var tmpPath = Path.GetTempPath();
                            tmpPath += objParsed.Name;
                            Directory.CreateDirectory(tmpPath);
                            tmpPath += "/";
                            FileStream stream = new FileStream(objParsed.Path, FileMode.Open, FileAccess.Read);
                            // Create an instance of MboxrdStorageReader class and pass the stream
                            MboxrdStorageReader reader = new MboxrdStorageReader(stream, false);
    
                            //totalItems is value which have to assign to Progress Indicator's MaxValue. It can be any integer value as     22, 530, 5570 etc
                         int totalItems = reader.GetTotalItemsCount(); 
                            progressInfo.progressIndicator.MinValue = 0;
                            progressInfo.progressIndicator.MaxValue = totalItems;
                            progressInfo.Window.ContentView.AddSubview(progressInfo.progressIndicator);
                            double progressValue =  0;
    
                            NSThread.Start(() => {
                                MailMessage message = reader.ReadNextMessage();// Start reading one messages at a time.
                                int icount = 0;
    
                                // Read all messages in a loop
                                while (message != null)
                                {
                                        progressValue += 1;
                                        BeginInvokeOnMainThread(() => {
                                        progressInfo.progressIndicator.DoubleValue = progressValue;
                                    });
                                    // Manipulate message - show contents
                                    string str = icount + ".eml";
                                    message.Save(tmpPath + "" + str, SaveOptions.DefaultEml);// Save message in EML or MSG format
    
                                    // Filling emailDataSource 
                                    emailDataSource.mailList.Add(new            MailItem(message.Attachments.Count(),message.Priority.ToString(),message.Subject, tmpPath + str));
                                    icount++;
                                    message = reader.ReadNextMessage();// read next message
                                    NSThread.SleepFor(.1);
                            }
                            // Close the streams
                            reader.Dispose();
                            stream.Close();
                        });
                }
            }
        }
    

    I am also attaching ProgressInfoController.cs, TreeOutlineDelegate.cs which are in Files.zip file .

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    Ok, your NSThread code is rather unsafe. Doing threading correctly is difficult, as you have to make sure all shared data structures are sharable and that you have discrete input / output handled safely.

    I'm not going to go into the specifics, as how to correctly handle threading is beyond the scope of a forum post, but I"d start with reading this document: https://docs.microsoft.com/en-us/xamarin/ios/user-interface/ios-ui/ui-thread

    It's iOS specific, but the same ideas hold. If you aren't a threading expect, sticking to async await is likely significantly more safe. I modified my original example to use async:

    https://gist.github.com/chamons/2a80e568ee20f9a41091eeaa10e15faf

  • qtsoftqtsoft Member ✭✭

    I am following link given by you. Right now I have a confusion and want your view on this confusion. My confusion is about MaxValue of NSProgressIndicator. As you always initializing MaxValue with 100. Is it fix value and I can not assign other integer value which can be less then 100 or more then 100.

  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    As noted in Apple's documentation here: https://developer.apple.com/documentation/appkit/nsprogressindicator/1501165-maxvalue?language=objc

    The value for MaxValue (and Min) are just scaling to determine the % of the bar to drop. 0 - 10, 0 - 1000, whatever makes calculating easy for your use case.

  • Udit1Udit1 Member ✭✭

    Ok. An idea is comming in my mind that first I should calculate % of my totalItem(maximum value) then I should update progress indicator as

        progressIndicator.DoubleValue = progressValue;
    

    I don't know its right and wrong.

    I am also following both links

    https://docs.microsoft.com/en-us/xamarin/ios/user-interface/ios-ui/ui-thread

    https://gist.github.com/chamons/2a80e568ee20f9a41091eeaa10e15faf

    But I have a confusion. In which following class I should override ViewDidLoad ().

    1. MainWinwowController of MainWinwowController.cs
    2. ProgressInfoController of ProgressInfoController.cs
  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    That sample just adds a NSTextView and a NSProgressIndicator to some View to show the general idea of breaking up a long task with async.

    You'll need to adapt it to your general problem, depending on what's getting you your performance issues.

  • Udit1Udit1 Member ✭✭

    Hi,

    @ChrisHamons

    I have used following both approach given by you..

    1. https://gist.github.com/chamons/2a80e568ee20f9a41091eeaa10e15faf
      
    2. public override void ViewDidLoad ()
      {
          base.ViewDidLoad ();
      
          // Create View
          var progressBar = new NSProgressIndicator (new CGRect (60, 0, 500, 300)) {
          DoubleValue = 0,
              Indeterminate = false,
              MinValue = 0,
              MaxValue = 100
       };
      
      // Add to Visual Tree
      View.AddSubview (progressBar);
      
      // Startup Worker Thread
      NSThread.Start (() => {
      
          // For some amount of work
          for (int i = 0; i < 100; ++i)
          {
              // Update UI
              BeginInvokeOnMainThread (() => {
                  progressBar.DoubleValue = i;
              });
      
              NSThread.SleepFor (.1);
          }
      });
      

      }

    **I usBoth are woking. ** I don't know which one is good.

    But I noticed one different thing in both approach.

    Approach 1 is not working in case of sheet Model(an progress indicator added in a window and this window used as sheet in main window). Is it possible or can not use sheet model in this case

    Approach 2 is working very fine in case of sheet Model(an progress indicator added in a window and this window used as sheet in main window). But when ever process is being complete then I was ending the sheet and stop the model in BeginInvokeOnMainThread(() block . Is it right place to end the sheet.

        NSThread.Start(() => {
                for (i = 0; i < 10000; i++)
                {
                     BeginInvokeOnMainThread(() => {
                     progressBar.progressIndicator.DoubleValue = i * 1;
                         textEdit.Value = i.ToString() + str;
                         if (i == 10000)
                         {
                             //progressBar.Window.Close();
                                //progressBar.Window.OrderOut(sender);
                                NSApplication.SharedApplication.StopModal();
                                NSApplication.SharedApplication.EndSheet(progressBar.Window);
                         }
                    });
                    NSThread.SleepFor(.001);
               }
            });
    

    When I click on stop button of sheet window as in attached image 3.png then sheet is ending but loop is running. How can stop the loop the loop. I used break statement but its not effective.

  • Udit1Udit1 Member ✭✭

    3.png is here....

    3.png 26.2K
  • ChrisHamonsChrisHamons USForum Administrator, Xamarin Team Xamurai

    The difference between the two approachs is how you are handling threading. The first one uses async, which is safer, as it makes it more difficult to make multi threading mistakes. The second uses a raw NSThread, which gives more flexibility but you have to be very careful in how you pass and access data between threads, and with touching UI objects on the correct thread.

    Have you considered just holding onto the NSWindow (or its controller) and calling Close on it when you want it gone, instead of the blunt hammer of killing it's message pump?

  • Udit1Udit1 Member ✭✭

    Hi

    @ChrisHamons

    As you describe the following approach to use NSThread. In this we are starting thread using NSThread.Start()

    NSThread.Start(() => {
            for (i = 0; i < 100000; i++)
            {
                 BeginInvokeOnMainThread(() => {
                 progressBar.progressIndicator.DoubleValue = i * 1;
                     textEdit.Value = i.ToString() + str;
             });
                NSThread.SleepFor(.001);
           }
        });
    

    After the completion of the thread execution I should stop and release any thing occupied by thread or I must leave it and no need to to extra thing.

    I also was trying to stop the loop on click the button between the range( 0 , 100000 ). But I was not able to stop the loop on click the button. How can stop the thread.

    How will handle above things async function

  • Udit1Udit1 Member ✭✭

    Hi,

    You are right. Actually I never worked in C#, Xamarin and Cocoa. I prefer QT framework. Now I am developing an app in C# and Xamarin under Mac. So I always stuck whenever face such problems.

    If you do not mind I want discuss another problem regarding to thread...

    I have two async function one is iterative and another one is recursive. Both are performing very heavy task. Fallowing are both function. Both function I was calling from a third function one after another. First i was calling WritePST() after that AddDirectory(). But both are executing parallel. How can I force second function to wait until first function do not completes its execution.

        public async void WritePST(string mbxPath)
    {
            if (!string.IsNullOrEmpty(mbxPath))
        {
                if ((attr & FileAttributes.Directory) == FileAttributes.Directory){
        }
                else{
                    //Doing Some Work.......
                    while(message != null)
                    {
            await Task.delay(2);
            progressBar.DoubleValue += 1; 
                        //Doing Some Work.......
                    }
                    var dir = new DirectoryInfo(delPath);
                    dir.Attributes = dir.Attributes & ~FileAttributes.ReadOnly;
                    dir.Delete(true);
                }
            }
        }
    
    1. private async void AddDirectory(FolderInfo folder, string sDir)
      {
      try{
      foreach (string f in Directory.GetFiles(sDir, "*.eml"))
      {
      await Task.delay(2);
      progressBar.DoubleValue += 1;
      MailMessage eml = MailMessage.Load(f);
      folder.AddMessage(MapiMessage.FromMailMessage(eml));
      }
      foreach (string d in Directory.GetDirectories(sDir))
      {
      FolderInfo fi = folder.AddSubFolder(new DirectoryInfo(d).Name);
      AddDirectoryT(fi, d);
      }
      }
      catch (System.Exception excpt){
      Console.WriteLine(excpt.Message);
      }
      }
Sign In or Register to comment.