IValueConverter not getting invoked in some scenarios - c#

I am using a collection of texts fetched from a web service, which should be used for a variety of controls.
The easiest and most dynamic way to do this, in my opinion, is to use an IValueConverter to get the given text as follows:
public class StaticTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null && parameter is string)
{
return App.StaticTexts.Items.SingleOrDefault(t => t.Name.Equals(parameter)).Content;
}
return null;
}
}
And then in the XAML I give the ID of the text ('Name') to the converter:
<phone:PhoneApplicationPage.Resources>
<Helpers:StaticTextConverter x:Name="TextConverter" />
</phone:PhoneApplicationPage.Resources>
<TextBlock Text="{Binding Converter={StaticResource TextConverter}, ConverterParameter=M62}" />
Then to change the text of some control, all that has to be done is to either change the ID in the parameter or change the text itself from some web interface.
My problem is
That the value converter only gets invoked when in some sort of DataTemplate context where the ItemSource has been set, as if the Binding property only works there.
Whenever I use this method anywhere else, the value converter is simply not invoked.
Does anyone have an idea of what I might be doing wrong?

Set DataContext="object" for your textblocks where the convertet is not working and the value converter will be invoked.
This workaround will do the trick in your scenario.

add source for the binding
use something like that
Text="{Binding Converter={StaticResource LocalizedStringsConventer} ,ConverterParameter=Wrong, Source=NULL}"

Related

How to set a xaml elements IsVisible property to true if a bound boolean property is false?

I'm wondering if or how I can set the IsVisible property of a xaml element to true if the boolean value of a bound property is false?
Depending on the value of a boolean property I want to conditionally render different elements in the view.
This is my code:
<ContentPage ...
x:Name="page">
<ListView BindingContext="{x:Reference page}" ItemSource="digitalInputs">
<ListView.ItemTemplate>
<DataTemplate x:DataType="services:DigitalInput">
<ViewCell>
<HorizontalStackLayout>
<!-- This seems to be working. Render a green ball, if boolean value is true -->
<Image Source="ballgreen" IsVisible="{Binding value}"/>
<!-- Doesn't work. I want to render a red ball if the boolean value is false. -->
<Image Source="ballred" IsVisible="{Binding !value}"/>
<Label Text="{Binding valueText}" />
</HorizontalStackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage>
I have thought about adding another boolean property to the DigitalInput class and set it's value to the opposite of value, but on the other hand I don't want to touch that class as it models 1:1 how I retrieve data from a web-service.
I have also thought about adding a converter, but I don't know how to do it in regards of BindingContext. I hope someone can help and shed some light on this issue for me.
Kind regards.
There is multiple ways to do this as you already mention yourself. The easiest being: add an inverted property to your model. Not the most elegant option.
In XAML itself you can't have any logic, so the ! operator won't work. A converter is indeed the way to go.
Implementing a InverseBoolConverter
To implement a IValueConverter which is the thing you need in this case, create a class that implements this interface. When you do, there is a couple of methods that you need to implement. In this case those are pretty straight-forward as it just inverts the boolean value.
However, since it's XAML and all based on strings, to make it really rock solid you might want to take into account some error handling. In this case, I'm going to assume the happy path. Find a sample implementation below:
public class InverseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !((bool)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
// Converting back rarely happens, a lot of the converters will throw an exception
//throw new NotImplementedException();
}
}
This code is taken from this SO question.
To now use it in your XAML, do this: <Image Source="ballred" IsVisible="{Binding value, Converter={converters:InverseBoolConverter}}"/>
And you'll need to add a xmlns entry to the root of your page like so: xmlns:converters="clr-namespace:YourProject.Namespace". Make sure that YourProject.Namespace matches the namespace of the InverseBoolConverter above.
Using the .NET MAUI Community Toolkit
The good news is, you don't need to do any of this! The .NET MAUI Community Toolkit has this built-in. You can just install the NuGet package, consume this converter and you're on your way.
Find the documentation for that on this page.

Xamarin.Forms. XAML Label IsVisible condition is not getting evaluated as expected

