MAUI应用添加/搜索日志触发DEBUG异常,请求排查
MAUI剧院行程日志应用异常排查与修复
问题现象
开发的MAUI剧院行程记录应用,执行添加日志或搜索日志操作时,会跳转到自动生成的App.g.i.cs代码,触发DEBUG && !DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION异常。断点调试显示子例程能完整执行,但结束时抛出异常,怀疑问题出在UI层面。目前新日志文件能正常创建,重启应用后可正常显示。
触发异常的自动生成代码段
#if DEBUG && !DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION UnhandledException += (sender, e) => { if (global::System.Diagnostics.Debugger.IsAttached) global::System.Diagnostics.Debugger.Break(); }; #endif
主页面XAML代码
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyTheatreLog.MainPage" xmlns:viewmodel="clr-namespace:MyTheatreLog.ViewModel" x:DataType="viewmodel:MainViewModel" Title="Theatre Log"> <Grid RowDefinitions="50, Auto, Auto, *, Auto" ColumnDefinitions=".8*,.05*,.15*" Padding="10" RowSpacing="10" ColumnSpacing="10"> <Entry Placeholder="New Log" Grid.ColumnSpan="3" Text="{Binding Show}" Grid.Row="1"/> <DatePicker Grid.Row="2" Grid.Column="0" Date="{Binding Date}"/> <Button Text="+" FontSize="30" WidthRequest="20" HeightRequest="20" Command="{Binding AddCommand}" Grid.Row="2" Grid.Column="3" HorizontalOptions="End"/> <CollectionView Grid.Row="3" Grid.ColumnSpan="3" ItemsSource="{Binding Shows}" SelectionMode="None" > <CollectionView.ItemTemplate> <DataTemplate x:DataType="viewmodel:ShowItem"> <SwipeView> <Grid Padding="0,5"> <Frame CornerRadius="8" HasShadow="True"> <Frame.GestureRecognizers> <TapGestureRecognizer NumberOfTapsRequired="2" Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MainViewModel}}, Path=DetailsCommand}" CommandParameter="{Binding .}"/> </Frame.GestureRecognizers> <Grid ColumnDefinitions="*, 90"> <Label Text="{Binding Title}" Grid.Column="0" FontSize="14"/> <Label Text="{Binding DateString}" Grid.Column="1"/> </Grid> </Frame> </Grid> </SwipeView> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> <Entry Placeholder="Search Logs" Grid.Row="4" Text="{Binding Searchentry}"/> <Button Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" Text="Search" Command="{Binding SearchCommand}"/> </Grid> </ContentPage>
ViewModel代码
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using System.Collections.ObjectModel; namespace MyTheatreLog.ViewModel { public class ShowItem { public string Title { get; set; } public string DateString { get; set; } } public partial class MainViewModel : ObservableObject { private LocalDatabase localDatabase; string appDataDirectory = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MyTheatreLog"); public MainViewModel() { if (!Directory.Exists(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MyTheatreLog"))) { Directory.CreateDirectory(Path.Combine(appDataDirectory, "MyTheatreLog")); } Shows = new ObservableCollection<ShowItem>(); localDatabase = new LocalDatabase(); LoadLogs(); } public void LoadLogs() { try { string[] logfiles = Directory.GetFiles(appDataDirectory, "*.txt"); Shows.Clear(); foreach (string file in logfiles) { using(StreamReader savefile = new StreamReader(file)) { Title = savefile.ReadLine(); Datestring = savefile.ReadLine(); savefile.Close(); } if (!string.IsNullOrEmpty(Title) && !string.IsNullOrEmpty(Datestring)) { var showItem = new ShowItem { Title = Title, DateString = Datestring }; Shows.Add(showItem); } } } catch(Exception ex) { Console.WriteLine("Error loading logs " + ex.Message); } } [ObservableProperty] ObservableCollection<ShowItem> shows; [ObservableProperty] string show; bool searching = false; [ObservableProperty] string title; [ObservableProperty] DateTime date = DateTime.Today; [ObservableProperty] string searchentry; [ObservableProperty] string filename; [ObservableProperty] String datestring; [RelayCommand] void Add() { // checks if entry is empty if (string.IsNullOrWhiteSpace(Show)) return; Datestring = Date.ToString("dd/MM/yyyy"); // adds show Show = Show.Trim(); string filetitle = Show.Replace(" ", "") + Datestring.Replace("/", ""); Filename = filetitle + ".txt"; using (StreamWriter savefile = new StreamWriter(System.IO.Path.Combine(appDataDirectory, Filename), true)) { //saves to file savefile.WriteLine(Show); savefile.WriteLine(Datestring); savefile.Close(); } LoadLogs(); Show = string.Empty; } [RelayCommand] void Search() { try { var Showsholder = new ObservableCollection<ShowItem>(); Showsholder.Clear(); if (searching == false) { // checks if entry is empty if (string.IsNullOrWhiteSpace(Searchentry)) return; // fills Shows placeholder foreach (var log in Shows) { Showsholder.Add(log); } Shows.Clear(); Searchentry = Searchentry.Trim(); Searchentry = Searchentry.ToLower(); foreach (var log in Showsholder) { string logtitle = log.Title; string logsearched = logtitle.ToLower(); // Adds logs that match search if (logsearched.Contains(Searchentry)) { Shows.Add(log); } } searching = true; } else { Shows.Clear(); LoadLogs(); searching = false; } Searchentry = string.Empty; } catch { Console.WriteLine("Error Searching"); } } [RelayCommand] async Task Details(ShowItem s) { //navigates to Details Page string filetitle = s.Title.Replace(" ", ""); string filedate = s.DateString.Replace("/", ""); Filename = filetitle + filedate + ".txt"; await Shell.Current.GoToAsync($"{"Details"}?Filename_={Filename}"); } } }
问题原因与修复方案
1. XAML布局索引越界
主页面Grid仅定义了3列(索引0、1、2),但添加按钮的Grid.Column="3"超出了有效索引范围,直接触发UI布局异常。
修复:将按钮的列索引改为2:
<Button Text="+" FontSize="30" WidthRequest="20" HeightRequest="20" Command="{Binding AddCommand}" Grid.Row="2" Grid.Column="2" HorizontalOptions="End"/>
2. UI线程更新冲突
LoadLogs直接在UI线程执行文件读取和集合更新操作,频繁的清空/添加动作可能导致绑定上下文异常。建议将IO操作移至后台线程,再回到UI线程更新集合。
修复:修改LoadLogs为异步方法:
public async Task LoadLogs() { try { // 后台线程读取文件 string[] logfiles = await Task.Run(() => Directory.GetFiles(appDataDirectory, "*.txt")); // 回到UI线程更新集合 MainThread.BeginInvokeOnMainThread(() => { Shows.Clear(); foreach (string file in logfiles) { using(StreamReader savefile = new StreamReader(file)) { Title = savefile.ReadLine(); DateString = savefile.ReadLine(); } if (!string.IsNullOrEmpty(Title) && !string.IsNullOrEmpty(DateString)) { var showItem = new ShowItem { Title = Title, DateString = DateString }; Shows.Add(showItem); } } }); } catch(Exception ex) { Console.WriteLine("Error loading logs " + ex.Message); } }
同时更新Add和Search方法的调用逻辑,改为异步RelayCommand:
[RelayCommand] async Task Add() { if (string.IsNullOrWhiteSpace(Show)) return; DateString = Date.ToString("dd/MM/yyyy"); Show = Show.Trim(); string filetitle = Show.Replace(" ", "") + DateString.Replace("/", ""); Filename = filetitle + ".txt"; using (StreamWriter savefile = new StreamWriter(System.IO.Path.Combine(appDataDirectory, Filename), true)) { savefile.WriteLine(Show); savefile.WriteLine(DateString); } await LoadLogs(); Show = string.Empty; } [RelayCommand] async Task Search() { try { var Showsholder = new ObservableCollection<ShowItem>(); if (!searching) { if (string.IsNullOrWhiteSpace(Searchentry)) return; foreach (var log in Shows) { Showsholder.Add(log); } Shows.Clear(); string searchKeyword = Searchentry.Trim().ToLower(); foreach (var log in Showsholder) { if (log.Title.ToLower().Contains(searchKeyword)) { Shows.Add(log); } } searching = true; } else { Shows.Clear(); await LoadLogs(); searching = false; } Searchentry = string.Empty; } catch { Console.WriteLine("Error Searching"); } }
3. 属性命名规范问题
ViewModel中Datestring属性不符合大驼峰命名规范,可能导致绑定隐式错误。
修复:修改属性定义为:
[ObservableProperty] string dateString;
同步修改所有引用该属性的代码,统一为DateString。
内容的提问来源于stack exchange,提问作者ep24601
相关产品推荐
相关产品推荐

