WPF中ItemsControl容器生成前后稳定切换主题资源字典方案问询
针对你遇到的「修改MergedDictionaries时的StartAt异常」和「Dispatcher调用后资源找不到」的问题,结合你用WindowsFormsApplicationBase做单实例、无App.xaml的场景,给你几个经过实践验证的稳定方案:
1. 提前预加载所有资源字典,避免延迟加载导致的资源缺失
你当前的GetXXXDictionary方法是懒加载逻辑,切换主题时才去加载XAML资源,这会导致Dispatcher调用时资源还未加载完成,进而出现StaticResource找不到的问题。建议在App启动时就把三个资源字典全部预加载完成:
public class App : System.Windows.Application { internal ResourceDictionary MLight { get; private set; } internal ResourceDictionary MDark { get; private set; } internal ResourceDictionary MMain { get; private set; } protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); // 启动时预加载所有资源字典,彻底避免切换时的加载延迟 MMain = new ResourceDictionary() { Source = new Uri("AppResources.xaml", UriKind.Relative) }; MLight = new ResourceDictionary() { Source = new Uri("Themes/Light.xaml", UriKind.Relative) }; MDark = new ResourceDictionary() { Source = new Uri("Themes/Dark.xaml", UriKind.Relative) }; // 初始化全局资源集合 Resources.MergedDictionaries.Clear(); Resources.MergedDictionaries.Add(MMain); var w = new MainWindow(); // 绑定窗口ContentRendered事件,确保首次主题切换在UI完全就绪后执行 w.ContentRendered += (s, args) => LoadTheme(AppTheme.Light); ShutdownMode = ShutdownMode.OnMainWindowClose; MainWindow = w; w.Show(); } }
2. 选择正确的Dispatcher优先级,避开UI内容生成阶段
直接修改MergedDictionaries时的StartAt异常,本质是UI元素(比如ItemsControl)正在生成容器,此时修改全局资源会打断WPF的内容生成流程。改用DispatcherPriority.ContextIdle优先级,这个优先级会等当前UI上下文的所有渲染、容器生成工作完成后再执行,完美避开冲突:
internal void LoadTheme(AppTheme t) { // 用ContextIdle优先级,确保UI生成完成后再修改资源 Dispatcher.BeginInvoke(new Action(() => { lock (this) // 加锁避免多线程同时调用导致的状态混乱 { var targetThemeDict = t == AppTheme.Dark ? MDark : MLight; // 先移除已有的主题字典(如果存在) var existingThemeDict = Resources.MergedDictionaries.FirstOrDefault(d => d == MDark || d == MLight); if (existingThemeDict != null) { Resources.MergedDictionaries.Remove(existingThemeDict); } // 添加目标主题字典 Resources.MergedDictionaries.Add(targetThemeDict); } }), System.Windows.Threading.DispatcherPriority.ContextIdle); }
3. 基于窗口ContentRendered事件控制首次主题切换时机
如果是窗口刚启动时的首次主题切换,建议等到窗口的ContentRendered事件触发后再执行——这个事件是WPF确认窗口所有内容都渲染完成的标志,此时修改资源绝对不会打断内容生成流程。
为什么你的测试示例没复现问题?
你提到测试示例每次都设置ItemsSource,这会触发ItemsControl重新生成所有容器,相当于每次都是“全新”的生成流程;而实际项目中ItemsSource是绑定或手动设置,容器生成是持续的(比如滚动加载更多项),此时修改资源更容易撞上内容生成的临界期,所以会出现测试时没问题、实际项目报错的情况。
内容的提问来源于stack exchange,提问作者silviubogan

