You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin:如何从外部ViewModel调用控制器的SaveSignature方法

解决Xamarin.Forms中从外部触发自定义签名控件的保存操作

针对你遇到的问题,这里提供两种实用的解决方案,既可以快速实现,也能符合MVVM设计规范:

方案1:直接暴露公开方法(快速实现)

先优化自定义控件SignaturePadControl,封装一个可外部调用的异步保存方法,同时更新绑定属性:

1. 修改SignaturePadControl代码

namespace Mobile.UI.Controls
{
  public partial class SignaturePadControl : Frame
  {
    // 修正绑定属性为双向绑定,确保控件更新能同步到ViewModel
    private static readonly Type _thisType = typeof(SignaturePadControl);
    public static BindableProperty SignatureProperty =
        BindableProperty.Create(
            propertyName: nameof(Signature),
            returnType: typeof(string),
            declaringType: _thisType,
            defaultValue: null,
            defaultBindingMode: BindingMode.TwoWay);

    public string Signature
    {
      get => (string)GetValue(SignatureProperty);
      set => SetValue(SignatureProperty, value);
    }

    public SignaturePadControl()
    {
        InitializeComponent();
    }

    // 封装公开的异步保存方法,供外部调用
    public async Task<string> CaptureAndSaveSignatureAsync()
    {
        var base64String = string.Empty;

        // 直接使用await,避免同步包装导致的死锁
        using (var stream = await SignaturePad.GetImageStreamAsync(SignatureImageFormat.Png))
        {
            if (stream == null) return null;

            var bytes = new byte[stream.Length];
            await stream.ReadAsync(bytes, 0, (int)stream.Length);
            
            base64String = Convert.ToBase64String(bytes);
            // 更新绑定属性和隐藏输入框的值
            Signature = base64String;
            HiddenRecipientSignature.Value = base64String;
        }
        return base64String;
    }

    // 保留原事件方法,兼容旧逻辑(可选)
    public async void SaveSignature(object sender, EventArgs e)
    {
        await CaptureAndSaveSignatureAsync();
    }
  }
}

2. 在BatchReviewViewModel中添加触发事件

public class BatchReviewViewModel : BatchViewModelBase<TrackerBatchItemModel>
{
    private string _recipientSignature;
    public string RecipientSignature
    {
        get => _recipientSignature;
        set => SetAndRaiseIfChanged(ref _recipientSignature, value);
    }

    // 添加事件,用于通知页面触发保存操作
    public event Func<Task> OnRequestSaveSignature;

    // 弹窗关闭时的逻辑
    public async Task HandleConfirm()
    {
        // 触发保存事件
        if (OnRequestSaveSignature != null)
        {
            await OnRequestSaveSignature.Invoke();
        }
    }
}

3. 在BatchReviewPage后台绑定事件

public partial class BatchReviewPage : ContentPage
{
    public BatchReviewPage(BatchReviewViewModel vm)
    {
        InitializeComponent();
        BindingContext = vm;
        
        // 监听ViewModel的保存事件,调用控件的保存方法
        vm.OnRequestSaveSignature += async () =>
        {
            await SignaturePadControl.CaptureAndSaveSignatureAsync();
        };
    }
}

4. 在TargetViewModel中触发保存

var batchReviewVm = await ShowPopup<BatchReviewViewModel>();
if (batchReviewVm == null) return;

batchReviewVm.OnPopupClose = async () =>
{
    switch (batchReviewVm.SelectedCloseOption)
    {
        case Batch.BatchViewModelBase<TrackerBatchItemModel>.CloseOption.Confirm:
            // 触发保存操作
            await batchReviewVm.HandleConfirm();
            // 此时RecipientSignature已更新,可直接使用
            var savedSignature = batchReviewVm.RecipientSignature;
            break;
        default:
            break;
    }
};

方案2:使用Command实现MVVM友好的触发(推荐)

通过给自定义控件添加绑定命令,让ViewModel直接控制保存操作,完全符合MVVM模式:

1. 给SignaturePadControl添加命令属性

