Search code examples
c#wpfdata-bindingprogress-bar

Databinding A Progress bar to a view model


I have a simple progress bar and status message textblock bound to a view model that should iterate in a foreach statement. it works fine in testing when a put a MessageBox in the foreach but if I take out the MessageBox the only status that's being captured is the last iteration and I can't determine what I need to do to correct that.

ViewModel -

    public class ExampleViewModel : INotifyPropertyChanged
    {        
        public RelayCommand<IList> Submit { get; }
        
        private KeyValuePair<Model, string[]> _selectedItem;
        public KeyValuePair<Model, string[]> SelectedItem
        {
            get => _selectedItem;
            set
            {
                _selectedItem = value;
                RaisePropertyChanged(nameof(SelectedItem));
            }
        }

        public ExampleViewModel()
        {
            Submit = new RelayCommand<IList>(ExecuteSubmit);
        }

        private void ExecuteSubmit(IList selectedItems)
        {
            string[] examples = new string[] {"a", "b", "c"};

            double percentAge = 100/examples.Count;

            if (examples.Count > 0)
            {
                foreach (var ex in examples)
                {
                    CurrentProgress = CurrentProgress + percentAge;
                    MessageBox.Show("Step " + ex);  // Status and Progress Bar update when this isn't commented out
                    //   Thread.Sleep(1000);
                    StatusMessages = "Option " + ex;
                }
            }
            return;
        }

        #region Status Fields
        private string _statusMessages;
        public string StatusMessages
        {
            get => _statusMessages;
            set
            {
                if (_statusMessages != value)
                {
                    _statusMessages = value;
                    RaisePropertyChanged(nameof(StatusMessages));
                }
            }
        }

        private double _currentProgress;
        public double CurrentProgress
        {
            get => _currentProgress;
            set
            {
                if (_currentProgress != value)
                {
                    _currentProgress = value;
                    RaisePropertyChanged(nameof(CurrentProgress));
                }
            }
        }
        #endregion

        public event PropertyChangedEventHandler PropertyChanged;
        public void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new
                    PropertyChangedEventArgs(propertyName));
            }
        }
    }

XAML -

            <ProgressBar Value="{Binding CurrentProgress, Mode=OneWay}"
                Margin="5,5" Height="10" Width="240" HorizontalAlignment="Left"/>
            <TextBlock Margin="5,5" Height="15" Width="240" TextWrapping="Wrap"
                Foreground="Red"
                Text="{Binding StatusMessages}"></TextBlock>


Solution

  • Calling Thread.Sleep(1000); on UI thread is wrong.. It freezes the UI and once the freeze ends it will start a new freeze so it could not even update any of the UI elements that are bound with the properties.. You can use DispatcherTimer instead

    Replace

    foreach (var ex in examples)
    {
        CurrentProgress = CurrentProgress + percentAge;
        MessageBox.Show("Step " + ex);  // Status and Progress Bar update when this isn't commented out
        //   Thread.Sleep(1000);
        StatusMessages = "Option " + ex;
    }
    

    With

    var timer = new DispatcherTimer
    {
        Interval = TimeSpan.FromSeconds(1)
    };
    int ticksCount = 0;
    var ticksLimit = examples.Count;
    timer.Tick += (_, _) =>
    {
        ticksCount++;
        if (ticksCount > ticksLimit)
            timer.Stop();
        else
        {
            var ex = examples[ticksCount - 1];
            CurrentProgress = CurrentProgress + percentAge;
            StatusMessages = "Option " + ex;
        }
    };
    timer.Start();