简体   繁体   中英

Progress bar not updating from background worker

What I'm trying to do is to have a progress bar as a separate window that will update based on the progress of other long processing work on another form. When I debug through the progress bar code, DoWork and ProgressChanged are both called as expected but the UI fails to update. The code even reaches the Worker_RunWorkerCompleted and closes the progressbar. I've read about 20 articles and can't nail down the issue. I know that I should be using a view model for WPF but the code below is simplified to see if I can just get the progress bar to update. I know I'm missing something simple but I can't put my finger on what it is.

XAML:

<Window x:Class="CGXAcquisition.Forms.Dialog.FileProgressBar"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="File Upload Progress" Height="100" Width="300">
    <Grid Margin="20">
        <ProgressBar Minimum="0" Maximum="100" Name="pbStatus" />
        <TextBlock Text="{Binding ElementName=pbStatus, Path=Value, StringFormat={}{0:0}%}" HorizontalAlignment="Center" VerticalAlignment="Center" />
    </Grid>
</Window>

The code behind:

public partial class FileProgressBar : Window, INotifyPropertyChanged
    {
        BackgroundWorker worker = new BackgroundWorker();

        private int fileLines = 0;
        private int totalFileLines = 0;

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        public int FileLines
        {
            get { return fileLines; }
            set 
            { 
                fileLines = value;
                Percentage = (int)Math.Round((double)fileLines / TotalFileLines);
                OnPropertyChanged("FileLines");
            }
        }
        public int TotalFileLines
        {
            get { return totalFileLines; }
            set { totalFileLines = value; }
        }
        public int Percentage { get; set; }

        public FileProgressBar(int totalLines)
        {
            InitializeComponent();
            TotalFileLines = totalLines;
            Loaded += Window_ContentRendered;
        }
        private void Window_ContentRendered(object sender, EventArgs e)
        {
            
            worker.WorkerReportsProgress = true;
            worker.WorkerSupportsCancellation = true;
            worker.DoWork += worker_DoWork;
            worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
            worker.ProgressChanged += worker_ProgressChanged;
            worker.RunWorkerAsync();
        }

        private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.Close();
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            for(int i = 0; i < 100; i++)
            {
                Thread.Sleep(100);
                int percent = i / 100;
                worker.ReportProgress(percent, i);
            }
        }

        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            pbStatus.Value = e.ProgressPercentage;
        }

    }

The code that creates the progress bar which exists on another form:

private void ShowProgressBar()
        {
            progress = new FileProgressBar(FileInfo.LineCount);
            progress.Show();
        }

I call ShowProgressBar() in my OnLoaded event

private void OnLoaded(object sender, RoutedEventArgs e)
        {
            ShowProgressBar();
        }

and in my constructor I add the event handler to the Loaded event

Loaded += OnLoaded;

I expected to see the UI update with the code I have. I've tried using a Dispatcher to update the UI but as far as I understand it, the worker_ProgressChanged should alleviate the need for a dispatcher. I've looked through StackOverflow, MSDN and several other blogs and nothing has helped me identify what I'm missing.

Thank you in advance for your help.

UPDATE I finally noticed that my math was wrong. This line int percent = i / 100; Should be int percent ((int)(double)i / 100 * 100);

I know that I could have just used i but I wanted to do it right.

The Backgroundworker is a deprecated API. Instead you should use Task.Run or asynchronous APIs where possible. To report progress you should use the Progress<T> class.

The main issue with your code: your calculations are wrong. Currently, the max value that your formula produces is 1 ( 100/100 ).
Furthermore, the BackgroundWorker.ReportProgress accepts an int as percentage argument. The implicit type cast from double to int will truncate the decimal places of the double value. Hence, 0.9d becomes 0 .
This means the ProgressBar.Value will be 0 the whole time until i == 100 . Then the value will be 1 and you close the Window .

The correct formula to calculate percentage is: value / max_value * 100 .
It is also important to understand that because of the division your participating numeric variables or the divisor must be of type double . Otherwise the intermediate result value will be truncated due to the implicit conversion from double to int . In other words you completely lose the decimal places (no rounding).

You don't tell any details about your background job. But from the window's name I assume you read from a file. In this case don't execute the job on a background thread. Instead use the asynchronous API of the StreamReader .

Additionally, controls never implement INotifyPropertyChanged . It has several downsides: performance, property can't serve as binding target, can't be animated etc.
Instead controls, or types that extend DependencyObject in general, should implement their properties as dependency properties.

Your improved implementation of FileProgressBar and fixed calculations should look as follows:

FileProgressBar.xaml.cs

public partial class FileProgressBar : Window
{
  public int FileLines
  {
    get => (int)GetValue(FileLinesProperty);
    set => SetValue(FileLinesProperty, value);
  }

  public static readonly DependencyProperty FileLinesProperty = DependencyProperty.Register(
    "FileLines",
    typeof(int),
    typeof(FileProgressBar),
    new PropertyMetadata(default(int), OnFileLinesChanegd));

  // If this property is used for data binding it must be a dependency property too
  public int TotalFileLines { get; set; }

  // If this property is used for data binding it must be a dependency property too
  public int Percentage { get; set; }

  public FileProgressBar(int totalLines)
  {
    InitializeComponent();
    this.TotalFileLines = totalLines;
    this.Loaded += Window_ContentRendered;
  }

  private static void OnFileLinesChanegd(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    var this_ = d as FileProgressBar;
    this_.Percentage = (int)Math.Round((double)e.NewValue / this_.TotalFileLines * 100d);
  }

  private async void Window_ContentRendered(object sender, EventArgs e)
  {
    var progressReporter = new Progress<double>(progress => pbStatus.Value = progress);
    await DoWorkAsync(progressReporter);
    OnWorkCompleted();
  }

  private void OnWorkCompleted() => Close();

  async Task DoWorkAsync(IProgress<double> progressReporter)
    => await Task.Run(async () => await LongRunningTaskAsync(progressReporter));

  private static async Task LongRunningTaskAsync(IProgress<double> progressReporter)
  {
    for (int i = 0; i < 100; i++)
    {
      await Task.Delay(TimeSpan.FromMilliseconds(100));
      double percent = i / 100d * 100d;
      progressReporter.Report(percent);
    }
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM