我正在开发一款专用于 Windows 的 MAUI C# 应用程序。这款应用程序的独特之处在于它与本地数据库配合使用,并调用填充服务器上数据库的 API。在此应用程序中,我有一个 p...
我正在开发一款专用于 Windows 的 MAUI C# 应用程序。这款应用程序的独特之处在于它与本地数据库配合使用,并调用一个 API 来填充服务器上的数据库。在此应用程序中,我有一个显示项目列表的页面。在此页面上,我还有一些按钮,允许我添加/更新/删除此列表中的项目。这些按钮会调出一个用于更新或添加项目的模式。
当我单击其中一个按钮时,模态框出现,然后我进行更改。由于模态框的背景是透明的,我可以看到修改已应用于列表。但是,模态框不会像代码中预期的那样自动关闭。我必须单击“退出”才能使模态框消失。
返回列表后,如果我想再次与列表交互,我会单击其中一个按钮。模式打开,我执行操作(添加/更新),我看到修改正在进行,但模式没有关闭并冻结。如果我单击“退出”,我会收到此错误:Application.Current.MainPage.Navigation.ModalStack.Count = 0,并且应用程序崩溃。
如果我将按钮中的所有逻辑都注释掉,我也会遇到同样的问题。
我尝试了很多方法,但我觉得我在主线程管理中明显缺少了一些东西。我不太了解 MAUI 中的线程。有人能帮我吗?
提前致谢。
这是我的代码,其中我的列表的视图
<?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="FntrAudit.Views.RequiredDisplayPage"
Title="Affichage Obligatoire">
<Grid RowDefinitions="Auto,*,Auto"
ColumnDefinitions="*,*,*">
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Text="Affichages obligatoires" FontSize="Large"></Label>
<ListView Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" ItemsSource="{Binding RequiredDisplays}">
<ListView.ItemTemplate>
<DataTemplate >
<ViewCell>
<StackLayout Orientation="Horizontal" VerticalOptions="Center" Spacing="10">
<CheckBox IsChecked="{Binding IsOk}" VerticalOptions="Center"/>
<Label Text="{Binding Intitule}" VerticalOptions="Center" HorizontalOptions="FillAndExpand" LineBreakMode="WordWrap"/>
<ImageButton Clicked="UpdateDoc" HeightRequest="20" WidthRequest="20" Source="update.png" CommandParameter="{Binding .}"></ImageButton>
<ImageButton Clicked="Delete_Clicked" HeightRequest="20" WidthRequest="20" Source="delete.png" CommandParameter="{Binding .}"></ImageButton>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button BackgroundColor="#AE1439" Grid.Row="2" Grid.Column="1" Text="Enregistrer et revenir à l'audit" Margin="5" Clicked="Record_Clicked" HorizontalOptions="Fill"></Button>
<Button BackgroundColor="#AE1439" Grid.Row="2" Grid.Column="0" Text="Ajouter document obligatoire" Margin="5" Clicked="AddDoc" HorizontalOptions="Fill"></Button>
</Grid>
</ContentPage>
背后的代码
using FntrAudit.DALApi;
using FntrAudit.Data;
using FntrAudit.Models;
using FntrAudit.Utils;
using FntrAudit.Views.Modal;
using Microsoft.EntityFrameworkCore;
using System.Collections.ObjectModel;
namespace FntrAudit.Views;
public partial class RequiredDisplayPage : ContentPage
{
private Audit _audit;
SqliteDbContext _db = new SqliteDbContext();
private ObservableCollection<RequiredDisplay> requiredDisplays;
private RestService<RequiredDisplay> _requiredDisplayService = new RestService<RequiredDisplay>();
private RestService<LogFromApp> _LogService = new RestService<LogFromApp>();
public ObservableCollection<RequiredDisplay> RequiredDisplays
{
get { return requiredDisplays; }
set
{
if (requiredDisplays != value)
{
requiredDisplays = value;
OnPropertyChanged(nameof(RequiredDisplays));
}
}
}
public RequiredDisplayPage(Audit audit)
{
InitializeComponent();
_audit = audit;
InitializeAsync();
}
private async void InitializeAsync()
{
await LoadRequiredDoc();
}
public async Task LoadRequiredDoc()
{
try
{
Console.WriteLine("Loading required documents...");
bool hasWeb = Connectivity.Current.NetworkAccess == NetworkAccess.Internet;
List<RequiredDisplay> requiredDisplays = new List<RequiredDisplay>();
if (_audit.RequiredDisplay.Any(rd => rd.IsOk))
{
RequiredDisplays = new ObservableCollection<RequiredDisplay>(_audit.RequiredDisplay);
}
else
{
RequiredDisplays = new ObservableCollection<RequiredDisplay>(_audit.RequiredDisplay);
}
await Task.Delay(1000);
this.BindingContext = this;
Console.WriteLine("Required documents loaded successfully.");
}
catch(Exception ex)
{
var t = ex.Message;
Console.WriteLine($"Error loading required documents: {ex.Message}");
}
}
private async void Delete_Clicked(object sender, EventArgs e)
{
try
{
bool answer = await DisplayAlert("Supprimer un document obligatoire", "Êtes-vous sûr de vouloir supprimer ce document ?", "Oui", "Non");
if (answer)
{
if (sender is ImageButton button && button.CommandParameter is RequiredDisplay toDelete)
{
try
{
Console.WriteLine($"Attempting to delete document: {toDelete.GuidRDisplay}");
_db.RequiredDisplay.Remove(toDelete);
await _db.SaveChangesAsync();
await _requiredDisplayService.DeleteTodoItemAsync(toDelete.GuidRDisplay, Constantes.URLAPI + "RequiredDisplay/");
RequiredDisplays.Remove(toDelete);
Console.WriteLine($"Document {toDelete.GuidRDisplay} deleted successfully.");
}
catch (DbUpdateConcurrencyException)
{
await _db.Entry(toDelete).ReloadAsync();
await DisplayAlert("Échec de la suppression", "Le document a été modifié ou supprimé par une autre transaction. Veuillez réessayer.", "OK");
Console.WriteLine($"Concurrency error while deleting document: {toDelete.GuidRDisplay}");
}
catch (Exception ex)
{
LogFromApp logFromApp = new LogFromApp()
{
DateCreation = DateTime.Now,
Intitule = "ModalRequired delete doc (UserContext ContextCurrent) " + ex.Message,
Trace = ex.ToString(),
UserInvolved = " "
};
await _LogService.Create(logFromApp, Constantes.URLAPILOG, true);
await DisplayAlert("Erreur", "Une erreur est survenue lors de la suppression du document.", "OK");
Console.WriteLine($"Error deleting document: {ex.Message}");
}
}
}
}
catch (Exception ex)
{
await DisplayAlert("Erreur", "Une erreur est survenue lors de la suppression du document.", "OK");
Console.WriteLine("Erreur lors de la suppression du document : " + ex.Message);
}
}
//public async void Delete_Clicked(object sender, EventArgs e)
//{
// bool answer = false;
// try
// {
// answer = await DisplayAlert("Supprimer un document obligatoire", "Êtes-vous sûr de vouloir supprimer ce document ?", "Oui", "Non");
// }
// catch(Exception ex)
// {
// var toto = ex.Message;
// }
// if (answer)
// {
// if (sender is ImageButton button && button.CommandParameter is RequiredDisplay toDelete)
// {
// try
// {
// _db.RequiredDisplay.Remove(toDelete);
// _db.SaveChanges();
// await _requiredDisplayService.DeleteTodoItemAsync(toDelete.GuidRDisplay, Constantes.URLAPI + "RequiredDisplay/");
// RequiredDisplays.Remove(toDelete);
// }
// catch (DbUpdateConcurrencyException ex)
// {
// // Option 1: Raffraîchir l'entité depuis la base de données
// await _db.Entry(toDelete).ReloadAsync();
// // Afficher un message d'erreur à l'utilisateur
// await DisplayAlert("Échec de la suppression", "Le document a été modifié ou supprimé par une autre transaction. Veuillez réessayer.", "OK");
// // Option 2: Tentative de réexécution de la suppression après rechargement
// // _db.RequiredDisplay.Remove(toDelete);
// // _db.SaveChanges();
// }
// catch (Exception ex)
// {
// // Afficher un message d'erreur générique
// await DisplayAlert("Erreur", "Une erreur est survenue lors de la suppression du document.", "OK");
// }
// }
// }
//}
public async void Record_Clicked(object sender, EventArgs e)
{
try
{
Console.WriteLine("Recording clicked.");
_audit.RequiredDisplay = RequiredDisplays.ToList();
await Application.Current.MainPage.Navigation.PopModalAsync();
Console.WriteLine("Modal closed successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"Error in Record_Clicked: {ex.Message}");
}
}
public async void AddDoc(object sender, EventArgs e)
{
try
{
RequiredDisplay requiredDisplay = new RequiredDisplay();
var modal = new ModalRequiredDisplay(requiredDisplay, true);
modal.RequiredDisplays = this.RequiredDisplays;
// await Application.Current.MainPage.Navigation.PushModalAsync(modal);
var tcs = new TaskCompletionSource<bool>();
modal.Disappearing += async (s, args) =>
{
Console.WriteLine("Modal disappearing...");
if (!tcs.Task.IsCompleted)
{
tcs.SetResult(true);
}
};
await Application.Current.MainPage.Navigation.PushModalAsync(modal);
await tcs.Task; // Wait for the modal to close
await LoadRequiredDoc();
}
catch(Exception ex)
{
Console.WriteLine($"Error in AddDoc: {ex.Message}");
}
}
public async void UpdateDoc(object sender, EventArgs e)
{
try
{
if (sender is ImageButton button && button.CommandParameter is RequiredDisplay toUpdate)
{
var modal = new ModalRequiredDisplay(toUpdate, false);
modal.RequiredDisplays = this.RequiredDisplays;
var tcs = new TaskCompletionSource<bool>();
modal.Disappearing += (s, args) =>
{
Console.WriteLine("Modal disappearing...");
if (!tcs.Task.IsCompleted)
{
tcs.SetResult(true);
}
};
await Application.Current.MainPage.Navigation.PushModalAsync(modal);
Console.WriteLine("Modal pushed to stack.");
await tcs.Task; // Wait for the modal to close
Console.WriteLine("Modal closed.");
await LoadRequiredDoc();
}
}catch(Exception ex)
{
var t = ex.Message;
}
}
}
情态动词
<?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="FntrAudit.Views.Modal.ModalRequiredDisplay"
BackgroundColor="#00000000"
Title="Affichage Obligatoire">
<ContentView HorizontalOptions="Center" VerticalOptions="Center">
<ScrollView>
<Frame BackgroundColor="White" CornerRadius="20" Padding="20"
HorizontalOptions="Center" VerticalOptions="Center"
WidthRequest="700" HeightRequest="600">
<VerticalStackLayout Spacing="15">
<Label Text="{Binding TitleModal}" TextColor="Black" FontSize="24" FontAttributes="Bold" HorizontalOptions="Center"></Label>
<Editor Text="{Binding Intitule}" Placeholder="Intitulé du document" AutoSize="TextChanges"></Editor>
<VerticalStackLayout Spacing="10">
<Label Text="Effectif de l'entreprise concernée (si besoin)" VerticalOptions="CenterAndExpand" FontAttributes="Bold"/>
<VerticalStackLayout Spacing="5">
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto" Padding="5">
<Label Text="Toutes" Grid.Column="0" VerticalOptions="CenterAndExpand" Margin="0,0,10,0"/>
<RadioButton x:Name="All" GroupName="Effectif" IsChecked="True" Grid.Column="2" VerticalOptions="CenterAndExpand"/>
</Grid>
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto" Padding="5">
<Label Text="de 1 à 10" Grid.Column="0" VerticalOptions="CenterAndExpand" Margin="0,0,10,0"/>
<RadioButton x:Name="Has1SalOrMore" GroupName="Effectif" IsChecked="{Binding Has1SalOrMore}" Grid.Column="2" VerticalOptions="CenterAndExpand"/>
</Grid>
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto" Padding="5">
<Label Text="de 11 à 50" Grid.Column="0" VerticalOptions="CenterAndExpand" Margin="0,0,10,0"/>
<RadioButton x:Name="Has11SalOrMore" GroupName="Effectif" IsChecked="{Binding Has11SalOrMore}" Grid.Column="2" VerticalOptions="CenterAndExpand"/>
</Grid>
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto" Padding="5">
<Label Text="de 51 à 300" Grid.Column="0" VerticalOptions="CenterAndExpand" Margin="0,0,10,0"/>
<RadioButton x:Name="Has50SalOrMore" GroupName="Effectif" IsChecked="{Binding Has50SalOrMore}" Grid.Column="2" VerticalOptions="CenterAndExpand"/>
</Grid>
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto" Padding="5">
<Label Text="de 301 à 1000" Grid.Column="0" VerticalOptions="CenterAndExpand" Margin="0,0,10,0"/>
<RadioButton x:Name="Has300SalOrMore" GroupName="Effectif" IsChecked="{Binding Has300SalOrMore}" Grid.Column="2" VerticalOptions="CenterAndExpand"/>
</Grid>
<Grid ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto" Padding="5">
<Label Text="1000 et plus" Grid.Column="0" VerticalOptions="CenterAndExpand" Margin="0,0,10,0"/>
<RadioButton x:Name="Has1000SalOrMore" GroupName="Effectif" IsChecked="{Binding Has1000SalOrMore}" Grid.Column="2" VerticalOptions="CenterAndExpand"/>
</Grid>
</VerticalStackLayout>
</VerticalStackLayout>
<HorizontalStackLayout Spacing="10">
<Button BackgroundColor="#007AFF" TextColor="White" Text="Enregistrer" Margin="5" Clicked="AddDoc_Clicked" HorizontalOptions="FillAndExpand"></Button>
<Button BackgroundColor="#FF3B30" TextColor="White" Text="Quitter" Margin="5" Clicked="Quit_Clicked" HorizontalOptions="FillAndExpand"></Button>
</HorizontalStackLayout>
</VerticalStackLayout>
</Frame>
</ScrollView>
</ContentView>
</ContentPage>
背后的代码
using CommunityToolkit.Maui.Alerts;
using FntrAudit.DALApi;
using FntrAudit.Data;
using FntrAudit.Models;
using FntrAudit.Services;
using FntrAudit.Utils;
using Microsoft.EntityFrameworkCore;
using System.Collections.ObjectModel;
namespace FntrAudit.Views.Modal;
public partial class ModalRequiredDisplay : ContentPage
{
private RequiredDisplay _document;
private bool _isCreation;
SqliteDbContext _db = new SqliteDbContext();
private ObservableCollection<RequiredDisplay> requiredDisplays;
private RestService<RequiredDisplay> _requiredDisplayService = new RestService<RequiredDisplay>();
private RestService<LogFromApp> _LogService = new RestService<LogFromApp>();
public ObservableCollection<RequiredDisplay> RequiredDisplays
{
get { return requiredDisplays; }
set
{
if (requiredDisplays != value)
{
requiredDisplays = value;
OnPropertyChanged(nameof(RequiredDisplays));
}
}
}
public ModalRequiredDisplay(RequiredDisplay document, bool isCreation)
{
InitializeComponent();
_document = document;
_isCreation = isCreation;
_document.TitleModal = _isCreation ? "Ajout d'un document obligatoire" : "Mise à jour d'un document obligatoire";
this.BindingContext = _document;
}
private async void Delete_Clicked(object sender, EventArgs e)
{
await CloseModalAsync();
}
private async void Quit_Clicked(object sender, EventArgs e)
{
await CloseModalAsync();
}
private async Task AddDoc(object sender, EventArgs e)
{
bool hasWeb = Connectivity.Current.NetworkAccess == NetworkAccess.Internet;
try
{
if (_isCreation)
{
RequiredDisplay required = new RequiredDisplay();
_document.IsOk = false;
_document.AuditId = 1;
_document.GuidRDisplay = Guid.NewGuid().ToString();
await _db.RequiredDisplay.AddAsync(_document);
await _db.SaveChangesAsync();
Func<Task> saveDocument = async () =>
{
await _requiredDisplayService.Create(_document, Constantes.URLAPI + "RequiredDisplay", true);
};
if (hasWeb)
{
await saveDocument.Invoke();
}
else
{
var connectivityService = new ConnectivityService();
connectivityService.AddTask(saveDocument);
}
}
else
{
_db.Update(_document);
await _db.SaveChangesAsync();
Func<Task> updateDocument = async () =>
{
await _requiredDisplayService.Create(_document, Constantes.URLAPI + "RequiredDisplay/RequiredDisplay/" + _document.GuidRDisplay, false);
};
if (hasWeb)
{
await updateDocument.Invoke();
}
else
{
var connectivityService = new ConnectivityService();
connectivityService.AddTask(updateDocument);
}
}
await ShowToasterAsync("Document obligatoire enregistré avec succès.");
}
catch (Exception ex)
{
LogFromApp logFromApp = new LogFromApp()
{
DateCreation = DateTime.Now,
Intitule = "ModalRequired (UserContext ContextCurrent) " + ex.Message,
Trace = ex.ToString(),
UserInvolved = " "
};
await _LogService.Create(logFromApp, Constantes.URLAPILOG, true);
}
finally
{
try
{
if (RequiredDisplays == null)
{
RequiredDisplays = new ObservableCollection<RequiredDisplay>();
}
RequiredDisplay requiredDisplay = null;
if (RequiredDisplays.Count > 0 && _document != null)
{
requiredDisplay = RequiredDisplays.FirstOrDefault(rd => rd.GuidRDisplay == _document.GuidRDisplay);
}
if (requiredDisplay != null)
{
int index = RequiredDisplays.IndexOf(requiredDisplay);
if (index != -1)
{
RequiredDisplays[index] = _document;
}
}
await CloseModalAsync();
}
catch (Exception closeEx)
{
Console.WriteLine("Erreur lors de la fermeture de la modal : " + closeEx.Message);
}
}
}
private async Task CloseModalAsync()
{
try
{
if (Application.Current.MainPage.Navigation.ModalStack.Count > 0)
{
await Application.Current.MainPage.Navigation.PopModalAsync();
Console.WriteLine("Modal closed successfully.");
}
else
{
Console.WriteLine("Modal stack is empty.");
}
}
catch (Exception ex)
{
Console.WriteLine($"Error in CloseModalAsync: {ex.Message}");
}
}
private async void DeleteDoc(object sender, EventArgs e)
{
bool answer = await DisplayAlert("Supprimer un document obligatoire", "Êtes-vous sûr de vouloir supprimer ce document ?", "Oui", "Non");
if (answer)
{
if (sender is ImageButton button && button.CommandParameter is RequiredDisplay toDelete)
{
try
{
_db.RequiredDisplay.Remove(toDelete);
await _db.SaveChangesAsync();
await _requiredDisplayService.DeleteTodoItemAsync(toDelete.GuidRDisplay, Constantes.URLAPI + "RequiredDisplay/");
RequiredDisplays.Remove(toDelete);
}
catch (DbUpdateConcurrencyException ex)
{
// Option 1: Raffraîchir l'entité depuis la base de données
await _db.Entry(toDelete).ReloadAsync();
// Afficher un message d'erreur à l'utilisateur
await DisplayAlert("Échec de la suppression", "Le document a été modifié ou supprimé par une autre transaction. Veuillez réessayer.", "OK");
// Option 2: Tentative de réexécution de la suppression après rechargement
// _db.RequiredDisplay.Remove(toDelete);
// _db.SaveChanges();
}
catch (Exception ex)
{
LogFromApp logFromApp = new LogFromApp()
{
DateCreation = DateTime.Now,
Intitule = "ModalRequired delete doc (UserContext ContextCurrent) " + ex.Message,
Trace = ex.ToString(),
UserInvolved = " "
};
await _LogService.Create(logFromApp, Constantes.URLAPILOG, true);
// Afficher un message d'erreur générique
await DisplayAlert("Erreur", "Une erreur est survenue lors de la suppression du document.", "OK");
}
}
}
}
private async Task ShowToasterAsync(string message)
{
var toast = Toast.Make(message);
await toast.Show();
}
public async void AddDoc_Clicked(object sender, EventArgs e)
{
await AddDoc(sender, e);
}
}