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

本文关键字:线程 图像 控件 UI 回调 WPF 计时器 更新 | 更新日期: 2023-09-27 18:19:54

我在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线程完成。

你知道我做错了什么吗?

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

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

事实上,这正是你想要做的事情。您希望在非UI线程中完成非UI工作,并在UI线程中执行UI工作。

也就是说,虽然这从根本上是你想做的,但你不需要自己这么明确地做这一切。您可以简单地使用DispatcherTimer,它将在UI线程中触发回调,而不是线程池线程。它或多或少是在做你正在手动做的大部分事情。

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

<Image Name="XContent"/>

当定时器启动时,

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