如何在.net MAUI视图模型中显示警报



我浏览了"使用。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

虽然Adarsh的回答显示了基本调用,但直接对那个UI方法的引用意味着你的视图模型知道;关于那个UI方法。这工作得很好(IF代码是在主(Dispatcher)线程;如果不是,你就会得到"错误的线索"。异常),但如果稍后想要添加"单元测试",则会干扰可测试性。保持视图模型独立于UI代码也是一种很好的做法。

这可以通过通过interface访问已注册的服务来避免。

我对杰拉尔德的回答作了如下修改。

MauiProgram.cs:

...
public static MauiApp CreateMauiApp()
{
...
builder.Services.AddSingleton<IAlertService, AlertService>();
...

App.xaml.cs(设置MainPage的跨平台版本):

...
public static IServiceProvider Services;
public static IAlertService AlertSvc;
public App(IServiceProvider provider)
{
InitializeComponent();
Services = provider;
AlertSvc = Services.GetService<IAlertService>();
MainPage = ...
}

其他文件中的接口和类声明:

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);
});
}
}

这是一个测试,显示了"fire and forget";方法可以从任何地方调用:

Task.Run(async () =>
{
await Task.Delay(2000);
App.AlertSvc.ShowConfirmation("Title", "Confirmation message.", (result =>
{
App.AlertSvc.ShowAlert("Result", $"{result}");
}));
});

注意:如果使用"…Async"方法,但不在窗口的Dispatcher线程(主线程)上,在运行时你会得到一个错误的线程异常。

来源:Gerald对另一个问题的回答显示了如何获得毛伊岛的IServiceProvider。

有多种方法。最简单的是:

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版本8).基本上,它所做的是创建自己的实现,在当前可见的页面上显示一个对话框,并为MVVM场景提供开箱使用的服务。

这是你想要的吗?

bool x =  await Application.Current.MainPage.DisplayAlert("Tittle","Hello","OK","NotOK");

最新更新