WPF 交替执行两个 UI 任务
WPF Executing two alternating UI tasks
我有一个 WPF UI,里面有 Helix3DToolkit Graphics。
那里有两个按钮,
第一个按钮 - 向其中添加一些 Visual3D 对象。
第二个按钮 - 使用
将当前 UI 的屏幕截图保存为 jpeg 图像
截图代码:
Viewport3DHelper.Export(vp.Viewport, filename);
所有这一切手动工作正常。但现在我想把它变成自动的。 (按钮 1 和 2 的几轮交替动作。即我只想按一个按钮,这将添加一些框,保存屏幕截图,添加更多,保存屏幕截图等)
我可以使用以下代码以编程方式控制按钮:
ButtonAutomationPeer peer = new ButtonAutomationPeer(MyButton);
IInvokeProvider invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
invokeProv.Invoke();
但是当我这样做时,我得到的只是相同内容的几份 UI。 (这是由于 UI 线程的性质,我相信)
我找到了一个部分解决方案,就是使用,
TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();
CancellationToken cancellationToken = new CancellationToken();
Task.Factory.StartNew(() => GetScreenShotAction()).ContinueWith(w =>
{
AddBoxes();
}, cancellationToken, TaskContinuationOptions.None, scheduler);
但是,这也不能正常工作(它随机工作,当我只截取 2 个屏幕截图时,但从不超过 2 个)。这可能是因为之前和之后的操作都在 UI 线程上。
任何关于如何完成这项工作的想法都将不胜感激。
我还使用 VC# 2010 和 .NET 4 框架。所以不能使用 async
和 await
.
谢谢!
更新
我已经在 GetScreenShotAction
方法中使用 this.Dispatcher.Invoke((Action)(() => { ... }));
。并且,另一个操作 AddBoxes
在同一个线程上。
此外,它是随机工作的,所以我猜测是因为我正在调用 Dispatcher,所以两者在技术上仍在同一个线程上发生。
您使用 Task
来进行多线程操作是正确的。问题是您需要 Invoke
您的代码在正确的 Thread
上。使用 Dispatcher
(又名 WPF UI 线程)通过 Application.Current.Dispatcher
通过根据需要调用 Invoke()
或 BeginInvoke()
来执行此操作。
我怀疑您需要等待 UI 呈现(在通过添加按钮修改可视化树之后),然后再截取屏幕截图。
This article 展示了如何使用带有 DispatcherPriority.Render
的调度程序来获取代码以等待所有渲染完成后再继续。
引用文章:
When the DispatcherPriority is set to Render (or lower), the code will
then execute all operations that are of that priority or higher. In
the example, the code already sets label1.Content to something else,
which will result in a render operation. So by calling
Dispatcher.Invoke, the code essentially asks the system to execute all
operations that are Render or higher priority, thus the control will
then render itself (drawing the new content). Afterwards, it will
then execute the provided delegate (which is our empty method).
刷新扩展方法的代码:
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
}
像这样使用它:
AddBoxes();
MyControlContainer.Refresh();
TakeScreenShot();
其中 MyControlContainer
是一个 UIElement
,也许是您添加盒子的容器?
我有一个 WPF UI,里面有 Helix3DToolkit Graphics。
那里有两个按钮,
第一个按钮 - 向其中添加一些 Visual3D 对象。
第二个按钮 - 使用
将当前 UI 的屏幕截图保存为 jpeg 图像
截图代码:
Viewport3DHelper.Export(vp.Viewport, filename);
所有这一切手动工作正常。但现在我想把它变成自动的。 (按钮 1 和 2 的几轮交替动作。即我只想按一个按钮,这将添加一些框,保存屏幕截图,添加更多,保存屏幕截图等)
我可以使用以下代码以编程方式控制按钮:
ButtonAutomationPeer peer = new ButtonAutomationPeer(MyButton);
IInvokeProvider invokeProv = peer.GetPattern(PatternInterface.Invoke) as IInvokeProvider;
invokeProv.Invoke();
但是当我这样做时,我得到的只是相同内容的几份 UI。 (这是由于 UI 线程的性质,我相信)
我找到了一个部分解决方案,就是使用,
TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();
CancellationToken cancellationToken = new CancellationToken();
Task.Factory.StartNew(() => GetScreenShotAction()).ContinueWith(w =>
{
AddBoxes();
}, cancellationToken, TaskContinuationOptions.None, scheduler);
但是,这也不能正常工作(它随机工作,当我只截取 2 个屏幕截图时,但从不超过 2 个)。这可能是因为之前和之后的操作都在 UI 线程上。
任何关于如何完成这项工作的想法都将不胜感激。
我还使用 VC# 2010 和 .NET 4 框架。所以不能使用 async
和 await
.
谢谢!
更新
我已经在 GetScreenShotAction
方法中使用 this.Dispatcher.Invoke((Action)(() => { ... }));
。并且,另一个操作 AddBoxes
在同一个线程上。
此外,它是随机工作的,所以我猜测是因为我正在调用 Dispatcher,所以两者在技术上仍在同一个线程上发生。
您使用 Task
来进行多线程操作是正确的。问题是您需要 Invoke
您的代码在正确的 Thread
上。使用 Dispatcher
(又名 WPF UI 线程)通过 Application.Current.Dispatcher
通过根据需要调用 Invoke()
或 BeginInvoke()
来执行此操作。
我怀疑您需要等待 UI 呈现(在通过添加按钮修改可视化树之后),然后再截取屏幕截图。
This article 展示了如何使用带有 DispatcherPriority.Render
的调度程序来获取代码以等待所有渲染完成后再继续。
引用文章:
When the DispatcherPriority is set to Render (or lower), the code will then execute all operations that are of that priority or higher. In the example, the code already sets label1.Content to something else, which will result in a render operation. So by calling Dispatcher.Invoke, the code essentially asks the system to execute all operations that are Render or higher priority, thus the control will then render itself (drawing the new content). Afterwards, it will then execute the provided delegate (which is our empty method).
刷新扩展方法的代码:
public static class ExtensionMethods
{
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
}
像这样使用它:
AddBoxes();
MyControlContainer.Refresh();
TakeScreenShot();
其中 MyControlContainer
是一个 UIElement
,也许是您添加盒子的容器?