How to override exception message generated by IValueConverter - c#

I would like to override message provided by the custom conversion class which inherits IValueConverter interface.
public class BoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
//string convertedValue = "";
bool initialValue = (bool)value;
if (initialValue == false)
{
return "0";
}
else if (initialValue == true)
{
return "1";
}
else return null;//{ return convertedValue; }
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
string initialValue = ((string)value).Trim();
try {
if (initialValue == "1") { return true; }
else if (initialValue == "0") { return false; }
else { return null; }
}
catch (Exception)
{
return null;
}
}
}
For example, if I enter 3 into text box binded with property I am receiving message: Value '3' could not be converted. I would like to provide longer message.

Related

Multibinding with converter on one of the bindings in .net MAUI

My issue is that when I use a converter on one of the multibinding's binding. It doesn't send the right thing to the converter. As per the DOC(https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/multibinding?view=net-maui-7.0) in the Consume a IMultiValueConverter it should work, so I don't know what I'm doing wrong...
My multibinding class is the following:
public class BooleanAndConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null || !targetType.IsAssignableFrom(typeof(bool)))
{
return false;
}
foreach (var value in values)
{
if (!(value is bool b))
{
return false;
}
else if (!b)
{
return false;
}
}
return true;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
I have a Boolean inverter class which is the following:
public class InverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(bool))
throw new InvalidOperationException("The target must be a boolean");
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then in my XAML I'm using it like so:
<Button Text="Passer à la ronde suivante" Command="{Binding NextRoundCommand}">
<Button.IsVisible>
<MultiBinding Converter="{StaticResource BooleanAndConverter}">
<Binding Path="isGameStarted"/>
<Binding Path="isPlayersPlaying" Converter="{StaticResource InvertedBoolConverter}"/>
</MultiBinding>
</Button.IsVisible>
</Button>
When the inverter converter gets called in the multibinding, instead of receiving a TagetType of bool, it receive a "System.object", so it throws the InvalidOperationException.
Why when using normal bindings it receive a targettype of bool and in multibinding it doesn't?
Thanks
I couldn't see other code, but I tested the sample code DataBindingDemos, it works on my side.
But I found that you didn't implement function ConvertBack for both of your (BooleanAndConverter and InverseBooleanConverter ).
And if I replace the code of ConvertBack to yous as follows, the app will throw exception.
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
You can refer to the following code:
public class InverterConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool? b = value as bool?;
if (b == null)
{
return false;
}
return !b.Value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Convert(value, targetType, parameter, culture);
}
}
public class AllTrueMultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values == null || !targetType.IsAssignableFrom(typeof(bool)))
{
return false;
// Alternatively, return BindableProperty.UnsetValue to use the binding FallbackValue
}
foreach (var value in values)
{
if (!(value is bool b))
{
return false;
// Alternatively, return BindableProperty.UnsetValue to use the binding FallbackValue
}
else if (!b)
{
return false;
}
}
return true;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
if (!(value is bool b) || targetTypes.Any(t => !t.IsAssignableFrom(typeof(bool))))
{
// Return null to indicate conversion back is not possible
return null;
}
if (b)
{
return targetTypes.Select(t => (object)true).ToArray();
}
else
{
// Can't convert back from false because of ambiguity
return null;
}
}
}
For more information, you can check: AllTrueMultiConverter.cs and InverterConverter.cs

How to limit max char in ValueConverter

How can I limit the number of characters in this TextBlock?
E.g.: It can only show up to 5 characters.
<TextBlock >
<Run Text="Tender Amount:"/>
<Run Text="{Binding TenderAmount,
Converter={StaticResource StringFormatConverter},
ConverterParameter='{}{0:C}'}" />
</TextBlock>
Here's the Code in my Converter
public sealed class StringFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
return null;
if (parameter == null)
return value;
return string.Format((string)parameter, value);
}
public object ConvertBack(object value, Type targetType, object parameter,
string language)
{
throw new NotImplementedException();
}
}
Use this:
public sealed class StringFormatConverter : IValueConverter
{
public int MaxLength { get; set; }
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
return null;
if (parameter == null)
return value;
var result = string.Format((string)parameter, value);
if (MaxLength > 0)
result = result.Substring(0, MaxLength);
return result;
}
public object ConvertBack(object value, Type targetType, object parameter,
string language)
{
throw new NotImplementedException();
}
}
Where you declare your converter resource, do this:
<stuff:StringFormatConverter x:Key="StringFormatConverter" MaxLength="5" />

How do you use NodaTime Classes in a PropertyGrid?

I'm converting some application code to use NodaTime classes instead of System.DateTime. Part of my application uses the PropertyGrid control to allow a user to edit a class containing both a LocalDate and an Instant. Without changing anything, the PropertyGrid displays the properties okay, but they are no longer editable. What's the best way of allowing the user to edit these fields.
For the sake of exposition, we can use this class as a representative of the type of thing I'd like to display and edit:
public class User
{
public string Name { get; set; }
public LocalDate BirthDate { get; set; }
public Instant NextAppointment { get; set; }
}
Best I've come up with so far:
Step 1: Create TypeConverter's so that the Noda classes are editable
public class ToAndFromStringTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
else
return base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(string))
return true;
else
return base.CanConvertTo(context, destinationType);
}
}
public class LocalDateTypeConverter : ToAndFromStringTypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
DateTime parsed;
if (!DateTime.TryParse((string)value, out parsed))
throw new ArgumentException("Cannot convert '" + (string)value + "' to LocalDate.");
else
return new LocalDate(parsed.Year, parsed.Month, parsed.Day);
}
else
{
return base.ConvertFrom(context, culture, value);
}
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
var tvalue = (LocalDate)value;
try
{
var x = tvalue.ToString("yyyy-MM-dd");
return x;
}
catch (NullReferenceException)
{
return "1900-1-1";
}
catch
{
throw new ArgumentException("Could not convert '" + value.ToString() + "' to LocalDate.");
}
}
else
return base.ConvertTo(context, culture, value, destinationType);
}
public class InstantTypeConverter : ToAndFromStringTypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
try
{
DateTime parsed = DateTime.Parse((string)value);
LocalDateTime dt = LocalDateTime.FromDateTime(parsed);
Instant i = dt.InZoneLeniently(DateTimeZoneProviders.Default.GetSystemDefault()).ToInstant();
return i;
}
catch
{
throw new ArgumentException("Cannot convert '" + (string)value + "' to Instant.");
}
}
else
{
return base.ConvertFrom(context, culture, value);
}
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
if (destinationType == typeof(string))
{
try
{
Instant tvalue = (Instant)value;
LocalDateTime local = tvalue.InZone(DateTimeZoneProviders.Default.GetSystemDefault()).LocalDateTime;
string output = LocalDateTimePattern.CreateWithInvariantCulture("yyyy-MM-dd HH:mm:ss.FFFFFF").Format(local);
return output;
}
catch
{
throw new ArgumentException("Could not convert '" + value.ToString() + "' to LocalDate.");
}
}
else
return base.ConvertTo(context, culture, value, destinationType);
}
}
Step 2: Register TypeConverters
Put this code at the top of your app:
TypeDescriptor.AddAttributes(typeof(LocalDate), new TypeConverterAttribute(typeof(LocalDateTypeConverter)));
TypeDescriptor.AddAttributes(typeof(Instant), new TypeConverterAttribute(typeof(InstantTypeConverter)));
Step 3: Use custom collection editor to handle things like List
public class NodaCollectionEditor : System.ComponentModel.Design.CollectionEditor
{
public NodaCollectionEditor(Type collection_type) : base(collection_type) { }
protected override object CreateInstance(Type itemType)
{
if (itemType == typeof(LocalDate))
return LocalDateHelper.MinValue;
else
return base.CreateInstance(itemType);
}
}
This can be registered by adding this attribute to any appropriate properties:
[System.ComponentModel.Editor(typeof(NodaCollectionEditor),typeof(System.Drawing.Design.UITypeEditor))]

Error validation with IDataErrorInfo and IValueConverter

