1

I have a button, clicking it will processing all files. I want to display the progress when running it. So if file 1 is processing, then the UI displays

"processing file 1"

; when processing file 2, the UI displays

"processing file 1"

"processing file 2"

So I use a Listbox to do it. The ItemSource of the ListBox is a collection in my ViewModel

private ObservableCollection<string> _displayedFiles;
public  ObservableCollection<string> DisplayedFiles
{
    get {return _displayedFiles;}
    set 
    {
        _displayedFiles = value;
        PropertyChanged(nameof(DisplayedFiles));
    }
}

Now the ViewModel is passed to the command class

public class MyCommand :ICommand
{
    private MyViewModel myViewModel;
    public MyCommand(MyViewModel myViewModel)
    {
       this.myViewModel = myViewModel;
    }

   public void Execute(object parameter)
   {
        foreach(var f in files)
        {
          Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate () { AddFiles(f);});
        }
    }

    private void AddFiles(string f)
    {
        this.ViewModel.DisplayedFiles.Add(f);
    }
}

However I found the UI is freeze and the list is not displayed one by one. It displays the whole bunch list together after the loop iteration completed.

Hello
  • 796
  • 8
  • 30

1 Answers1

0

When you use Dispatcher.Invoke - you are basically telling the GUI thread to do something. Since you are doing heavy lifting in that thread the GUI will freeze.

For the progress bar things to work, you always do the time taking intensive task on a different thread and only do progress bar GUI updates on GUI thread.

Let me show you a basic psuedo example:

Imagine my code is:

private void DoTheTaskAndUpdateGUI(List<string> filesToProcess)
{
    int completionPct = 0;
    for(int idx = 0;idx<filesToProcess.Count; idx++)
    {
        ProcessFile(filesToProcess[idx]);
        completionPct = ((idx + 1)/filesToProcess.Count) * 100;
        UpdateGUI(completionPct);
    }
}

If all of this is running on single GUI thread, my UI will always be frozen until the loop finishes. To make UI a bit responsive, I can rearrange the fucntionality like:

private void DoTheTaskAndUpdateGUI(List<string> filesToProcess)
{
    var guiDispatcher = Dispatcher.CurrentDispatcher;
        Task.Factory.StartNew(()=> 
        {
            int completionPct = 0;
            for(int idx = 0;idx<filesToProcess.Count; idx++)
            {
                ProcessFile(filesToProcess[idx]);
                completionPct = ((idx + 1)/filesToProcess.Count) * 100;
                guiDispatcher.Invoke(UpdateGUI(completionPct));
            }
        });
}

I have written this code in notepad so don't assume it compiles successfully. but understand the thought behind it.

  • DoTheTaskAndUpdateGUI method is called on GUI Thread

  • We hold the Dispatcher instance

  • A new Thread/Task starts and the heavy lifting begins in it
  • GUI Thread is now free
  • Whenever the intensive task reaches Dispatcher.Invoke call it updates GUI by requesting GUI thread to do so
Prateek Shrivastava
  • 1,877
  • 1
  • 10
  • 17
  • Even I remove the `Dispatcher.Invoke`, it is same. – Hello Jan 15 '20 at 23:04
  • It will be because the Execute function itself is being called from GUI thread. Read about Task or Thread Class. Remember that any GUI update has to be done on GUI thread so Dispatcher.Invoke will be required there. – Prateek Shrivastava Jan 15 '20 at 23:06