You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 15:49:52