I have a class with a List<string> Tags that I want to display in a TextBox. For this I use a IValueConverter.
ListToStringConverter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var list = value as List<string>;
var returnvalue = string.Empty;
if(list != null)
list.ForEach(item => returnvalue += item + ", ");
return returnvalue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var strValue = value as string;
if(strValue != null)
return strValue.Split(new char[] { ',' });
return null;
}
Class Test:
public class Test : IDataErrorInfo
{
public Test()
{
Tags = new List<string>();
}
public List<string> Tags { get; set; }
string errors;
const string errorsText = "Error in Test class.";
public string Error
{
get { return errors; }
}
public string this[string propertyName]
{
get
{
errors = null;
switch (propertyName)
{
case "Tags":
if (Tags.Count <= 1)
{
errors = errorsText;
return "...more tags plz..";
}
break;
}
return null;
}
}
}
Now I want to Validate the Tags:
<TextBox Text="{Binding Test.Tags, Converter={StaticResource ListToStringConverter}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
But there is no error displayed. Maybe because of the conversion but how can i still accomplish a validation?
I don't see why it shouldn't work. If you will run this at first time - SL will highlight the TextBox with red border. But if you will try to change the list of tags - you will see nothing, because you in your converter you have a bug in ConvertBack you return type array of strings (string[]), but property expects object with type List, so you just need to update the ConvertBack method to:
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var strValue = value as string;
if (strValue != null)
return strValue.Split(new char[] { ',' }).ToList();
return null;
}
One more note about Convert method, you can use Join method:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var list = value as List<string>;
if (list != null)
return String.Join(", ", list);
return null;
}
And one more thing about combining the strings. Don't use operator +, because you can have performance issues with it, use StringBuilder instead.

workflow 4 activity designer IValueConverter

Lets say i have an activity with InArgument<int> ProductId
I'd like to expose in the activity designer a combobox to show all Products and the user can select a product.
I can show the list of product in the combo no problem. But how do I bind the selected product to the InArgument<int> of my custom activity?
I suppose I need some kind of ValueConverter? Not sure how to code the value converter for this case, if anybody has an idea, suggestion, will be helpful. I have to convert the InArgument<int> to an int? and the convert back from int to InArgument<int>
Thanks,
public class ArgumentToInt32Converter: IValueConverter {
object IValueConverter.Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
object convertedValue = null;
if (value != null) {
ModelItem argumentModelItem = value as ModelItem;
if (argumentModelItem != null && argumentModelItem.Properties["Expression"] != null && argumentModelItem.Properties["Expression"].Value != null) {
if (argumentModelItem.Properties["Expression"].ComputedValue.GetType() == typeof(Literal <Int32> )) {
convertedValue = (argumentModelItem.Properties["Expression"].ComputedValue as Literal <Int32> ).Value;
} else {
convertedValue = null
}
}
}
return convertedValue;
}
object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
// Convert Int32 value to InArgument<Int32>
Int32 itemContent = (Int32) value;
VisualBasicValue <Int32> vbArgument = new VisualBasicValue <Int32> (itemContent);
InArgument <Int32> inArgument = new InArgument <Int32> (vbArgument);
return inArgument;
}
}
Modified from this answer
This is my attempt at making a more generic solution to this. I have several properties - some IEnumerable, some string, some int, and to make a value converter for each seems like the wrong approach. I'd be interested to know what cases I haven't caught here because I am relatively new to WF. Hopefully this helps someone.
public class ArgumentConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
object convertedValue = null;
if(value != null)
{
var argumentModelItem = value as ModelItem;
if(argumentModelItem != null)
{
ModelProperty argumentModelProperty = argumentModelItem.Properties["Expression"];
if(argumentModelProperty != null && argumentModelProperty.Value != null)
{
var computedValue = argumentModelProperty.ComputedValue;
var activity = (Activity) computedValue;
convertedValue = WorkflowInvoker.Invoke(activity)["Result"];
}
}
}
return convertedValue;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// here targetType should be InArgument<T>
// assume a single generic argument
Type arg0 = targetType.GetGenericArguments()[0];
ConstructorInfo argConstructor = targetType.GetConstructor(new[] {arg0});
var argument = argConstructor.Invoke(new[] { value });
return argument;
}
#endregion
}

Categories