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 .
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>
What i want is, when the value is changed, it should call CreateChart()and use the new values.
I try to call in an onPropertyChange method OnValueChanged a bindable Property with reflection, but the property is always null and i dont get the value of the property Value
public partial class CorrectWrongRingChart : ContentView
{
public CorrectWrongRingChart()
{
InitializeComponent();
}
public static readonly BindableProperty ChartProperty =
BindableProperty.Create(
nameof(CorrectWrongChart),
typeof(Chart),
typeof(CorrectWrongRingChart));
public Chart CorrectWrongChart
{
get { return (Chart)GetValue(ChartProperty); }
set => SetValue(ChartProperty, value);
}
public static readonly BindableProperty ValueProperty =
BindableProperty.Create(
nameof(Value),
typeof(CorrectWrongValue),
typeof(CorrectWrongRingChart),
propertyChanged: OnValueChanged);/*(b, o, n) => { ((CorrectWrongRingChart)b).OnPropertyChanged("Text");});*/
public CorrectWrongValue Value
{
get { return (CorrectWrongValue)GetValue(ValueProperty); }
set => SetValue(ValueProperty, value);
}
private static void OnValueChanged(BindableObject bindable, object oldValue, object newValue)
{
((CorrectWrongRingChart)bindable).OnPropertyChanged("Text");
//((CorrectWrongRingChart)bindable).OnPropertyChanged("Correct");
//((CorrectWrongRingChart)bindable).OnPropertyChanged("Wrong");
var valueProperty = ValueProperty.GetType().GetProperty("Value");
var value = (CorrectWrongValue)valueProperty.GetValue("Value");
var ChartProperty = ValueProperty.GetType().GetProperty("CorrectWrongChart");
if (value != null)
{
ChartProperty.SetValue("CorrectWrongChart", CreateChart(value));
}
}
public static readonly BindableProperty TextProperty =
BindableProperty.Create(
nameof(Text),
typeof(string),
typeof(CorrectWrongRingChart),
defaultValue: string.Empty);
public string Text => $"{Value?.CorrectCount ?? 0}/{Value?.TotalCount ?? 0}";
//public double Correct => Value.CorrectPercentage;
//public double Wrong => Value.WrongPercentage;
private static Chart CreateChart(CorrectWrongValue value)
{
var chart = new Microcharts.DonutChart();
chart.IsAnimated = false;
ChartEntry corretEntry = new ChartEntry((float)value.CorrectPercentage)
{
Color = SKColor.Parse("#00FF00")
};
ChartEntry wrongEntry = new ChartEntry((float)value.WrongPercentage)
{
Color = SKColor.Parse("#FF0000")
};
chart.Entries = new List<ChartEntry>() { corretEntry, wrongEntry };
return chart;
}
}
Xaml:
<Grid >
<forms:ChartView x:Name="chart1" WidthRequest="130" HeightRequest="130" Chart="{Binding CorrectWrongChart, Source={x:Reference Root}}">
</forms:ChartView>
<Label Text="{ Binding Text, Source={x:Reference Root} }"
VerticalOptions="Center"
HorizontalOptions="Fill"
HorizontalTextAlignment="Center"
TextColor="Black"
FontSize="19"
FontFamily="{ StaticResource AppBoldFontFamily }" />
</Grid>
If you want to get Value when the value is changed , you could get it directly like following
private static void OnValueChanged(BindableObject bindable, object oldValue, object newValue)
{
var currentValue = newValue;
// do something you want
// you could get other property like following
// var view = bindable as CorrectWrongRingChart;
// var currentText = view.Text;
}
The property Value will change automatically when we change the value of it source .So we don't need to invoke the following lines any more , which maybe will lead to infinite loop .
if (value != null)
{
ChartProperty.SetValue("CorrectWrongChart", CreateChart(value));
}
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;
}
}