1

I have some code trying to set the value of a progress bar in WPF using a method like this:

private void SetPercentage(int channel, int percentage)
{
    switch(channel)
    {
        case 1:
            ch1Bar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new DelegateMethod(() => ch1Bar.Value = (double)percentage));
            break;
        case 2:
            ch2Bar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new DelegateMethod(() => ch2Bar.Value = (double)percentage));
            break;
        case 3:
            ch3Bar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new DelegateMethod(() => ch3Bar.Value = (double)percentage));
            break;
        case 4:
            ch4Bar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new DelegateMethod(() => ch4Bar.Value = (double)percentage));
            break;
        case 5:
            ch5Bar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new DelegateMethod(() => ch5Bar.Value = (double)percentage));
            break;
        case 6:
            ch6Bar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new DelegateMethod(() => ch6Bar.Value = (double)percentage));
            break;
        case 7:
            ch7Bar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new DelegateMethod(() => ch7Bar.Value = (double)percentage));
            break;
        case 8:
            ch8Bar.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Send, new DelegateMethod(() => ch8Bar.Value = (double)percentage));
            break;
    }
}

Every chXBar where X is 1 through 8 is a separate progress bar. This method is being called in a loop within another thread (created manually with the Thread class). The loop sets one channel value at a time, and quite slowly (with a Thread.Sleep to slow it down).

However none of these invokes seem to work; the values on the progress bars don't change (they always stay zero). The code compiles fine and there are no exceptions thrown in debugging. I really don't want to write 8 delegates and 8 methods to use them.

Does anyone have any pointers?

(P.S. I'm using .NET 4.5 on Windows 7 x64)

PrinceBilliard
  • 181
  • 1
  • 17
  • 1
    What are the values being set? Are they within the range specified by the `Minimum` and `Maximum` of the `ProgressBar`? Note that the default range is `0.0` to `1.0`, not `0` to `100`. – Mike Strobel Oct 31 '14 at 18:48
  • Is `percentage` different enough that you would perceive the change ? Is that function being called too fast, so it gets 100 before you can see the intermediate values? It sounds like the `Invoke` is working fine since you don't get an exception. – BradleyDotNET Oct 31 '14 at 18:48
  • 1
    This question could use a lot of elaboration. What is `chXBar`? Where is it set? How is this method called? If you are having trouble with code in a `switch` statement, where is the `switch` statement? – Peter Duniho Oct 31 '14 at 18:55
  • I've edited the question to include the whole function. I recall every progress bar being ranged 0 to 100, and indeed those same progress bars are getting changed properly elsewhere in the same thread without an invoke. In that *other* case the system decided not to throw the `InvalidOperation` exception that is the bane of my existence. – PrinceBilliard Nov 01 '14 at 20:11
  • Invoke() != BeginInvoke() – The incredible Jan Sep 19 '22 at 08:41

2 Answers2

1

Heres a minimal demo showing how to use bindings and INotifyPropertyChanged to update the display when the values are changed on another thread. There are a few deviations from best practice to keep it simple.

XAML:

<Window x:Class="WPFThreadDemo.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>
        <StackPanel>
            <ProgressBar Value="{Binding Channel1}" Height="100"/>
            <ProgressBar Value="{Binding Channel2}" Height="100"/>
        </StackPanel>

    </Grid>
</Window>

In the xaml, the Value property of each progress bar is bound to a property on the data context which provides the value to display. Each time the data context raises NotifyPropertyChanged for a property, the progress bars value will be updated from the data context.

C#:

Here we have a simple model class to represent the data being displayed by the progress bars, and a worker which updates the model. The code behind for the window instantiates a new model, uses it to create a new worker, and then starts the worker process.

I've used a dictionary to store the values to reduce the amount of copy pasting required to add new channels. Its not required; each one could be represented by a property with its own backing field.

using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Windows;

namespace WPFThreadDemo
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            // Create a new model and set the data context to point at it
            Model model = new Model();
            this.DataContext = model;

            // Set up a new worker to do some work on another thread and update the mode
            Worker worker = new Worker(model);
            worker.DoWork(100);
        }
    }


    public class Model : INotifyPropertyChanged
    {

        // Implementation of INotifyPropertyChanged
        // The progress bar will be hooked up to this event by the binding
        // When the event is raised with a name used in a binding, the model is queried
        // for the new value and the display is updated
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
            {
                var eventArgs = new PropertyChangedEventArgs(propertyName);
                handler(this, eventArgs);
            }
        }

        // Store for the channel values
        Dictionary<int, int> _channels = new Dictionary<int, int>();

        // Simple wrapper around the dictionary which creates a default value of 0 if it doesn't exist yet
        private int GetValue(int channel)
        {
            if (!_channels.ContainsKey(channel))
            {
                _channels[channel] = 0;
            }

            return _channels[channel];

        }

        // If the value is new or has changed, store it and raise property changed notification to update display
        public void SetValue(int channel, int value)
        {
            int oldValue;
            bool valueExists = _channels.TryGetValue(channel, out oldValue);

            // nothing to do if the value already exists and it hasn't changed
            if (valueExists && oldValue == value) return;

            _channels[channel] = value;
            RaisePropertyChanged("Channel" + channel.ToString());
        }


        // WPF binding works against public properties so we need to provide properties to bind against

        public int Channel1
        {
            get
            {
                return GetValue(1);
            }
            set
            {
                SetValue(1, value);
            }
        }

        public int Channel2
        {
            get
            {
                return GetValue(2);
            }
            set
            {
                SetValue(2, value);
            }
        }
    }

    // Simple worker mock which updates the model values on another thread
    public class Worker
    {
        Model _valueStore;

        public Worker(Model valueStore)
        {
            _valueStore = valueStore;
        }

        public void DoWork(int duration)
        {
            ThreadPool.QueueUserWorkItem(
                (x) =>
                {
                    for (int channel = 0; channel < 2; channel++)
                    {
                        for (int value = 0; value < 100; value++)
                        {
                            _valueStore.SetValue(channel + 1, value + 1);
                            Thread.Sleep(duration);
                        }
                    }
                });
        }
    }
}

Normally, I would use an interface for the worker and the model and create / inject them using whatever IOC container is in use for the current project to remove the tight coupling and improve testability, but this example is designed to be as simple as possible.

sga101
  • 1,904
  • 13
  • 12
  • I've pasted in the code you gave for the Model class, and expanded it to 8 channels like I have, and set the `{Binding ChannelX}` in each progress bar (with X from 1 to 8), and the code compiles with no warnings or errors, runs without exceptions, but the progress bar values still don't change. – PrinceBilliard Nov 06 '14 at 14:15
  • Nevermind! I just had to assign my instance of `Model` to `this.DataContext` in `MainWindow`'s constructor and it works. – PrinceBilliard Nov 06 '14 at 21:25
0

If you're using WPF, you really should be binding the progress bar value to a variable. WPF automatically handles cross-thread INotifyPropertyChanged events, tho not INotifyCollectionChanged.

Joel Lucsy
  • 8,520
  • 1
  • 29
  • 35