Xamarin forms limit number of decimal places in an entry field - xamarin.forms

I have an entry field inside of a collection view cell. The entry field should accept numeric value only with certain decimal places. I don't want more input if they exceed the decimal places. The decimal places vary for each cell. How do I create this validation in Model or using behaviour/trigger?
Currently, my code is like this
<Entry Placeholder="Quantity"
Keyboard="Numeric"
Text="{Binding EnteredQuantity, Mode=TwoWay}"/>
And in the model part, I am doing this
private string enteredQuantity = "1";
public string EnteredQuantity
{
get => enteredQuantity;
set
{
if (!decimal.TryParse(value, out decimal parsedQuantity))
{
enteredQuantity = "1";
OnPropertyChanged(enteredQuantity);
}
else
{
if (parsedQuantity > 9999.99M)
{
enteredQuantity =string.Format("9999.99", parsedQuantity);
OnPropertyChanged(enteredQuantity);
}
else
{
string formatString = "{" + "0:0.".PadRight(4 + (int) BaseDecimalPlaces, '#') + "}"; //Format of {0:0.##}
if (IsAlternateUnitUsed && selectedUnit.Value == "A")
{
formatString = "{" + "0:0.".PadRight(4 + (int) AlternateDecimalPlaces, '#') + "}";
}
enteredQuantity = string.Format(formatString, parsedQuantity);
OnPropertyChanged(enteredQuantity);
}
}
}
}
For the above test, the format string is {0:0.##} and decimal places are 2.
I am trying to restrict the value in the Binding property. However, this does not reflect on the front end. The frontend will show any decimal places like in the picture below. Does anybody have an idea how to solve this?
Has

Use behavior to achieve this
Xaml Code
...
xmlns:local="clr-namespace:DummyTestApp"
...
<Entry HorizontalOptions="FillAndExpand" VerticalOptions="CenterAndExpand">
<Entry.Behaviors>
<local:LengthValidateBehavior MaxLength="7"/>
</Entry.Behaviors>
</Entry>
Behavior Class
public class LengthValidateBehavior : Behavior<Entry>
{
public static BindableProperty MaxLengthProperty = BindableProperty.Create(nameof(MaxLength), typeof(int), typeof(LengthValidateBehavior), 5/* default value*/);
public int MaxLength
{
get
{
return (int)GetValue(MaxLengthProperty);
}
set
{
SetValue(MaxLengthProperty, value);
}
}
protected override void OnAttachedTo(Entry entry)
{
entry.TextChanged += OnEntryTextChanged;
base.OnAttachedTo(entry);
}
protected override void OnDetachingFrom(Entry entry)
{
entry.TextChanged -= OnEntryTextChanged;
base.OnDetachingFrom(entry);
}
void OnEntryTextChanged(object sender, TextChangedEventArgs args)
{
if (sender is Entry entry)
{
if (args.NewTextValue.Length > MaxLength)// write your logic here
{
entry.Text = args.OldTextValue;
}
}
}
}
Similarly use your own logic to restrict the entry as per your own requirements.

Related

Create table in SQLite with generic class 'Quantity' as a picker - ERROR not a valid type for SQLite DB

Morning All,
xamarin forms - populate picker in MVVM from SQLite DB issue
So...Ive used the following post
https://www.c-sharpcorner.com/article/populate-picker-using-mvvm/
to help me include a populated picker using MVVM in my project, using a picker to allowing the user to select a 'Quantity' to order for each product (which all works fine as its populated from code, but now refactoring to load from SQLite..please see current code below)..
//ProductModel
public class ProductModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _ProductId;
private string _BrandName;
public ObservableCollection<Quantity> _ListQuantites;
private Quantity _selectedQuantity;
//ETC have removed the other properties for sake of this Q
//Constructor
public ProductModel()
{
//Subscription
this.PropertyChanged += OnPropertyChanged;
}
[PrimaryKey, AutoIncrement]
public int ProductId
{
get { return _ProductId; }
set
{
if (_ProductId == value) return;
_ProductId = value;
OnPropertyChanged();
}
}
public ObservableCollection<Quantity> ListQuantites
{
get
{
return _ListQuantites;
}
set
{
_ListQuantites = value;
OnPropertyChanged();
}
}
public Quantity SelectedQuantity
{
get
{
return _selectedQuantity;
}
set
{
if (value == null)
{
_selectedQuantity = _selectedQuantity;
}
else
{
_selectedQuantity = value;
OnPropertyChanged();
}
}
}
//OnPropertyChanged
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(SelectedQuantity))
{
//test quantity amount
}
}
// [NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Up Until now I was populating the ProductModel with its picker, from the constructor of the ProductViewModel page with:
WineList = new ObservableCollection<ProductModel>();
WineList.Add(new ProductModel { ProductId = 1, BrandName = "Mc guigans", Grape = "Red", ListQuantites = List_Quantites, Image = "W.png", Description = "Fruity Flav", Size="700ml", Price = 10.00M, SubTotalForItem = 0.00M, Genre = "Wine" });
//etc...and so on
//then
List_Quantites = PickerService.GetQuantitiesForProductPage();
//Picker service
public static ObservableCollection<QuantityModel> GetQuantitiesForProductPage()
{
var quantities = new ObservableCollection<QuantityModel>()
{
new QuantityModel() {Key=1, Value="0"},
new QuantityModel() {Key=2, Value="1"},
new QuantityModel() {Key=3, Value="2"},
new QuantityModel() {Key=4, Value="3"}
//etc
};
return quantities;
}
//XAML
<Picker Grid.Column="3" Grid.Row="0" Title=" " VerticalOptions="Center" x:Name="productPicker" VerticalTextAlignment="Center" HorizontalOptions="EndAndExpand" ItemsSource="{Binding ListQuantites}" ItemDisplayBinding="{Binding Value}" SelectedIndexChanged="QuantityChanged" SelectedItem ="{Binding SelectedQuantity}"/>
So...yeah as I said all works fine and dandy...But now I would like to load the ProductModel list from a table in SQLite....I have already used SQLite basic CRUD operations to create, load, view, update, edit...etc for orders made...so this is also working...the problem I seem to be having is creating the table, it is failing when I try to create an entry in the table with 'Quantity'....obviously...so I have changed the code...change 'Quantity' to object, then the plan being to populate this on the VM...but this also didnt work...
found another post relating to this:
https://forums.xamarin.com/discussion/2546/create-table-in-xamarin-throws-exception-because-of-generic-list
So 'Quantity' class is not a valid type for SQLite DB value...but turns out neither is object...has anyone idea for a work around for this...or some advice on how to refactor to resolve this?
any help or point in the right direction is appreciated thank Y

