Binding three properties to DatePicker - xaml

What would be best way to bind date in DatePicker to something like this:
"startedAt": {
"year": 2021,
"month": 9,
"day": 11
}
In json it looks like this, but these will be three separate ints in c# properties. My entry class that I use for my observable collection and Date class:
public class ListEntry : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public int? id { get; set; }
public Media media { get; set; }
public int? _score;
public int? score
{
get => _score;
set
{
if (value == _score)
return;
_score = value;
OnPropertyChanged();
}
}
public int _Progress;
public int progress
{
get => _Progress;
set
{
if (value == _Progress)
return;
_Progress = value;
OnPropertyChanged();
}
}
public string status { get; set; }
public Date startedAt { get; set; }
}
public class Date : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public int? _year;
public int? year
{
get => _year;
set
{
if (value == _year)
return;
_year = value;
OnPropertyChanged();
}
}
public int? _month;
public int? month
{
get => _month;
set
{
if (value == _month)
return;
_month = value;
OnPropertyChanged();
}
}
public int? _day;
public int? day
{
get => _day;
set
{
if (value == _day)
return;
_day = value;
OnPropertyChanged();
}
}
}
What I need to achieve is when I change date in DatePicker these three properties will change too.
I've tried something like this on my own
<DatePicker>
<DatePicker.Date>
<MultiBinding Mode="TwoWay" StringFormat="{}{0}/{1}/{2}">
<Binding Path="startedAt.month" TargetNullValue="2" />
<Binding Path="startedAt.day" TargetNullValue="3" />
<Binding Path="startedAt.year" TargetNullValue="2000" />
</MultiBinding>
</DatePicker.Date>
</DatePicker>
It picks up dates correctly, but changing one doesn't change properties and I'm getting errors. And it seems buggy, When i scroll down the page and go back to the top it will go back to the original binding instead of my date selected. It's not gonna work or I've done it wrong?
[0:] MultiBinding: '/20/2021' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '//' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '/20/1998' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '//' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '/16/2021' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '//' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '/1/2021' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '//' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '/20/2021' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '//' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '/23/2021' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '//' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '/20/2021' cannot be converted to type 'System.DateTime'
[0:] MultiBinding: '//' cannot be converted to type 'System.DateTime'
Perhaps I need converter of some sort and I can't really change classes, it might not deserialize i think?
EDIT:
foreach (var Group in AnimeGroupObservable)
{
foreach (var Entry in Group)
{
if (Entry.startedAt.year == null)
{
Entry.startedAt.Time = new DateTime(1970, 01, 01);
}
else
{
Entry.startedAt.Time = new DateTime((int)Entry.startedAt.year, (int)Entry.startedAt.month, (int)Entry.startedAt.day);
}
}
}
I added DateTime to my model
public DateTime? _Time;
public DateTime? Time
{
get => _Time;
set
{
if (value == _Time)
return;
_Time = value;
OnPropertyChanged();
}
}

You can try use the Completed event in Entry and DateSelected event in DatePicker to set the link between them. Change the date once the text in entry changed and change the text after date picked.
Here is my sample page and code:
xmal:
<StackLayout x:Name="stk"s/>
<Entry x:Name="E_Year" Completed="Entry Completed"/>
<Entry x:Name="E_ Month" Completed="Entry Completed"/>
<Entry x:Name="E_Day" Completed="Entry Completed"/>
<DatePicker x:Name"mydtp" DateSelected="0nDateSelected"/>
</StackLayout>
codebehind:
private void Entry _Completed(object sender, EventArgs e)
{
int _year = Convert. ToInt16(E_Year.Text);
int _month = Convert. ToInt16(E_Month.Text);
int _day = Convert.ToInt16(E_Day.Text);
DateTime _dateTime = new DateTime(_year,_month,_day);
mydtp.Date =_dateTime;
}
private void OnDateSelected (object sender, EventArgs e)
{
DateTime dateTime = mydtp.Date;
E_Year.Text = dateTime.Year.ToString();
E_Month.Text = dateTime Month.ToString();
E_Day.Text = dateTime.Day.ToString();
}

Related

How do I use a XAML markup extension to convert a DateTime into a string?

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>

Deserializing Enum from DescriptionAttribute using custom JSON.NET JsonConverter stopped working