In my project I have to select several options on a page.
Every option has a type MyCustomType and has public string Name property declared.
Every option is displayed via label. When I click on the label I display list of options and select it.
As one option is selected, an empty label with placeholder text (like select an item) for another option should appear below the label with the just selected option.
I use separate labels for every option, not a ListView element (customer's requirement for the particular look & feel).
Number of options is limited, let say it equals to four.
In my viewmodel I have declared the list property (it has been initialized in viewmodel constructor):
public List<MyCustomType> AllOptions { get; }
In my XAML page labels are declared as:
<Label Text="{Binding AllOptions[0].Name}" >
<Label Text="{Binding AllOptions[1].Name}" IsVisible="{Binding AllOptions[0], Converter={StaticResource NullToFalseBoolConverter}}">
<Label Text="{Binding AllOptions[2].Name}" IsVisible="{Binding AllOptions[1], Converter={StaticResource NullToFalseBoolConverter}}">
<Label Text="{Binding AllOptions[3].Name}" IsVisible="{Binding AllOptions[2], Converter={StaticResource NullToFalseBoolConverter}}">
Converter NullToFalseBoolConverter looks like that:
public class NullToFalseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The problem is that IsVisible condition, specified into label declarations does not work.
All labels are displayed.
And breakpoint, set to the first line of Convert method of NullToFalseBoolConverter is not getting reached.
I don't understand why does it happen.
Any ideas?
Instead of trying binding in that matter, consider using either a layout type that supports both an ItemSource and also a data template. So this would be something like a ListView/CollectionView/Stacklayout
So, for example, if you decide to use a StackLayout for example:
<StackLayout
...
BindableLayout.ItemsSource="{Binding AllOptions}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Name}" IsVisible="{Binding ., Converter={StaticResource NullToFalseBoolConverter}}">
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
The beauty of this approach is that since your labels all follow the same approach you can now not only A) write cleaner code, and B) leverage MVVM patterns. Now, of course this does mean that each control gets the converters applied to them as well; however, if still want to not include it for the first element then all we have to do is change your type to include an index property. If you're wondering what the syntax Binding . means, it means that we are just binding the whole object of that collection for that element.
public int Index {get; set;}
Set it where you build that array, and then in the converter all you have to do is:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var element = (MyCustomType) value;
if(element.Index != 0)
return value != null;
else
return true;
}
EDIT
So as an addition to what you want to do in terms of passing the selected item to your command you can do the following. I am assuming that your overall page is a , but the same concept applies to any type of page really.
Set an x:Name property on the , give it any name you want. ex:
<ContentPage
...
x:Name="root">
Define a <GestureRecognizer> on your Label
<Label Text="{Binding Name}" IsVisible="{Binding ., Converter={StaticResource NullToFalseBoolConverter}}"> <Label.GestureRecognizers> <TapGestureRecognizer Command="{Binding BindingContext.YourCommandName, Source={x:Reference root}}" CommandParameter="{Binding .}"/> </Label.GestureRecognizers>
In your view model that is bond to the page, create the following command as follows:
public ICommand YourCommandName => new Command(x =>
YourCustomMethodHere(x));
Finally create the method that handles the object you selected
public void YourCustomMethodHere(MyCustomType type) {}
So what the above XAML code does is that we are binding the command of the Label to the overall parent view model, when an item is inside of a DataTemplate that has been defined by its ItemSource, its view model is actually the model that is being used a data template; that is why we are setting its source VM to be the one for the overall parent. The CommandParameter="{Binding .}" is the same logic as before, we are binding that whole data template item, in this case the MyCustomType that has been rendered for that element. This way each time that label is tapped, we are passing that label and its data to the command that we now defined in the VM.
That's funny, but the following approach fixed my problem.
I've had AllOptions declared as List (did not work), and as ObservableCollection (did not work too).
I should declare my list as array:
public MyCustomType[] AllOptions { get; }
And my labels start displaying properly, one after another is set.
And upon processing its values, if I get null value, that means, that we're reached the end of populated options.

UWP Binding to ComboBoxItem using resources and converter