Xaramin form -calling variable from other .cs file

I am doing a quiz game in Xaramin. forms. and for the score function. if the user got a correct answer, I want the score will add 1.but in my case even the give the correct answer, the score is not adding.
I am also trying to bind to the "score" variable to a label. I want to know if i put a correct code or not.
Button
private void submit_Clicked(object sender, EventArgs e)
{
string answer = this.answer.Text;
string canswer = "correct";
if (answer != null)
{
string ranswer = answer.Replace(" ", string.Empty);
if (ranswer.ToLower() == canswer)
{
DisplayAlert("GoodJob", "You got the correct answer", "OK");
bindingModel b = new bindingModel();
b.score++;
(sender as Button).IsEnabled = false;
}
else
{
DisplayAlert("Unfortunately", "Your answer is wrong", "OK");
(sender as Button).IsEnabled = false;
}
}
}
ViewModel
public class bindingModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int displayScore => Score;
public int score = 0;
void OnPropertyChanged(int score)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(score.ToString()));
}
public int Score
{
get => score;
set
{
if (score != value)
{
score = value;
OnPropertyChanged(score);
}
}
}
}
Model
<Label Text="{Binding Score}"/>
in your page constructor, keep a reference to your VM
bindingModel VM;
// this is your constructor, the name will match your page name
public MyPage()
{
InitializeComponent();
this.BindingContext = VM = new bindingModel();
...
}
then in your event handler, you do NOT need to create a new bindingModel
// update the Count on the VM
VM.Count++;
Answer
There's two things broken here:
You are re-initializing your ViewModel instead of referencing the same instance
You are passing the wrong value into PropertyChangedEventArgs
1. Referencing the View Model
You are re-initializing the ViewModel every time by calling bindingModel b = new bindingModel();
Lets initialize the ViewModel once, store it as a field, set it as the BindingContext for our ContentPage, and reference that field in submit_Clicked
public partial class QuizPage : ContentPage
{
readonly bindingModel _bindingModel;
public QuizPage()
{
_bindingModel = new bindingModel();
BindingContext = _bindingModel;
}
private async void submit_Clicked(object sender, EventArgs e)
{
string answer = this.answer.Text;
string canswer = "correct";
Button button = (Button)sender;
if (answer != null)
{
string ranswer = answer.Replace(" ", string.Empty);
if (ranswer.ToLower() == canswer)
{
await DisplayAlert("GoodJob", "You got the correct answer", "OK");
_bindingModel.score++;
button.IsEnabled = false;
}
else
{
await DisplayAlert("Unfortunately", "Your answer is wrong", "OK");
button.IsEnabled = false;
}
}
}
}
2. PropertyChangedEventArgs
You need to pass in the name of the property to PropertyChangedEventArgs.
They way PropertyChanged works is that it announces the name of the property that has changed. In this case, it needs to broadcast that the Score property has changed.
Let's use nameof(Score) to pass in the string "Score" to PropertyChangedEventArgs:
void OnScorePropertyChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(displayScore)));
}
public int Score
{
get => score;
set
{
if (score != value)
{
score = value;
OnScorePropertyChanged();
}
}
}

