I'm developing a Xamarin.Forms application with Prism framework in a MVVM architecture. I need to collect the signature from the screen, so I decided to include the SignaturePad library. With NuGet I included Xamarin.Controls.SignaturePad and Xamarin.Controls.SignaturePad.Forms packages.
In page layout (built with XAML) I have the signature widget:
<signature:SignaturePadView
x:Name="padView"
HeightRequest="130"
CaptionText="Sign"
CaptionTextColor="Black"
ClearText="Clean"
ClearTextColor="Black"
BackgroundColor="White"
SignatureLineColor="Black"
StrokeWidth="2"
StrokeColor="Black"
BindingContext="{Binding Sign, Mode=TwoWay}" />
In the ViewModel the widget binding:
private SignaturePadView _sign;
public SignaturePadView Sign
{
get { return _sign; }
set { SetProperty(ref _sign, value); }
}
In the ViewModel constructor:
_sign = new SignaturePadView();
There is also a button, in the action of this button I need to read the sign image and save it to the database. I tried this:
Stream sig = await Sign.GetImageStreamAsync(SignatureImageFormat.Png);
var signatureMemoryStream = sig as MemoryStream;
byte[] data = signatureMemoryStream.ToArray();
All this code is written in the portable project.
Unfortunately it doesn't work because the sig object is always null. I think that the problem is the widget binding but I'm not sure.
Here is another way of working with the SignaturePad (which helps to avoid putting views in your viewmodel). I could have used a event aggregator system to send a message from VM to View but using a Func was a easiest solution for me.
Pay attention, I don't use Prism at all, so the final solution could be a bit different...
The XAML part of the signature view is almost the same without BindingContext set (from my file TestPage.xaml)
<signature:SignaturePadView Margin="-10, 0, -10, 0"
x:Name="SignatureView"
HorizontalOptions="FillAndExpand"
VerticalOptions="FillAndExpand"
HeightRequest="150"
CaptionText="Signature"
CaptionTextColor="Blue"
ClearText="Effacer"
ClearTextColor="Black"
PromptText=""
PromptTextColor="Green"
BackgroundColor="Silver"
SignatureLineColor="Black"
StrokeWidth="3"
StrokeColor="Black" />
In the codebehind of my page (TestPage.xaml.cs)
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
var vm = (TestViewModel)BindingContext; // Warning, the BindingContext View <-> ViewModel is already set
vm.SignatureFromStream = async () =>
{
if (SignatureView.Points.Count() > 0)
{
using (var stream = await SignatureView.GetImageStreamAsync(SignaturePad.Forms.SignatureImageFormat.Png))
{
return await ImageConverter.ReadFully(stream);
}
}
return await Task.Run(() => (byte[])null);
};
}
Where ImageConverter.ReadFully(...) is just a stream to byte converter
public static class ImageConverter
{
public static async Task<byte[]> ReadFully(Stream input)
{
byte[] buffer = new byte[16 * 1024];
using (var ms = new MemoryStream())
{
int read;
while ((read = await input.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}
}
And the viewmodel looks like this
public class TestViewModel : ViewModelBase
{
public Func<Task<byte[]>> SignatureFromStream { get; set; }
public byte[] Signature { get; set; }
public ICommand MyCommand => new Command(async () =>
{
Signature = await SignatureFromStream();
// Signature should be != null
});
}