65.9K
CodeProject 正在变化。 阅读更多。
Home

Xamarin.Forms 中的自定义对话框:插件 "Rg.Plugins.Popup" 页面异步

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2019年12月8日

CPOL

1分钟阅读

viewsIcon

16999

本文提出了一种将 "Rg.Plugins.Popup" 异步集成到您的代码中的解决方案。

引言

本文提出了一种将 "Rg.Plugins.Popup" 包集成到 Xamarin.Forms 应用程序中的解决方案。

背景

该第三方包是一个自定义对话框提供程序。下面,我将演示如何从 DialogService 异步调用其页面。解决方案在于使用 TaskCompletionSource 用于外部异步操作。

通过 TaskCompletionSource 创建的任务的状态由 TaskCompletionSource 上的方法显式控制。这使得能够将外部异步操作的**完成****传播**到**底层 Task**。这种**分离**也确保了**消费者无法在没有**访问相应的 TaskCompletionSource 的情况下**转换状态**。[来自 MS 文档]

结果

确认对话框

确定对话框

IDialogService 实现

以下是来自 DialogService.cs 文件的代码片段。

public async Task<bool> ShowConfirmationDialogAsync
    (string message, string yesButtonText = "Yes", string noButtonText = "No")
{
     var confirmationDialog = new ConfirmationDialog(message)
     {
         YesButtonText = yesButtonText,
         NoButtonText = noButtonText
     };
     await _popupNavigation.PushAsync(confirmationDialog);
     var result = await confirmationDialog.GetResult();
     await _popupNavigation.PopAllAsync();
     return (bool)result;
}

_popupNavigation 对象是 Rg.Plugins.Popup.Services.PopupNavigation.Instance

在 ViewModel 层中的用法

在这里,我调用该服务向最终用户提示对话框,并等待 UI 响应以采取级联操作。

private ICommand _selectedProductCommand;
public ICommand SelectedProductCommand => _selectedProductCommand ?? 
    (_selectedProductCommand = new Command<Product>(async (selectedProduct) =>
{
   var confirmed = await _dialogService.ShowConfirmationDialogAsync
                   ($"Are you sure you want to delete Item: {selectedProduct.Name} ?");
   if (!confirmed)
       return;

   ProductList.Remove(selectedProduct);
}));

代码结构

设计

对话框基本定义

DialogBase.xaml 包含一个 ContentView 作为操作占位符。操作将从具体的对话框定义中传递过来。

<popup:PopupPage xmlns:popup="clr-namespace:Rg.Plugins.Popup.Pages;
assembly=Rg.Plugins.Popup" xmlns="http://xamarin.com/schemas/2014/forms" 
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        x:Class="XamF.Controls.CustomDialogs.Base.DialogBase"
        x:Name="dialogBasePage">
    <ContentPage.Content>
        <Frame WidthRequest="250" BackgroundColor="White" 
         HorizontalOptions="Center" VerticalOptions="Center" 
         Padding="30" CornerRadius="0">
            <Grid RowSpacing="20">
                <Grid.RowDefinitions>
                    <RowDefinition Height="*"></RowDefinition>
                    <RowDefinition Height="Auto"></RowDefinition>
                </Grid.RowDefinitions>
                <Label x:Name="lblMessage" 
                Grid.Row="0" VerticalTextAlignment="Center" 
                 HorizontalTextAlignment="Center" HorizontalOptions="Center" 
                 VerticalOptions="StartAndExpand" FontSize="Medium"></Label>
                <ContentView Grid.Row="1" 
                 Content="{Binding Source={x:Reference dialogBasePage}, 
                 Path=ActionsPlaceHolder}"  
                 HorizontalOptions="Center" VerticalOptions="End">
                </ContentView>
            </Grid>
        </Frame>
    </ContentPage.Content>
</popup:PopupPage>

DialogBase.cs 包含一个 protected TaskCompletionSource,以便具体的对话框可以使用它来 SetResult

public abstract partial class DialogBase : PopupPage
{
    #region Variables
    protected Dictionary<DialogAction, Task> DialogActions;
    protected Action OnApearing;
    protected TaskCompletionSource<object> Proccess;
    //protected ILocator _locator;
    protected IPopupNavigation _popupNavigation;
    #endregion Variables
    #region Properties
    protected string _message;
    protected string Message { get => _message; set => _message = value; }
    #endregion Properties
    public DialogBase()
    {
        InitializeComponent();
        //_locator = App.Locator;
        _popupNavigation = PopupNavigation.Instance;
        this.CloseWhenBackgroundIsClicked = false;
    }
    #region Bindable Properties
    public static readonly BindableProperty ActionsPlaceHolderProperty
        = BindableProperty.Create
          (nameof(ActionsPlaceHolder), typeof(View), typeof(DialogBase));
    public View ActionsPlaceHolder
    {
        get { return (View)GetValue(ActionsPlaceHolderProperty); }
        set { SetValue(ActionsPlaceHolderProperty, value); }
    }
    #endregion Bindable Properties
    protected override void OnAppearing()
    {
        base.OnAppearing();
        this.lblMessage.Text = _message;
        OnApearing?.Invoke();
        Proccess = new TaskCompletionSource<object>();
    }
    public virtual Task<object> GetResult()
    {
        return Proccess.Task;
    }
}

确认对话框定义

XAML

<dialogBase:DialogBase
    xmlns:dialogBase="clr-namespace:XamF.Controls.CustomDialogs.Base" 
    xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamF.Controls.CustomDialogs.ConfirmationDialog">
    <dialogBase:DialogBase.ActionsPlaceHolder>
        <StackLayout Orientation="Horizontal" Grid.Row="1">
            <Button x:Name="btnYes" Text="Yes" WidthRequest="80"></Button>
            <Button x:Name="btnNo" Text="No" WidthRequest="80"></Button>
        </StackLayout>
    </dialogBase:DialogBase.ActionsPlaceHolder>
</dialogBase:DialogBase>

代码隐藏

public partial class ConfirmationDialog : DialogBase
{
    public string YesButtonText { get; set; }
    public string NoButtonText { get; set; }
    public ConfirmationDialog(string message)
    {
        InitializeComponent();
        _message = message;
        OnApearing = () =>
        {
            this.btnNo.Text = NoButtonText;
            this.btnYes.Text = YesButtonText;
        };
        this.btnNo.Clicked += (sender, args) =>
        {
            Proccess.SetResult(false);
        };

        this.btnYes.Clicked += (sender, args) =>
        {
            Proccess.SetResult(true);
        };
    }
}

结论

目标是开发一个可重用的库,它封装了一个第三方 UI 包,并以基于任务的异步方式调用它,可以提供给消费者(ViewModel)。

历史

  • 2019年12月8日:初始版本
© . All rights reserved.