namespace Mobile.UI.Controls
{
  public partial class SignaturePadControl : Frame
  {
    // 双向绑定的Signature属性(同方案1)
    private static readonly Type _thisType = typeof(SignaturePadControl);
    public static BindableProperty SignatureProperty =
        BindableProperty.Create(
            propertyName: nameof(Signature),
            returnType: typeof(string),
            declaringType: _thisType,
            defaultValue: null,
            defaultBindingMode: BindingMode.TwoWay);

    public string Signature
    {
      get => (string)GetValue(SignatureProperty);
      set => SetValue(SignatureProperty, value);
    }

    // 添加保存命令的绑定属性
    public static readonly BindableProperty SaveSignatureCommandProperty =
        BindableProperty.Create(
            nameof(SaveSignatureCommand),
            typeof(ICommand),
            typeof(SignaturePadControl),
            null);

    public ICommand SaveSignatureCommand
    {
        get => (ICommand)GetValue(SaveSignatureCommandProperty);
        set => SetValue(SaveSignatureCommandProperty, value);
    }

    public SignaturePadControl()
    {
        InitializeComponent();
        // 订阅消息,接收ViewModel的保存指令
        MessagingCenter.Subscribe<BatchReviewViewModel>(this, "TriggerSaveSignature", async (sender) =>
        {
            await CaptureAndSaveSignatureAsync();
        });
    }

    // 封装保存方法(同方案1)
    public async Task<string> CaptureAndSaveSignatureAsync()
    {
        var base64String = string.Empty;

        using (var stream = await SignaturePad.GetImageStreamAsync(SignatureImageFormat.Png))
        {
            if (stream == null) return null;

            var bytes = new byte[stream.Length];
            await stream.ReadAsync(bytes, 0, (int)stream.Length);
            
            base64String = Convert.ToBase64String(bytes);
            Signature = base64String;
            HiddenRecipientSignature.Value = base64String;
        }
        return base64String;
    }
  }
}

2. 在BatchReviewViewModel中添加命令

public class BatchReviewViewModel : BatchViewModelBase<TrackerBatchItemModel>
{
    private string _recipientSignature;
    public string RecipientSignature
    {
        get => _recipientSignature;
        set => SetAndRaiseIfChanged(ref _recipientSignature, value);
    }

    // 保存签名的命令
    public ICommand SaveSignatureCommand { get; }

    public BatchReviewViewModel()
    {
        SaveSignatureCommand = new AsyncCommand(async () =>
        {
            // 发送消息通知控件执行保存
            MessagingCenter.Send(this, "TriggerSaveSignature");
        });
    }
}

3. 在BatchReviewPage的XAML中绑定命令

<controls:SignaturePadControl 
    x:Name="SignaturePadControl"
    Signature="{Binding RecipientSignature}"
    SaveSignatureCommand="{Binding SaveSignatureCommand}"/>

4. 在TargetViewModel中触发命令

var batchReviewVm = await ShowPopup<BatchReviewViewModel>();
if (batchReviewVm == null) return;

batchReviewVm.OnPopupClose = async () =>
{
    switch (batchReviewVm.SelectedCloseOption)
    {
        case Batch.BatchViewModelBase<TrackerBatchItemModel>.CloseOption.Confirm:
            // 直接执行ViewModel的保存命令
            await batchReviewVm.SaveSignatureCommand.ExecuteAsync(null);
            // 读取已保存的签名
            var savedSignature = batchReviewVm.RecipientSignature;
            break;
        default:
            break;
    }
};

关键注意点

  1. 双向绑定:确保Signature属性的绑定模式为TwoWay,这样控件的更新能同步到ViewModel的RecipientSignature。
  2. 异步处理:避免使用同步包装异步方法(如AsyncHandler.RunSync),直接用await防止死锁。
  3. MVVM规范:如果项目严格遵循MVVM,优先选择方案2;如果需要快速实现,方案1更直接。

内容的提问来源于stack exchange,提问作者Daniele

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 09:59:57