如何在 .NET MAUI ViewModel 中显示警报
How to DisplayAlert in a .NET MAUI ViewModel
我在 Microsoft Learn 上浏览了“Build mobile and desktop apps with .NET MAUI”路径。现在我有一个简单的工作 MAUI 应用程序,我正在尝试使用 CommunityToolkit.MVVM
.
使其成为 MVVM
该课程有一个名为 OnCall
的点击事件,看起来像这样
private async void OnCall(object sender, EventArgs e)
{
var confirmCall = DisplayAlert(
"Dial a Number",
$"Would you like to call {translatedNumber}?",
"Yes",
"No"
);
if (await confirmCall)
{
try
{
PhoneDialer.Open(translatedNumber);
}
catch (ArgumentNullException)
{
await DisplayAlert("Unable to dial", "Phone number was not valid.", "OK");
}
catch (FeatureNotSupportedException)
{
await DisplayAlert("Unable to dial", "Phone dialing not supported.", "OK");
}
catch (Exception)
{
await DisplayAlert("Unable to dial", "Phone dialing failed.", "OK");
}
}
}
所以我将其移至我的 ViewModel 并将其设为命令,如下所示
[ICommand]
public async void OnCall ()
{
var confirmCall = DisplayAlert(
"Dial a Number",
$"Would you like to call {translatedNumber}?",
"Yes",
"No"
);
if (await confirmCall)
{
try
{
PhoneDialer.Open(translatedNumber);
}
catch (ArgumentNullException)
{
await DisplayAlert("Unable to dial", "Phone number was not valid.", "OK");
}
catch (FeatureNotSupportedException)
{
await DisplayAlert("Unable to dial", "Phone dialing not supported.", "OK");
}
catch (Exception)
{
await DisplayAlert("Unable to dial", "Phone dialing failed.", "OK");
}
}
}
我的问题是如何从 ViewModel 中的命令调用 DisplayAlert
。
这是你要找的吗?
bool x = await Application.Current.MainPage.DisplayAlert("Tittle","Hello","OK","NotOK");
有多种方法可以做到这一点。最简单的是这个:
if (await confirmCall)
{
try
{
PhoneDialer.Open(translatedNumber);
}
catch (ArgumentNullException)
{
await Application.Current.MainPage.DisplayAlert("Unable to dial", "Phone number was not valid.", "OK");
}
catch (FeatureNotSupportedException)
{
await Application.Current.MainPage.DisplayAlert("Unable to dial", "Phone dialing not supported.", "OK");
}
catch (Exception)
{
await Application.Current.MainPage.DisplayAlert("Unable to dial", "Phone dialing failed.", "OK");
}
}
它所做的是通过 Application
对象找到当前页面并调用 DisplayAlert
。
为了使其更易于维护(并且可能对依赖注入友好),您可以将其包装在一个服务中,例如像这样简单:
public class DialogService : IDialogService
{
public async Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons)
{
return await Application.Current.MainPage.DisplayActionSheet(title, cancel, destruction, buttons);
}
public async Task<bool> DisplayConfirm(string title, string message, string accept, string cancel)
{
return await Application.Current.MainPage.DisplayAlert(title, message, accept, cancel);
}
}
现在您可以创建该服务的一个实例,如果在某个时候您想以另一种方式显示您的对话框,您可以在此处更换实现。
如果您决定也添加接口并将其注册到您的依赖注入容器中,您还可以让服务被注入并更容易地或根据其他潜在变量换出实现。
第三个选择是查看像 ACR.UserDialogs 这样的插件(即将支持 .NET MAUI)。基本上,它所做的是创建自己的实现,在当前可见页面上显示一个对话框,并为您提供开箱即用的服务,以便在 MVVM 场景中使用。
虽然 Adarsh 的回答显示了基本调用,但 直接 引用 UI 方法意味着您的视图模型“知道” UI 方法。这工作正常(如果代码在主(调度程序)线程上;如果不是,你会得到“错误的线程”异常),但会干扰可测试性,如果你以后想添加“单元测试”。保持视图模型独立于 UI 代码也是一种很好的做法。
这可以通过 interface
访问已注册的服务来避免。
我对 Gerald 的回答使用了以下变体。
MauiProgram.cs:
...
public static MauiApp CreateMauiApp()
{
...
builder.Services.AddSingleton<IAlertService, AlertService>();
...
App.xaml.cs(设置 MainPage 的 cross-platform):
...
public static IServiceProvider Services;
public static IAlertService AlertSvc;
public App(IServiceProvider provider)
{
InitializeComponent();
Services = provider;
AlertSvc = Services.GetService<IAlertService>();
MainPage = ...
}
其他文件中的接口声明和class:
public interface IAlertService
{
// ----- async calls (use with "await" - MUST BE ON DISPATCHER THREAD) -----
Task ShowAlertAsync(string title, string message, string cancel = "OK");
Task<bool> ShowConfirmationAsync(string title, string message, string accept = "Yes", string cancel = "No");
// ----- "Fire and forget" calls -----
void ShowAlert(string title, string message, string cancel = "OK");
/// <param name="callback">Action to perform afterwards.</param>
void ShowConfirmation(string title, string message, Action<bool> callback,
string accept = "Yes", string cancel = "No");
}
internal class AlertService : IAlertService
{
// ----- async calls (use with "await" - MUST BE ON DISPATCHER THREAD) -----
public Task ShowAlertAsync(string title, string message, string cancel = "OK")
{
return Application.Current.MainPage.DisplayAlert(title, message, cancel);
}
public Task<bool> ShowConfirmationAsync(string title, string message, string accept = "Yes", string cancel = "No")
{
return Application.Current.MainPage.DisplayAlert(title, message, accept, cancel);
}
// ----- "Fire and forget" calls -----
/// <summary>
/// "Fire and forget". Method returns BEFORE showing alert.
/// </summary>
public void ShowAlert(string title, string message, string cancel = "OK")
{
Application.Current.MainPage.Dispatcher.Dispatch(async () =>
await ShowAlertAsync(title, message, cancel)
);
}
/// <summary>
/// "Fire and forget". Method returns BEFORE showing alert.
/// </summary>
/// <param name="callback">Action to perform afterwards.</param>
public void ShowConfirmation(string title, string message, Action<bool> callback,
string accept="Yes", string cancel = "No")
{
Application.Current.MainPage.Dispatcher.Dispatch(async () =>
{
bool answer = await ShowConfirmationAsync(title, message, accept, cancel);
callback(answer);
});
}
}
这是测试,表明可以从任何地方调用“即发即弃”方法:
Task.Run(async () =>
{
await Task.Delay(2000);
App.AlertSvc.ShowConfirmation("Title", "Confirmation message.", (result =>
{
App.AlertSvc.ShowAlert("Result", $"{result}");
}));
});
注意:如果您改为使用“...Async”方法,但不在 window 的 Dispatcher 线程(主线程)上,则在运行时您会得到一个错误的线程异常.
出处: 展示了如何获取 Maui 的 IServiceProvider。
我在 Microsoft Learn 上浏览了“Build mobile and desktop apps with .NET MAUI”路径。现在我有一个简单的工作 MAUI 应用程序,我正在尝试使用 CommunityToolkit.MVVM
.
该课程有一个名为 OnCall
的点击事件,看起来像这样
private async void OnCall(object sender, EventArgs e)
{
var confirmCall = DisplayAlert(
"Dial a Number",
$"Would you like to call {translatedNumber}?",
"Yes",
"No"
);
if (await confirmCall)
{
try
{
PhoneDialer.Open(translatedNumber);
}
catch (ArgumentNullException)
{
await DisplayAlert("Unable to dial", "Phone number was not valid.", "OK");
}
catch (FeatureNotSupportedException)
{
await DisplayAlert("Unable to dial", "Phone dialing not supported.", "OK");
}
catch (Exception)
{
await DisplayAlert("Unable to dial", "Phone dialing failed.", "OK");
}
}
}
所以我将其移至我的 ViewModel 并将其设为命令,如下所示
[ICommand]
public async void OnCall ()
{
var confirmCall = DisplayAlert(
"Dial a Number",
$"Would you like to call {translatedNumber}?",
"Yes",
"No"
);
if (await confirmCall)
{
try
{
PhoneDialer.Open(translatedNumber);
}
catch (ArgumentNullException)
{
await DisplayAlert("Unable to dial", "Phone number was not valid.", "OK");
}
catch (FeatureNotSupportedException)
{
await DisplayAlert("Unable to dial", "Phone dialing not supported.", "OK");
}
catch (Exception)
{
await DisplayAlert("Unable to dial", "Phone dialing failed.", "OK");
}
}
}
我的问题是如何从 ViewModel 中的命令调用 DisplayAlert
。
这是你要找的吗?
bool x = await Application.Current.MainPage.DisplayAlert("Tittle","Hello","OK","NotOK");
有多种方法可以做到这一点。最简单的是这个:
if (await confirmCall)
{
try
{
PhoneDialer.Open(translatedNumber);
}
catch (ArgumentNullException)
{
await Application.Current.MainPage.DisplayAlert("Unable to dial", "Phone number was not valid.", "OK");
}
catch (FeatureNotSupportedException)
{
await Application.Current.MainPage.DisplayAlert("Unable to dial", "Phone dialing not supported.", "OK");
}
catch (Exception)
{
await Application.Current.MainPage.DisplayAlert("Unable to dial", "Phone dialing failed.", "OK");
}
}
它所做的是通过 Application
对象找到当前页面并调用 DisplayAlert
。
为了使其更易于维护(并且可能对依赖注入友好),您可以将其包装在一个服务中,例如像这样简单:
public class DialogService : IDialogService
{
public async Task<string> DisplayActionSheet(string title, string cancel, string destruction, params string[] buttons)
{
return await Application.Current.MainPage.DisplayActionSheet(title, cancel, destruction, buttons);
}
public async Task<bool> DisplayConfirm(string title, string message, string accept, string cancel)
{
return await Application.Current.MainPage.DisplayAlert(title, message, accept, cancel);
}
}
现在您可以创建该服务的一个实例,如果在某个时候您想以另一种方式显示您的对话框,您可以在此处更换实现。
如果您决定也添加接口并将其注册到您的依赖注入容器中,您还可以让服务被注入并更容易地或根据其他潜在变量换出实现。
第三个选择是查看像 ACR.UserDialogs 这样的插件(即将支持 .NET MAUI)。基本上,它所做的是创建自己的实现,在当前可见页面上显示一个对话框,并为您提供开箱即用的服务,以便在 MVVM 场景中使用。
虽然 Adarsh 的回答显示了基本调用,但 直接 引用 UI 方法意味着您的视图模型“知道” UI 方法。这工作正常(如果代码在主(调度程序)线程上;如果不是,你会得到“错误的线程”异常),但会干扰可测试性,如果你以后想添加“单元测试”。保持视图模型独立于 UI 代码也是一种很好的做法。
这可以通过 interface
访问已注册的服务来避免。
我对 Gerald 的回答使用了以下变体。
MauiProgram.cs:
...
public static MauiApp CreateMauiApp()
{
...
builder.Services.AddSingleton<IAlertService, AlertService>();
...
App.xaml.cs(设置 MainPage 的 cross-platform):
...
public static IServiceProvider Services;
public static IAlertService AlertSvc;
public App(IServiceProvider provider)
{
InitializeComponent();
Services = provider;
AlertSvc = Services.GetService<IAlertService>();
MainPage = ...
}
其他文件中的接口声明和class:
public interface IAlertService
{
// ----- async calls (use with "await" - MUST BE ON DISPATCHER THREAD) -----
Task ShowAlertAsync(string title, string message, string cancel = "OK");
Task<bool> ShowConfirmationAsync(string title, string message, string accept = "Yes", string cancel = "No");
// ----- "Fire and forget" calls -----
void ShowAlert(string title, string message, string cancel = "OK");
/// <param name="callback">Action to perform afterwards.</param>
void ShowConfirmation(string title, string message, Action<bool> callback,
string accept = "Yes", string cancel = "No");
}
internal class AlertService : IAlertService
{
// ----- async calls (use with "await" - MUST BE ON DISPATCHER THREAD) -----
public Task ShowAlertAsync(string title, string message, string cancel = "OK")
{
return Application.Current.MainPage.DisplayAlert(title, message, cancel);
}
public Task<bool> ShowConfirmationAsync(string title, string message, string accept = "Yes", string cancel = "No")
{
return Application.Current.MainPage.DisplayAlert(title, message, accept, cancel);
}
// ----- "Fire and forget" calls -----
/// <summary>
/// "Fire and forget". Method returns BEFORE showing alert.
/// </summary>
public void ShowAlert(string title, string message, string cancel = "OK")
{
Application.Current.MainPage.Dispatcher.Dispatch(async () =>
await ShowAlertAsync(title, message, cancel)
);
}
/// <summary>
/// "Fire and forget". Method returns BEFORE showing alert.
/// </summary>
/// <param name="callback">Action to perform afterwards.</param>
public void ShowConfirmation(string title, string message, Action<bool> callback,
string accept="Yes", string cancel = "No")
{
Application.Current.MainPage.Dispatcher.Dispatch(async () =>
{
bool answer = await ShowConfirmationAsync(title, message, accept, cancel);
callback(answer);
});
}
}
这是测试,表明可以从任何地方调用“即发即弃”方法:
Task.Run(async () =>
{
await Task.Delay(2000);
App.AlertSvc.ShowConfirmation("Title", "Confirmation message.", (result =>
{
App.AlertSvc.ShowAlert("Result", $"{result}");
}));
});
注意:如果您改为使用“...Async”方法,但不在 window 的 Dispatcher 线程(主线程)上,则在运行时您会得到一个错误的线程异常.
出处: