persianLife
persianLife

Reputation: 1225

Updating progressbar from a background thread

I have a progressbar and its value is binded to a property:

                <ProgressBar x:Name="progressBar"
                         Margin="0,2,0,0"
                         Height="20"
                         Value="{Binding CompassLogLoadPercent}"
                         Foreground="Blue"
                         Visibility="{Binding CompassLogLoadCompleted, 
                Converter={StaticResource BooleanToVisibilityConverter}}"
                         ToolTip="Loading">
            </ProgressBar>

and the property:

       public double CompassLogLoadPercent {
        get { return _compassLogLoadPercent; }
        private set {
            if (value != _compassLogLoadPercent) {
                _compassLogLoadPercent = value;
                NotifyPropertyChanged();
            }
        }
    }

and in a seperate thread its value is updated:

   for (int j = 0; j < lines.Count(); j++) {
      ...
      CompassLogLoadPercent = ((double) j /lines.Count())*100;
   }

and the thread is created using TASK:

Task.Run(() => { LoadLogFile(fileName); });

Why is progressbar not updating and how should I fix this?

UPDATE: More Info

Datacontext: (Im sure that the dataContext is Correct)

cLT.progressBar.DataContext = logSession;

and implementation of INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void NotifyPropertyChanged(
        [CallerMemberName] String propertyName = "") {
        PropertyChangedEventHandler eventHandler = PropertyChanged;

        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Upvotes: 1

Views: 2100

Answers (1)

Ian Griffiths
Ian Griffiths

Reputation: 14567

The problem lies somewhere in something you haven't shown us. The basic technique is sound. (In particular, there's nothing wrong with raising PropertyChanged event notifications on a worker thread, because WPF's data binding system detects when that happens, and automatically arranges to update the target UI element on the UI thread.)

Here's a complete example that does work. Here's your XAML:

<Window x:Class="BackgroundThreadUpdate.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ProgressBar
            x:Name="progressBar"
            VerticalAlignment="Top" Height="20"
            Value="{Binding CompassLogLoadPercent}">
        </ProgressBar>
        <Button Content="Button" HorizontalAlignment="Left" Margin="10,25,0,0" VerticalAlignment="Top"
                Width="75" RenderTransformOrigin="-1.24,-0.045" Click="Button_Click_1"/>

    </Grid>
</Window>

and here's your codebehind:

using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace BackgroundThreadUpdate
{
    public partial class MainWindow : Window
    {
        private MySource _src;
        public MainWindow()
        {
            InitializeComponent();
            _src = new MySource();
            DataContext = _src;
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                for (int i = 0; i < 100; ++i)
                {
                    Thread.Sleep(100);
                    _src.CompassLogLoadPercent = i;
                }
            });
        }
    }

    public class MySource : INotifyPropertyChanged
    {
        private double _compassLogLoadPercent;
        public double CompassLogLoadPercent
        {
            get
            {
                return _compassLogLoadPercent;
            }
            set
            {
                if (_compassLogLoadPercent != value)
                {
                    _compassLogLoadPercent = value;
                    OnPropertyChanged("CompassLogLoadPercent");
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

This illustrates a working version of the technique you're trying to use.

So the fact that yours doesn't work must be due to something you've not shown us. Some possible explanations:

  • Your UI thread might be blocked. If the UI threads is busy, the data binding updates will not be processed. (When data binding detects a change from a data source on a worker thread, it posts a message to the relevant dispatcher thread, and that message won't be processed if the dispatcher thread is busy.)
  • The data source might not be in the DataContext for your ProgressBar - you've not shown us where you set the context, so that could well be wrong.
  • The code that raises the PropertyChanged event (your NotifyPropertyChanged code) might be wrong - you've not shown that code, and it's not clear how it knows what property name to use when raising the event.

To check for the first one, just see if your UI is responsive to user input while this background work is in progress. If it's not, then that's why the updates aren't getting through.

Updated 25th February to add relevant link

In thinking about what else I could say about how to handle this scenario, I came to the conclusion that it was too big to fit into a single StackOverflow answer. so I wrote a series of blog posts about performance considerations when doing non-trivial processing on a background thread that needs to load information into a UI: http://www.interact-sw.co.uk/iangblog/2013/02/14/wpf-async-too-fast

Upvotes: 4

Related Questions