I have an Entry on a Xamarin Forms ContentPage that I have bound to a ViewModel property QtyIn which is an Int32:
private int _qtyIn;
public int QtyIn
{
get { return _qtyIn; }
set
{
if (_qtyIn != value)
{
_qtyIn = value;
RaisePropertyChanged("QtyIn");
}
}
}
Here is my XAML:
<Entry Text="{Binding Path=Source.QtyIn, Mode=TwoWay, Converter={StaticResource intToStringConverter}}" />
And my IValueConverter:
public class IntToStringConverter : IValueConverter
{
// from Int32 to String
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value.ToString();
}
// String to Int
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int parsedInt = 0;
if (int.TryParse(value.ToString(), out parsedInt))
{
return parsedInt;
}
return value;
}
}
The problem is when I try and enter a negative number, starting with the '-' minus sign, the binding fails because it can't convert '-' to an Int32. This is the error I see in the mono output when running this app on an Android device:
05-03 15:19:27.923 I/mono-stdout(19384): Binding: - can not be converted to type 'System.Int32'
Does anyone know how to bind an integer to an Text property that allows negative numbers? I can't find any documentation about this on Xamarin's website or forums.
In WPF, I would use the UpdateSourceTrigger=LostFocus property to only do the conversion after the whole number is entered, but Xamarin Forms doesn't have this property available.
Have you tried returning a 0 if the value passed is "-"?
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int parsedInt = 0;
if (value == "-") return 0;
if (int.TryParse(value.ToString(), out parsedInt))
{
return parsedInt;
}
return value;
}
Your problem is, you return value, which is a string with - and try to pass it to an Int32 (back in your ViewModel).
Instead put second state into your logic. Return a null if parsing did fail - this also helps avoiding problems with any other character input.
public class IntToStringConverter : IValueConverter
{
// from Int32 to String
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value?.ToString() ?? "";
}
// String to Int
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int parsedInt = 0;
if (int.TryParse(value.ToString(), out parsedInt))
{
return parsedInt;
}
return null;
}
}
And here is the ViewModel:
private int? _qtyIn;
public int? QtyInNullable // you should bind on this one in your entry
{
get { return _qtyIn; }
set
{
if (_qtyIn != value)
{
_qtyIn = value;
if (value != null)
OnPropertyChanged("QtyInNullable");
OnPropertyChanged("QtyIn");
}
}
}
public int QtyIn // you should bind on this one in your entry
{
get { return _qtyIn ?? 0; }
}
And bind it:
<Entry Text="{Binding Path=Source.QtyInNullable, Mode=TwoWay, Converter={StaticResource intToStringConverter}}" />
Related
I have created a markup extension to convert a DateTime into a string
public class DateTimeConverterExtension : IMarkupExtension<string> {
public DateTime Source { get; set; }
public string ProvideValue(IServiceProvider serviceProvider) {
var delta = DateTime.Now -Source;
if (delta.TotalDays > 0) {
return string.Format(StringResources.DaysAgo, delta.TotalDays);
}
if (delta.TotalHours > 0) {
return string.Format(StringResources.HoursAgo, delta.TotalHours);
}
if (delta.TotalMinutes > 0) {
return string.Format(StringResources.MinutesAgo, delta.TotalMinutes);
}
return string.Format(StringResources.MinutesAgo, 0);
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider) {
return (this as IMarkupExtension<string>).ProvideValue(serviceProvider);
}
}
But when I use this in XAML:
<Label Text="{markupExtensions:DateTimeConverter Source={Binding Time}}" />
I get the following error:
No property, BindableProperty, or event found for "Source", or mismatching type between value and property.
Where did I go wrong?
You can convert Datetime with Converter.
To work with Converter, you need to create a class that implements the IValueConverter interface
code like:
public class DatetimeToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return string.Empty;
var datetime = (DateTime)value;
//put your custom formatting here
return datetime.ToLocalTime().ToString("g");
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
then use it in xaml:
<ResourceDictionary>
<local:DatetimeToStringConverter x:Key="cnvDateTimeConverter"></local:DatetimeToStringConverter>
</ResourceDictionary>
...
<Label Text="{Binding Date, Converter={StaticResource cnvDateTimeConverter}}"></Label>
I have the following converter that takes two properties :
public class ProgressConverter : BindableObject, IMarkupExtension, IValueConverter
{
public static readonly BindableProperty CurrentProgressProperty = BindableProperty.Create("CurrentProgress", typeof(int), typeof(ProgressConverter));
public static readonly BindableProperty GoalProgressProperty = BindableProperty.Create("GoalProgress", typeof(int), typeof(ProgressConverter));
public int CurrentProgress
{
get { return(int) GetValue(CurrentProgressProperty); }
set { SetValue(CurrentProgressProperty, value);}
}
public int GoalProgress
{
get { return (int)GetValue(GoalProgressProperty); }
set { SetValue(GoalProgressProperty, value);}
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double progress = (double)CurrentProgress / (double)GoalProgress;
return progress;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public object ProvideValue(IServiceProvider serviceProvider)
{
//throw new NotImplementedException();
return this;
}
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
return ProvideValue(serviceProvider);
}
}
and I am using it inside progressbar to set progress like this (this works fine no problem , will take 2 and divide it by 5 then put the results into progressbar progress ):
<ProgressBar x:Name="Progressbar" ProgressColor="Purple"
Progress="{Binding Converter={local:ProgressConverter CurrentProgress=1,GoalProgress=5}}">
</ProgressBar>
But when I do this , it wont work (Point#1) : (the value for both CurrentProgress and GoalProgress inside the converter are 0).
<ProgressBar x:Name="Progressbar" ProgressColor="Purple"
Progress="{Binding Converter={local:ProgressConverter CurrentProgress={Binding xCurrentProgress},GoalProgress={Binding xGoalProgress}}}">
</ProgressBar>
Both xCurrentProgress and xGoalProgress are set in the parent itemSource .
Model :
public class badge {
string Name {get;set;}
string Description {get;set;}
int xCurrentProgress {get;set;}
int xGoalProgress {get;set;}
}
Itemsource setting :
public BadgeView(BadgesGroup badgesGroup)
{
InitializeComponent();
BadgeLogo.Source = badgesGroup.Logo;
var cardsview = new CardsView
{
IsClippedToBounds = true,
IsCyclical = true,
MoveWidthPercentage = 0.3,
WidthRequest = 250,
HeightRequest=250,
ItemsSource = badgesGroup.Badges,
ItemTemplate = new DataTemplate(() => new BadgePopUp())
};
cardsview.Children.Add(new IndicatorsControl());
cardsview.Children.Add(new RightArrowControl());
cardsview.Children.Add(new LeftArrowControl());
card.Children.Add(cardsview);
}
How do I make it work for point#1
How can I make the converter takes values from binding like this :
<ProgressBar x:Name="Progressbar" ProgressColor="Purple"
Progress="{Binding Converter={local:ProgressConverter CurrentProgress={Binding xCurrentProgress},GoalProgress={Binding xGoalProgress}}}">
</ProgressBar>
Because using above code would result both values of CurrentProgress and GoalProgress pass 0 not the actual value stored in them .
I am confused in XAML namespace declaration.
Please help to clarify my case. I have class BooleanToObjectConverter : IValueConverter in CoTraveller namespace:
public class EmailValidatorBehavior : Behavior<Entry>
{
const string digitRegex = #"^(?("")("".+?(?<!\\)""#)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])#))" +
#"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$";
static readonly BindablePropertyKey IsValidPropertyKey = BindableProperty.CreateReadOnly("IsValid", typeof(bool), typeof(EmailValidatorBehavior), false);
public static readonly BindableProperty IsValidProperty = IsValidPropertyKey.BindableProperty;
static readonly BindablePropertyKey IsVisiblePropertyKey = BindableProperty.CreateReadOnly("IsVisible", typeof(bool), typeof(EmailValidatorBehavior), false);
public static readonly BindableProperty IsVisibleProperty = IsVisiblePropertyKey.BindableProperty;
public bool IsValid
{
get { return (bool)base.GetValue(IsValidProperty); }
private set { base.SetValue(IsValidPropertyKey, value); }
}
public bool IsVisible
{
get { return (bool)base.GetValue(IsVisibleProperty); }
private set { base.SetValue(IsVisiblePropertyKey, 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 e)
{
if (e.NewTextValue.Length > 0)
{
IsVisible = true;
Entry entry = (Entry)sender;
IsValid = Regex.IsMatch(e.NewTextValue, digitRegex);
if (IsValid) // Check only if we have a valid email
{
// Here we validate if the email contains our requirements
String email = entry.Text;
int pos = email.IndexOf("#"); // Exclude the domain
string username = email.Substring(0, pos);
if (username.Contains(FRIEND) || username.Contains(FRIENDS))
{
IsValid = true;
}
else
IsValid = false;
}
}
else
IsVisible = false;
}
public class BooleanToObjectConverter<Image> : IValueConverter
{
public Image FalseObject { set; get; }
public Image TrueObject { set; get; }
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (bool)value ? this.TrueObject : this.FalseObject;
}
//public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
//{
// throw new NotImplementedException();
//}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return ((Image)value).Equals(this.TrueObject);
}
//public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
//{
// throw new NotImplementedException();
//}
}
}
I want to use FalseObject and TrueObject properties in App resources like:
<Application
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CoTraveller.RegistrationPage"
xmlns:local="clr-namespace:CoTraveller">
<Application.Resources>
<!-- Application resource dictionary -->
<ResourceDictionary>
<local:BooleanToObjectConverter x:Key="boolToStyleImage"
x:TypeArguments="Style">
<local:BooleanToObjectConverter.FalseObject>
<Style TargetType="Image">
<Setter Property="HeightRequest" Value="24" />
<Setter Property="Source"
Value="your_wrong_image_here.png" />
</Style>
</local:BooleanToObjectConverter.FalseObject>
<x:BooleanToObjectConverter.TrueObject>
<Style TargetType="Image">
<Setter Property="HeightRequest" Value="24" />
<Setter Property="Source"
Value="your_correct_image_here.png" />
</Style>
</x:BooleanToObjectConverter.TrueObject>
</local:BooleanToObjectConverter>
</ResourceDictionary>
</Application.Resources>
</Application>
but have error: Severity Code Description Project File Line Suppression State
Error XLS0415 The attachable property 'FalseObject' was not found in type 'BooleanToObjectConverter'. CoTraveller App.xaml 11
What is the problem?
Derive from DependencyObject in order to be able to use the bindable DependencyProperties:
public class BooleanToObjectConverter: DependencyObject, IValueConverter
{
public Image FalseObject
{
get
{
return (Image)GetValue(FalseObjectProperty);
}
set
{
SetValue(FalseObjectProperty, value);
}
}
public static readonly DependencyProperty FalseObjectProperty =
DependencyProperty.Register(
"FalseObject",
typeof(Image),
typeof(BooleanToObjectConverter<Image>),
new PropertyMetadata(null));
public Image TrueObject
{
get
{
return (Image)GetValue(TrueObjectProperty);
}
set
{
SetValue(TrueObjectProperty, value);
}
}
public static readonly DependencyProperty TrueObjectProperty =
DependencyProperty.Register(
"TrueObject",
typeof(Image),
typeof(BooleanToObjectConverter<Image>),
new PropertyMetadata(null));
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (bool)value ? this.TrueObject : this.FalseObject;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
return ((Image)value).Equals(this.TrueObject);
}
}
Side note: The use of the generic here doesn't make a lot of sense so I removed it.
If the original intention was to do use an actual generic type see: Generic Type in Xaml Resources because it is not that straight forward to specify a generic type argument for entries in a resources dictionary.
I have a ComboBox with some values, and I want to have two things working at once.
Here is my ComboBox and I want to show the 10 as default value and also to bind it to a double? Distance property.
<ComboBox Grid.Row="5" Grid.Column="1"
SelectedIndex="1"
SelectedValue="{Binding Distance, Mode=TwoWay, Converter={StaticResource StringToDoubleConverter}}">
<ComboBoxItem>1</ComboBoxItem>
<ComboBoxItem IsSelected="True">10</ComboBoxItem>
<ComboBoxItem>100</ComboBoxItem>
<ComboBoxItem>1000</ComboBoxItem>
</ComboBox>
And here is the converter:
public class StringToDoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
ComboBoxItem item = value as ComboBoxItem;
if (item != null)
{
double d;
if (double.TryParse(item.Content.ToString(), out d))
return d;
}
return null;
}
}
The problem is that in this code, The selected item 10 is not show at the start of the application.
If I will remove the line with the converter, then it will show the selected item 10, but then, I can't bind it to the double? Distance property. I dont want to write a code behind for it, such as: Convert.ToDouble(combobox1.SelectedValue)...
What can I do to make both things work?
You need to populate combo box items from ViewModel. Moreover you should not use SelectedValue property, instead of it you should use SelectedItem. See the below given code.
XAML
<ComboBox x:Name="cmb" ItemsSource="{Binding DistanceCollection}"
SelectedItem="{Binding Distance, Converter={StaticResource StringToDoubleConverter}, Mode=TwoWay}"/>
ViewModel
public class viewModel : INotifyPropertyChanged
{
public viewModel()
{
DistanceCollection = new ObservableCollection<string>
{
"1",
"10",
"100",
"1000"
};
Distance = double.Parse(DistanceCollection[1].ToString());
}
public ObservableCollection<string> DistanceCollection { get; set; }
private double _Distance;
public double Distance
{
get
{
return _Distance;
}
set
{
_Distance = value;
OnPropertyChanged("Distance");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName = null)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Converter
public class StringToDoubleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
string item = value as string;
if (!string.IsNullOrWhiteSpace(item))
{
double d;
if (double.TryParse(item, out d))
return d;
}
return null;
}
}
I have a ContentProperty set on one of my class but when i coded in XAML it produces a XAMLParseException. Was wondering if anyone can advice me on what i can do.
Code Behind:
[ContentProperty(Name="Converters")]
class ChainedValueConverter: DependencyObject, IValueConverter
{
public static readonly DependencyProperty ConvertersProperty = DependencyProperty.Register("Converters", typeof(ObservableCollection<IValueConverter>), typeof(ChainedValueConverter), null);
public ChainedValueConverter()
{
SetValue(ConvertersProperty, new ObservableCollection<IValueConverter>());
}
public ObservableCollection<IValueConverter> Converters
{
get
{
return (ObservableCollection<IValueConverter>)GetValue(ConvertersProperty);
}
}
public object Convert(object value, Type targetType, object parameter, string language)
{
object _cValue = value;
foreach (IValueConverter converter in Converters)
{
_cValue = converter.Convert(_cValue, targetType, parameter, language);
if (_cValue == DependencyProperty.UnsetValue)
break;
}
return _cValue;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
object _cValue = value;
foreach (IValueConverter converter in Converters)
{
_cValue = converter.ConvertBack(_cValue, targetType, parameter, language);
if (_cValue == DependencyProperty.UnsetValue)
break;
}
return _cValue;
}
}
/// <summary>
/// Converts boolean value to visibility.
/// </summary>
class BooleanToVisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return (Boolean)value ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return (Visibility)value == Visibility.Visible ? true : false;
}
}
XAML:
<common:ChainedValueConverter x:Key="A">
<common:BooleanToVisibilityConverter />
</common:ChainedValueConverter>
The above codes when ran produces a XamlParseException with WinRT information: E_UNKNOWN_ERROR
I really like your idea of a chained converter and tried to find a solution. It seems you simply need to initialize your collection in the constructor (create a new instance). See my implementation:
[ContentProperty(Name = "Converters")]
public class ChainedConverter : DependencyObject, IValueConverter
{
public ChainedConverter()
{
Converters = new ObservableCollection<IValueConverter>();
}
public static readonly DependencyProperty ConvertersProperty =
DependencyProperty.Register("Converters", typeof (ObservableCollection<IValueConverter>),
typeof (ChainedConverter), new PropertyMetadata(default(ObservableCollection<IValueConverter>)));
public ObservableCollection<IValueConverter> Converters
{
get { return (ObservableCollection<IValueConverter>) GetValue(ConvertersProperty); }
set { SetValue(ConvertersProperty, value); }
}
public object Convert(object value, Type targetType, object parameter, string language)
{
foreach (var c in Converters)
value = c.Convert(value, targetType, parameter, language);
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
foreach (var c in Converters)
value = c.ConvertBack(value, targetType, parameter, language);
return value;
}
}