.NET MAUI迁移后查看历史日志触发COM组件未检测到异常
.NET MAUI迁移后切换日志页触发COMException异常
我们将旧Xamarin.Forms项目迁移至.NET MAUI后,应用能正常启动并完成登录,但在登录后的用户操作日志记录页点击箭头查看前一日志时,SetModelBinding方法中触发System.Runtime.InteropServices.COMException异常,错误信息为No installed components were detected,错误发生在代码标注位置。
脱敏代码
public DailyLogPage(int dailylogid = 0) { dlog = new DailyLogControl(); myid = Guid.NewGuid(); loadingControl = new LoadingControl(); this.Title = "Daily Log"; IconImageSource = "Navigation_Add.png"; this.Disappearing += MainViewPage_Disappearing; BasePage.InitPage(this); SetDataRepository(); InitializeCriteria(); CreateDailyLogUI(); CreatePageActions(); SetMessagingCenterSubscriptions(); SetHomeOpco(); AutoSaveAndSync(); } #region UICode private void CreateDailyLogUI() { CreateTxtDevice(); CreateGrid(); CreateMainScrollView(); stackBody.SetBinding(StackLayout.BackgroundColorProperty, "PageColor.BodyBackground"); this.SetBinding(ContentPage.BackgroundColorProperty, "PageColor.PageBackground"); SetModelBinding(dailylogCriteria.DailyLog); } private void CreateTxtDevice() { txtDevice = new AutoCompleteView { Placeholder = "Enter text", ShowSearchButton = false, SuggestionsHeightRequest = 120, SelectedCommand = new Command(() => { }), }; txtDevice.EntText.IsVisible = false; } private void CreateGrid() { AddButton = new Button { HeightRequest = 45, ImageSource = "add_data.png", BackgroundColor = Colors.Transparent }; AddButton.Clicked += AddButton_Clicked; sideStack = new StackLayout(); grid = new Grid { ColumnDefinitions = { new ColumnDefinition { Width = new GridLength(.05, GridUnitType.Star) }, new ColumnDefinition { Width = new GridLength(.5, GridUnitType.Star) }, new ColumnDefinition { Width = new GridLength(9.45, GridUnitType.Star) }, } }; grid.Children.Add(sideStack); Grid.SetColumn(sideStack, 0); sideStack.Children.Add(AddButton); grid.Children.Add(sideStack); Grid.SetColumn(sideStack, 1); } private void CreateMainScrollView() { dailyLogHeader = new DailyLogStepControlHeader() { IsClippedToBounds = true }; stack = new StackLayout { }; var scrollView = new StackLayout { Children = { stack } }; dailyLogSyncControl = new DailyLogSyncControl { VerticalOptions = LayoutOptions.End, IsClippedToBounds = true }; //Create StackBody stackBody = new StackLayout { Padding = new Thickness(5, 5, 5, 30), Children = { dailyLogHeader, scrollView } }; //Create StackMain stackmain = new StackLayout { Padding = new Thickness(7), Children = { dlog, stackBody, dailyLogSyncControl } }; //Create MainScrollView mainScrollView = new ScrollView { Content = stackmain }; Content = mainScrollView; } private void CreatePageActions() { #region actions //wire up the next & previous page action button commands here Action NextPage = async () => { if (!model.DailyLog.ValidData) { await DisplayAlert(UIConstants.TITLE, "Location & Purpose are required.", "Ok"); return; } await model.SaveData(true); if (dailylogCriteria.HasNext) { dailylogCriteria = _data.GetNextDailyLog(model.DailyLog.DailyLogId); } else { var newDlog = CreateDailyLog(false); await _data.SaveDailyLog(newDlog); dailylogCriteria = _data.GetLastDailyLog(); } SetModelBinding(dailylogCriteria.DailyLog); }; Action PreviousPage = async () => { await model.SaveData(true); if (dailylogCriteria.HasPrevious) { dailylogCriteria = _data.GetPreviousDailyLog(model.DailyLog.DailyLogId); } else { return; } SetModelBinding(dailylogCriteria.DailyLog); }; Action<int> GotoPage = (pageid) => { //CAN THIS FUNCTION EVER BE RUN? //await model.SaveData(true); //if (pageid > 0 && pageid <= dailylogCriteria.TotalDailyLogs) //{ // SetModelBinding(model.DailyLog); //} }; #endregion dlog.SetNextPage(NextPage); dlog.SetPreviousPage(PreviousPage); dlog.SetGotoPage(GotoPage); dlog.IsClippedToBounds = true; } private DailyLog CreateDailyLog(bool isLogin) { var newDlog = new DailyLog(); //This is redacted data. just setting DailyLog properties to user data(ie, username, opco, etc) return newDlog; } private void SetModelBinding(DailyLog dailyLog) { stackmain.Children.Remove(dlog); model = new DailyLogViewModel(dailyLog) { IsFetching = true, PageCount = dailylogCriteria.TotalDailyLogs, PageNo = dailylogCriteria.CurrentPage }; BindingContext = model; stackmain.Children.Insert(0, dlog); <---- 错误触发位置 stack.Children.Clear(); ToggleLoading(); //remove old controls Task.Delay(300).ContinueWith((t) => { this.Dispatcher.Dispatch(async () => { try { await FetchData(); } catch { //try one more attempt from sql lite await ReFetchData(); } }); }); }
问题分析与修复方案
核心原因
这个异常源于MAUI控件生命周期管理的兼容性问题:
- 复用同一个
DailyLogControl实例反复从可视化树中移除/插入,导致MAUI底层COM容器的控件状态混乱; - 控件操作未严格遵循MAUI的UI线程规范,即使当前操作在主线程,异步回调的时序也可能引发控件状态不一致。
修复步骤
- 创建新控件实例替代复用
在移除旧dlog后,重新创建DailyLogControl实例并绑定命令,避免复用旧实例的生命周期问题:private void SetModelBinding(DailyLog dailyLog) { stackmain.Children.Remove(dlog); // 重新创建控件实例 dlog = new DailyLogControl(); // 重新绑定操作命令 dlog.SetNextPage(NextPage); dlog.SetPreviousPage(PreviousPage); dlog.SetGotoPage(GotoPage); dlog.IsClippedToBounds = true; model = new DailyLogViewModel(dailyLog) { IsFetching = true, PageCount = dailylogCriteria.TotalDailyLogs, PageNo = dailylogCriteria.CurrentPage }; BindingContext = model; stackmain.Children.Insert(0, dlog); stack.Children.Clear(); ToggleLoading(); Task.Delay(300).ContinueWith((t) => { this.Dispatcher.Dispatch(async () => { try { await FetchData(); } catch { await ReFetchData(); } }); }); } - 强制UI操作在主线程执行
若SetModelBinding可能被异步逻辑触发,将所有控件操作包裹在Dispatcher.Dispatch中:this.Dispatcher.Dispatch(() => { stackmain.Children.Remove(dlog); // ...其他UI操作 stackmain.Children.Insert(0, dlog); }); - 排查
DailyLogControl内部适配问题
检查DailyLogControl是否使用了Xamarin专属API(如Renderer、特定原生控件绑定),需替换为MAUI对应的实现(如Handler机制)。
内容的提问来源于stack exchange,提问作者Zack Watson
相关产品推荐
相关产品推荐

