如何在 .NET MAUI ViewModel 中显示警报

mas*_*uko 26 c# mvvm maui

我在 Microsoft Learn 上完成了“使用 .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");
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

所以我将其移至我的 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");
      }
   }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是如何DisplayAlert从 ViewModel 中的命令进行调用。

Too*_*eve 35

虽然阿达什的回答显示了重要的呼吁,但直接引用该 UI 方法意味着您的视图模型“知道”该 UI 方法。这工作得很好(如果代码位于主(调度程序)线程上;如果不是,您将得到“错误线程”异常),但如果您稍后想要添加“单元测试”,则会干扰可测试性。保持视图模型独立于 UI 代码也被认为是很好的做法。

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

我对杰拉尔德的答案使用了以下变体。

MauiProgram.cs:

    ...
    public static MauiApp CreateMauiApp()
    {
        ...
        builder.Services.AddSingleton<IAlertService, AlertService>();
        ...
Run Code Online (Sandbox Code Playgroud)

App.xaml.cs(跨平台的,其中设置了 MainPage):

    ...
    public static IServiceProvider Services;
    public static IAlertService AlertSvc;

    public App(IServiceProvider provider)
    {
        InitializeComponent();

        Services = provider;
        AlertSvc = Services.GetService<IAlertService>();

        MainPage = ...
    }
Run Code Online (Sandbox Code Playgroud)

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

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);
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

这是测试,表明可以从任何地方调用“即发即忘”方法:

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

注意:如果您使用“...Async”方法,但不在窗口的调度程序线程(主线程)上,则在运行时您将收到错误的线程异常。

图片来源:Gerald 对另一个问题的回答展示了如何获取毛伊岛的 IServiceProvider。


Ger*_*uis 17

有多种方法可以做到这一点。最简单的一个是这样的:

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");
   }
}
Run Code Online (Sandbox Code Playgroud)

它的作用是遍历该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);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在您可以创建该服务的一个实例,如果在某个时候您想以另一种方式显示对话框,您可以在此处交换实现。

如果您决定也添加接口并将其注册到依赖项注入容器中,您还可以让服务被注入并更容易地交换实现,或者取决于其他潜在的变量。

第三个选项是查看像ACR.UserDialogs这样的插件(从版本 8 开始支持 .NET MAUI)。基本上,它的作用是创建自己的实现,在当前可见的页面上显示一个对话框,并为您提供开箱即用的服务,以便在 MVVM 场景中使用。

  • 如果您使用的是 Shell,您可以执行以下操作:await Shell.Current.DisplayAlert(); (8认同)

小智 12

这是你要找的吗?

bool x =  await Application.Current.MainPage.DisplayAlert("Tittle","Hello","OK","NotOK");
Run Code Online (Sandbox Code Playgroud)

  • 尽量避免在回答中提出问题,否则看起来您的帖子应该是评论。您可以编辑您的答案,说一些类似的话“您可以访问 [Application.MainPage 属性](https://learn.microsoft.com/en-us/dotnet/api/xamarin.forms.application .mainpage?view=xamarin-forms)”,然后添加示例代码。 (5认同)