CalendarDatePicker returns null when selecting the same date second time - xaml

I'm using CalendarDatePicker binded to Property, and the Closed event binded to a method, both in my ViewModel. The LoadPage method uses SelectedDate property to load some data. Everything is working fine except for the time when i try to pick the same date that is picked already. In the converter i can see that the value picked is null and i get an exception because it cannot cast null to DateTimeOffset. Any idea why picked date end up being null? And how to fix this issue?
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
var dateoffset = (DateTimeOffset)value;
return dateoffset.Date;
}
...
<CalendarDatePicker x:Bind ViewModel.SelectedDate,
Converter={StaticResource DateTimeConverter}, Mode=TwoWay}"
Closed="{x:Bind ViewModel.LoadPage}">
</CalendarDatePicker>

I have fixed it by checking in converter for null and returning DateTime.MinValue value, and then in property setter raising PropertyChanged event.
It is not really nice, i would welcome better solution.
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
if (value == null) return DateTime.MinValue;
DateTimeOffset sourceTime = (DateTimeOffset)value;
DateTime targetTime = sourceTime.DateTime;
return targetTime;
}
public DateTime CurrentDate
{
get { return _currentDate; }
set
{
if (value == DateTime.MinValue)
{
RaisePropertyChanged(nameof(CurrentDate));
return;
}
Set(ref _currentDate, value);
}
}

Related

How can I bind a comma separated list in a URL to an array of objects?

