我有一个简单的WPF窗口:Loaded="StartTest"
和
<Grid>
<ListBox ItemsSource="{Binding Logging, IsAsync=True}"></ListBox>
</Grid>
我在方法StartTest
:
LogModel LogModel = new LogModel();
void StartTest(object sender, RoutedEventArgs e)
{
DataContext = LogModel;
for (int i = 1; i<= 10; i++)
{
LogModel.Add("Test");
Thread.Sleep(100);
}
}
班级LogModel
是:
public class LogModel : INotifyPropertyChanged
{
public LogModel()
{
Dispatcher = Dispatcher.CurrentDispatcher;
Logging = new ObservableCollection<string>();
}
Dispatcher Dispatcher;
public ObservableCollection<string> Logging { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void Add(string text)
{
Dispatcher.BeginInvoke((Action)delegate ()
{
Logging.Add(text);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Logging"));
});
}
}
当然问题是UI不会在循环中更新。
我错过了什么?
如何实现UI更新?
答案 0 :(得分:2)
ObservableCollection
在修改后已经引发了PropertyChanged事件。您也不必在UI线程中引发事件。
您的模型可以像以下一样简单:
class LogModel
{
public ObservableCollection<string> Logging { get; } = new ObservableCollection<string>();
public void Add(string text)
{
Logging.Add(text);
}
}
您需要做的只是将其设置为视图的DataContext
,例如:
LogModel model = new LogModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = model;
}
我认为StartTest
是一个点击处理程序,这意味着它在UI线程上运行。这意味着它将阻止 UI线程,直到循环结束。一旦循环结束,UI将被更新。
如果您希望UI在循环期间保持响应,请使用Task.Delay
而不是Thread.Slepp,例如:
private async void Button_Click(object sender, RoutedEventArgs e)
{
for(int i=0;i<10;i++)
{
await Task.Delay(100);
model.Add("Blah!");
}
}
<强>更新强>
您不需要使用ObservableCollection
作为数据绑定源。您可以使用任何对象,包括数组或List。在这种情况下,您必须在代码中引发PropertyChanged事件:
class LogModel:INotifyPropertyChanged
{
public List<string> Logging { get; } = new List<string>();
public event PropertyChangedEventHandler PropertyChanged;
public void Add(string text)
{
Logging.Add(text);
PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Logging"));
}
}
这将告诉视图加载所有内容并再次显示它们。当你只想显示从数据库加载的数据而不修改它们时,这是完全没问题的,因为它使得映射实体更容易实现ViewModels。在这种情况下,您只需在作为命令结果附加新ViewModel时更新视图。
当你需要更新calection时,这不有效。 ObservableCollection实现了INotifyCollectionChanged接口,该接口为每次更改引发事件。如果添加新项目,则只会呈现该项目。
另一方面你应该避免在紧密循环中修改集合,因为它会引发多个事件。如果您加载了50个新项目,请不要在循环中拨打Add
50次。创建一个新的ObservableCollection,替换旧的并引发PropertyChanged事件,例如:
class LogModel:INotifyPropertyChanged
{
public ObservableCollection<string> Logging { get; set; } = new ObservableCollection<string>();
public event PropertyChangedEventHandler PropertyChanged;
public void Add(string text)
{
Logging.Add(text);
PropertyChanged.Invoke(this,new PropertyChangedEventArgs("Logging"));
}
public void BulkLoad(string[] texts)
{
Logging = new ObservableCollection<string>(texts);
PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Logging"));
}
}
仍然需要显式实现,因为Logging
属性已被替换,并且不能自行引发任何事件
答案 1 :(得分:1)
在循环中未更新UI的原因是对Dispatcher.BeginInvoke
的调用。这会在调度程序队列中放置一个新的DispatcherOperation
。但是你的循环已经是一个调度程序操作,它继续在Dispatcher
的线程上。因此,排队的所有操作都将在循环操作完成后执行。
也许你想在后台线程上运行StartTest
?然后,UI将更新。
顺便说一句,请勿使用Dispatcher
阻止Thread.Sleep
的主题。它可以防止Dispatcher
尽可能顺利地完成任务。
答案 2 :(得分:0)
这是DoEvents
事,overhere:
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
甚至可能更好https://stackoverflow.com/a/11899439/138078。
当然,测试应该以不需要的方式进行不同的编写。