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; } };
关键注意点
- 双向绑定:确保
Signature属性的绑定模式为TwoWay,这样控件的更新能同步到ViewModel的RecipientSignature。 - 异步处理:避免使用同步包装异步方法(如
AsyncHandler.RunSync),直接用await防止死锁。 - MVVM规范:如果项目严格遵循MVVM,优先选择方案2;如果需要快速实现,方案1更直接。
内容的提问来源于stack exchange,提问作者Daniele
相关产品推荐
相关产品推荐

