从WPF中的计时器回调更新UI线程中的图像控件

时间:2015-10-30 19:37:31

标签: c# wpf multithreading asynchronous begininvoke

我在Xaml文件中有一个图像控件,如下所示:

<Viewbox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" VerticalAlignment="Top" HorizontalAlignment="Center">
    <Image Name="Content"/>
</Viewbox>

我想每隔10秒用不同的图像更新此图像。 我创建了一个system.threading.Timer实例,使用回调初始化它,将UI控件作为状态对象传递,并将间隔设置为10秒,如下所示:

contentTimer = new Timer(OnContentTimerElapsed, Content , 0 , (long) CONTENT_DISPLAY_TIME);

回调如下:

    private void OnContentTimerElapsed( object sender )
    {
        Image content = (Image)sender;

        //update the next content to be displayed
        nCurrentImage = (nCurrentImage % NUM_IMAGES) + 1;                        

        //get the url of the image file, and create bitmap from the jpeg
        var path = System.IO.Path.Combine(Environment.CurrentDirectory, "../../DisplayContent/Image_" + nCurrentImage.ToString() + ".jpg");
        Uri ContentURI = new Uri(path);
        var bitmap = new BitmapImage(ContentURI);

        //update the image control, by launching this code in the UI thread
        content.Dispatcher.BeginInvoke(new Action(() => { content.Source = bitmap; }));
    }

我仍然遇到以下异常:

An unhandled exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll
Additional information: The calling thread cannot access this object because a different thread owns it.

我能够通过更新numCurrentImage变量获得解决方案,然后在UI线程上运行的回调中更新MainWindow类中的Content.Source,如下所示(请注意,我在获取帧时)来自kinect的30fps):

    int nCurrentImage;
    Public MainWindow()
    {
        InitializeComponent();

        nCurrentImage = 1;

        System.Timers.Timer contentTimer = new System.Timers.Timer(OnContentTimerElapsed, CONTENT_DISPLAY_TIME);
        contentTimer.Elapsed += OnContentTimerElapsed;

        ...
        //Some kinect related initializations
        ...
        kinect.multiSourceReader.MultiSourceFrameArrived += OnMultiSourceFrameArrived;
    }

    private void OnContentTimerElapsed( object sender )
    {            
        //update the next content to be displayed
        nCurrentImage = (nCurrentImage % NUM_IMAGES) + 1;
    }

    private void OnMultiSourceFrameArrived(object sender, MultiSourceFrameArrivedEventArgs e)
    {
        UpdateContent(nCurrentImage);
    }

   private void UpdateContent(int numImage)
    {
        var path = System.IO.Path.Combine(Environment.CurrentDirectory, "../../DisplayContent/Image_" + numImage.ToString() + ".jpg");
        Uri ContentURI = new Uri(path);
        var bitmap = new BitmapImage(ContentURI);
        Content.Source = bitmap;
    }

即使这样有效,但是以这种方式更新它并没有很好的编程意义,因为一半的工作是由一个线程完成的,其余的是由UI线程完成的。

任何想法我做错了什么?

2 个答案:

答案 0 :(得分:1)

  

即使这样可行,但是这样做更难以编程,因为一半的工作是由一个线程完成的,其余部分是由UI线程完成的。

实际上这完全你想要做什么。您希望在非UI线程中执行非UI工作,并在UI线程中执行UI工作。

尽管如此,虽然这基本上是你想做的事情,但你不需要自己这么明确地完成所有这些。您可以简单地使用DispatcherTimer,它将在UI线程中触发回调,而不是线程池线程。它或多或少地做了很多你手动做的事情。

答案 1 :(得分:-1)

更新XAML图像元素,我喜欢用X命名它们,以提醒我它是一个XAML元素。

<Image Name="XContent"/>

当计时器触发时,

...
bitmap.Freeze();

XContent.Dispatcher.Invoke(()=>{
   XContent.Source = bitmap;
}