I have a class named VerseRangeReference that has the properties Chapter, FirstVerse and LastVerse.
I have decorated it with a TypeConverterAttribute [TypeConverter(typeof(VerseRangeReferenceConverter))]
I have an action on a controller like this
public Task<ViewResult> Verses(VerseRangeReference[] verses)
But the value of verses is always a single element with the value null. Here is my type converter
public class VerseRangeReferenceConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.GetType() == typeof(string))
{
string source = (string)value;
return VerseRangeReference.ParseMultiple(source);
}
return null;
}
}
The result of VerseRangeReference.ParseMultiple(source) is a valid array of instances of VerseRange.
I had to implement a custom model binder. If someone can think of a way to do this with a TypeConverter then I will accept that answer instead because model binders are more complicated.
public class VerseRangeReferenceArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string modelName = bindingContext.ModelName;
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult != ValueProviderResult.None)
{
VerseRangeReference[] verseRangeReferences = VerseRangeReference.ParseMultiple(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(verseRangeReferences);
}
return Task.CompletedTask;
}
}
public class VerseRangerReferenceArrayModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(VerseRangeReference[]))
return new BinderTypeModelBinder(typeof(VerseRangeReferenceArrayModelBinder));
return null;
}
}
This must be registered.
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new VerseRangerReferenceArrayModelBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You can use a type converter to bind a comma separated string to a sequence of values. However, the type converter should convert from the string to the sequence directly. This means that the type converter should be configured for something like IEnumerable<T> or T[]. To simplify matters I will continue my explanation for IEnumerable<int> but if you want to use arrays instead you should just make sure that the type converter converts to an array instead of something that implements IEnumerable<T>.
You can configure a type converter for IEnumerable<int> using TypeDescriptor.AddAttributes:
TypeDescriptor.AddAttributes(
typeof(IEnumerable<int>),
new TypeConverterAttribute(typeof(EnumerableIntTypeConverter)));
This configures EnumerableIntTypeConverter as a type converter that can convert IEnumerable<int>.
This call has to be made when the process starts and in the case of ASP.NET Core this can conveniently be done in the Startup.Configure method.
Here is the EnumerableIntTypeConverter that converts the comma separated string of numbers to a list of ints:
internal class EnumerableIntTypeConverter : TypeConverter
{
private const char Separator = ',';
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
=> sourceType == typeof(string);
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!(value is string #string))
throw new NotSupportedException($"{GetType().Name} cannot convert from {(value != null ? value.GetType().FullName : "(null)")}.");
if (#string.Length == 0)
return Enumerable.Empty<int>();
var numbers = new List<int>();
var start = 0;
var end = GetEnd(#string, start);
while (true)
{
if (!int.TryParse(
#string.AsSpan(start, end - start),
NumberStyles.AllowLeadingSign,
culture,
out var number))
throw new FormatException($"{GetType().Name} cannot parse string with invalid format.");
numbers.Add(number);
if (end == #string.Length)
break;
start = end + 1;
end = GetEnd(#string, start);
}
return numbers;
}
private static int GetEnd(string #string, int start)
{
var end = #string.IndexOf(Separator, start);
return end >= 0 ? end : #string.Length;
}
}
The parsing uses System.Memory to avoid allocating a new string for each number in the list. If your framework doesn't have the int.TryParse overload that accepts a Span<char> you can use string.Substring instead.

Modelbinder defaults to 0 when returning null instead of the value set in the controller

I have a custom model binder, being used in a REST API, which looks as follows:
public class CustomQueryModelBinder : IModelBinder
{
public Task<ModelBindingResult> BindModelAsync(ModelBindingContext bindingContext)
{
if (!String.IsNullOrWhiteSpace(bindingContext.ModelName) && bindingContext.ModelType == typeof(short) && bindingContext.ValueProvider.GetValue(bindingContext.ModelName) != null)
{
short value;
var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).FirstValue as string;
if (String.IsNullOrWhiteSpace(val))
{
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, val);
}
else if (Int16.TryParse(val, out value) && value >= 0)
{
return ModelBindingResult.SuccessAsync(bindingContext.ModelName, value);
}
else
{
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "The value is invalid.");
}
}
return ModelBindingResult.FailedAsync(bindingContext.ModelName);
}
}
And in cases where the custom value is not specified in the URI it should default to a valid value (greater than 0) however it is always defaulting to 0, even though the controller looks as follows:
public async Task<IActionResult> GetAsync(
[ModelBinder(BinderType = typeof(CustomQueryModelBinder))]short value = 100,
Basically value here should be getting set to 100 as its default value when it returns as null from the ModelBinder.
However this is not happening and it is constantly being returned as 0 which is resulting in System.ArgumentOutOfRangeException when trying to do a Get.
We are using RC1.
Replacing short value = 100 with short? value = 100 seems to have worked for me. Hooray for nullable types.

WPF Set the Corner Radius of a Border through a Converter

I would like to set the bottom Corner Radius of a ListView Item for just the "last" item in the list. I've attempted to do so with a Converter (which in fact finds the last row), but to no avail.
The desirable effect is when the Converter returns true after finding the last item in the ListView, the border CornerRadius on the last ListViewItem is set to CornerRadius="0,0,10,10". For all other items in the ListView, CornerRadius="0,0,0,0"
What I've done so far.
The Converter...
public class IsLastItemConverter : IValueConverter
{
#region IValueConverter Members
public object TrueValue { get; set; }
public object FalseValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ListViewItem item = value as ListViewItem;
ListView listView = ItemsControl.ItemsControlFromItemContainer(item) as ListView;
if (listView != null)
{
int index = listView.ItemContainerGenerator.IndexFromContainer(item);
if (index == listView.Items.Count - 1)
{
TrueValue = true;
return (bool)TrueValue;
}
else
{
FalseValue = false;
return (bool)FalseValue;
}
}
FalseValue = false;
return (bool)FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Just Convert Back
return true;
}
#endregion
}
The XAML...
<local:IsLastItemConverter x:Key="lastItemConverter"
TrueValue="0,0,10,10" FalseValue="0,0,0,0"/>
<DataTemplate x:Key="CellContentTemplate">
<Border
x:Name="CellContentBorder"
Background="{StaticResource GrayCharcoal}"
BorderThickness="4,4,4,4"
BorderBrush="{StaticResource Gray}"
CornerRadius="{Binding Converter={StaticResource lastItemConverter},
RelativeSource={RelativeSource AncestorType={x:Type ListViewItem}}}"
HorizontalAlignment="Left"
Width="210"
Height="50"
ContextMenu="{StaticResource MarginalUnitsContextMenu}"
ScrollViewer.VerticalScrollBarVisibility="Hidden">...
Thoughts and ideas much appreciated - Glenn
I would try returning a real CornerRadius instead of a string (which, you are essentially returning).
My assumption is following:
When passing as string "0,0,0,0" in xaml as CornerRadius, the appropriate Converter is used to convert this string in to a CornerRadius struct.
As you are now using a custom converter, possibly the string to CornerRadius Converter is not anymore used...
EDIT (in light of Clemens answer): The Converter for the property does seem to kick in when a string was returned from the custom converter associated with the binding!
This is because CornerRadius has an associated TypeConverter:
[TypeConverterAttribute(typeof(CornerRadiusConverter))]
public struct CornerRadius : IEquatable<CornerRadius>
which handles this conversion. This converter handles the Conversion from string to CornerRadius.
(Taken from http://msdn.microsoft.com/en-us/library/system.componentmodel.typeconverterattribute.aspx )

how to parse non-string values in Opencsv HeaderColumnNameMappingStrategy

I'm using a HeaderColumnNameMappingStrategy to map a csv file with a header into a JavaBean. String values parse fine but any "true" or "false" value in csv doesn't map to JavaBean and I get the following exception from the PropertyDescriptor:
java.lang.IllegalArgumentException: argument type mismatch
The code where it occurs is in CsvToBean, line 64:
protected T processLine(MappingStrategy<T> mapper, String[] line) throws
IllegalAccessException, InvocationTargetException, InstantiationException, IntrospectionException {
T bean = mapper.createBean();
for(int col = 0; col < line.length; col++) {
String value = line[col];
PropertyDescriptor prop = mapper.findDescriptor(col);
if (null != prop) {
Object obj = convertValue(value, prop);
// this is where exception is thrown for a "true" value in csv
prop.getWriteMethod().invoke(bean, new Object[] {obj});
}
}
return bean;
}
protected PropertyEditor getPropertyEditor(PropertyDescriptor desc) throws
InstantiationException, IllegalAccessException {
Class<?> cls = desc.getPropertyEditorClass();
if (null != cls) return (PropertyEditor) cls.newInstance();
return getPropertyEditorValue(desc.getPropertyType());
}
I can confirm (via debugger) that the setter method id correctly retrieved at this point.
The problem occurs in desc.getPropertyEditorClass() since it returns null. I assumed primitive types and its wrappers are supported. Are they not?
I've run into this same issue. The cleanest way is probably to override getPropertyEditor like pritam did above and return a custom PropertyEditor for your particular object. The quick and dirty way would be to override convertValue in anonymous class form, like this:
CsvToBean<MyClass> csvToBean = new CsvToBean<MyClass>(){
#Override
protected Object convertValue(String value, PropertyDescriptor prop) throws InstantiationException,IllegalAccessException {
if (prop.getName().equals("myWhatever")) {
// return an custom object based on the incoming value
return new MyWhatever((String)value);
}
return super.convertValue(value, prop);
}
};
This is working fine for me with OpenCSV 2.3. Good luck!
I resolved this by extending CsvToBean and adding my own PropertyEditors. Turns out opencsv just supports primitive types and no wrappers.
Pritam's answer is great and this is a sample for dealing with datetime format.
PropertyEditorManager.registerEditor(java.util.Date.class, DateEditor.class);
You should write your own editor class like this:
public class DateEditor extends PropertyEditorSupport{
public static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
#Override
public void setAsText(String text){
setValue(sdf.parse(text));}
}

Mvvm light toolkit and navigate by frame

Do you have a tutoriel which explain navigate with uri. When my application start i load in my frame "Login.xaml" and his viewModel. When i click on my button "Log" (i use a relaycommand) i want that my frame load "Acceuil.xaml".
how make it ?
thx
You are trying too hard. Frame navigation is very simple - just create your frame, such as 'MyFrame' then created Hyperlinks with a simple NavigateUri value of "/Acceuil.xaml". If you want to show/hide the links from the status / details of your view model, use a property which you bind to and update in the view model. For example. You can use a UserInfo property, then a converter class such as this to show / hide based upon the UserInfo property being a null value or a class result:
public class HideWhenNullConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
{
return Visibility.Collapsed;
}
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Hope this helps you get started. Another tip is to add some logic to your application to prevent attempts to navigate to unauthenticated locations. For example:
private void mainFrame_Navigating(object sender, System.Windows.Navigation.NavigatingCancelEventArgs e)
{
List<string> anonUrls = new List<string>();
anonUrls.Add("/Welcome");
anonUrls.Add("/Register");
anonUrls.Add("/ValidateEmail");
var myAnonUrl = (from u in anonUrls
where e.Uri.OriginalString.StartsWith(u)
select u).Count();
if ((WebContext.Current.User == null ||
WebContext.Current.User.IsAuthenticated == false) &&
myAnonUrl == 0)
{
origUri = e.Uri;
e.Cancel = true;
mainFrame.Navigate(new Uri("/Welcome", UriKind.Relative));
}
}
Hoepfully this helps you to understand the navigation frame a little more.