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
Related
I have a combo-box im populating with a collection of objects in my ViewModel.
<ComboBox x:Name="ChangelistComboBox"
SelectedIndex="{Binding MyObjectSelectionIndex, Mode=TwoWay}"
ItemsSource="{Binding MyObjectList, Mode=OneWay}"
Margin="5"
Grid.Column="0">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MyObjectToComboBoxConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Im using a Converter to convert parameters from the object to a displaystring that shows up in the ComboBox
[ValueConversion(typeof(MyObject), typeof(string))]
class MyObjectToComboBoxConverter : ValueConverterBase
{
public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
try {
MyObject theObject = (MyObject)value;
int id = theObject.Id;
return (((id != -1) ? id.ToString() : "default") + " : " + theObject.Description);
} catch(InvalidCastException e) {
return (String)value;
}
}
public override object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return new MyObject(); //Not used
}
}
abstract class ValueConverterBase : IValueConverter
{
public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return DependencyProperty.UnsetValue;
}
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return DependencyProperty.UnsetValue;
}
}
In the Model the list is defined as:
private ObservableCollection<MyObject> _MyObjectList;
public ObservableCollection<MyObject> MyObjectList {
get
{
if (_MyObjectList != null) { return _MyObjectList; } else { return new ObservableCollection<MyObject>(); }
}
set
{
if (_MyObjectList != value) {
_MyObjectList = value;
NotifyPropertyChanged("MyObjectList");
}
}
}
In the ViewModel the MyObjectList is simply referenced from the model through the interface:
public ObservableCollection<MyObject> MyObjectList {
get
{
if (Model != null) {
return Model.MyObjectList;
} else {
return new ObservableCollection<MyObject>();
}
}
}
Without the TryCatch, this converter crashes when my MyObjectList is updated. It gives an error like Cannot cast type string to object on the MyObject theObject = (MyObject)value; line
With the TryCatch the converter works as intended. It even returns a correctly assembled string. The problem is I get InvalidCastExceptions in the error log, which isnt good. Also I have no idea why it works despite the exception.
The only hunch I have is that for some reason the object is being converted twice, once from object to string and then it tries to convert the string to string and fails there. I cant figure out why it would be doing that though.
MVVM IValueConverter Convert method getting empty string argument when expecting a float
Just found this which seems to be the same issue that I'm experiencing. I dont fully understand the explanation for it given there, but if modifying the Convert to accept an empty string is a viable solution i can live with that
Still feels like a hack but beggars cant be choosers i guess
You bind the ComboBox source to a list of MyObject, so the value should never be of type string. I guess there is a problem in your model state.
The way you are binding your view is correct, also the first version of the converter:
[ValueConversion(typeof(MyObject), typeof(string))]
class MyObjectToComboBoxConverter : IValueConverter
{
public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
try {
var theObject = (MyObject)value;
var id = theObject.Id;
return (((id != -1) ? id.ToString() : "default") + " : " + theObject.Description);
} catch(InvalidCastException e) {
return (string)value;
}
}
public override object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
Please start to change your ViewModel to:
public ObservableCollection<MyObject> MyObjectList { get; set; } = new ObservableCollection<MyObject>();
You need to implement the OnPropertyChangeEvent on the properties of the MyObject model (https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-create-and-bind-to-an-observablecollection)
so you only need to set the ObservableCollection in your ViewModel to an empty instance by default.
Then adding items:
MyObjectList.Add(new MyObject{ id=1, Description="First"});
Using a TextBox in WPF I have the problem that if I use ',' instead of '.' every time I try to get the value, the text inside the TextBox is transformed with the same number without comma..
How can I disable this automatic transformation?
<TextBox
x:Name="XValue"
Text="{Binding XInitValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="80" VerticalAlignment="Center"
TextChanged="XValue_TextChanged"
</TextBox>
private void XValue_TextChanged(object sender, TextChangedEventArgs e)
{
double a = XInitValue;
}
I solved it using an converter
public class DecimalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator == ".")
return value.ToString().Replace(",", ".");
else
return value.ToString().Replace(".", ",");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator == ".")
return value.ToString().Replace(".", ",");
else
return value.ToString().Replace(",", ".");
}
}
It really works! Thanks!
I have change it a bit to be more general, not use the CurrentCulture in the converter itself and return error if the entered value is ended with decimal separator. Without last part I could not enter the decimal separator at all if the UpdateSourceTrigger=PropertyChanged.
public class DecimalConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return null;
}
if (value is string stringValue && targetType == typeof(decimal))
{
var decSeparator = culture.NumberFormat.NumberDecimalSeparator;
var normString = decSeparator == "."
? stringValue.Replace(",", ".")
: stringValue.Replace(".", ",");
if (!normString.EndsWith(decSeparator) && decimal.TryParse(normString, out var decResult))
{
return decResult;
}
}
return DependencyProperty.UnsetValue;
}
}
Certainly the CurrentCulture should be set in App.xml.cs or somewhere else on starting the application:
var culture = CultureInfo.GetCultureInfo("de-DE");
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
FrameworkElement.LanguageProperty.OverrideMetadata(
typeof(FrameworkElement),
new FrameworkPropertyMetadata(
XmlLanguage.GetLanguage(culture.IetfLanguageTag)));
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" />
I'm trying to binding the TextColor from a label on ViewCell:
Label myLabel = new Label { Text = "SomeText" };
myLabel.SetBinding(Label.TextColorProperty,
new Binding("TheTextColor", BindingMode.TwoWay, new LabelTextColorConverter()));
Here's the converter:
public class LabelTextColorConverter : IValueConverter
{
public bool OldValue { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
OldValue = (bool) value;
Debug.WriteLine("asdadasdsadsada");
if ((bool)value)
return Color.Red;
else
return Color.Silver;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Debug.WriteLine("qwqweqewqeeqe");
return OldValue;
}
}
The debug output doesn't appear, and the color doesn't change either. I don't see anything wrong.
Why do you need Two Way binding for this? I don't think it's necessary.
myLabel.SetBinding(Label.TextColorProperty, new Binding("TheTextColor", BindingMode.OneWay, new LabelTextColorConverter()));
Then:
public class LabelTextColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool val = (bool)value;
if (val)
return Color.Red;
else
return Color.Silver;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
... and it should work fine. Also make sure you're setting BindingContext for your page/controls correctly.
This code work well, but the button visibility is collapsed in the design.
how can i set this to visible?
<!--Resources-->
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<Button Visibility="{Binding Converter={StaticResource BoolToVis}, Source={x:Static local:ConfigUser.Prc}}" Grid.Row="1"/>
If I get right what you want. What you need is the button to appear in design mode and also appear when your boolean is set to true at runtime.
You can create your converter that test if it's in design mode in addition of your boolean:
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
public class DesignVisibilityConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
if (value is bool) {
return ((bool) value) || DesignerProperties.GetIsInDesignMode(Application.Current.MainWindow)
? Visibility.Visible
: Visibility.Collapsed;
}
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
This one defaults to visible. Here is my Inverse BoolToVis
[ValueConversion(typeof(bool), typeof(Visibility))]
public class InverseBooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value != null && (bool)value) ?
Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value != null) && ((Visibility)value == Visibility.Collapsed);
}
}