How to reference and bind a property at the same time?

Here is what I mean by the title. I have an Entry with a property called "IsValid" (bool) which is bound to a behaviour (it is called Validator) which checks if the input is in range (0 - 10 in this case) and colour the background of the entry red or transparent. That works fine. However, as I have the same logic in my ViewModel (to check if the input is in range) and show a dialog message if it is not, I wanted to directly bind to the validator's IsValid and use the bind fild in my viewModel (IsBinReferenceValid) abd thus remove the locig from the vm. Currently, the property in my vm IsBinReferenceValid is not changed in any way which indicates that the binding does not work.
Here is the xaml code:
<userControl:DetailedEntry
PlaceholderLabel="{x:Static locale:BinPrintLang.BinRef}"
Text="{Binding BinTextEntry}"
TextColor="{StaticResource PrimaryColor}"
BgColor="White"
BorderColor="{StaticResource DisableColor}"
VerticalOptions="CenterAndExpand"
IsLabelVisible="True"
Label="Bin Reference"
IsImportant="True"
IsValid="{Binding Source={x:Reference InputLengthValidator}, Path=IsValid}">
<userControl:DetailedEntry.EntryBehavior>
<ui:InputLengthValidator x:Name="InputLengthValidator"
MinValue="0"
MaxValue="10"
IsValid="{Binding Source=IsBinReferenceValid, Mode=OneWayToSource}"/>
</userControl:DetailedEntry.EntryBehavior>
</userControl:DetailedEntry>
Any ideas how I can reference and bind to a property at the same time, is that even possible (That is, if that is where the problem is coming from)?
Base validator code:
public class ValueInRangeValidator : Validator<Entry>
{
private static BindableProperty MinValueProperty =
BindableProperty.Create("MinValue", typeof(decimal?), typeof(ValueInRangeValidator));
public decimal? MinValue
{
get { return (decimal?) GetValue(MinValueProperty); }
set
{
SetValue(MinValueProperty, value);
OnPropertyChanged();
}
}
public static BindableProperty MaxValueProperty =
BindableProperty.Create("MaxValue", typeof(decimal?), typeof(ValueInRangeValidator));
public decimal? MaxValue
{
get { return (decimal?) GetValue(MaxValueProperty); }
set
{
SetValue(MaxValueProperty, value);
OnPropertyChanged();
}
}
public virtual void Bindable_TextChanged(object sender, TextChangedEventArgs e)
{
decimal i = 0;
IsValid = decimal.TryParse(e.NewTextValue, out i);
IsValid = IsValid && (MinValue == null ? i >= decimal.MinValue : i >= MinValue);
IsValid = IsValid && (MaxValue == null ? i <= decimal.MaxValue : i <= MaxValue);
}
protected override void OnAttachedTo(Entry bindable)
{
bindable.TextChanged += Bindable_TextChanged;
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.TextChanged -= Bindable_TextChanged;
}
}
InputLengthValidator code:
public class InputLengthValidator : ValueInRangeValidator
{
public override void Bindable_TextChanged(object sender, TextChangedEventArgs e)
{
var max = (int) MaxValue;
var min = (int) MinValue;
var textLenght = e.NewTextValue.Length;
IsValid = textLenght >= min && textLenght < max;
}
}
I managed to get the validation working by subscribing to another (custom) bindable property of my DetailedEntry control called IsWarning.
<userControl:DetailedEntry
rid.Row="1"
Grid.Column="0"
Label="{x:Static locale:GoodsReceiptLang.NumLabels}"
Text="{Binding NumberOfLabels, Mode=TwoWay}"
TextColor="{StaticResource PrimaryColor}"
Keyboard="Numeric"
IsImportant="True"
IsWarning="{Binding ShowWarning}">
</userControl:DetailedEntry>
My VM:
private bool CanPrint()
{
var errors = new List<string>();
ShowWarning = false;
if (SelectedPrinter == null)
errors.Add(CommonLang.SelectPrinterErrorMsg);
if (string.IsNullOrEmpty(NumberOfLabels) || !int.TryParse(NumberOfLabels, out int numLabels))
{
ShowWarning = true;
errors.Add(CommonLang.NotValidInput);
}
if (errors.Any())
{
ShowErrorMessage(string.Join(" ", errors.ToArray()));
return false;
}
return true;
}

