1

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));
        }
    }
persianLife
  • 1,206
  • 3
  • 17
  • 22
  • 2
    You should really use a [`Dispatcher`](http://msdn.microsoft.com/en-us/magazine/cc163328.aspx)... – Patryk Ćwiek Feb 11 '13 at 09:05
  • 1
    You don't show where you set your `DataContext`. – Daniel Kelley Feb 11 '13 at 09:07
  • Check this answer: [stackoverflow.com/questions/4621623][1] [1]: http://stackoverflow.com/questions/4621623/wpf-multithreading-ui-dispatcher-in-mvvm – faceman Feb 11 '13 at 09:13
  • 1
    All the people suggesting that this is a direct result of updating the property from a worker thread are mistaken: WPF has *always* allowed data sources to raise PropertyChange events on any thread, and it automatically marshals the binding update back to the dispatcher thread for you. (So does Silverlight. XAML for Win8 Store apps does not.) So whatever's happening, that's not the issue here. We need more information: as Daniel Kelley asked, where are you setting your DataContext? Also, is your UI responsive while this happens, or could the UI thread be busy? – Ian Griffiths Feb 11 '13 at 10:38
  • @IanGriffiths thx for the feedBack i have update the code with more info – persianLife Feb 11 '13 at 11:03

1 Answers1

4

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

Ian Griffiths
  • 14,302
  • 2
  • 64
  • 88
  • U are totaly right, the problem is that UI thread is busy, any trick to solve this? – persianLife Feb 11 '13 at 11:11
  • but my UI is responsive through since all this is in the background thread, but im adding lot of item using ObservableCollection. – persianLife Feb 11 '13 at 11:19
  • It's possible that your background thread is generating so much work for the UI thread that it's totally swamping it. Moving work to a background thread makes sense when that work is slow. But it sounds like you're doing something that produces a lot of data quickly? You may actually be slowing things down by doing them on a separate thread! From your code, it looks like you're reading data out of a file and populating the UI with that data - is that right? How long does that actually take? – Ian Griffiths Feb 11 '13 at 13:48
  • this take approximately 50 sec – persianLife Feb 11 '13 at 13:57
  • This is a complicated area - too big to handle adequately in the comments! I've added a link to my answer that provides some information that may be useful in dealing with this problem. – Ian Griffiths Feb 25 '13 at 16:48