I have such ComboBox:
<ComboBox Grid.Column="1" Padding="0" Margin="5,0,0,0" VerticalAlignment="Center" HorizontalContentAlignment="Center" IsEnabled="{Binding CanUserEdit}" SelectedValue="{Binding ConfigValue, Converter={StaticResource BoolToStringConverter}, Mode=TwoWay}">
<ComboBoxItem x:Uid="NoButton" />
<ComboBoxItem x:Uid="YesButton" />
</ComboBox>
It's supposed to be normal Yes/No kind of ComboBox, but I wanted to avoid binding to some Yes/No ItemsSource to avoid unnecessary complications.
BoolToStringConverter looks like that:
public class BoolToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var val = value as bool?;
if (val == true)
return ResourceLoader.GetForCurrentView().GetString("YesButton/Content");
else
return ResourceLoader.GetForCurrentView().GetString("NoButton/Content");
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
var val = value as string;
if (val == ResourceLoader.GetForCurrentView().GetString("YesButton/Content"))
return new bool?(true);
else
return new bool?(false);
}
}
So generally speaking I have string from resources inside ComboBoxItem while value inside ViewModel is an object (it's not bool, it's not so simple as I'm using TemplateSelector, ComboBox is supposed to be used only for boolean values, others would be normal TextBox with string inside).
I take the value from ViewModel convert it to exact same string from resources but it's not mapping the SelectedValue when control is loaded (ComboBox is empty even though it contains Yes/No values as it should). But "ConvertBack" works normally. When I select something in this ComboBox (for example "No" value) it will properly go inside ConvertBack method, compare the string and set the correct bool? value inside ViewModel. Therefore ConvertBack works well, but initial Convert doesn't set the SelectedValue properly because it's seems to not recognize "Yes" as "Yes" and "No" as "No" at this point (probably because it tries to compare references between string and ComboBoxItem). How can I resolve that?
It works when I use x:String instead of ComboBoxItem ...but x:String cannot be localized and I don't want to make it hardcoded for certain language.
The problem is the type mismatch.
In your XAML, the child item type of the ComboBox is ComboBoxItem, and your BoolToStringConverter.Convert method returns a string. These two types cannot establish the correct equivalent relationship.
You can try setting the SelectedValuePath property in the ComboBox:
<ComboBox Grid.Column="1"
...
SelectedValuePath="Content">
<ComboBoxItem x:Uid="NoButton" />
<ComboBoxItem x:Uid="YesButton" />
</ComboBox>
But I recommend using ItemsSource for data source binding and using DataTemplate to set the layout of children.
Here is an example about binding, you can do the same on ComboBox
Best regards.

Design view shows `System.Object` in TextBlock when applying a converter to a Number in Blend

In Microsoft Blend for Visual Studio (Express 2013 and Community 2015 RC), I have created some sample data with a collection of items consisting of a number and a string.
The number amount is bound to a TextBlock:
<TextBlock Text="{Binding amount}" />
The numbers displays fine, except I want them formatted to a string which displays 2 decimals. Since StringFormat is not available for Universal Apps, I tried adding a converter which attempts to achieve the same thing:
public class StringFormatConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return string.Format(parameter as string, value);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return null;
}
}
I add a resource for it:
<Page.Resources>
<ResourceDictionary>
<local:StringFormatConverter x:Name="StringFormat"/>
</ResourceDictionary>
</Page.Resources>
I adjust my TextBlock's binding:
<TextBlock Text="{
Binding amount,
Converter={StaticResource StringFormat},
ConverterParameter='{}{0:f2}'}" />
But when I do this, the view panel displays System.Object for each amount.
Even when I add a converter that does nothing and just returns the value or the toString() of the value, I get the same result! This only happens with the sample data type Number.
How can I format the binding to a number from a sample data set in Blend?
Not sure how helpful this is going to be after all these years, but why don't you do it in code behind? You just have to update the "Text" property of the TextBlock after the necessary conversion is done.

Run-time culture change and IValueConverter in a binding

The application I'm working on has an option to switch the interface language. This successfully changes most of the UI itself which uses, for example,
<Run Text="{Binding Resources.CreditsTitle, Source={x:Static models:ResourceService.Current}, Mode=OneWay}" />
Aside from static UI elements there are some displaying a few models' properties; for most of them no localisation is necessary, save for just one (for now). The models come from a different project within the solution, and are read-only. The one property that needs to be displayed in its localised form is represented by its ID in the model, and I wrote a converter for it that returns a Resource string.
public class RankIDToRankNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
var ID = (int)value;
switch (ID)
{
case 1:
return Resources.Rank_Expert;
case 2:
return Resources.Rank_Intermediate;
And so on.
In the XAML document it gets bound as
<Run Text="{Binding Model.RankID, Converter={StaticResource RankIDToRankNameConverterKey}, Mode=OneWay}"
Style="{DynamicResource PickupTextElementStyleKey}"
FontSize="14" />
…and it works, but just once, when the UI element gets drawn for the first time (obviously it also works when the RankID property changes but that happens extremely rarely). It doesn't change when culture is changed. I'm guessing it's because the property changed event does not fire, so WPF sees no reason to update it.
What's the “proper” way to get it to update when culture is changed?
I have solved this issue by adding a new method to the underlying model. Since I'm using Livet and models are NotificationObject descendants, I added a new public method Update() to the model in question with
this.RaisePropertyChanged("changed_property_name_here");
and adding
try
{
InstanceOfModelStorage.UnderlyingModel.Update();
}
catch (NullReferenceException e) { }
to the culture change handler.

Categories