[英]What is missing in this update UI via dispatcher/databinding
我有一個簡單的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更新?
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
作為數據綁定源。 您可以使用任何對象,包括數組或列表。 在這種情況下,盡管您必須在代碼中引發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時更新視圖。
但是,當您需要更新冷卻時,這種方法效率不高。 ObservableCollection實現INotifyCollectionChanged接口,該接口將為每次更改引發一個事件。 如果添加新項目,則只會渲染該項目。
另一方面,您應該避免在緊密循環中修改集合,因為它將引發多個事件。 如果您加載了50個新商品,請勿循環調用“ Add
50次”。 創建一個新的ObservableCollection,替換舊的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
屬性將被替換並且本身不會引發任何事件
循環中未更新UI的原因是對Dispatcher.BeginInvoke
的調用。 這會將新的DispatcherOperation
放置在調度程序隊列中。 但是您的循環已經是調度程序操作,它在Dispatcher
的線程上繼續。 因此,您排隊的所有操作都將在循環操作完成后執行。
也許您想在后台線程上運行StartTest
? 然后,UI將更新。
順便說一句,不要用Thread.Sleep
阻塞Dispatcher
的線程。 它阻止Dispatcher
盡可能順利地執行其工作。
這是DoEvents
的事情, overhere :
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
甚至更好的https://stackoverflow.com/a/11899439/138078 。
當然,應該以不需要測試的不同方式編寫測試。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.