Windows Metro Style app data binding

I have a requirement to load images from a folder in the project to a stackpanel. Under each image a name should also be shown. The image folder can change at any time and the number of images can also change.(with a maximum of 50 images) I want to know if I can use data binding to handle this. I thought of having image ID's, their sources and the name for each image in an XML so that I can change that XML file whenever the image folder changes, without changing the rest of the code. Is that feasible? If so how? Can someone please guide me? Thank you in advance.
One solution would be to use a Filepicker to let the user select the images inside the folder, and then bind the selected images to an Itemscontrol. That itemscontrol can then be put inside the Stackpanel. Here's a quick sample using that solution.
Here's the codebehind for picking the image files:
private List<EditableImage> availableImagesList = new List<EditableImage>();
private async void FilePicker_Clicked(object sender, RoutedEventArgs e)
{
FileOpenPicker openPicker = new FileOpenPicker();
openPicker.ViewMode = PickerViewMode.List;
openPicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
//TODO: add supported image file types
openPicker.FileTypeFilter.Add("jpg,png,gif");
// We prompt the user to pick one or more files
IReadOnlyList<StorageFile> files = await openPicker.PickMultipleFilesAsync();
if (files.Count > 0)
{
availableImages.DataContext = null;
String fp = ""; // The path of the picked image
int index = availableImagesList.Count;
foreach (StorageFile file in files)
{
// Copying the selected image to local app data folder
//TODO: check if the selected file is actually and image
if (file != null )
{
StorageFile fileCopy = await file.CopyAsync(ApplicationData.Current.LocalFolder, file.DisplayName + file.FileType, NameCollisionOption.ReplaceExisting);
fp = fileCopy.Path;
}
//Creating the image
CustomImage picToAdd = new CustomImage(index+1, file.DisplayName, fp);
//Adding the image as an UI element to the app bar
availableImagesList.Add(picToAdd);
}
availableImages.DataContext = availableImagesList;
}
}
The CustomImage model:
public class CustomImage
{
private static Uri _baseUri = new Uri("ms-appx:///");
private int _id;
public int Id
{
get { return _id; }
set
{
this.SetProperty(ref this._id, value);
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
this.SetProperty(ref this._name, value);
}
}
private string _imgPath;
public string ImgPath
{
get { return _imgPath; }
set
{
this.SetProperty(ref this._imgPath, value);
}
}
private String _imagePath = null;
private ImageSource _image = null;
public ImageSource Image
{
get
{
if (this._image == null && this._imagePath != null)
{
this._image = new BitmapImage(new Uri(CustomImage._baseUri, this._imagePath));
}
return this._image;
}
set
{
this._imagePath = null;
this.SetProperty(ref this._image, value);
}
}
public void SetImage(String path)
{
this._image = null;
this._imagePath = path;
this.OnPropertyChanged("Image");
}
public CustomImage(int id, string name, string imagepath)
{
SetImage(imagepath);
_id = id;
_name = name;
}
}
Here's the XAML for the ItemsControl inside the Stackpanel:
<StackPanel x:Name="loadedImages" HorizontalAlignment="Left" Orientation="Horizontal">
<!--Displaying the selected images in stackpanel-->
<ItemsControl ItemsSource="{Binding}" ItemsPanel="{StaticResource LoadedItemsPanel}">
<ItemsControl.ItemTemplate>
<!--The template for each object that is displayed as an UI element-->
<DataTemplate>
<Grid Height="88" Margin="2,0" Width="88" >
<Image Source="{Binding Image}"/>
<TextBlock Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
In your page resources, you must also define:
<ItemsPanelTemplate x:Key="LoadedItemsPanel">
<WrapGrid Orientation="Horizontal"/>
</ItemsPanelTemplate>

