我必须在面板中加载许多用户控件。如何使用async / await方法为延迟加载添加它而不冻结ui?
foreach (var item in parsedValues)
{
_addUser = new MaterialCircular(item.Title,item.Category,item.Type,item.SubType,item.Date, AppVariable.GetBrush(Convert.ToString(FindElement.Settings[AppVariable.ChartColor] ?? AppVariable.CHART_GREEN)));
_currentUser = _addUser;
waterfallFlow.Children.Add(_currentUser);
}
答案 0 :(得分:1)
一般来说,没有一种特殊的“等待”方法可以将控件加载到UI中,因此可以在后台完成并解冻UI。最终,所有UI更新都按顺序在主UI线程上执行。话虽这么说,你可以有多个线程和/或任务(不是说两者都是相同的)通过UI Dispatcher向UI发布更改。
加载大量控件肯定会使主UI线程超载,以至于UI会在WPF处理工作负载时冻结。
这是一个潜在的解决方案,展示了如何使用等待的任务通过调度程序加载(和限制)所有控件,而不会冻结UI。对社区反馈也非常感兴趣,因为我过去一直在努力解决这个问题。
此解决方案需要您进行实验和调整。这是基于以下观察:WPF在添加大量子控件方面肯定很慢,并且假设 UI冻结是由于UI线程忙于处理添加控件而不是处理鼠标事件;所以这个解决方案基于限制。
的Xaml:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="500">
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button Content="Load Controls" Click="Button1_Click"></Button>
<Label>Status:</Label>
<Label Name="lbl_Status"></Label>
</StackPanel>
<ScrollViewer Name="scroll">
<StackPanel Orientation="Vertical" Name="panel"></StackPanel>
</ScrollViewer>
</DockPanel>
和关联的C#,加载10,000个文本框
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public partial class MainWindow : Window
{
private volatile bool _waitForDispatcher = false;
public MainWindow()
{
InitializeComponent();
// we use this hook to detect when the dispatcher queue gets empty
// so we can better adjust our throttling to add the controls to the panel
this.Dispatcher.Hooks.DispatcherInactive += (object s1, EventArgs e1) =>
{
this._waitForDispatcher = false;
};
}
private async void Button1_Click(object sender, RoutedEventArgs e)
{
// doesn't have to be awaited if that's all this click handler does
// but if this function needs to do other things to the UI after this task completes
// then you definitely must await this task.
await Task.Run(() =>
{
_waitForDispatcher = true;
for (int i = 0; i< 10000; i++)
{
this.Dispatcher.Invoke(() => {
// add the control.
TextBox tb = new TextBox() { Text = "Text box " + i, Width = 100, Height = 22, Margin = new Thickness(0, 0, 0, 10) };
this.panel.Children.Add(tb);
this.lbl_Status.Content = "Added item " + i; // will provide a good indicator on the screen for UI freeze, especially as you resize the app
});
// throttle: for every 50 controls that we add, we wait for the dispatcher queue to clear.
// but limit that to 50 ms so floods of mouse events don't take over and prevent the controls from loading
if ((i % 50) == 0)
{
int j = 0;
while (_waitForDispatcher && j++ < 12) // limit to 50 ms to make sure we don't let busy mouse events block it all.
{
Thread.Sleep(5); // tweak this. Gives time for the dispatcher queue to flush
}
Console.WriteLine("Waited " + (j * 5) + " ms for dispatcher");
_waitForDispatcher = true;
}
}
});
}
}
}
对社区的建设性反馈非常感兴趣。注意:Dispatcher.BeginInvoke()比Dispatcher.Invoke()差得多 - 万一人们想知道。