从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线程完成。
你知道我做错了什么吗?
尽管这是有效的,但以这种方式更新它并没有很好的编程意义,因为一半的工作由一个线程完成,其余的由UI线程完成。
事实上,这正是你想要做的事情。您希望在非UI线程中完成非UI工作,并在UI线程中执行UI工作。
也就是说,虽然这从根本上是你想做的,但你不需要自己这么明确地做这一切。您可以简单地使用DispatcherTimer
,它将在UI线程中触发回调,而不是线程池线程。它或多或少是在做你正在手动做的大部分事情。
更新XAML图像元素,我喜欢用X来命名它们,以提醒我这是XAML元素。
<Image Name="XContent"/>
当定时器启动时,
...
bitmap.Freeze();
XContent.Dispatcher.Invoke(()=>{
XContent.Source = bitmap;
}