Save selected items when using caliburn.micro /Telerik RadGridView /Silverlight

I am using Caliburn micro(1.3)/MVVM and Silverlight. When I update the itemsource RadGridView, I lose the selected items. I found a blog about implementing a behavior to save the selected items when you are implementing MVVM. I can get the selected items, but I cannot set them back once the itemsource is refreshed. Can someoneshow me how to implement this using caliburn.micro and the RadGridVIew? I think the best way to go is to create a caliburn micro convention, but I can only find a reference for creating a convention for selectedItem, not selectedItems.
Can someone show me how to accomplish this? I tried the following, but it does not work.
private static void SetRadGridSelecteditemsConventions()
{
ConventionManager
.AddElementConvention<DataControl>(DataControl.ItemsSourceProperty, "SelectedItem", "SelectionChanged")
.ApplyBinding = (viewModelType, path, property, element, convention) =>
{
ConventionManager.SetBinding(viewModelType, path, property, element, convention, DataControl.ItemsSourceProperty);
if (ConventionManager.HasBinding(element, DataControl.SelectedItemProperty))
return true;
var index = path.LastIndexOf('.');
index = index == -1 ? 0 : index + 1;
var baseName = path.Substring(index);
foreach (var selectionPath in
from potentialName in ConventionManager.DerivePotentialSelectionNames(baseName)
where viewModelType.GetProperty(potentialName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance) != null
select path.Replace(baseName, potentialName))
{
var binding = new Binding(selectionPath) { Mode = BindingMode.TwoWay };
BindingOperations.SetBinding(element, DataControl.SelectedItemProperty, binding);
}
return true;
};
}
Thanks,
Stephane
You should use a behavior for this since the SelectedItems property is readonly.
Telerik has an example for this, only the example is not specific for caliburn.micro.
If you add the following class to your project:
public class MultiSelectBehavior : Behavior<RadGridView>
{
public INotifyCollectionChanged SelectedItems
{
get { return (INotifyCollectionChanged)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(INotifyCollectionChanged), typeof(MultiSelectBehavior), new PropertyMetadata(OnSelectedItemsPropertyChanged));
private static void OnSelectedItemsPropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
var collection = args.NewValue as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged += ((MultiSelectBehavior)target).ContextSelectedItems_CollectionChanged;
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItems_CollectionChanged;
}
void ContextSelectedItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(SelectedItems as IList, AssociatedObject.SelectedItems);
SubscribeToEvents();
}
void GridSelectedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UnsubscribeFromEvents();
Transfer(AssociatedObject.SelectedItems, SelectedItems as IList);
SubscribeToEvents();
}
private void SubscribeToEvents()
{
AssociatedObject.SelectedItems.CollectionChanged += GridSelectedItems_CollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged += ContextSelectedItems_CollectionChanged;
}
}
private void UnsubscribeFromEvents()
{
AssociatedObject.SelectedItems.CollectionChanged -= GridSelectedItems_CollectionChanged;
if (SelectedItems != null)
{
SelectedItems.CollectionChanged -= ContextSelectedItems_CollectionChanged;
}
}
public static void Transfer(IList source, IList target)
{
if (source == null || target == null)
return;
target.Clear();
foreach (var o in source)
{
target.Add(o);
}
}
}
This behavior takes care of the synchronization between collection RadGridView.SelectedItems and MultiSelectBehavior.SelectedItems.
Now we need to have an ObservableCollection in the ViewModel
//Collection holding the selected items
private ObservableCollection<object> selectedGridItems;
public ObservableCollection<object> SelectedGridItems
{
get
{
if (selectedGridItems == null)
selectedGridItems = new ObservableCollection<object>();
return selectedGridItems;
}
set
{
if (selectedGridItems == value) return;
selectedGridItems = value;
NotifyOfPropertyChange(() => SelectedGridItems);
}
}
//Deselect all selected items in the gridview
public void ClearSelectedGridItems()
{
SelectedGridItems.Clear();
}
Last thing is bind the behavior in the view
<telerik:RadGridView x:Name="CustomLogs" AutoGenerateColumns="true" SelectionMode="Extended">
<i:Interaction.Behaviors>
<local:MultiSelectBehavior SelectedItems="{Binding SelectedGridItems}"/>
</i:Interaction.Behaviors>
</telerik:RadGridView>
Thats it, hope it helps you!

Resources