Looking for help with Newtonsoft Json on asp.net core 2.2.
I have a JsonEnumConverter<T> which was responsible for serializing/deserializing values from DescriptionAttribute from an Enum type. It was working fine until about 2 weeks ago and now it has completely stopped working.
here's what I have:
//From PerformersController:
public async Task<ActionResult<PagedPerformers>> GetPagedPerformersAsync([FromQuery] PerformerRequest performerRequest) { ... }
[JsonObject]
public class PerformerRequest : PageRequest
{
[FromQuery(Name = "performer_id")]
[JsonProperty(PropertyName = "performer_id", Order = 1)]
public override string Id { get; set; }
....
}
[JsonConverter(typeof(JsonEnumConverter<SortDirectionType>))]
public enum SortDirectionType
{
[Description("asc")]
ASCENDING,
[Description("desc")]
DESCENDING
}
public abstract class PageRequest
{
[FromQuery(Name = "page")]
[JsonProperty("page")]
public int Page { get; set; }
[FromQuery(Name = "limit")]
[JsonProperty("limit")]
public int PageSize { get; set; } = 100;
[FromQuery(Name = "sort_field")]
[JsonProperty("sort_field")]
public string SortField { get; set; } //= "Id";
[FromQuery(Name = "sort_dir")] [JsonConverter(typeof(JsonEnumConverter<SortDirectionType>))]
[JsonProperty("sort_dir")]
public SortDirectionType SortDirection { get; set; }
[FromQuery(Name = "id")]
[JsonProperty("id")]
public virtual string Id { get; set; }
}
public class JsonEnumConverter<T> : JsonConverter where T : struct, IComparable, IConvertible, IFormattable
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(T);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var type = typeof(T);
if (!type.IsEnum) throw new InvalidOperationException();
var enumDescription = (string)reader.Value;
return enumDescription.GetEnumValueFromDescription<T>();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var type = typeof(T);
if (!type.IsEnum) throw new InvalidOperationException();
if (value != null)
{
if (value is Enum sourceEnum)
{
writer.WriteValue(sourceEnum.GetDescriptionFromEnumValue());
}
}
}
}
public static class EnumExtensions
{
public static string GetDescriptionFromEnumValue(this Enum #enum)
{
FieldInfo fi = #enum.GetType().GetField(#enum.ToString());
DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(
typeof(DescriptionAttribute),
false);
if (attributes != null &&
attributes.Length > 0)
return attributes[0].Description;
else
return #enum.ToString();
}
public static T GetEnumValueFromDescription<T>(this string description)
{
var type = typeof(T);
if (!type.IsEnum)
throw new InvalidOperationException();
foreach (var field in type.GetFields())
{
if (Attribute.GetCustomAttribute(field,
typeof(DescriptionAttribute)) is DescriptionAttribute attribute)
{
if (attribute.Description == description)
return (T)field.GetValue(null);
}
else
{
if (field.Name == description)
return (T)field.GetValue(null);
}
}
throw new ArgumentException($"No matching value for enum {nameof(T)} found from {description}.",$"{nameof(description)}"); // or return default(T);
}
}
this was working absolutely fine until recently. Now I'm not sure whats going on I get ValidationProblemDetails response right away. If I suppress asp.net core 2.2 model state invalid filter then modelState.IsValid will still have false. If I put a breakpoint in ReadJson of my JsonEnumConverter it wont even hit. Even tried to set JsonSerializerSettings in startup with no success or luck. Have already tried replacing Description with EnumMember and StringEnumConverter. Still the same issue. Seems like there is some issue with ModelBinder and Json.NET not playing well with each other.
NOTE: This issue is happening on ASP.NET Core 2.2. Suggesting solutions for 3.0 is not helpful!!
If you are using aspnet core 3 / netstandard 2.1, you can try this library https://github.com/StefH/System.Text.Json.EnumExtensions which defines some extensions to the JsonStringEnumConverter to support attributes like EnumMember, Display and Description.

Use IMarkupExtension together with StringFormat

I'm using the TranslateExtension from Xamarin. Is it possible to add a StringFormat to the call?
Currently, I have
<Label Text="{i18n:Translate User}" />
but I would need something like this
<Label Text="{i18n:Translate User, StringFormat='{0}:'}" />
If I do the latter, I get
Xamarin.Forms.Xaml.XamlParseException: Cannot assign property "StringFormat": Property does not exists, or is not assignable, or mismatching type between value and property
I know I could add another translation with a colon, but it would be nice to have a different option.
A bit late to the party, but doing it with the standard extension and just XAML, go like this:
<Label Text="{Binding YourDynamicValue, StringFormat={i18n:Translate KeyInResources}}"/>
Your translation should look something like: Static text {0}. Where {0} is replaced by the value you bind to.
The problem is that the Translate extension just gets your string out of the resources, and doesn't have a StringFormat property etc. But you can assign the retrieved resource value to the StringFormat of the Binding.
You can add a parameter property to TranslateExtension.
My TranslateExtension looks like this. You can take the Parameter parts and add it to the one from the Xamarin sample.
[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
public string Text { get; set; }
public string Parameter { get; set; }
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
{
try
{
if (Text == null)
return null;
var culture = new CultureInfo(CultureHelper.CurrentIsoLanguage);
var result = LocalizationResources.ResourceManager.GetString(Text, culture);
if (string.IsNullOrWhiteSpace(Parameter))
{
return string.IsNullOrWhiteSpace(result) ? "__TRANSLATE__" : result;
}
return string.IsNullOrWhiteSpace(result) ? "__TRANSLATE__" : string.Format(result, Parameter);
}
catch (Exception ex)
{
TinyInsights.TrackErrorAsync(ex);
return "__TRANSLATE__";
}
}
}
Here I have updated the Xamarin sample:
[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
readonly CultureInfo ci = null;
const string ResourceId = "UsingResxLocalization.Resx.AppResources";
static readonly Lazy<ResourceManager> ResMgr = new Lazy<ResourceManager>(() => new ResourceManager(ResourceId, IntrospectionExtensions.GetTypeInfo(typeof(TranslateExtension)).Assembly));
public string Text { get; set; }
public string StringFormat {get;set;}
public TranslateExtension()
{
if (Device.RuntimePlatform == Device.iOS || Device.RuntimePlatform == Device.Android)
{
ci = DependencyService.Get<ILocalize>().GetCurrentCultureInfo();
}
}
public object ProvideValue(IServiceProvider serviceProvider)
{
if (Text == null)
return string.Empty;
var translation = ResMgr.Value.GetString(Text, ci);
if (translation == null)
{
#if DEBUG
throw new ArgumentException(
string.Format("Key '{0}' was not found in resources '{1}' for culture '{2}'.", Text, ResourceId, ci.Name),
"Text");
#else
translation = Text; // HACK: returns the key, which GETS DISPLAYED TO THE USER
#endif
}
if(!string.IsNullOrWhitespace(StringFormat)
return string.Format(StringFormat, translation);
return translation;
}
}

Cannot bind a negative Integer to an Entry Text

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}}" />

XAML ComboBox default SelectedIndex is not shown

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;
}
}