简体   繁体   English

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

[英]Updating Image control in UI thread from a timer callback in WPF

I have an image control in a Xaml file as follows: 我在Xaml文件中有一个图像控件,如下所示:

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

I'd like to update this image with a different image every 10 seconds. 我想每10秒用另一张图片更新此图片。 I create a system.threading.Timer instance, initialize it with a callback, pass in the UI control as a state object and set the interval to 10 seconds as follows: 我创建一个system.threading.Timer实例,使用回调对其进行初始化,将UI控件作为状态对象传递,并将时间间隔设置为10秒,如下所示:

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

The callback looks as follows: 回调如下所示:

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

I still keep getting the following exception: 我仍然不断收到以下异常:

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.

I was able to get a solution by updating just the numCurrentImage variable, and then updating the Content.Source in the MainWindow class in callbacks running on the UI thread, something as follows (note, I'm getting frames at 30fps from a kinect): 我可以通过只更新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;
    }

Even though that works, it just doesn't make good programming sense to update it that way, since half of the work is being done by one thread, and the rest by the UI thread. 即使这样行​​得通,以这种方式进行更新也没有很好的编程意义,因为一半的工作是由一个线程完成的,而其余的工作则由UI线程完成。

Any Ideas what I'm doing wrong? 有任何想法我在做什么错吗?

Even though that works, it just doesn't make good programming sense to update it that way, since half of the work is being done by one thread, and the rest by the UI thread. 即使这样行​​得通,以这种方式进行更新也没有很好的编程意义,因为一半的工作是由一个线程完成的,而其余的工作则由UI线程完成。

Actually this is exactly what you want to be doing. 实际上,这正是您要执行的操作。 You want to be doing your non-UI work in a non-UI thread, and doing your UI work in a UI thread. 您想在非UI线程中进行非UI工作,并在UI线程中进行UI工作。

That said, while this is fundamentally what you want to be doing, you don't need to do all of this yourself so explicitly. 就是说,虽然从根本上讲这就是您想要做的事情,但是您不必如此明确地自己做所有这一切。 You can simply use a DispatcherTimer and it will fire the callback in the UI thread, rather than a thread pool thread. 您可以简单地使用DispatcherTimer ,它将在UI线程而不是线程池线程中触发回调。 It is, more or less, doing much of what you're doing manually. 它或多或少地是在手动执行大部分工作。

Update the XAML image element, I like to name them all with an X to remind me it's a XAML element. 更新XAML图像元素,我想用X来命名它们,以提醒我这是XAML元素。

<Image Name="XContent"/>

When timer fires, 计时器启动时

...
bitmap.Freeze();

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM