C#Dispatcher.Invoke不起作用

时间:2014-10-31 18:41:42

标签: c# wpf

我有一些代码尝试使用如下方法设置WPF中进度条的值:

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;
    }
}

每个chXBar,其中X是1到8,是一个单独的进度条。在另一个线程(使用Thread类手动创建)中的循环中调用此方法。循环一次设置一个通道值,并且非常慢(使用Thread.Sleep来减慢它)。

然而,这些调用似乎都不起作用;进度条上的值不会改变(它们始终保持为零)。代码编译很好,调试中没有抛出异常。我真的不想写8个代表和8个方法来使用它们。

有没有人有任何指示?

(P.S。我在Windows 7 x64上使用.NET 4.5)

2 个答案:

答案 0 :(得分:1)

下面是一个最小的演示,演示如何使用绑定和INotifyPropertyChanged在另一个线程上更改值时更新显示。与最佳做法有一些偏差,以保持简单。

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>

在xaml中,每个进度条的Value属性绑定到数据上下文中的属性,该属性提供要显示的值。每次数据上下文为属性引发NotifyPropertyChanged时,将从数据上下文更新进度条值。

C#:

这里我们有一个简单的模型类来表示进度条显示的数据,以及一个更新模型的工人。窗口后面的代码实例化一个新模型,用它来创建一个新工人,然后启动工作进程。

我使用字典来存储值,以减少添加新频道所需的复制粘贴量。它不是必需的;每个人都可以用具有自己的支持字段的属性来表示。

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);
                        }
                    }
                });
        }
    }
}

通常,我会为worker和model使用一个接口,并使用当前项目中使用的任何IOC容器来创建/注入它们,以消除紧耦合并提高可测试性,但是这个示例设计得如此简单尽可能。

答案 1 :(得分:0)

如果您正在使用WPF,那么您确实应该将进度条值绑定到变量。 WPF自动处理跨线程的INotifyPropertyChanged事件,而不是INotifyCollectionChanged。