此更新 UI 通过 dispatcher/databinding 缺少什么
What is missing in this update UI via dispatcher/databinding
我有一个简单的 WPF window: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);
}
}
而classLogModel
是:
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 更新?
循环中UI没有更新的原因是对Dispatcher.BeginInvoke
的调用。这会在调度程序队列中放置一个新的 DispatcherOperation
。但是你的循环已经是一个调度程序操作,它在 Dispatcher
的线程上继续。所以你排队的所有操作都会在循环操作完成后执行。
也许您想在后台线程上 运行 StartTest
?然后,UI 将更新。
顺便说一下,不要用Thread.Sleep
阻塞Dispatcher
的线程。它会阻止 Dispatcher
尽可能顺利地完成任务。
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"));
}
}
这将告诉视图加载所有 内容并再次显示它们。当您只想显示从数据库加载的数据而不修改它们时,这非常好,因为它使映射实体到 ViewModel 变得容易得多。在这种情况下,您只需在通过命令附加新的 ViewModel 时更新视图。
当您需要更新冷却器时,这不是有效的。 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
属性 正在被替换并且本身不能引发任何事件
是DoEvents
的东西,overhere:
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
甚至可能更好。
当然应该以不需要它的方式编写不同的测试。
我有一个简单的 WPF window: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);
}
}
而classLogModel
是:
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 更新?
循环中UI没有更新的原因是对Dispatcher.BeginInvoke
的调用。这会在调度程序队列中放置一个新的 DispatcherOperation
。但是你的循环已经是一个调度程序操作,它在 Dispatcher
的线程上继续。所以你排队的所有操作都会在循环操作完成后执行。
也许您想在后台线程上 运行 StartTest
?然后,UI 将更新。
顺便说一下,不要用Thread.Sleep
阻塞Dispatcher
的线程。它会阻止 Dispatcher
尽可能顺利地完成任务。
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"));
}
}
这将告诉视图加载所有 内容并再次显示它们。当您只想显示从数据库加载的数据而不修改它们时,这非常好,因为它使映射实体到 ViewModel 变得容易得多。在这种情况下,您只需在通过命令附加新的 ViewModel 时更新视图。
当您需要更新冷却器时,这不是有效的。 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
属性 正在被替换并且本身不能引发任何事件
是DoEvents
的东西,overhere:
public static void DoEvents()
{
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
new Action(delegate { }));
}
甚至可能更好。
当然应该以不需要它的方式编写不同的测试。