我正在开发一个实时 WPF/Silverlight(以及很快的 WP7)可视化组件,并且我正在寻找最佳解决方案来强制以游戏循环样式重绘整个组件。重绘应该是按需的,但我不想通过重绘调用来备份消息泵。我的组件中的大部分绘图都是使用非 WPF 原语(例如 Bitmap Interop、Direct2D)完成的,因此我的代码不使用 InvalidateVisual,因此,目前看起来像这样
// Pseudocode, doesnt compile, just to convey the meaning
public void InvalidateElement()
{
if (CurrentlyDrawing)
return;
Dispatcher.BeginInvoke(() =>
{
CurrentlyDrawing = true;
DoDrawInternal();
CurrentlyDrawing = false;
}
}
好的,这很棒。如果我多次调用 InvalidateElement,我会获得良好的响应能力。但是,我想要做的是确保我可以尽快将数据推送到可视化组件,但仅在组件能够绘制时进行绘制,而不是在输入流完成后继续绘制以赶上数据。
不,我无法覆盖 OnRender,我在 WPF 中使用非 WPF 绘图;-)
基本上我想要的是类似于 WindowsForms 中旧的 Invalidate() / OnPaint 的东西,或者更好的是 DirectX 中的游戏循环。
目前我遇到的情况是,如果我有一个外部线程以高速率将数据推送到可视化组件,那么如果我停止推送数据,我会在组件停止绘制之前再获得 20 秒的刷新时间。我想在数据进入后立即停止绘制。
我的另一个想法是在可视化组件中处理 CompositionTarget.Rendering,然后实现某种基本队列来推送数据,并且 Rendering 事件会尽快消耗这些数据。
总结
给定一个 WPF 可视化组件 V 和每 1 毫秒推送一次数据的数据源 D,我如何确保无论 D 的数据速率如何,V 都会以 30FPS(或其他任何速率)绘制数据。可以做到)并以块的形式更新自身,有点像游戏渲染循环在 DirectX 中的作用?
当数据停止时,V 应该一次性重新绘制到目前为止的所有内容。当数据太快时,V 一次绘制更大的块来补偿。
如果您需要更多信息,我很乐意分享。现在我刚刚发布了一个概要来衡量是否有任何快速修复,但可以根据要求提供更完整的问题和代码示例。
此致,
I'm working on a real-time WPF/Silverlight (and soon WP7) visualization component and I'm looking for the best solution to force a redraw of the entire component in a Game-loop style. Redraw should be on-demand, but I don't want to back up the message pump with re-draw calls. Most of the drawing in my component is done using non-WPF primitives (e.g. Bitmap Interop, Direct2D) so my code does not use InvalidateVisual, and as a result, currently looks like this
// Pseudocode, doesnt compile, just to convey the meaning
public void InvalidateElement()
{
if (CurrentlyDrawing)
return;
Dispatcher.BeginInvoke(() =>
{
CurrentlyDrawing = true;
DoDrawInternal();
CurrentlyDrawing = false;
}
}
Ok so this is great. If I call InvalidateElement lots of times I get good responsiveness. However, what I want to do is ensure I can push data to my visualization component as fast as possible but only draw when the component is able to draw, and not keep drawing to catch up with the data once the input stream completes.
No I can't override OnRender, I'm using non-WPF drawing inside WPF ;-)
Basically what I want is something like the old Invalidate() / OnPaint in WindowsForms, or better yet, a game loop in DirectX.
At the moment I get the situation where if I have an external thread that pushes data to the visualization component at a high rate then if I Stop pushing data I get another 20 seconds worth of refreshes to get through before the component stops drawing. I want to stop drawing as soon as data has gone in.
Another idea I had was to handle CompositionTarget.Rendering in the visualization component then implement some sort of rudimentary Queue to push data to and the Rendering event consumes this data as fast as it can.
In Summary
Given a WPF visualization component, V, and a datasource which pushes it data every 1ms, D, how can I ensure that no matter the datarate of D, V draws data at 30FPS (or whatever it can do) and updates itself in chunks, sort of how a game render loop does in DirectX?
When the data stops, V should redraw everything it has up to now in one go. When the data is too fast, V draws larger chunks at a time to compensate.
If you need more information I'd be happy to share it. Right now I've just posted a synopsis to gauge if there are any quick fixes but a fuller Q with code examples can be provided on request.
Best regards,
发布评论
评论(5)
我不确定如果您使用非 WPF 元素进行绘图并需要 WinForms 提供的 Invalidate() 方法,为什么您要在前端使用 WPF?不能直接将 UI 切换为使用 WinForms 吗?
I'm not sure why you would use WPF for your front-end if you're drawing using non-WPF elements and require the Invalidate() method that was provided by WinForms? Can't you just switch the UI to use WinForms?
您可能需要考虑在 CompositionTarget.Rendering 事件上进行渲染并在无效状态上进行限制。
Silverlight 游戏循环示例 (F#):
玩游戏: http://trelford.com/blog/post /LightCycles.aspx
阅读来源:https://bitbucket.org/ptrelford/lightcycles
You might want to consider rendering on the CompositionTarget.Rendering event and throttling on the invalidated state.
Silverlight game loop example (F#):
Play the game: http://trelford.com/blog/post/LightCycles.aspx
Read the source: https://bitbucket.org/ptrelford/lightcycles
您可以侦听
CompositionTarget.Rendering
事件(该事件在 WPF 渲染 UI 之前触发),并在其中进行绘图。另一个花絮.. InvalidateVisuals() 与 Form.Invalidate() 完全不同,因为它也会导致昂贵的重新布局。如果您想要类似 Form.Invalidate() 的内容,请创建一个 DrawingGroup(或位图图像)“backingStore”,在 OnRender() 期间将其放置在 DrawingContext 中,然后随时更新它。 WPF 将自动更新并重新绘制 UI。
You can listen to the
CompositionTarget.Rendering
event, which is triggered right before WPF renders the UI, and do your drawing in there.Another tidbit.. InvalidateVisuals() is nothing like Form.Invalidate(), as it also causes re-layout which is expensive. If you want something like Form.Invalidate(), then create a DrawingGroup (or bitmap image) "backingStore", place it in the DrawingContext during OnRender(), and then update it whenever you want. WPF will automatically update and repaint the UI.
您是否考虑过使用以 30FPS 运行的调度计时器,然后拍摄当前数据的快照并在每个计时器滴答处渲染它?如果您想在没有任何更改的情况下避免重绘,您可以简单地保留 LastChanged 和 LastRendered 的时间戳,仅在 LastChanged > 时才执行实际重绘。最后渲染。基本上更新数据和渲染数据是相互解耦的;主要技巧是确保当渲染线程想要渲染数据时,您可以以某种方式获得数据的连贯快照(即您需要某种锁定。)
Have you thought of using a dispatch timer running at 30FPS, then take a snapshot of the current data and rendering it at each timer tick? If you want to avoid redrawing if nothing has changed, you can simply keep timestamps for LastChanged and LastRendered, only performing an actual redraw if LastChanged > LastRendered. Basically updating the data and rendering the data are decoupled from one-another; the main trick is making sure you can somehow get a coherent snapshot of the data when the rendering thread wants to render it (i.e. you'll need some sort of locking.)
我最近正在处理一个需要类似游戏循环风格的项目。虽然我的示例纯粹是用 F# 编写的,但您也可以弄清楚如何在 C# 中做到这一点,可以使用一些互操作代码来初始化计时器并连接事件,如下面的链接
http://dl.dropbox.com/u/23500975/Demos/loopstate.zip
示例中没有展示如何重绘,只是每 500ms 更新一次底层股票数据,它几乎适用于 WPF 的任何类型的绘图机制。核心思想是使用可组合事件,在 F# 中,事件是一等公民 + IObservable(C# 的反应式扩展),因此我们可以轻松地组合函数,依次返回一组事件或单个事件。有一个函数 Observable.await,它接收一个 Observable 并且还有一个要返回的状态。
我只是在这里使用了一些 FP 术语,但它应该可以与正常的 C# (OO) 处理方式一起正常工作。
希望这有帮助!
——法赫德
I was recently working with a project that required a game loop like style. Although my example is purely in F#, you can figure it out how you can do that way in C# too, may be use some interop code to initialize the timer and hooking up events as given in this below link,
http://dl.dropbox.com/u/23500975/Demos/loopstate.zip
The sample doesn't show how to redraw, it just updates the underlying stock data for every 500ms, It should pretty much work for any kind of drawing mechanisms with WPF. The core idea is to use composable events, in F# an event is a first-class citizen + an IObservable (reactive extensions for C#), so we can easily compose functions that in-turn return a set of events or a single event. There is a function Observable.await, which takes in an Observable and also has a state to return.
I just used some of FP terms here, but it should work just fine with normal C# (OO) way of doing things here.
Hope this helps!
-Fahad