I'm trying to get SkiaSharp working with Xamarin Forms and Prism. I have it working with the following page behind code
public partial class RoomLayoutPage : ContentPage
{
SKCanvasView canvasView;
public RoomLayoutPage()
{
InitializeComponent();
canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
private void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs e)
{
SKSurface surface = e.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
SKPaint blackPaint = new SKPaint
{
Color = SKColors.Black,
};
canvas.DrawRect(0, 0, 100, 100, blackPaint);
}
}
but now I want to move this to my Prism ViewModel. If I move it then Content is not valid
How do I reference a xaml element from a ViewModel? I'd prefer not to do it this way because my ViewModel is then coupled to the view.
(Prefered way) If I put an SKCanvasView on my page
I can bind to the event with the EventToCommandBehaviour
<forms:SKCanvasView>
<forms:SKCanvasView.Behaviors>
<behaviors:EventToCommandBehavior Command="{Binding OnCanvasViewPaintSurface}" EventName="PaintSurface"/>
</forms:SKCanvasView.Behaviors>
</forms:SKCanvasView>
But I'm not sure how to bind the SKPaintSurfaceEventArgs for
OnCanvasViewPaintSurface = new DelegateCommand<SKPaintSurfaceEventArgs>(OnCanvasViewPaintAction);
I'm assuming it's one of the EventArgs options from here https://prismlibrary.github.io/docs/xamarin-forms/EventToCommandBehavior.html ?
Thank you
So using a value converter solves it..
public class SkiaEventArgsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var sKPaintSurfaceEventArgs = value as SKPaintSurfaceEventArgs;
if (sKPaintSurfaceEventArgs == null)
{
throw new ArgumentException("Expected value to be of type SKPaintSurfaceEventArgs", nameof(value));
}
return sKPaintSurfaceEventArgs;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Not entirely sure why it couldn't convert it automagically but it works
Related
I would like to partially mask the password field from dots to asterisk. I tried using a converter but it doesn't work. What is the best way to achieve this in xamarin forms.
<Entry IsPassword="True"
Placeholder="password"
Text="{Binding Password.Value, Mode=TwoWay, Converter={StaticResource
MaskedPasswordConverter}}"
MaxLength="6">
public class MaskedPasswordConverter : IValueConverter
{
private string _value;
public object Convert(object value, Type targetType, object parameter, CultureInfo
culture)
{
var str = (value ?? "").ToString();
_value = str;
var maskedStr = "";
if (!string.IsNullOrEmpty(str) && str.Length > 2)
{
var domainStr = str.IndexOf('#');
var lengthOfMask = domainStr - 2;
maskedStr = str.Substring(0, 2) + new string('*', lengthOfMask) +
str.Substring(domainStr);
}
return maskedStr;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
return value;
}
}
I suggest you use behaviors for this.
You can find out more about Xamarin forms behaviors here
More examples here
Hope this helps.
If you want to use IValueConverter to mask partial password using asterisk, I think you can set binding mode as OneWay, then please confirm that there is # character in your Password.
I suggest you can use this way to mask email, don't mask password, but you still want to do ,this is the sample that you can take a look:
<Entry
MaxLength="6"
Placeholder="password"
Text="{Binding password, Mode=OneWay, Converter={StaticResource converter1}}" />
public partial class Page24 : ContentPage, INotifyPropertyChanged
{
private string _password;
public string password
{
get
{ return _password; }
set
{
_password = value;
RaisePropertyChanged("password");
}
}
public Page24()
{
InitializeComponent();
password = "123#56";
this.BindingContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
IValueConverter:
public class Passwordconverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var str = (value ?? "").ToString();
var maskedStr = "";
if (!string.IsNullOrEmpty(str) && str.Length > 2)
{
var domainStr = str.IndexOf('#');
var lengthOfMask = domainStr - 2;
maskedStr = str.Substring(0, 2) + new string('*', lengthOfMask) + str.Substring(domainStr);
}
return maskedStr;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This is the screenshot:
But I still suggest you can use custom render to mask your password using asterisk, this is the sample about this, you can take a look:
How to change password masking character in Xamarin forms - Entry
I am new with Xamarin.Forms, I have a listview with an item source.
The ItemSource of the ListView is an ObserverableCollection
Unfortuntaley, when I remove an item from the ItemSource, the ItemDisapperaing event is not trigged. Yes, the list item is removed from the UI in the Android device.
However I implemented the ItemAppearing and that works fine! It triggered.
Adding item working scenario:
The ShopCart.Instance.AddItem(randomItem) called.
ShopItemListView.ItemsSource is refreshed with the newly added item.
TotalAmount's Text is updated with the right amount.
So the ShopItem_NewItemAdded function is called!
Clearing items from Shopcart scenario (not working)
ShopCart.Instance.Clear() called.
ShopItemListView.ItemShource will be empty! The items not on the UI!
But the TotalAmount's text does not changed to 0.0! It still has the previous value.
It seems on the UI element the TotalAmount is not refreshed! In Debug I see after the Clear the ShopItemListView.ItemsSource list is empty.
So the ShopItem_ItemRemoved function is not called!
Here the Xaml.cs
public ShopView()
{
InitializeComponent();
BindingContext = new ShopViewModel();
ShopItemListView.ItemsSource = Shop.Instance.ShopItems;
// This is triggered, it works
ShopItemListView.ItemAppearing += ShopItem_NewItemAdded;
// This event is not triggered, it does not work
ShopItemListView.ItemDisappearing += ShopItem_ItemRemoved;
}
// This event is not triggered, it does not work
private void ShopItem_ItemRemoved(object sender, ItemVisibilityEventArgs e)
{
// If I put a breakpoint here the debugger never comes into this method
TotalAmount.Text = Shop.Instance.ShopItems.Sum(si => si.Price).ToString();
if (Shop.Instance.ShopItems.Count == 0) {
ShopInformation.IsVisible = false;
}
}
// This is triggered, it works.
private void ShopItem_NewItemAdded(object sender, ItemVisibilityEventArgs e)
{
TotalAmount.Text = Shop.Instance.ShopItems.Sum(si => si.Price).ToString();
ShopInformation.IsVisible = true;
}
Here is the shopcart singleton instance
public sealed class ShopCart : INotifyPropertyChanged
{
private static readonly ShopCart _instance = new ShopCart();
private ShopCart()
{
ShopItems = new ObservableCollection<ShopCartItem>();
}
public static ShopCart Instance
{
get
{
return _instance;
}
}
public ObservableCollection<ShopCartItem> ShopItems { get; set; }
public void AddItem(ShopCartItem ShopCartItem)
{
ShopItems.Add(ShopCartItem);
OnPropertyChanged("ShopItems");
}
public void Clear()
{
ShopItems.Clear();
OnPropertyChanged("ShopItems");
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
The ShopCart implement the INotifyPropertyChanged interface. But it seems the UI element is not notified.
How can I refresh the TotalAmount Label on the UI after I removed every shop items from the shop cart?
Maybe you mistake the usage of the ItemDisappearing. This method is for virtualization usage only. It is not guaranteed to fire for all visible items when the List is removed from the screen.
It means if you have a list of 100 items, but you can only see 10 at a time because of the size of the cells once you scroll down and a new cell comes into view, and the top cell scrolls off the screen the ItemDisappearing event will fire.
If you just want to show the listView's ItemsSource(in your case ShopItems's Count) count on the screen. You can use Binding and Converter:
Create a converter firstly:
public class ArrayToCountConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ObservableCollection<ShopCartItem> collection = value as ObservableCollection<ShopCartItem>;
return "There are" + collection.Count + "items";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then use it like:
<ContentPage.Resources>
<ResourceDictionary>
<local:ArrayToCountConverter x:Key="ArrayToCountConverter"/>
</ResourceDictionary>
</ContentPage.Resources>
<Label Text="{Binding ShopItems, Converter={StaticResource ArrayToCountConverter}}"/>
Also you need to set the BindingContext:
public ShopCart shopCart = ShopCart.Instance;
public MainPage()
{
InitializeComponent();
this.BindingContext = shopCart;
MyListView.ItemsSource = shopCart.ShopItems;
}
In this way when you add or clear the ShopItems in the ViewModel, the label will show the count.
I'm working with Xamarin Forms and I want to load a listview with imagecells, also I'm binding the data with XAML.
My webservice provider returns me the binary code of the images, ¿someone knows how I can convert this to show the image?
This is my XAML listview template:
<ListView x:Name="lv_products">
<ListView.ItemTemplate>
<DataTemplate>
<ImageCell
Text="{Binding Name}"
Detail="{Binding Description}"
ImageSource="{Binding Image, Converter={StaticResource cnvImage}}">
</ImageCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And the converter:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value is byte[])
{
byte[] binary = (byte[])value;
Image image = new Image();
image.Source = ImageSource.FromStream(() => new MemoryStream(binary));
return image.Source;
}
return null;
}
But picture appears empty (transparent).
Here is working converter. I use MemoryStream and ImageSource.FromStream.
public class ByteImageConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
var image = value as byte[];
if (image == null)
return null;
return ImageSource.FromStream(() => new MemoryStream(image));
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Just as sample here is my page
public partial class Page : ContentPage
{
readonly ViewModel _bindingContext = new ViewModel();
public Page()
{
InitializeComponent();
BindingContext = _bindingContext;
LoadImage();
}
private async void LoadImage()
{
var assembly = typeof (ByteImageConverter).GetTypeInfo().Assembly;
var stream = assembly
.GetManifestResourceStream("TestImage.c5qdlJqrb04.jpg");
using (var ms = new MemoryStream())
{
await stream.CopyToAsync(ms);
_bindingContext.Image = ms.ToArray();
}
}
}
public class ViewModel : INotifyPropertyChanged
{
private byte[] _image;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(
[CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public byte[] Image
{
get { return _image; }
set
{
_image = value;
OnPropertyChanged();
}
}
}
If you have a URL which returns the image file, why aren't you just use the URL as ImageSource ?
<ImageCell Text="{Binding Name}"
Detail="{Binding Description}"
ImageSource="{Binding ImageURL}">
</ImageCell>
You can convert byte array to Bitmap image, and assign that bitmap to the ImageView. I did this in Xamarin.Android, dnt know will it work with forms or not.
bitmap = BitmapFactory.DecodeByteArray(byte, 0, byte.Length);
Then use imageView.FromBitmap() to display this image.
I am trying to use Convert function from IValueConverter, but I have to call another function in it. I will use his return value but I got that error telling me to return an object value in the converter, any idea how can I avoid this please.
public void Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
RestClient client = new RestClient();
client.BaseUrl = "http://";
RestRequest request = new RestRequest();
request.Method = Method.GET;
request.AddParameter("action", "REE");
request.AddParameter("atm_longitude", location.Longitude);
client.ExecuteAsync(request, ParseFeedCallBack_ListDistance);
}
public void ParseFeedCallBack_ListDistance(IRestResponse response)
{
if (response.StatusCode == HttpStatusCode.OK)
{
ParseXMLFeedDistance(response.Content);
}
}
private string ParseXMLFeedDistance(string feed)
{
.... return myvalueToBind;
}
A simple way to calculate the distance between two coordinates, in this case, assuming you have the coordinates of the device,
using System.Device.Location;
public class GeoCalculator
{
public static double Distance(double deviceLongitude, double deviceLatitude, double atmLongitude, double atmLatitude)
{
//Coordinates of ATM (or origin).
var atmCoordinates = new GeoCoordinate(atmLatitude, atmLongitude);
//Coordinates of Device (or destination).
var deviceCordinates = new GeoCoordinate(deviceLatitude, deviceLongitude);
//Distance in meters.
return atmCoordinates.GetDistanceTo(deviceCordinates);
}
}
Hence your converter can look like:
public class DistanceConverter : IValueConverter
{
/// <summary>
/// This is your device coordinate.
/// </summary>
private static GeoCoordinate devCoordinate = new GeoCoordinate(61.1631, -149.9721);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var location = value as LocationModel;
if (location != null)
{
return GeoCalculator.Distance(devCoordinate.Longitude, devCoordinate.Latitude, location.Longitude, location.Latitude);
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Keep in mind that I would personally not use a converter for this. I would simply expose a simple property in my model that does this calculation as it's a simple logic. If you happen to be a purist and don't like any logic in your model, looping through the list and setting a property on your model would work too.
I want to be able to change the StyleClass of an element through the equivalent of
IsParentSelect ? "Selected" : "", to change the button appearance with CSS. So, I made a Converter to do this for me.
However, I've been having a headache trying to figure out why the Binding isn't working for the StyleClass attribute, because it does work for the attribute Text.
I'm getting a NullPointerException in LightLambda Class when using the Binding on the StyleClass attribute.
Anyone have an idea why I'm getting this Exception?
Thank you very much!
The Resources
<ContentPage.Resources>
<StyleSheet Source="../Styles/Styles.css" />
<ResourceDictionary>
<converters:BoolConverter x:Key="boolConverter" />
</ResourceDictionary>
</ContentPage.Resources>
The Binding:
<Button StyleClass="{Binding IsParentSelected, Converter={StaticResource boolConverter}, ConverterParameter=Selected}" />
The ViewModel
public class IdentificationViewModel : BaseViewModel
{
public IdentificationViewModel()
{
Title = "Identification";
IsParentSelected = true;
}
bool isParentSelected = false;
public bool IsParentSelected
{
get { return isParentSelected; }
set { SetProperty(ref isParentSelected, value); }
}
}
The Converter
public class BoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var s = ((string)parameter).Split(':');
if ((bool)value)
return s[0].Trim();
if (s.Length > 1)
return s[1].Trim();
return String.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var s = ((string)parameter).Split(':');
return (string)value == s[0].Trim();
}
}