專案需實作硬體偵測功能,後台執行緒持續將偵測結果加入集合,但 UI 介面 (ListBox) 始終不顯示新資料。嘗試使用 DispatcherTimer 搭配 UIHelper.DoEvents() 強制重繪,仍無效果。
網路上常見的 DoEvents 實作:
public static void DoEvents() {
DispatcherFrame nestedFrame = new DispatcherFrame();
DispatcherOperation exitOperation = Dispatcher.CurrentDispatcher.BeginInvoke(
DispatcherPriority.Background, new DispatcherOperationCallback(ExitFrame), nestedFrame);
Dispatcher.PushFrame(nestedFrame);
if (exitOperation.Status != DispatcherOperationStatus.Completed) exitOperation.Abort();
}
private static object ExitFrame(object state) {
((DispatcherFrame)state).Continue = false; return null;
}
在資料新增後呼叫 UIHelper.DoEvents(),但 ListBox 依然不更新。根因在於 資料來源集合未實作變更通知,而非 UI 執行緒阻塞。
List<T> 不實作 INotifyCollectionChanged,UI 綁定無法感知項目增減。必須改用 ObservableCollection<T> (或 BindingList<T>),其內建 CollectionChanged 事件會自動通知綁定目標重繪。
public partial class Window2 : Window {
DispatcherTimer _mainTimer;
ObservableCollection<DeviceCheckInfo> list;
public Window2() {
InitializeComponent();
_mainTimer = new DispatcherTimer();
_mainTimer.Interval = TimeSpan.FromSeconds(1);
_mainTimer.Tick += _mainTimer_Tick;
_mainTimer.IsEnabled = true;
}
void _mainTimer_Tick(object sender, EventArgs e) {
if (progressBar1.Value == progressBar1.Maximum) progressBar1.Value = 0;
progressBar1.Value++;
DeviceCheckInfo device = new DeviceCheckInfo {
CheckResult = true,
Name = "發卡器" + progressBar1.Value,
CheckContent = "打卡短短"
};
Dispatcher.BeginInvoke(new Action(() => {
if (list != null) list.Add(device);
// lbtest.ItemsSource = list; // 已在 Loaded 設定,無需重複指派
}));
}
private void Window_Loaded(object sender, RoutedEventArgs e) {
list = new ObservableCollection<DeviceCheckInfo>() {
new DeviceCheckInfo { Name="三合一讀卡器", CheckContent="duankou", CheckResult=true },
new DeviceCheckInfo { Name="發卡器", CheckContent="tongdao", CheckResult=false },
new DeviceCheckInfo { Name="打印機", CheckContent="youzhi", CheckResult=true }
};
lbtest.ItemsSource = list;
}
private void button1_Click(object sender, RoutedEventArgs e) {
DeviceCheckInfo device = new DeviceCheckInfo {
CheckResult = true,
Name = "發卡器" + progressBar1.Value,
CheckContent = "打卡短短"
};
list.Add(device); // 直接新增,UI 自動更新
}
}
public class DeviceCheckInfo {
public string Name { get; set; }
public string CheckContent { get; set; }
public bool CheckResult { get; set; }
}
<ListBox Name="lbtest" /> <ProgressBar Name="progressBar1" Maximum="100" Height="20" />
| 集合類型 | 實作介面 | UI 綁定自動更新 | 適用場景 |
|---|---|---|---|
| List<T> | IList, ICollection | 否 | 純後台資料處理,無 UI 綁定 |
| ObservableCollection<T> | INotifyCollectionChanged, IList | 是 (WPF/Silverlight/WinRT) | WPF/UI 綁定首選 |
| BindingList<T> | IBindingList, INotifyPropertyChanged | 是 (WinForms/WPF) | 跨平台或需排序/篩選 |
WPF 資料綁定的核心在於「通知機制」。永遠不要為了刷新 UI 而呼叫 DoEvents,這會導致重進入風險與效能問題。正確做法是:資料來源使用實作 INotifyCollectionChanged 的集合 (如 ObservableCollection),並在 UI 執行緒上修改集合,綁定引擎會自動處理視覺樹更新。本例中將 List<DeviceCheckInfo> 改為 ObservableCollection<DeviceCheckInfo> 即徹底解決介面不更新問題。