如何让WPF应用等待UI线程完全加载窗口后再执行后续处理?
解决方案
核心思路是放弃固定时长的定时器,改用WPF元素的Loaded事件监听窗口/视图的加载完成状态,确保所有视图完全渲染后再启动快照逻辑。
修改后的代码实现
private int _loadedViewsCount; private int _totalViewsToLoad; protected override void DoStartCapture() { try { m_dispatcherTimers = new List<DispatcherTimer>(); _loadedViewsCount = 0; _totalViewsToLoad = m_mimicGroupsToCapture.Sum(g => g.Mimics.Count); // 先创建所有定时器但暂不启动 foreach (var mimicGroup in m_mimicGroupsToCapture) { var dispatcherTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(Convert.ToInt32(mimicGroup.CaptureIntervalInSeconds)), Tag = mimicGroup }; dispatcherTimer.Tick += M_mainDispatcherTimer_Tick; m_dispatcherTimers.Add(dispatcherTimer); } // 打开视图并绑定加载完成事件 foreach (var mimicGroup in m_mimicGroupsToCapture) { foreach (var mimic in mimicGroup.Mimics) { var view = OpenView(mimic.View, 2); if (view is FrameworkElement frameworkElement) { // 绑定Loaded事件,触发后解绑避免重复计数 frameworkElement.Loaded += OnViewLoaded; } else { // 如果OpenView不返回视图对象,通过服务获取并绑定事件 Dispatcher.BeginInvoke(DispatcherPriority.Background, () => { var targetView = ViewsIntegrationService.GetOpenViews() .FirstOrDefault(v => v.ViewId == mimic.View.ViewId); // 假设视图有唯一标识 if (targetView is FrameworkElement elem) { elem.Loaded += OnViewLoaded; } else { Trace.TraceWarning($"Failed to locate view for {mimic.View.Name}"); MarkViewAsLoaded(); } }); } } } // 处理无视图需要加载的情况 if (_totalViewsToLoad == 0) { m_openViews = ViewsIntegrationService.GetOpenViews(); StartAllTimers(); return; } // 添加超时机制,防止因视图加载失败卡住流程 var timeoutTimer = new DispatcherTimer { Interval = TimeSpan.FromMinutes(2) }; timeoutTimer.Tick += (s, e) => { timeoutTimer.Stop(); if (_loadedViewsCount < _totalViewsToLoad) { Trace.TraceWarning($"View loading timed out. Loaded {_loadedViewsCount}/{_totalViewsToLoad} views"); } m_openViews = ViewsIntegrationService.GetOpenViews(); StartAllTimers(); }; timeoutTimer.Start(); } catch (Exception ex) { Trace.TraceError("View Capture Service error. Exception: " + ex.Message); StopCapture(); throw; } } private void OnViewLoaded(object sender, RoutedEventArgs e) { if (sender is FrameworkElement frameworkElement) { frameworkElement.Loaded -= OnViewLoaded; // 解绑事件避免重复触发 } MarkViewAsLoaded(); } private void MarkViewAsLoaded() { _loadedViewsCount++; if (_loadedViewsCount == _totalViewsToLoad) { m_openViews = ViewsIntegrationService.GetOpenViews(); StartAllTimers(); } } private void StartAllTimers() { foreach (var timer in m_dispatcherTimers) { timer.Start(); } }
关键修改说明
改用Loaded事件跟踪加载状态
WPF的Loaded事件会在元素的可视化树构建完成、布局计算和渲染全部完成后触发,是判断窗口完全加载的可靠依据,替代原来的固定时长定时器,避免因加载时长波动导致快照提前执行。统计视图加载进度
先计算需要加载的视图总数,每一个视图触发Loaded事件后就计数,当计数等于总数时,再启动所有快照定时器。兼容OpenView的两种返回情况
- 如果
OpenView直接返回视图对象,直接绑定Loaded事件 - 如果
OpenView不返回对象,通过ViewsIntegrationService获取视图,并利用Dispatcher.BeginInvoke确保在UI队列的后续环节获取到已打开的视图。
- 如果
添加超时机制
防止因个别视图加载失败导致流程卡住,设置2分钟超时,超时后强制启动快照逻辑并记录警告日志。
内容的提问来源于stack exchange,提问作者Ray
相关产品推荐
相关产品推荐

